Transactions
Token Transfers
Tokens
Internal Transactions
Coin Balance History
Logs
Code
Read Contract
Write Contract
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
This contract has been verified via Sourcify.
View contract in Sourcify repository
- Contract name:
- BuyAndBurnController_V2
- Optimization enabled
- true
- Compiler version
- v0.8.20+commit.a1b79de6
- Optimization runs
- 200
- EVM Version
- paris
- Verified at
- 2026-03-13T08:01:56.952434Z
Constructor Arguments
000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2700000000000000000000000098bf93ebf5c380c0e6ae8e192a7e2ae08edacc020000000000000000000000001715a3e4a142d8b698131108995174f37aeba10d000000000000000000000000fef68179be7150ead7a766331d0087ee26f060980000000000000000000000005a7ab76985b5fe102a5d77fa052566a92c3844b30000000000000000000000000f7f24c7f22e2ca7052f051a295e1a5d3369cace
Arg [0] (address) : 0x322cea42a77c2f18b8e79cc46efbacf73b6a8e6b
Arg [1] (address) : 0xa1077a294dde1b09bb078844df40758a5d0f9a27
Arg [2] (address) : 0x98bf93ebf5c380c0e6ae8e192a7e2ae08edacc02
Arg [3] (address) : 0x1715a3e4a142d8b698131108995174f37aeba10d
Arg [4] (address) : 0xfef68179be7150ead7a766331d0087ee26f06098
Arg [5] (address) : 0x5a7ab76985b5fe102a5d77fa052566a92c3844b3
Arg [6] (address) : 0x0f7f24c7f22e2ca7052f051a295e1a5d3369cace
src/BuyAndBurnController_V2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
// PulseX Router interface
interface IPulseXRouter {
function WETH() external pure returns (address);
function factory() external view returns (address);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
}
interface IPulseXFactory {
function getPair(address tokenA, address tokenB) external view returns (address pair);
function createPair(address tokenA, address tokenB) external returns (address pair);
}
interface IPulseXPair {
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function token0() external view returns (address);
}
interface IWPLS {
function deposit() external payable;
function withdraw(uint256 wad) external;
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
}
interface ISTATE {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
}
/**
* @title BuyAndBurnController_V2
* @author State Protocol Team
* @notice Enhanced Buy & Burn Controller with optimal ratio-aware splitting and complete governance control
* @dev This contract manages the STATE/WPLS liquidity pool and executes buy & burn operations to permanently
* remove STATE tokens from circulation while deepening liquidity.
*
* KEY FEATURES:
* ============
* 1. Pool Creation: Governance can create and initialize the STATE/WPLS pool with one-click function
* 2. PLS Handling: Automatic conversion of collected PLS to WPLS for operations
* 3. Optimal Buy & Burn: Dynamic ratio-aware splitting ensures 100% fund utilization
* 4. Liquidity Management: Add liquidity with automatic LP token burning
* 5. Vault Integration: Sources STATE tokens from SWAP_V3 vault with allowance system
*
* ARCHITECTURE:
* ============
* - Uses single SWAP_V3 address for vault operations (consolidated from previous SWAP_VAULT + SWAP)
* - All operations are governance-only (onlyOwner) for security
* - ReentrancyGuard protection on all state-changing functions
* - SafeERC20 for safe token operations
* - Integrates with PulseX DEX (Router, Factory, Pair)
*
* WORKFLOW:
* ========
* 1. Governance sets up SWAP_V3 vault allowance via setupSwapVaultAllowance()
* 2. Governance creates pool via createPoolOneClick() (one-time setup)
* 3. Contract collects PLS from protocol operations (via receive())
* 4. Governance calls executeFullBuyAndBurn() to:
* a. Convert PLS → WPLS
* b. Calculate optimal split for swap vs liquidity
* c. Swap WPLS → STATE
* d. Add liquidity and burn LP tokens permanently
*
* @custom:security All external functions are governance-controlled (onlyGovernance modifier)
* @custom:security Uses nonReentrant on all state-changing functions
* @custom:security 5% maximum slippage protection (MAX_SLIPPAGE_BPS = 500)
* @custom:lp-burn LP tokens are permanently burned to 0x...dEaD address
*/
contract BuyAndBurnController_V2 is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
// ============ State Variables ============
/// @notice Governance address (access control)
/// @dev All protocol operations are governance-controlled
address public governance;
// ============ Immutable Core Addresses ============
/// @notice STATE token contract address
/// @dev Main protocol token that gets bought and burned
address public immutable STATE;
/// @notice Wrapped PLS (WPLS) contract address
/// @dev Used for DEX operations and liquidity provision
address public immutable WPLS;
/// @notice PulseX Router contract address
/// @dev Handles all DEX swap and liquidity operations
address public immutable ROUTER;
/// @notice PulseX Factory contract address
/// @dev Used to get/create STATE/WPLS pair
address public immutable FACTORY;
/// @notice SWAP_V3 (AuctionSwap) contract address
/// @dev Vault source for STATE tokens used in liquidity operations
/// @dev Consolidated from previous SWAP_VAULT + SWAP (both pointed to same address)
address public immutable SWAP_V3;
/// @notice AuctionAdmin contract address
/// @dev Can call governance coordination functions like ownership transfer
address public immutable auctionAdmin;
// ============ Pool Management ============
/// @notice STATE/WPLS liquidity pool address
/// @dev Set once by governance via setStateWplsPool() or createPoolOneClick()
/// @dev All buy & burn operations target this pool
address public stateWplsPool;
// ============ Constants ============
/// @notice Burn address for permanent token removal
/// @dev LP tokens are sent here to permanently lock liquidity
address private constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;
/// @notice Maximum slippage tolerance in basis points (500 = 5%)
/// @dev Applied to all DEX operations for front-running protection
uint256 private constant MAX_SLIPPAGE_BPS = 500;
// ============ Events ============
/// @notice Emitted when STATE/WPLS pool is set or created
/// @param pool Address of the STATE/WPLS liquidity pool
/// @param stateAmount Amount of STATE tokens added to pool
/// @param wplsAmount Amount of WPLS tokens added to pool
/// @param liquidity Amount of LP tokens minted (and burned)
event PoolCreated(address indexed pool, uint256 stateAmount, uint256 wplsAmount, uint256 liquidity);
/// @notice Emitted when buy & burn operation is executed
/// @param wplsUsed Total WPLS used (swap + liquidity provision)
/// @param stateBought Amount of STATE tokens purchased via swap
/// @param liquidityBurned Amount of LP tokens permanently burned
event BuyAndBurnExecuted(uint256 wplsUsed, uint256 stateBought, uint256 liquidityBurned);
/// @notice Emitted when PLS is converted to WPLS
/// @param plsAmount Amount of PLS converted
/// @param wplsAmount Amount of WPLS received (always equal to plsAmount)
event PLSConverted(uint256 plsAmount, uint256 wplsAmount);
/// @notice Emitted when full buy & burn cycle completes (PLS conversion + buy & burn)
/// @param plsConverted Amount of PLS that was converted to WPLS
/// @param wplsProcessed Amount of WPLS used in buy & burn operation
event FullBuyAndBurnExecuted(uint256 plsConverted, uint256 wplsProcessed);
/// @notice Emitted when initial pool is created via one-click function
/// @param pool Address of the newly created STATE/WPLS pool
/// @param stateAmount Amount of STATE tokens added to initial liquidity
/// @param totalWpls Total WPLS amount used (from WPLS transfer + PLS conversion)
/// @param plsSent Amount of PLS sent with transaction (converted to WPLS)
event InitialPoolCreated(address indexed pool, uint256 stateAmount, uint256 totalWpls, uint256 plsSent);
/// @notice Emitted when additional liquidity is added to existing pool
/// @param pool Address of the STATE/WPLS pool
/// @param stateAmount Amount of STATE tokens added
/// @param wplsAmount Amount of WPLS tokens added
/// @param liquidityBurned Amount of LP tokens minted and immediately burned
event LiquidityAdded(address indexed pool, uint256 stateAmount, uint256 wplsAmount, uint256 liquidityBurned);
/// @notice Emitted when governance address is transferred
/// @param previousGovernance Address of the previous governance
/// @param newGovernance Address of the new governance
event GovernanceTransferred(address indexed previousGovernance, address indexed newGovernance);
/**
* @notice Initializes the BuyAndBurnController with core protocol addresses
* @dev All addresses are immutable after deployment for security
* @param _state STATE token contract address
* @param _wpls WPLS (Wrapped PLS) contract address
* @param _router PulseX Router contract address for DEX operations
* @param _factory PulseX Factory contract address for pool management
* @param _swapV3 SWAP_V3 (AuctionSwap) contract address - vault source for STATE tokens
* @param _auctionAdmin AuctionAdmin contract address (governance coordinator)
* @param _governance Governance address (access control)
*/
constructor(
address _state,
address _wpls,
address _router,
address _factory,
address _swapV3,
address _auctionAdmin,
address _governance
) Ownable(msg.sender) {
require(_state != address(0), "Invalid STATE");
require(_wpls != address(0), "Invalid WPLS");
require(_router != address(0), "Invalid router");
require(_factory != address(0), "Invalid factory");
require(_swapV3 != address(0), "Invalid SWAP_V3");
require(_auctionAdmin != address(0), "Invalid admin");
require(_governance != address(0), "Invalid governance");
STATE = _state;
WPLS = _wpls;
ROUTER = _router;
FACTORY = _factory;
SWAP_V3 = _swapV3;
auctionAdmin = _auctionAdmin;
governance = _governance;
renounceOwnership();
}
/// @notice Modifier for governance-only functions
modifier onlyGovernance() {
require(msg.sender == governance, "Only governance");
_;
}
/// @notice Transfer governance via AuctionAdmin (for centralized governance transfer)
/// @dev Only callable by AuctionAdmin during protocol-wide governance transfer
/// @param newGovernance Address of the new governance
function transferGovernanceByAdmin(address newGovernance) external {
require(msg.sender == auctionAdmin, "Only admin");
require(newGovernance != address(0), "Invalid governance");
address previousGovernance = governance;
governance = newGovernance;
emit GovernanceTransferred(previousGovernance, newGovernance);
}
/**
* @notice Set the STATE/WPLS pool address manually (governance only)
* @dev Alternative to createPoolOneClick() if pool already exists on-chain
* @dev Only callable once - cannot change pool after initialization
* @param poolAddress Address of the existing STATE/WPLS pool
* @custom:security onlyGovernance - governance-controlled
* @custom:security Cannot be changed after first call (pool creation functions check stateWplsPool == address(0))
*/
function setStateWplsPool(address poolAddress) external onlyGovernance {
require(poolAddress != address(0), "Invalid pool address");
stateWplsPool = poolAddress;
emit PoolCreated(poolAddress, 0, 0, 0); // Emit with zero amounts since pool already exists
}
/**
* @notice ONE-CLICK Pool Creation: Create STATE/WPLS pool with tokens from SWAP_V3 vault
* @dev Complete pool initialization in single transaction - governance convenience function
* @dev Automatically sources STATE tokens from SWAP_V3 vault, only requires PLS/WPLS from governance
* @dev LP tokens are burned immediately to permanently lock initial liquidity
*
* PREREQUISITES:
* - SWAP_V3.initializeCompleteSystem() must have been called (sets unlimited allowance automatically)
* - If wplsAmount > 0, governance must approve this contract for WPLS transfer
* - Can send PLS as msg.value (will be converted to WPLS automatically)
*
* PROCESS:
* 1. Converts any sent PLS to WPLS
* 2. Pulls STATE from SWAP_V3 vault using pre-set allowance
* 3. Pulls WPLS from governance wallet (if wplsAmount > 0)
* 4. Creates pool if doesn't exist
* 5. Adds initial liquidity
* 6. Burns all LP tokens permanently
*
* @param stateAmount STATE tokens for initial liquidity (sourced from SWAP_V3 vault)
* @param wplsAmount WPLS tokens for initial liquidity from governance wallet (optional if sending PLS)
* @custom:security onlyGovernance + nonReentrant
* @custom:security Can only be called once (requires stateWplsPool == address(0))
*/
function createPoolOneClick(uint256 stateAmount, uint256 wplsAmount) external payable onlyGovernance nonReentrant {
require(stateWplsPool == address(0), "Pool already initialized");
require(stateAmount > 0, "Invalid STATE amount");
// Calculate total WPLS needed
uint256 totalWplsAmount = wplsAmount;
// Convert any sent PLS to WPLS
if (msg.value > 0) {
IWPLS(WPLS).deposit{value: msg.value}();
totalWplsAmount += msg.value;
}
require(totalWplsAmount > 0, "No WPLS provided");
// Transfer STATE tokens from SWAP_V3 vault (allowance must be pre-set by governance)
ISTATE(STATE).transferFrom(SWAP_V3, address(this), stateAmount);
// If wplsAmount > 0, transfer WPLS from governance (must be pre-approved)
if (wplsAmount > 0) {
IERC20(WPLS).transferFrom(msg.sender, address(this), wplsAmount);
}
// Create pool if it doesn't exist
address pool = IPulseXFactory(FACTORY).getPair(STATE, WPLS);
if (pool == address(0)) {
pool = IPulseXFactory(FACTORY).createPair(STATE, WPLS);
}
// Approve router for both tokens
ISTATE(STATE).approve(ROUTER, stateAmount);
IERC20(WPLS).approve(ROUTER, totalWplsAmount);
// Add initial liquidity
(uint256 amountState, uint256 amountWpls, uint256 liquidity) = IPulseXRouter(ROUTER).addLiquidity(
STATE,
WPLS,
stateAmount,
totalWplsAmount,
(stateAmount * 95) / 100, // 5% slippage tolerance
(totalWplsAmount * 95) / 100,
address(this), // Controller holds initial LP tokens
block.timestamp + 300
);
// Set pool address and burn initial LP tokens
stateWplsPool = pool;
IERC20(pool).transfer(BURN_ADDRESS, liquidity);
emit PoolCreated(pool, amountState, amountWpls, liquidity);
emit InitialPoolCreated(pool, stateAmount, totalWplsAmount, msg.value);
}
/**
* @notice Add more liquidity to existing STATE/WPLS pool
* @dev Allows governance to manually increase pool depth without waiting for buy & burn cycle
* @dev STATE tokens sourced from SWAP_V3 vault, PLS/WPLS from governance wallet
* @dev LP tokens are sent directly to burn address - permanently locked
*
* PREREQUISITES:
* - SWAP_V3.initializeCompleteSystem() must have been called (sets unlimited allowance automatically)
* - Pool must exist (stateWplsPool != address(0))
* - If wplsAmount > 0, governance must approve this contract for WPLS transfer
*
* PROCESS:
* 1. Converts any sent PLS to WPLS
* 2. Pulls STATE from SWAP_V3 vault
* 3. Pulls WPLS from governance wallet (if wplsAmount > 0)
* 4. Adds liquidity to existing pool
* 5. Burns LP tokens immediately (sent to BURN_ADDRESS in addLiquidity call)
* 6. Any unused tokens remain in controller for future buy & burn
*
* @param stateAmount Amount of STATE tokens to add (from SWAP_V3 vault)
* @param wplsAmount Amount of WPLS tokens to add from governance wallet (optional if sending PLS)
* @return liquidity Amount of LP tokens that were burned
* @custom:security onlyGovernance + nonReentrant
* @custom:security LP tokens burned atomically in same transaction
*/
function addMoreLiquidity(
uint256 stateAmount,
uint256 wplsAmount
) external payable onlyGovernance nonReentrant returns (uint256 liquidity) {
require(stateWplsPool != address(0), "Pool not initialized");
require(stateAmount > 0, "Invalid STATE amount");
// Calculate total WPLS needed
uint256 totalWplsAmount = wplsAmount;
// Convert any sent PLS to WPLS
if (msg.value > 0) {
IWPLS(WPLS).deposit{value: msg.value}();
totalWplsAmount += msg.value;
}
require(totalWplsAmount > 0, "No WPLS/PLS provided");
// Transfer STATE tokens from SWAP_V3 vault (allowance must be pre-set by governance)
ISTATE(STATE).transferFrom(SWAP_V3, address(this), stateAmount);
// If wplsAmount > 0, transfer WPLS from governance (must be pre-approved)
if (wplsAmount > 0) {
IERC20(WPLS).transferFrom(msg.sender, address(this), wplsAmount);
}
// Approve router for both tokens
ISTATE(STATE).approve(ROUTER, stateAmount);
IERC20(WPLS).approve(ROUTER, totalWplsAmount);
// Add liquidity - LP tokens go directly to BURN address
(uint256 amountState, uint256 amountWpls, uint256 lpAmount) =
IPulseXRouter(ROUTER).addLiquidity(
STATE,
WPLS,
stateAmount,
totalWplsAmount,
(stateAmount * 95) / 100, // 5% slippage tolerance
(totalWplsAmount * 95) / 100,
BURN_ADDRESS, // LP tokens burned immediately!
block.timestamp + 300
);
require(lpAmount > 0, "No LP tokens minted");
// Keep unused tokens in contract for future buy & burn operations
// STATE and WPLS leftovers can be utilized by executeBuyAndBurn()
// Clean up allowances
ISTATE(STATE).approve(ROUTER, 0);
IERC20(WPLS).approve(ROUTER, 0);
emit LiquidityAdded(stateWplsPool, amountState, amountWpls, lpAmount);
return lpAmount;
}
/**
* @notice Convert accumulated PLS to WPLS (governance callable)
* @dev Standalone function for manual PLS conversion if needed
* @dev Usually not needed - executeFullBuyAndBurn() does this automatically
* @dev Useful for separate conversion before executeBuyAndBurn() if governance prefers granular control
* @custom:security onlyGovernance + nonReentrant
*/
function convertPLSToWPLS() external onlyGovernance nonReentrant {
uint256 plsBalance = address(this).balance;
require(plsBalance > 0, "No PLS to convert");
IWPLS(WPLS).deposit{value: plsBalance}();
emit PLSConverted(plsBalance, plsBalance);
}
/**
* @notice Execute optimal buy & burn with dynamic ratio calculation
* @dev Core buy & burn logic - requires WPLS already in contract
* @dev For full automation (PLS conversion + buy & burn), use executeFullBuyAndBurn() instead
*
* ALGORITHM:
* 1. Gets current pool reserves (STATE/WPLS)
* 2. Calculates optimal split: portion for swap vs portion for liquidity
* 3. Swaps WPLS → STATE via PulseX
* 4. Adds liquidity with purchased STATE + remaining WPLS
* 5. Burns all LP tokens permanently
*
* OPTIMIZATION:
* - Uses iterative algorithm (max 10 iterations) to find optimal split
* - Ensures 100% fund utilization - no leftover WPLS
* - Considers pool ratio changes from the swap itself
*
* @custom:security onlyGovernance + nonReentrant
* @custom:security 5% slippage protection on all DEX operations
* @custom:iteration Loop hard-capped at 10 iterations
*/
function executeBuyAndBurn() external onlyGovernance nonReentrant {
require(stateWplsPool != address(0), "Pool not initialized");
uint256 wplsBalance = IERC20(WPLS).balanceOf(address(this));
require(wplsBalance > 0, "No WPLS available");
uint256 existingStateBalance = ISTATE(STATE).balanceOf(address(this));
// Get current pool reserves
(uint256 stateReserve, uint256 wplsReserve) = getPoolReserves();
require(stateReserve > 0 && wplsReserve > 0, "Invalid pool state");
// Calculate optimal split considering existing STATE and ratio changes
(uint256 wplsForSwap, uint256 expectedState, uint256 stateForLP, uint256 wplsForLP) =
calculateOptimalSplit(wplsBalance, existingStateBalance, stateReserve, wplsReserve);
require(wplsForSwap > 0, "No WPLS for swap");
require(wplsForLP > 0, "No WPLS for LP");
// Step 1: Swap WPLS for STATE
uint256 stateBought = _swapWPLSForSTATE(wplsForSwap, expectedState);
// Step 2: Use total STATE (bought + existing) for liquidity
uint256 totalStateAvailable = stateBought + existingStateBalance;
require(totalStateAvailable >= stateForLP, "Insufficient STATE");
// Step 3: Add liquidity and burn
uint256 liquidityBurned = _addLiquidityAndBurn(stateForLP, wplsForLP);
emit BuyAndBurnExecuted(wplsForSwap + wplsForLP, stateBought, liquidityBurned);
}
/**
* @notice Calculate optimal WPLS split considering pool ratio changes and existing STATE
* @dev Pure function - performs iterative calculation to find best split ratio
* @dev Goal: Maximize buy & burn efficiency while ensuring 100% fund utilization
*
* ALGORITHM EXPLANATION:
* - Accounts for existing STATE balance to reduce WPLS needed for swapping
* - Start with 70/30 split (70% for swap, 30% for liquidity)
* - Iterate up to 10 times to refine the split
* - Each iteration checks if we'll have enough STATE (bought + existing) for LP
* - Adjusts split if total STATE < STATE needed for liquidity
* - Converges to optimal split where all WPLS is utilized
*
* WHY ITERATIVE:
* - Swapping changes the pool ratio
* - After swap, the ratio determines how much STATE we need for remaining WPLS
* - Must calculate considering the new ratio AFTER the swap
*
* @param totalWPLS Total WPLS available for buy & burn operation
* @param existingState Existing STATE balance in contract from previous operations
* @param stateReserve Current STATE reserve in pool (before operation)
* @param wplsReserve Current WPLS reserve in pool (before operation)
* @return wplsForSwap Amount of WPLS to use for buying STATE
* @return expectedState Amount of STATE expected from swap (approximate, excludes 0.29% fee)
* @return stateForLP Amount of STATE needed for liquidity provision
* @return wplsForLP Amount of WPLS to use for liquidity provision
* @custom:formula Constant product AMM: k = stateReserve * wplsReserve
* @custom:estimation Fee-less calculation for split optimization, actual swap applies 0.29% PulseX fee
* @custom:slippage 5% protection accounts for fees + price impact in actual swap execution
*/
function calculateOptimalSplit(
uint256 totalWPLS,
uint256 existingState,
uint256 stateReserve,
uint256 wplsReserve
) public pure returns (
uint256 wplsForSwap,
uint256 expectedState,
uint256 stateForLP,
uint256 wplsForLP
) {
uint256 k = stateReserve * wplsReserve;
// SCENARIO 1: Check if existing STATE is enough to pair with all WPLS
// Calculate STATE needed at current pool ratio
uint256 stateNeededAtCurrentRatio = (totalWPLS * stateReserve) / wplsReserve;
if (existingState >= stateNeededAtCurrentRatio) {
// We have enough STATE - no swap needed, add all as liquidity
wplsForSwap = 0;
expectedState = 0;
wplsForLP = totalWPLS;
stateForLP = stateNeededAtCurrentRatio;
return (wplsForSwap, expectedState, stateForLP, wplsForLP);
}
// SCENARIO 2: We need more STATE - calculate minimum swap required
// Start with deficit-based calculation
uint256 stateDeficit = stateNeededAtCurrentRatio - existingState;
// Estimate WPLS needed to buy the deficit (approximate, will be refined in iterations)
uint256 minSwapEstimate = (stateDeficit * wplsReserve) / stateReserve;
// SCENARIO 3: Initial swap percentage (50/50 split is optimal for constant product AMM)
uint256 startingSwapPercent = existingState > 0 ? 30 : 50;
wplsForSwap = (totalWPLS * startingSwapPercent) / 100;
// If we have existing STATE, start closer to the minimum needed
if (existingState > 0 && minSwapEstimate < wplsForSwap) {
wplsForSwap = minSwapEstimate;
}
// Iterate to find optimal split (max 10 iterations)
for (uint256 i = 0; i < 10; i++) {
// Calculate STATE from swap using constant product formula
uint256 newWplsReserve = wplsReserve + wplsForSwap;
expectedState = stateReserve - (k / newWplsReserve);
// Calculate remaining WPLS for LP
wplsForLP = totalWPLS - wplsForSwap;
// Calculate new pool ratio after swap
uint256 finalStateReserve = stateReserve - expectedState;
uint256 finalWplsReserve = newWplsReserve;
// STATE needed for LP at new ratio
stateForLP = (wplsForLP * finalStateReserve) / finalWplsReserve;
// Check if we have enough STATE (bought + existing)
uint256 totalStateAvailable = expectedState + existingState;
if (totalStateAvailable >= stateForLP) {
// We have enough STATE - check if we can optimize further
if (totalStateAvailable > stateForLP && existingState > 0 && i < 9) {
// We have excess STATE - try reducing swap more aggressively
uint256 excess = totalStateAvailable - stateForLP;
// More aggressive reduction when we have existing STATE
uint256 reduction = (excess * wplsReserve) / stateReserve;
if (wplsForSwap > reduction && wplsForSwap >= reduction) {
wplsForSwap = wplsForSwap > reduction ? wplsForSwap - reduction : 0;
// Don't reduce to zero if we still need some STATE
if (wplsForSwap == 0 && existingState < stateForLP) {
wplsForSwap = (stateForLP - existingState) * wplsReserve / stateReserve;
}
continue; // Re-calculate with reduced swap
}
}
// Optimal split found
break;
} else {
// Need to buy more STATE - increase swap amount
uint256 deficit = stateForLP - totalStateAvailable;
uint256 adjustment = (deficit * wplsReserve) / stateReserve;
if (wplsForSwap + adjustment <= totalWPLS) {
wplsForSwap += adjustment;
} else {
// Can't increase more, use all remaining WPLS for swap
wplsForSwap = totalWPLS;
wplsForLP = 0;
break;
}
}
}
return (wplsForSwap, expectedState, stateForLP, wplsForLP);
}
/**
* @notice Get current pool reserves (STATE, WPLS)
* @dev Reads reserves from PulseX pair contract and orders them correctly
* @dev Handles token ordering (token0/token1) automatically
* @return stateReserve Current STATE token reserve in pool
* @return wplsReserve Current WPLS token reserve in pool
* @custom:security View function - safe to call anytime
*/
function getPoolReserves() public view returns (uint256 stateReserve, uint256 wplsReserve) {
require(stateWplsPool != address(0), "Pool not set");
(uint112 reserve0, uint112 reserve1,) = IPulseXPair(stateWplsPool).getReserves();
address token0 = IPulseXPair(stateWplsPool).token0();
if (token0 == STATE) {
stateReserve = uint256(reserve0);
wplsReserve = uint256(reserve1);
} else {
stateReserve = uint256(reserve1);
wplsReserve = uint256(reserve0);
}
}
/**
* @notice Internal function to swap WPLS for STATE
* @dev Executes swap via PulseX Router with slippage protection
* @param wplsAmount Amount of WPLS to swap
* @param minStateOut Minimum STATE expected (before slippage adjustment)
* @return stateReceived Actual amount of STATE received from swap
* @custom:security Applies MAX_SLIPPAGE_BPS (5%) protection
* @custom:security Calculates received amount from before/after balance (not trusting return value alone)
* @custom:approval Exact amount approval to trusted PulseX Router (atomic transaction reverts all on failure)
*/
function _swapWPLSForSTATE(uint256 wplsAmount, uint256 minStateOut) internal returns (uint256 stateReceived) {
IERC20(WPLS).approve(ROUTER, wplsAmount);
address[] memory path = new address[](2);
path[0] = WPLS;
path[1] = STATE;
uint256 stateBefore = ISTATE(STATE).balanceOf(address(this));
IPulseXRouter(ROUTER).swapExactTokensForTokens(
wplsAmount,
(minStateOut * (10000 - MAX_SLIPPAGE_BPS)) / 10000, // Apply slippage protection
path,
address(this),
block.timestamp + 300
);
uint256 stateAfter = ISTATE(STATE).balanceOf(address(this));
stateReceived = stateAfter - stateBefore;
}
/**
* @notice Internal function to add liquidity and burn LP tokens
* @dev Adds liquidity to STATE/WPLS pool and immediately burns received LP tokens
* @param stateAmount Amount of STATE to add to liquidity
* @param wplsAmount Amount of WPLS to add to liquidity
* @return liquidityBurned Amount of LP tokens minted and burned
* @custom:security LP tokens sent to BURN_ADDRESS (0x...dEaD) - permanent removal
* @custom:security 5% slippage protection on both tokens
* @custom:security Calculates burned amount from before/after balance
*/
function _addLiquidityAndBurn(uint256 stateAmount, uint256 wplsAmount) internal returns (uint256 liquidityBurned) {
ISTATE(STATE).approve(ROUTER, stateAmount);
IERC20(WPLS).approve(ROUTER, wplsAmount);
uint256 lpBefore = IERC20(stateWplsPool).balanceOf(address(this));
// Calculate minimum amounts with slippage protection
uint256 amountStateMin = (stateAmount * (10000 - MAX_SLIPPAGE_BPS)) / 10000;
uint256 amountWplsMin = (wplsAmount * (10000 - MAX_SLIPPAGE_BPS)) / 10000;
IPulseXRouter(ROUTER).addLiquidity(
STATE,
WPLS,
stateAmount,
wplsAmount,
amountStateMin, // Apply slippage protection
amountWplsMin, // Apply slippage protection
address(this),
block.timestamp + 300
);
uint256 lpAfter = IERC20(stateWplsPool).balanceOf(address(this));
liquidityBurned = lpAfter - lpBefore;
// Burn the LP tokens permanently
IERC20(stateWplsPool).transfer(BURN_ADDRESS, liquidityBurned);
}
/**
* @notice Get controller status for UI/monitoring
* @dev Returns comprehensive state information for frontend display
* @return plsBalance Native PLS balance in controller
* @return wplsBalance WPLS token balance in controller (ready for buy & burn)
* @return stateBalance STATE token balance in controller (from previous operations)
* @return poolAddress Address of STATE/WPLS pool (address(0) if not initialized)
* @return poolStateReserve Current STATE reserve in pool (0 if pool not initialized)
* @return poolWplsReserve Current WPLS reserve in pool (0 if pool not initialized)
* @custom:security View function - safe for public access
*/
function getControllerStatus() external view returns (
uint256 plsBalance,
uint256 wplsBalance,
uint256 stateBalance,
address poolAddress,
uint256 poolStateReserve,
uint256 poolWplsReserve
) {
plsBalance = address(this).balance;
wplsBalance = IERC20(WPLS).balanceOf(address(this));
stateBalance = ISTATE(STATE).balanceOf(address(this));
poolAddress = stateWplsPool;
if (poolAddress != address(0)) {
(poolStateReserve, poolWplsReserve) = getPoolReserves();
}
}
/**
* @notice ONE-CLICK GOVERNANCE FUNCTION: Convert PLS to WPLS and execute buy & burn in single transaction
* @dev ⭐ RECOMMENDED FUNCTION - This is the main function governance should use for routine operations
* @dev Combines PLS conversion + buy & burn in single atomic transaction
*
* COMPLETE WORKFLOW:
* ==================
* Step 1: Convert specified PLS amount to WPLS
* - Governance specifies exact amount to process
* - Remaining PLS stays in contract for future operations
*
* Step 2: Execute buy & burn using WPLS + any leftover STATE
* - Uses WPLS from conversion + existing WPLS balance
* - Includes any leftover STATE from previous operations
* - Gets current pool reserves
* - Calculates optimal split for WPLS (swap vs liquidity)
* - Swaps WPLS for STATE
* - Adds liquidity using ALL available STATE (bought + leftover) + remaining WPLS
* - Burns all LP tokens permanently
*
* SAFETY:
* - Gracefully handles cases where pool not ready or no funds
* - All-or-nothing for buy & burn portion (won't partial execute)
* - Emits events for monitoring
* - Utilizes leftover STATE from previous operations (no waste)
*
* @param plsAmountToUse Amount of PLS to convert and use (0 = use only existing WPLS/STATE)
* @custom:security onlyGovernance + nonReentrant
* @custom:usage Call this periodically to process accumulated PLS from protocol
* @custom:reserves Reads reserves before swap for split calculation, then after swap for liquidity calculation
* @custom:dust Integer division dust accumulates and gets included in subsequent operations
*/
function executeFullBuyAndBurn(uint256 plsAmountToUse) external onlyGovernance nonReentrant {
require(plsAmountToUse <= address(this).balance, "Insufficient PLS balance");
// Step 1: Convert specified PLS amount to WPLS
uint256 plsConverted = 0;
if (plsAmountToUse > 0) {
IWPLS(WPLS).deposit{value: plsAmountToUse}();
plsConverted = plsAmountToUse;
emit PLSConverted(plsAmountToUse, plsAmountToUse);
}
// Step 2: Execute buy & burn using all available WPLS and STATE
uint256 wplsBalance = IERC20(WPLS).balanceOf(address(this));
uint256 existingStateBalance = ISTATE(STATE).balanceOf(address(this));
if (wplsBalance > 0 && stateWplsPool != address(0)) {
// Get current pool reserves
(uint256 stateReserve, uint256 wplsReserve) = getPoolReserves();
if (stateReserve > 0 && wplsReserve > 0) {
// Calculate optimal split considering existing STATE balance
(uint256 wplsForSwap, uint256 expectedState, , ) =
calculateOptimalSplit(wplsBalance, existingStateBalance, stateReserve, wplsReserve);
// Track if we performed a swap
uint256 stateBought = 0;
// Step 2.1: Swap WPLS for STATE (only if needed)
if (wplsForSwap > 0) {
stateBought = _swapWPLSForSTATE(wplsForSwap, expectedState);
}
// Step 2.2: Get ACTUAL pool reserves (after swap if it happened)
(uint256 newStateReserve, uint256 newWplsReserve) = getPoolReserves();
// Step 2.3: Get actual balances
uint256 totalStateAvailable = ISTATE(STATE).balanceOf(address(this));
uint256 totalWplsAvailable = IERC20(WPLS).balanceOf(address(this));
// Step 2.4: Add liquidity if we have both STATE and WPLS
// This works whether we swapped or not!
if (totalStateAvailable > 0 && totalWplsAvailable > 0) {
// Calculate optimal amounts to use ALL available tokens
// based on the current pool ratio
uint256 stateToUse;
uint256 wplsToUse;
// Calculate how much WPLS we need to pair with all our STATE
uint256 wplsNeededForAllState = (totalStateAvailable * newWplsReserve) / newStateReserve;
if (wplsNeededForAllState <= totalWplsAvailable) {
// We have enough WPLS to match all STATE - use all STATE
stateToUse = totalStateAvailable;
wplsToUse = wplsNeededForAllState;
// Result: All STATE used, small WPLS leftover
} else {
// We have more STATE than needed - use all WPLS
wplsToUse = totalWplsAvailable;
stateToUse = (totalWplsAvailable * newStateReserve) / newWplsReserve;
// Result: All WPLS used, small STATE leftover
}
// Step 2.5: Add liquidity with optimized amounts
if (stateToUse > 0 && wplsToUse > 0) {
uint256 liquidityBurned = _addLiquidityAndBurn(stateToUse, wplsToUse);
emit BuyAndBurnExecuted(wplsForSwap + wplsToUse, stateBought, liquidityBurned);
}
}
}
}
// Emit with actual WPLS used (wplsBalance before operation - balance after operation)
uint256 wplsUsed = wplsBalance - IERC20(WPLS).balanceOf(address(this));
emit FullBuyAndBurnExecuted(plsConverted, wplsUsed);
}
/**
* @notice Receive function to accept PLS for buy & burn operations
* @dev PLS sent here will be automatically converted to WPLS and processed
* @dev Call executeFullBuyAndBurn() to convert and burn
* @custom:security Anyone can send PLS (contributes to protocol-owned liquidity)
* @custom:usage Protocol operations can send PLS fees directly to this contract
*/
receive() external payable {
// PLS received - will be converted to WPLS when executeFullBuyAndBurn() is called
}
}
/introspection/IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
/ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
/Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
/ERC20/utils/SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
/IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
/IERC1363.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
/Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
Compiler Settings
{"viaIR":true,"remappings":[":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",":forge-std/=lib/forge-std/src/",":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",":openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"runs":200,"enabled":true},"metadata":{"bytecodeHash":"ipfs"},"libraries":{},"evmVersion":"paris","compilationTarget":{"src/BuyAndBurnController_V2.sol":"BuyAndBurnController_V2"}}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_state","internalType":"address"},{"type":"address","name":"_wpls","internalType":"address"},{"type":"address","name":"_router","internalType":"address"},{"type":"address","name":"_factory","internalType":"address"},{"type":"address","name":"_swapV3","internalType":"address"},{"type":"address","name":"_auctionAdmin","internalType":"address"},{"type":"address","name":"_governance","internalType":"address"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"type":"address","name":"owner","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"type":"address","name":"account","internalType":"address"}]},{"type":"error","name":"ReentrancyGuardReentrantCall","inputs":[]},{"type":"event","name":"BuyAndBurnExecuted","inputs":[{"type":"uint256","name":"wplsUsed","internalType":"uint256","indexed":false},{"type":"uint256","name":"stateBought","internalType":"uint256","indexed":false},{"type":"uint256","name":"liquidityBurned","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FullBuyAndBurnExecuted","inputs":[{"type":"uint256","name":"plsConverted","internalType":"uint256","indexed":false},{"type":"uint256","name":"wplsProcessed","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"GovernanceTransferred","inputs":[{"type":"address","name":"previousGovernance","internalType":"address","indexed":true},{"type":"address","name":"newGovernance","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"InitialPoolCreated","inputs":[{"type":"address","name":"pool","internalType":"address","indexed":true},{"type":"uint256","name":"stateAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"totalWpls","internalType":"uint256","indexed":false},{"type":"uint256","name":"plsSent","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"LiquidityAdded","inputs":[{"type":"address","name":"pool","internalType":"address","indexed":true},{"type":"uint256","name":"stateAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"wplsAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"liquidityBurned","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"type":"address","name":"previousOwner","internalType":"address","indexed":true},{"type":"address","name":"newOwner","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"PLSConverted","inputs":[{"type":"uint256","name":"plsAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"wplsAmount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"PoolCreated","inputs":[{"type":"address","name":"pool","internalType":"address","indexed":true},{"type":"uint256","name":"stateAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"wplsAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"liquidity","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"FACTORY","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"ROUTER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"STATE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"SWAP_V3","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"WPLS","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[{"type":"uint256","name":"liquidity","internalType":"uint256"}],"name":"addMoreLiquidity","inputs":[{"type":"uint256","name":"stateAmount","internalType":"uint256"},{"type":"uint256","name":"wplsAmount","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"auctionAdmin","inputs":[]},{"type":"function","stateMutability":"pure","outputs":[{"type":"uint256","name":"wplsForSwap","internalType":"uint256"},{"type":"uint256","name":"expectedState","internalType":"uint256"},{"type":"uint256","name":"stateForLP","internalType":"uint256"},{"type":"uint256","name":"wplsForLP","internalType":"uint256"}],"name":"calculateOptimalSplit","inputs":[{"type":"uint256","name":"totalWPLS","internalType":"uint256"},{"type":"uint256","name":"existingState","internalType":"uint256"},{"type":"uint256","name":"stateReserve","internalType":"uint256"},{"type":"uint256","name":"wplsReserve","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"convertPLSToWPLS","inputs":[]},{"type":"function","stateMutability":"payable","outputs":[],"name":"createPoolOneClick","inputs":[{"type":"uint256","name":"stateAmount","internalType":"uint256"},{"type":"uint256","name":"wplsAmount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"executeBuyAndBurn","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"executeFullBuyAndBurn","inputs":[{"type":"uint256","name":"plsAmountToUse","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"plsBalance","internalType":"uint256"},{"type":"uint256","name":"wplsBalance","internalType":"uint256"},{"type":"uint256","name":"stateBalance","internalType":"uint256"},{"type":"address","name":"poolAddress","internalType":"address"},{"type":"uint256","name":"poolStateReserve","internalType":"uint256"},{"type":"uint256","name":"poolWplsReserve","internalType":"uint256"}],"name":"getControllerStatus","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"stateReserve","internalType":"uint256"},{"type":"uint256","name":"wplsReserve","internalType":"uint256"}],"name":"getPoolReserves","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"governance","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"owner","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"renounceOwnership","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setStateWplsPool","inputs":[{"type":"address","name":"poolAddress","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"stateWplsPool","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferGovernanceByAdmin","inputs":[{"type":"address","name":"newGovernance","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"transferOwnership","inputs":[{"type":"address","name":"newOwner","internalType":"address"}]},{"type":"receive","stateMutability":"payable"}]
Contract Creation Code
0x61014034620003e357601f62002ef038819003918201601f191683019291906001600160401b03841183851017620003e8578160e09284926040968752833981010312620003e3576200005281620003fe565b9160209262000063848401620003fe565b9062000071838501620003fe565b906200008060608601620003fe565b956200008f60808701620003fe565b96620000ac60c0620000a460a08a01620003fe565b9801620003fe565b9360009586549460018060a01b037f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e097338289168a8c80a36001805581831615620003af57818416156200037c57818516156200034757818616156200031157818d1615620002db57818c1615620002a757169485156200026f575060805260a05260c05260e052610100968752610120958652600280546001600160a01b0319908116909217905590811633918216178355908280a35190612adc928362000414843960805183818161029c0152818161085701528181610b7001528181610e530152818161151301528181611867015281816122e4015281816124ce01526127fa015260a0518381816101bc01528181610375015281816106750152818161072c0152818161080d01528181610cdf01528181610ec901528181611306015281816113af015281816114cf0152818161180e01528181611c2001528181612445015261284f015260c0518381816102f801528181610f5901528181611794015281816123fb01526127b6015260e051838181610ef80152611d0a01525182818161025e01528181610e1a0152611d4d015251818181610b2c0152610bcc0152f35b6064908a519062461bcd60e51b825260048201526012602482015271496e76616c696420676f7665726e616e636560701b6044820152fd5b8a5162461bcd60e51b815260048101889052600d60248201526c24b73b30b634b21030b236b4b760991b6044820152606490fd5b8a5162461bcd60e51b815260048101889052600f60248201526e496e76616c696420535741505f563360881b6044820152606490fd5b8a5162461bcd60e51b815260048101889052600f60248201526e496e76616c696420666163746f727960881b6044820152606490fd5b8a5162461bcd60e51b815260048101889052600e60248201526d24b73b30b634b2103937baba32b960911b6044820152606490fd5b8a5162461bcd60e51b815260048101889052600c60248201526b496e76616c69642057504c5360a01b6044820152606490fd5b8a5162461bcd60e51b815260048101889052600d60248201526c496e76616c696420535441544560981b6044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b0382168203620003e35756fe6080604081815260049081361015610022575b505050361561002057600080fd5b005b600092833560e01c9081630c1e18a014611d39575080632dd3100014611cf55780632f3dc359146117c357806332fe7b261461177f5780634d15df501461173b57806350fd7cf7146116785780635aa6e6751461164f578063715018a6146115f55780638c189d111461148c5780638da5cb5b1461146457806394b036e114610db5578063a163cd5414610c9f578063b3a06a4014610b9f578063c486456c14610b5b578063dfdd4ea414610b17578063e81a840114610aee578063eb02241414610ac4578063ebb56c9a146107ac578063ec8be6ea146101ef578063ef8ef56f146101a75763f2fde38b0361001257346101a35760203660031901126101a3576001600160a01b0382358181169391929084900361019f57610143611d92565b8315610189575050600054826001600160601b0360a01b821617600055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a380f35b51631e4fbdf760e01b8152908101849052602490fd5b8480fd5b8280fd5b8382346101eb57816003193601126101eb57517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b5080fd5b50826101fa36611d7c565b6002549193916001600160a01b03906102169082163314611dbe565b61021e611f8e565b61022d81600354161515611fb1565b84156102398115611dfc565b829434610727575b85156106ed5784516323b872dd60e01b8082526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016828b0190815230602082810191909152604082018b9052999a9899987f0000000000000000000000000000000000000000000000000000000000000000871697909390918a90829081906060010381888c5af1801561064457908a92916106d0575b508061064e575b5050865163095ea7b360e01b8082527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b038116858401908152602081018e9052929c91935090918a908d9081906040010381888c5af19b8c15610644578b9c9b9a9b610627575b5088518381526001600160a01b038216858201908152602081019b909b527f000000000000000000000000000000000000000000000000000000000000000088169a8c90829081900360400181898f5af1801561061d5790889291610600575b501694605f820290828204605f1417156105ed57605f8c029b808d04605f036105da5761012c4201928342116105c7576064928c9d9e9284928d9e60248e9f519e8f9062e8e33760e81b82528c820152015260448d0152828c01520460848a01520460a488015261dead60c488015260e48701526060866101048186885af19889156105bd5783978497859b610583575b508a1561054a578b859160448c51809481938883528b8a8401528160248401525af18015610540576044928d9594928792610523575b508b51978895869485528401528160248401525af19081156105185750907f64b83944e79c3ce8d4c297411de637c3e102d064677aac0c163976ebdcd6f50e9392916104eb575b5060035485519485526020850192909252604084018690521691606090a26001805551908152f35b61050a90883d8a11610511575b6105028183611e69565b810190611eae565b50876104c3565b503d6104f8565b8651903d90823e3d90fd5b61053990873d8911610511576105028183611e69565b508e61047c565b8a513d87823e3d90fd5b895162461bcd60e51b81528085018d90526013602482015272139bc81314081d1bdad95b9cc81b5a5b9d1959606a1b6044820152606490fd5b91985099506105aa91965060603d81116105b6575b6105a28183611e69565b810190611f2f565b9691979096998c610446565b503d610598565b88513d85823e3d90fd5b634e487b7160e01b875260118652602487fd5b634e487b7160e01b865260118552602486fd5b634e487b7160e01b855260118452602485fd5b610616908d803d10610511576105028183611e69565b508d6103b5565b8a513d88823e3d90fd5b61063d908c8d3d10610511576105028183611e69565b508c610355565b89513d87823e3d90fd5b885192835233848401908152306020820152604081019190915282908190606001038186897f0000000000000000000000000000000000000000000000000000000000000000165af180156106c6576106a9575b87816102e7565b6106bf90883d8a11610511576105028183611e69565b50896106a2565b87513d85823e3d90fd5b6106e690833d8511610511576105028183611e69565b508c6102e0565b845162461bcd60e51b81526020818a01526014602482015273139bc815d41314cbd41314c81c1c9bdd9a59195960621b6044820152606490fd5b9450817f000000000000000000000000000000000000000000000000000000000000000016803b156107a857858891865192838092630d0e30db60e41b825234905af1801561079e5790869161078a575b50506107843484611e8b565b94610241565b61079390611e3f565b61019f578488610778565b85513d88823e3d90fd5b8580fd5b50346101a357826003193601126101a3576002546001600160a01b0392906107d79084163314611dbe565b6107df611f8e565b6107ee83600354161515611fb1565b81516370a0823160e01b808252308383015260249291602091828286817f00000000000000000000000000000000000000000000000000000000000000008b165afa918215610aba578892610a8b575b508115610a5557829085875180998193825230888301527f0000000000000000000000000000000000000000000000000000000000000000165afa958615610a4b578796610a17575b50610890612279565b9181151580610a0e575b156109d75790876108ab9392612001565b9492959186949194156109a257851561096f576108d36108cc8692896123eb565b998a611e8b565b1061093a57887ff102ca0afd04492ec61f7b3f6ad41ccde11037391b8387bc42d54e3a1683b60789896109308a6109148b61090e818d61279d565b92611e8b565b9251938493846040919493926060820195825260208201520152565b0390a16001805580f35b865162461bcd60e51b81529283015260129082015271496e73756666696369656e7420535441544560701b6044820152606490fd5b875162461bcd60e51b8152808501839052600e818501526d04e6f2057504c5320666f72204c560941b6044820152606490fd5b875162461bcd60e51b81528085018390526010818501526f04e6f2057504c5320666f7220737761760841b6044820152606490fd5b865162461bcd60e51b815280860185905260128188015271496e76616c696420706f6f6c20737461746560701b6044820152606490fd5b5082151561089a565b9095508181813d8311610a44575b610a2f8183611e69565b81010312610a3f57519438610887565b600080fd5b503d610a25565b85513d89823e3d90fd5b855162461bcd60e51b8152808501849052601181870152704e6f2057504c5320617661696c61626c6560781b6044820152606490fd5b9091508281813d8311610ab3575b610aa38183611e69565b81010312610a3f5751903861083e565b503d610a99565b86513d8a823e3d90fd5b508234610aeb5780600319360112610aeb5750610adf612279565b82519182526020820152f35b80fd5b8382346101eb57816003193601126101eb5760035490516001600160a01b039091168152602090f35b8382346101eb57816003193601126101eb57517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b8382346101eb57816003193601126101eb57517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b50346101a35760203660031901126101a3576001600160a01b0382358181169391929084900361019f57827f0000000000000000000000000000000000000000000000000000000000000000163303610c6f578315610c37575050600254826001600160601b0360a01b821617600255167f5f56bee8cffbe9a78652a74a60705edede02af10b0bbb888ca44b79a0d42ce808380a380f35b906020606492519162461bcd60e51b83528201526012602482015271496e76616c696420676f7665726e616e636560701b6044820152fd5b906020606492519162461bcd60e51b8352820152600a60248201526927b7363c9030b236b4b760b11b6044820152fd5b50346101a357826003193601126101a3576002546001600160a01b039290610cca9084163314611dbe565b610cd2611f8e565b47928315610d7e579084917f00000000000000000000000000000000000000000000000000000000000000001690813b156101a3578351630d0e30db60e41b81529183918391829088905af18015610d7457610d60575b50507ff322c85477f2e4e0aeb2199370ebd7e5fd19c1431cec752dbb823f8022ff41c7918151908082526020820152a16001805580f35b610d6990611e3f565b6101a3578238610d29565b83513d84823e3d90fd5b506020606492519162461bcd60e51b83528201526011602482015270139bc8141314c81d1bc818dbdb9d995c9d607a1b6044820152fd5b508290610dc136611d7c565b939060018060a01b03610dd981600254163314611dbe565b610de1611f8e565b806003541661142157610df5821515611dfc565b8590346113aa575b81156113745784516323b872dd60e01b8082526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001686830190815230602082810191909152604082018790527f00000000000000000000000000000000000000000000000000000000000000008581169b909594919392909190849082908e9082908f90829060600103925af180156112065790849291611357575b50806112df575b5050875163e6a4390560e01b81526001600160a01b03808616828a019081527f00000000000000000000000000000000000000000000000000000000000000009182166020820152909b919592507f00000000000000000000000000000000000000000000000000000000000000008516908490879081906040010381845afa958615611206578a93928d8b938e93849a6112c0575b50888a1615611237575b5050935163095ea7b360e01b8082527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b038116948301948552602085018c905295909391928792849291839182906040015b03925af1801561122d578d8b8e898f96610fe68e988c988c98611210575b5051988997889687948552840160209093929193604081019460018060a01b031681520152565b0393165af18015611206576111e9575b50605f8702878104605f14881517156111d657605f8702878104605f036111c35761012c4201918242116111b0579286928c9d9e928b9c9d958e8c9d97519c8d98899762e8e33760e81b895230956064900494606490049389019761105a98611f4a565b03921691818c5a94606095f19182156111a65789938a958b9461117c575b501696876001600160601b0360a01b600354161760035561dead89519163a9059cbb60e01b835282015282602482015281816044818d8c5af18015611172579361112c899a95947fc8e774967d7f45e5814dbb800335ed47014999568397f08e79b0f322047f743f9461114a98947f24a8e122edf6b2afbf7f99f15bdc5a99378bdb8dd31b1e0ec190ff9854c4fd209c97611154575b50508651938493846040919493926060820195825260208201520152565b0390a251928352602083019190915234604083015281906060820190565b0390a26001805580f35b8161116a92903d10610511576105028183611e69565b508d8061110e565b89513d8c823e3d90fd5b919450925061119a91945060603d81116105b6576105a28183611e69565b9491939094928b611078565b88513d8b823e3d90fd5b634e487b7160e01b8d5260118b5260248dfd5b634e487b7160e01b8c5260118a5260248cfd5b634e487b7160e01b8b526011895260248bfd5b6111ff90843d8611610511576105028183611e69565b508b610ff6565b8a513d8d823e3d90fd5b611226908a3d8c11610511576105028183611e69565b5038610fbf565b8b513d8e823e3d90fd5b95516364e329cb60e11b81526001600160a01b0380871695820195865290911660208501529097509293859284928391829060400103925af1908115611172578a84610fa18c95948c948491611293575b5098918f9150610f48565b6112b39150833d85116112b9575b6112ab8183611e69565b810190611ec6565b38611288565b503d6112a1565b6112d8919a50883d8a116112b9576112ab8183611e69565b9838610f3e565b89519283523389840190815230602082015260408101919091528290819060600103818c877f0000000000000000000000000000000000000000000000000000000000000000165af180156111a65761133a575b8181610ea8565b61135090823d8411610511576105028183611e69565b5089611333565b61136d90833d8511610511576105028183611e69565b508c610ea1565b845162461bcd60e51b8152602081860152601060248201526f139bc815d41314c81c1c9bdd9a59195960821b6044820152606490fd5b8091507f000000000000000000000000000000000000000000000000000000000000000016803b156107a857858491865192838092630d0e30db60e41b825234905af1801561079e5790869161140d575b50506114073487611e8b565b90610dfd565b61141690611e3f565b61019f5784876113fb565b835162461bcd60e51b8152602081850152601860248201527f506f6f6c20616c726561647920696e697469616c697a656400000000000000006044820152606490fd5b8382346101eb57816003193601126101eb57905490516001600160a01b039091168152602090f35b509190346101eb57816003193601126101eb5782516370a0823160e01b808252308284015293479392839260209284929091906001600160a01b039085836024817f000000000000000000000000000000000000000000000000000000000000000086165afa9283156115eb5785936115bc575b508351998a5230908a01528489602481847f0000000000000000000000000000000000000000000000000000000000000000165afa9384156115b15793611581575b60c0985060035416938461156d575b82519788528701528501526060840152608083015260a0820152f35b94509450611579612279565b959094611551565b92508388813d83116115aa575b6115988183611e69565b81010312610a3f5760c0975192611542565b503d61158e565b8351903d90823e3d90fd5b9092508581813d83116115e4575b6115d48183611e69565b81010312610a3f57519138611500565b503d6115ca565b84513d87823e3d90fd5b8334610aeb5780600319360112610aeb5761160e611d92565b600080546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b8382346101eb57816003193601126101eb5760025490516001600160a01b039091168152602090f35b50346101a35760203660031901126101a35781356001600160a01b03818116939184900361019f576116af90600254163314611dbe565b8215611702575060607fc8e774967d7f45e5814dbb800335ed47014999568397f08e79b0f322047f743f91836001600160601b0360a01b600354161760035584815191818352816020840152820152a280f35b6020606492519162461bcd60e51b83528201526014602482015273496e76616c696420706f6f6c206164647265737360601b6044820152fd5b508234610aeb576080366003190112610aeb575061176760809260643590604435906024359035612001565b92939091815194855260208501528301526060820152f35b8382346101eb57816003193601126101eb57517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b50346101a357602080600319360112611cf1576002546001600160a01b0393803592916117f39086163314611dbe565b6117fb611f8e565b478311611cb057859280611c1b575b50847f00000000000000000000000000000000000000000000000000000000000000001690845195876370a0823160e01b9182895230848a01528589602481885afa988915611c11578299611bde575b50875183815230858201527f00000000000000000000000000000000000000000000000000000000000000008216918782602481865afa918215611bd45790849392918997969592611ba0575b508b15159081611b92575b50611970575b50505060249087519485938492835230908301525afa908115611966578691611919575b50611908907fdef1ee9e36158fba837966672238b8b668a41806d5b2370a824777c9b69c301895611ff4565b908351928352820152a16001805580f35b90508181813d831161195f575b6119308183611e69565b81010312610a3f57517fdef1ee9e36158fba837966672238b8b668a41806d5b2370a824777c9b69c30186118dc565b503d611926565b84513d88823e3d90fd5b90919293945061197e612279565b9181151580611b89575b611999575b508795949392506118b8565b916119af91899897959c9b9a999694938c612001565b5050908b9181611b76575b5060246119c5612279565b9790948c5192838092898252308b8301525afa908115611b6c578d91611b3b575b508a5196858852308789015289886024818c5afa908115611b2f578e8b9c9d9e9f9b999a9b92611af6575b602498999a5083151580611aed575b611a33575b97505050508395965061198d565b50611a4786611a428386611efc565b611f0f565b95828711611ad457505050925b83151580611acb575b611a6a575b80808f611a25565b611aa281611a9c611abf937ff102ca0afd04492ec61f7b3f6ad41ccde11037391b8387bc42d54e3a1683b6079761279d565b93611e8b565b918c51938493846040919493926060820195825260208201520152565b0390a138808080611a62565b50801515611a5d565b82611a4292939750611ae7945096611efc565b92611a54565b50821515611a20565b509697905088813d8311611b28575b611b0f8183611e69565b81010312610a3f578d6024968b9951918a999850611a11565b503d611b05565b8e8d51903d90823e3d90fd5b809d50898092503d8311611b65575b611b548183611e69565b81010312610a3f578c9b51386119e6565b503d611b4a565b8b513d8f823e3d90fd5b81611b829293506123eb565b90386119ba565b50821515611988565b9050600354161515386118b2565b935095905082813d8311611bcd575b611bb98183611e69565b81010312610a3f5786948b925190386118a7565b503d611baf565b8a513d86823e3d90fd5b8680929a508193503d8311611c0a575b611bf88183611e69565b81010312610a3f57889051973861185a565b503d611bee565b88513d84823e3d90fd5b9250847f00000000000000000000000000000000000000000000000000000000000000001686813b15610aeb57849183875180948193630d0e30db60e41b83525af18015610a4b57611c9d575b507ff322c85477f2e4e0aeb2199370ebd7e5fd19c1431cec752dbb823f8022ff41c7848481519080825285820152a13861180a565b611ca990969196611e3f565b9438611c68565b60649184519162461bcd60e51b8352820152601860248201527f496e73756666696369656e7420504c532062616c616e636500000000000000006044820152fd5b8380fd5b8382346101eb57816003193601126101eb57517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b8490346101eb57816003193601126101eb577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b6040906003190112610a3f576004359060243590565b6000546001600160a01b03163303611da657565b60405163118cdaa760e01b8152336004820152602490fd5b15611dc557565b60405162461bcd60e51b815260206004820152600f60248201526e4f6e6c7920676f7665726e616e636560881b6044820152606490fd5b15611e0357565b60405162461bcd60e51b8152602060048201526014602482015273125b9d985b1a590814d510551148185b5bdd5b9d60621b6044820152606490fd5b67ffffffffffffffff8111611e5357604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff821117611e5357604052565b91908201809211611e9857565b634e487b7160e01b600052601160045260246000fd5b90816020910312610a3f57518015158103610a3f5790565b90816020910312610a3f57516001600160a01b0381168103610a3f5790565b9061251c91828102928184041490151715611e9857565b81810292918115918404141715611e9857565b8115611f19570490565b634e487b7160e01b600052601260045260246000fd5b90816060910312610a3f578051916040602083015192015190565b9490989796929360e096929461010087019a60018060a01b039687809216895216602088015260408701526060860152608085015260a08401521660c08201520152565b600260015414611f9f576002600155565b604051633ee5aeb560e01b8152600490fd5b15611fb857565b60405162461bcd60e51b8152602060048201526014602482015273141bdbdb081b9bdd081a5b9a5d1a585b1a5e995960621b6044820152606490fd5b91908203918211611e9857565b93909192936000946000948580956120198484611efc565b9161202885611a428689611efc565b808310156122555784611a42876120428661204795611ff4565b611efc565b9582158015959061224757606461206360ff601e5b1684611efc565b0497868061223e575b612236575b509591949093855b600a8710612090575b505050505050505093929190565b9194995092959a50819499506120a7878793611e8b565b986120b28a85611f0f565b6120bc908d611ff4565b9b8c9a6120c98a84611ff4565b9b6120d49083611ff4565b6120de908d611efc565b906120e891611f0f565b9b8d8d996120f591611e8b565b8d8082106121e05750898111806121d9575b806121cf575b612119575b5050612082565b82611a428a6120428d61212b95611ff4565b808b1180806121c5575b61213f5750612112565b85969798999a9b92939495506000146121bc5761215b91611ff4565b985b8915806121b3575b612197575b505b600019811461218357600101959194909692612079565b634e487b7160e01b84526011600452602484fd5b6121ac919950611a4289612042888694611ff4565b973861216a565b50808610612165565b5050839861215d565b50818c1015612135565b506009821061210d565b5087612107565b898495969798999a9b506120426121fd949d9593611a4293611ff4565b90836122098383611e8b565b1161221e579061221891611e8b565b9761216c565b50505095509650505050503880808080808080612082565b975038612071565b5088811061206c565b606461206360ff603261205c565b9850985050505050915083929190565b51906001600160701b0382168203610a3f57565b6003546001600160a01b03908116919082156123b757604051630240bc6b60e21b815290606082600481875afa93841561234e57600092839561235a575b5090602060049260405193848092630dfe168160e01b82525afa91821561234e5760009261232e575b50807f00000000000000000000000000000000000000000000000000000000000000001691161460001461231e576001600160701b03809116921690565b6001600160701b03928316921690565b61234791925060203d81116112b9576112ab8183611e69565b90386122e0565b6040513d6000823e3d90fd5b929094506060833d82116123af575b8161237660609383611e69565b81010312610aeb5761238783612265565b90604061239660208601612265565b94015163ffffffff811603610aeb5750919360206122b7565b3d9150612369565b60405162461bcd60e51b815260206004820152600c60248201526b141bdbdb081b9bdd081cd95d60a21b6044820152606490fd5b6040805163095ea7b360e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b038181166004808501919091526024840186905296600096909560209490939192907f000000000000000000000000000000000000000000000000000000000000000082169086816044818d865af1801561279357612776575b5086519467ffffffffffffffff94606087018681118882101761276357895260028752878701928936853787511561275057835286516001101561273d57928b95928b9c959289989560248c9b9d9e9d857f0000000000000000000000000000000000000000000000000000000000000000168d819e82908c015251928380926370a0823160e01b9d8e835230908301525afa9d8e15612732579d6126fd575b5061252861271091611ee5565b049561012c420142116126ea57959290918e94928c519788956338ed173960e01b875260a48701928c880152602487015260a060448701525180915260c485019190865b8c8282106126c45750505050839182869230606483015261012c420160848301520393165af180156126ba57908594939291612615575b505060249086519586938492835230908301525afa92831561260c575085926125d7575b50506125d4929350611ff4565b90565b90809250813d8311612605575b6125ee8183611e69565b81010312611cf1576125d4929350518392386125c7565b503d6125e4565b513d87823e3d90fd5b90919293503d808b843e6126298184611e69565b820191858184031261269f578051908282116126b6570182601f8201121561269f5780519182116126a35785808360051b938a519061266a83870183611e69565b8152019282010192831161269f579085809594939201905b82821061268f57506125a3565b8151815286959182019101612682565b8a80fd5b634e487b7160e01b8b526041855260248bfd5b8b80fd5b87513d8c823e3d90fd5b919496509282959750819087600194511681520194019101908f9593919288959361256c565b634e487b7160e01b8f526011895260248ffd5b909c508981813d831161272b575b6127158183611e69565b8101031261272757519b61252861251b565b8e80fd5b503d61270b565b8d51903d90823e3d90fd5b634e487b7160e01b8b5260328c5260248bfd5b634e487b7160e01b8c5260328d5260248cfd5b634e487b7160e01b8c5260418d5260248cfd5b61278c90873d8911610511576105028183611e69565b503861247b565b88513d8c823e3d90fd5b6040805163095ea7b360e01b8082526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081811660048086019190915260248501879052979660209660009593949193909291907f00000000000000000000000000000000000000000000000000000000000000009089816044818b868c165af18015612a6257612a89575b5087519182526001600160a01b038316828c01908152602081018690527f000000000000000000000000000000000000000000000000000000000000000092908a908290819060400103818b8b88165af18015612a6257918c9998979593916024979593612a6c575b508a86600354168a51988980926370a0823160e01b9e8f835230908301525afa968715612a62578897612a2f575b50612710806128d586611ee5565b04906128e087611ee5565b0461012c420191824211612a1b57928f8b9361291a918b948f9760609b9a9998519c8d9b8c9a8b9862e8e33760e81b8a5230968a01611f4a565b0393165af18015612a11576129f3575b506003541693835190815230888201528581602481885afa9081156129e9579086929184916129b6575b5061296461dead92604492611ff4565b98848651978894859363a9059cbb60e01b85528401528b60248401525af19182156129ac575050612993575050565b816129a992903d10610511576105028183611e69565b50565b51903d90823e3d90fd5b8381939492503d83116129e2575b6129ce8183611e69565b810103126101a35751859190612964612954565b503d6129c4565b84513d85823e3d90fd5b612a0a9060603d81116105b6576105a28183611e69565b505061292a565b85513d86823e3d90fd5b8f8b6011602492634e487b7160e01b835252fd5b9096508a81813d8311612a5b575b612a478183611e69565b81010312612a57575195386128c7565b8780fd5b503d612a3d565b89513d8a823e3d90fd5b612a82908c8d3d10610511576105028183611e69565b5038612899565b612a9f908a3d8c11610511576105028183611e69565b503861283056fea2646970667358221220015d55e1b1307a780ade48dc08e83d26ec4a1b99a9ee701027361db14fd42ed064736f6c63430008140033000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2700000000000000000000000098bf93ebf5c380c0e6ae8e192a7e2ae08edacc020000000000000000000000001715a3e4a142d8b698131108995174f37aeba10d000000000000000000000000fef68179be7150ead7a766331d0087ee26f060980000000000000000000000005a7ab76985b5fe102a5d77fa052566a92c3844b30000000000000000000000000f7f24c7f22e2ca7052f051a295e1a5d3369cace
Deployed ByteCode
0x6080604081815260049081361015610022575b505050361561002057600080fd5b005b600092833560e01c9081630c1e18a014611d39575080632dd3100014611cf55780632f3dc359146117c357806332fe7b261461177f5780634d15df501461173b57806350fd7cf7146116785780635aa6e6751461164f578063715018a6146115f55780638c189d111461148c5780638da5cb5b1461146457806394b036e114610db5578063a163cd5414610c9f578063b3a06a4014610b9f578063c486456c14610b5b578063dfdd4ea414610b17578063e81a840114610aee578063eb02241414610ac4578063ebb56c9a146107ac578063ec8be6ea146101ef578063ef8ef56f146101a75763f2fde38b0361001257346101a35760203660031901126101a3576001600160a01b0382358181169391929084900361019f57610143611d92565b8315610189575050600054826001600160601b0360a01b821617600055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a380f35b51631e4fbdf760e01b8152908101849052602490fd5b8480fd5b8280fd5b8382346101eb57816003193601126101eb57517f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a276001600160a01b03168152602090f35b5080fd5b50826101fa36611d7c565b6002549193916001600160a01b03906102169082163314611dbe565b61021e611f8e565b61022d81600354161515611fb1565b84156102398115611dfc565b829434610727575b85156106ed5784516323b872dd60e01b8082526001600160a01b037f000000000000000000000000fef68179be7150ead7a766331d0087ee26f0609816828b0190815230602082810191909152604082018b9052999a9899987f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b871697909390918a90829081906060010381888c5af1801561064457908a92916106d0575b508061064e575b5050865163095ea7b360e01b8082527f00000000000000000000000098bf93ebf5c380c0e6ae8e192a7e2ae08edacc026001600160a01b038116858401908152602081018e9052929c91935090918a908d9081906040010381888c5af19b8c15610644578b9c9b9a9b610627575b5088518381526001600160a01b038216858201908152602081019b909b527f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2788169a8c90829081900360400181898f5af1801561061d5790889291610600575b501694605f820290828204605f1417156105ed57605f8c029b808d04605f036105da5761012c4201928342116105c7576064928c9d9e9284928d9e60248e9f519e8f9062e8e33760e81b82528c820152015260448d0152828c01520460848a01520460a488015261dead60c488015260e48701526060866101048186885af19889156105bd5783978497859b610583575b508a1561054a578b859160448c51809481938883528b8a8401528160248401525af18015610540576044928d9594928792610523575b508b51978895869485528401528160248401525af19081156105185750907f64b83944e79c3ce8d4c297411de637c3e102d064677aac0c163976ebdcd6f50e9392916104eb575b5060035485519485526020850192909252604084018690521691606090a26001805551908152f35b61050a90883d8a11610511575b6105028183611e69565b810190611eae565b50876104c3565b503d6104f8565b8651903d90823e3d90fd5b61053990873d8911610511576105028183611e69565b508e61047c565b8a513d87823e3d90fd5b895162461bcd60e51b81528085018d90526013602482015272139bc81314081d1bdad95b9cc81b5a5b9d1959606a1b6044820152606490fd5b91985099506105aa91965060603d81116105b6575b6105a28183611e69565b810190611f2f565b9691979096998c610446565b503d610598565b88513d85823e3d90fd5b634e487b7160e01b875260118652602487fd5b634e487b7160e01b865260118552602486fd5b634e487b7160e01b855260118452602485fd5b610616908d803d10610511576105028183611e69565b508d6103b5565b8a513d88823e3d90fd5b61063d908c8d3d10610511576105028183611e69565b508c610355565b89513d87823e3d90fd5b885192835233848401908152306020820152604081019190915282908190606001038186897f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a27165af180156106c6576106a9575b87816102e7565b6106bf90883d8a11610511576105028183611e69565b50896106a2565b87513d85823e3d90fd5b6106e690833d8511610511576105028183611e69565b508c6102e0565b845162461bcd60e51b81526020818a01526014602482015273139bc815d41314cbd41314c81c1c9bdd9a59195960621b6044820152606490fd5b9450817f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2716803b156107a857858891865192838092630d0e30db60e41b825234905af1801561079e5790869161078a575b50506107843484611e8b565b94610241565b61079390611e3f565b61019f578488610778565b85513d88823e3d90fd5b8580fd5b50346101a357826003193601126101a3576002546001600160a01b0392906107d79084163314611dbe565b6107df611f8e565b6107ee83600354161515611fb1565b81516370a0823160e01b808252308383015260249291602091828286817f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a278b165afa918215610aba578892610a8b575b508115610a5557829085875180998193825230888301527f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b165afa958615610a4b578796610a17575b50610890612279565b9181151580610a0e575b156109d75790876108ab9392612001565b9492959186949194156109a257851561096f576108d36108cc8692896123eb565b998a611e8b565b1061093a57887ff102ca0afd04492ec61f7b3f6ad41ccde11037391b8387bc42d54e3a1683b60789896109308a6109148b61090e818d61279d565b92611e8b565b9251938493846040919493926060820195825260208201520152565b0390a16001805580f35b865162461bcd60e51b81529283015260129082015271496e73756666696369656e7420535441544560701b6044820152606490fd5b875162461bcd60e51b8152808501839052600e818501526d04e6f2057504c5320666f72204c560941b6044820152606490fd5b875162461bcd60e51b81528085018390526010818501526f04e6f2057504c5320666f7220737761760841b6044820152606490fd5b865162461bcd60e51b815280860185905260128188015271496e76616c696420706f6f6c20737461746560701b6044820152606490fd5b5082151561089a565b9095508181813d8311610a44575b610a2f8183611e69565b81010312610a3f57519438610887565b600080fd5b503d610a25565b85513d89823e3d90fd5b855162461bcd60e51b8152808501849052601181870152704e6f2057504c5320617661696c61626c6560781b6044820152606490fd5b9091508281813d8311610ab3575b610aa38183611e69565b81010312610a3f5751903861083e565b503d610a99565b86513d8a823e3d90fd5b508234610aeb5780600319360112610aeb5750610adf612279565b82519182526020820152f35b80fd5b8382346101eb57816003193601126101eb5760035490516001600160a01b039091168152602090f35b8382346101eb57816003193601126101eb57517f0000000000000000000000005a7ab76985b5fe102a5d77fa052566a92c3844b36001600160a01b03168152602090f35b8382346101eb57816003193601126101eb57517f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b6001600160a01b03168152602090f35b50346101a35760203660031901126101a3576001600160a01b0382358181169391929084900361019f57827f0000000000000000000000005a7ab76985b5fe102a5d77fa052566a92c3844b3163303610c6f578315610c37575050600254826001600160601b0360a01b821617600255167f5f56bee8cffbe9a78652a74a60705edede02af10b0bbb888ca44b79a0d42ce808380a380f35b906020606492519162461bcd60e51b83528201526012602482015271496e76616c696420676f7665726e616e636560701b6044820152fd5b906020606492519162461bcd60e51b8352820152600a60248201526927b7363c9030b236b4b760b11b6044820152fd5b50346101a357826003193601126101a3576002546001600160a01b039290610cca9084163314611dbe565b610cd2611f8e565b47928315610d7e579084917f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a271690813b156101a3578351630d0e30db60e41b81529183918391829088905af18015610d7457610d60575b50507ff322c85477f2e4e0aeb2199370ebd7e5fd19c1431cec752dbb823f8022ff41c7918151908082526020820152a16001805580f35b610d6990611e3f565b6101a3578238610d29565b83513d84823e3d90fd5b506020606492519162461bcd60e51b83528201526011602482015270139bc8141314c81d1bc818dbdb9d995c9d607a1b6044820152fd5b508290610dc136611d7c565b939060018060a01b03610dd981600254163314611dbe565b610de1611f8e565b806003541661142157610df5821515611dfc565b8590346113aa575b81156113745784516323b872dd60e01b8082526001600160a01b037f000000000000000000000000fef68179be7150ead7a766331d0087ee26f060981686830190815230602082810191909152604082018790527f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b8581169b909594919392909190849082908e9082908f90829060600103925af180156112065790849291611357575b50806112df575b5050875163e6a4390560e01b81526001600160a01b03808616828a019081527f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a279182166020820152909b919592507f0000000000000000000000001715a3e4a142d8b698131108995174f37aeba10d8516908490879081906040010381845afa958615611206578a93928d8b938e93849a6112c0575b50888a1615611237575b5050935163095ea7b360e01b8082527f00000000000000000000000098bf93ebf5c380c0e6ae8e192a7e2ae08edacc026001600160a01b038116948301948552602085018c905295909391928792849291839182906040015b03925af1801561122d578d8b8e898f96610fe68e988c988c98611210575b5051988997889687948552840160209093929193604081019460018060a01b031681520152565b0393165af18015611206576111e9575b50605f8702878104605f14881517156111d657605f8702878104605f036111c35761012c4201918242116111b0579286928c9d9e928b9c9d958e8c9d97519c8d98899762e8e33760e81b895230956064900494606490049389019761105a98611f4a565b03921691818c5a94606095f19182156111a65789938a958b9461117c575b501696876001600160601b0360a01b600354161760035561dead89519163a9059cbb60e01b835282015282602482015281816044818d8c5af18015611172579361112c899a95947fc8e774967d7f45e5814dbb800335ed47014999568397f08e79b0f322047f743f9461114a98947f24a8e122edf6b2afbf7f99f15bdc5a99378bdb8dd31b1e0ec190ff9854c4fd209c97611154575b50508651938493846040919493926060820195825260208201520152565b0390a251928352602083019190915234604083015281906060820190565b0390a26001805580f35b8161116a92903d10610511576105028183611e69565b508d8061110e565b89513d8c823e3d90fd5b919450925061119a91945060603d81116105b6576105a28183611e69565b9491939094928b611078565b88513d8b823e3d90fd5b634e487b7160e01b8d5260118b5260248dfd5b634e487b7160e01b8c5260118a5260248cfd5b634e487b7160e01b8b526011895260248bfd5b6111ff90843d8611610511576105028183611e69565b508b610ff6565b8a513d8d823e3d90fd5b611226908a3d8c11610511576105028183611e69565b5038610fbf565b8b513d8e823e3d90fd5b95516364e329cb60e11b81526001600160a01b0380871695820195865290911660208501529097509293859284928391829060400103925af1908115611172578a84610fa18c95948c948491611293575b5098918f9150610f48565b6112b39150833d85116112b9575b6112ab8183611e69565b810190611ec6565b38611288565b503d6112a1565b6112d8919a50883d8a116112b9576112ab8183611e69565b9838610f3e565b89519283523389840190815230602082015260408101919091528290819060600103818c877f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a27165af180156111a65761133a575b8181610ea8565b61135090823d8411610511576105028183611e69565b5089611333565b61136d90833d8511610511576105028183611e69565b508c610ea1565b845162461bcd60e51b8152602081860152601060248201526f139bc815d41314c81c1c9bdd9a59195960821b6044820152606490fd5b8091507f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2716803b156107a857858491865192838092630d0e30db60e41b825234905af1801561079e5790869161140d575b50506114073487611e8b565b90610dfd565b61141690611e3f565b61019f5784876113fb565b835162461bcd60e51b8152602081850152601860248201527f506f6f6c20616c726561647920696e697469616c697a656400000000000000006044820152606490fd5b8382346101eb57816003193601126101eb57905490516001600160a01b039091168152602090f35b509190346101eb57816003193601126101eb5782516370a0823160e01b808252308284015293479392839260209284929091906001600160a01b039085836024817f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2786165afa9283156115eb5785936115bc575b508351998a5230908a01528489602481847f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b165afa9384156115b15793611581575b60c0985060035416938461156d575b82519788528701528501526060840152608083015260a0820152f35b94509450611579612279565b959094611551565b92508388813d83116115aa575b6115988183611e69565b81010312610a3f5760c0975192611542565b503d61158e565b8351903d90823e3d90fd5b9092508581813d83116115e4575b6115d48183611e69565b81010312610a3f57519138611500565b503d6115ca565b84513d87823e3d90fd5b8334610aeb5780600319360112610aeb5761160e611d92565b600080546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b8382346101eb57816003193601126101eb5760025490516001600160a01b039091168152602090f35b50346101a35760203660031901126101a35781356001600160a01b03818116939184900361019f576116af90600254163314611dbe565b8215611702575060607fc8e774967d7f45e5814dbb800335ed47014999568397f08e79b0f322047f743f91836001600160601b0360a01b600354161760035584815191818352816020840152820152a280f35b6020606492519162461bcd60e51b83528201526014602482015273496e76616c696420706f6f6c206164647265737360601b6044820152fd5b508234610aeb576080366003190112610aeb575061176760809260643590604435906024359035612001565b92939091815194855260208501528301526060820152f35b8382346101eb57816003193601126101eb57517f00000000000000000000000098bf93ebf5c380c0e6ae8e192a7e2ae08edacc026001600160a01b03168152602090f35b50346101a357602080600319360112611cf1576002546001600160a01b0393803592916117f39086163314611dbe565b6117fb611f8e565b478311611cb057859280611c1b575b50847f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a271690845195876370a0823160e01b9182895230848a01528589602481885afa988915611c11578299611bde575b50875183815230858201527f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b8216918782602481865afa918215611bd45790849392918997969592611ba0575b508b15159081611b92575b50611970575b50505060249087519485938492835230908301525afa908115611966578691611919575b50611908907fdef1ee9e36158fba837966672238b8b668a41806d5b2370a824777c9b69c301895611ff4565b908351928352820152a16001805580f35b90508181813d831161195f575b6119308183611e69565b81010312610a3f57517fdef1ee9e36158fba837966672238b8b668a41806d5b2370a824777c9b69c30186118dc565b503d611926565b84513d88823e3d90fd5b90919293945061197e612279565b9181151580611b89575b611999575b508795949392506118b8565b916119af91899897959c9b9a999694938c612001565b5050908b9181611b76575b5060246119c5612279565b9790948c5192838092898252308b8301525afa908115611b6c578d91611b3b575b508a5196858852308789015289886024818c5afa908115611b2f578e8b9c9d9e9f9b999a9b92611af6575b602498999a5083151580611aed575b611a33575b97505050508395965061198d565b50611a4786611a428386611efc565b611f0f565b95828711611ad457505050925b83151580611acb575b611a6a575b80808f611a25565b611aa281611a9c611abf937ff102ca0afd04492ec61f7b3f6ad41ccde11037391b8387bc42d54e3a1683b6079761279d565b93611e8b565b918c51938493846040919493926060820195825260208201520152565b0390a138808080611a62565b50801515611a5d565b82611a4292939750611ae7945096611efc565b92611a54565b50821515611a20565b509697905088813d8311611b28575b611b0f8183611e69565b81010312610a3f578d6024968b9951918a999850611a11565b503d611b05565b8e8d51903d90823e3d90fd5b809d50898092503d8311611b65575b611b548183611e69565b81010312610a3f578c9b51386119e6565b503d611b4a565b8b513d8f823e3d90fd5b81611b829293506123eb565b90386119ba565b50821515611988565b9050600354161515386118b2565b935095905082813d8311611bcd575b611bb98183611e69565b81010312610a3f5786948b925190386118a7565b503d611baf565b8a513d86823e3d90fd5b8680929a508193503d8311611c0a575b611bf88183611e69565b81010312610a3f57889051973861185a565b503d611bee565b88513d84823e3d90fd5b9250847f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a271686813b15610aeb57849183875180948193630d0e30db60e41b83525af18015610a4b57611c9d575b507ff322c85477f2e4e0aeb2199370ebd7e5fd19c1431cec752dbb823f8022ff41c7848481519080825285820152a13861180a565b611ca990969196611e3f565b9438611c68565b60649184519162461bcd60e51b8352820152601860248201527f496e73756666696369656e7420504c532062616c616e636500000000000000006044820152fd5b8380fd5b8382346101eb57816003193601126101eb57517f0000000000000000000000001715a3e4a142d8b698131108995174f37aeba10d6001600160a01b03168152602090f35b8490346101eb57816003193601126101eb577f000000000000000000000000fef68179be7150ead7a766331d0087ee26f060986001600160a01b03168152602090f35b6040906003190112610a3f576004359060243590565b6000546001600160a01b03163303611da657565b60405163118cdaa760e01b8152336004820152602490fd5b15611dc557565b60405162461bcd60e51b815260206004820152600f60248201526e4f6e6c7920676f7665726e616e636560881b6044820152606490fd5b15611e0357565b60405162461bcd60e51b8152602060048201526014602482015273125b9d985b1a590814d510551148185b5bdd5b9d60621b6044820152606490fd5b67ffffffffffffffff8111611e5357604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff821117611e5357604052565b91908201809211611e9857565b634e487b7160e01b600052601160045260246000fd5b90816020910312610a3f57518015158103610a3f5790565b90816020910312610a3f57516001600160a01b0381168103610a3f5790565b9061251c91828102928184041490151715611e9857565b81810292918115918404141715611e9857565b8115611f19570490565b634e487b7160e01b600052601260045260246000fd5b90816060910312610a3f578051916040602083015192015190565b9490989796929360e096929461010087019a60018060a01b039687809216895216602088015260408701526060860152608085015260a08401521660c08201520152565b600260015414611f9f576002600155565b604051633ee5aeb560e01b8152600490fd5b15611fb857565b60405162461bcd60e51b8152602060048201526014602482015273141bdbdb081b9bdd081a5b9a5d1a585b1a5e995960621b6044820152606490fd5b91908203918211611e9857565b93909192936000946000948580956120198484611efc565b9161202885611a428689611efc565b808310156122555784611a42876120428661204795611ff4565b611efc565b9582158015959061224757606461206360ff601e5b1684611efc565b0497868061223e575b612236575b509591949093855b600a8710612090575b505050505050505093929190565b9194995092959a50819499506120a7878793611e8b565b986120b28a85611f0f565b6120bc908d611ff4565b9b8c9a6120c98a84611ff4565b9b6120d49083611ff4565b6120de908d611efc565b906120e891611f0f565b9b8d8d996120f591611e8b565b8d8082106121e05750898111806121d9575b806121cf575b612119575b5050612082565b82611a428a6120428d61212b95611ff4565b808b1180806121c5575b61213f5750612112565b85969798999a9b92939495506000146121bc5761215b91611ff4565b985b8915806121b3575b612197575b505b600019811461218357600101959194909692612079565b634e487b7160e01b84526011600452602484fd5b6121ac919950611a4289612042888694611ff4565b973861216a565b50808610612165565b5050839861215d565b50818c1015612135565b506009821061210d565b5087612107565b898495969798999a9b506120426121fd949d9593611a4293611ff4565b90836122098383611e8b565b1161221e579061221891611e8b565b9761216c565b50505095509650505050503880808080808080612082565b975038612071565b5088811061206c565b606461206360ff603261205c565b9850985050505050915083929190565b51906001600160701b0382168203610a3f57565b6003546001600160a01b03908116919082156123b757604051630240bc6b60e21b815290606082600481875afa93841561234e57600092839561235a575b5090602060049260405193848092630dfe168160e01b82525afa91821561234e5760009261232e575b50807f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b1691161460001461231e576001600160701b03809116921690565b6001600160701b03928316921690565b61234791925060203d81116112b9576112ab8183611e69565b90386122e0565b6040513d6000823e3d90fd5b929094506060833d82116123af575b8161237660609383611e69565b81010312610aeb5761238783612265565b90604061239660208601612265565b94015163ffffffff811603610aeb5750919360206122b7565b3d9150612369565b60405162461bcd60e51b815260206004820152600c60248201526b141bdbdb081b9bdd081cd95d60a21b6044820152606490fd5b6040805163095ea7b360e01b81527f00000000000000000000000098bf93ebf5c380c0e6ae8e192a7e2ae08edacc026001600160a01b038181166004808501919091526024840186905296600096909560209490939192907f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2782169086816044818d865af1801561279357612776575b5086519467ffffffffffffffff94606087018681118882101761276357895260028752878701928936853787511561275057835286516001101561273d57928b95928b9c959289989560248c9b9d9e9d857f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b168d819e82908c015251928380926370a0823160e01b9d8e835230908301525afa9d8e15612732579d6126fd575b5061252861271091611ee5565b049561012c420142116126ea57959290918e94928c519788956338ed173960e01b875260a48701928c880152602487015260a060448701525180915260c485019190865b8c8282106126c45750505050839182869230606483015261012c420160848301520393165af180156126ba57908594939291612615575b505060249086519586938492835230908301525afa92831561260c575085926125d7575b50506125d4929350611ff4565b90565b90809250813d8311612605575b6125ee8183611e69565b81010312611cf1576125d4929350518392386125c7565b503d6125e4565b513d87823e3d90fd5b90919293503d808b843e6126298184611e69565b820191858184031261269f578051908282116126b6570182601f8201121561269f5780519182116126a35785808360051b938a519061266a83870183611e69565b8152019282010192831161269f579085809594939201905b82821061268f57506125a3565b8151815286959182019101612682565b8a80fd5b634e487b7160e01b8b526041855260248bfd5b8b80fd5b87513d8c823e3d90fd5b919496509282959750819087600194511681520194019101908f9593919288959361256c565b634e487b7160e01b8f526011895260248ffd5b909c508981813d831161272b575b6127158183611e69565b8101031261272757519b61252861251b565b8e80fd5b503d61270b565b8d51903d90823e3d90fd5b634e487b7160e01b8b5260328c5260248bfd5b634e487b7160e01b8c5260328d5260248cfd5b634e487b7160e01b8c5260418d5260248cfd5b61278c90873d8911610511576105028183611e69565b503861247b565b88513d8c823e3d90fd5b6040805163095ea7b360e01b8082526001600160a01b037f00000000000000000000000098bf93ebf5c380c0e6ae8e192a7e2ae08edacc0281811660048086019190915260248501879052979660209660009593949193909291907f000000000000000000000000322cea42a77c2f18b8e79cc46efbacf73b6a8e6b9089816044818b868c165af18015612a6257612a89575b5087519182526001600160a01b038316828c01908152602081018690527f000000000000000000000000a1077a294dde1b09bb078844df40758a5d0f9a2792908a908290819060400103818b8b88165af18015612a6257918c9998979593916024979593612a6c575b508a86600354168a51988980926370a0823160e01b9e8f835230908301525afa968715612a62578897612a2f575b50612710806128d586611ee5565b04906128e087611ee5565b0461012c420191824211612a1b57928f8b9361291a918b948f9760609b9a9998519c8d9b8c9a8b9862e8e33760e81b8a5230968a01611f4a565b0393165af18015612a11576129f3575b506003541693835190815230888201528581602481885afa9081156129e9579086929184916129b6575b5061296461dead92604492611ff4565b98848651978894859363a9059cbb60e01b85528401528b60248401525af19182156129ac575050612993575050565b816129a992903d10610511576105028183611e69565b50565b51903d90823e3d90fd5b8381939492503d83116129e2575b6129ce8183611e69565b810103126101a35751859190612964612954565b503d6129c4565b84513d85823e3d90fd5b612a0a9060603d81116105b6576105a28183611e69565b505061292a565b85513d86823e3d90fd5b8f8b6011602492634e487b7160e01b835252fd5b9096508a81813d8311612a5b575b612a478183611e69565b81010312612a57575195386128c7565b8780fd5b503d612a3d565b89513d8a823e3d90fd5b612a82908c8d3d10610511576105028183611e69565b5038612899565b612a9f908a3d8c11610511576105028183611e69565b503861283056fea2646970667358221220015d55e1b1307a780ade48dc08e83d26ec4a1b99a9ee701027361db14fd42ed064736f6c63430008140033