diff --git a/onchain/.gitignore b/onchain/.gitignore index e4f1ab3..66e7d86 100644 --- a/onchain/.gitignore +++ b/onchain/.gitignore @@ -13,5 +13,8 @@ docs/ # Dotenv file .env .secret +.swp -/broadcast/ \ No newline at end of file +/broadcast/ + +tags diff --git a/onchain/README.md b/onchain/README.md index d9daabd..265ba83 100644 --- a/onchain/README.md +++ b/onchain/README.md @@ -110,6 +110,10 @@ address: 0xCc7467616bBDB574D04C7e9d2B0982c59F33D43c ## Deployment on Base Sepolia +### Multisig + +address: 0xf6a3eef9088A255c32b6aD2025f83E57291D9011 + ### Harberg address: 0x54838DC097E7fC4736B801bF1c1FCf1597348265 @@ -130,3 +134,40 @@ open features: todos: - write unit test for capital exit function +- would anchorLiquidityShare affect capitalInefficiency? +- could the UBI pool run dry? +- what happens if discovery runs out? + +## Simulation data: + +```json +{ "VWAP":0, + "comEthBal":1234, + "comHarbBal":0, + "comStakeShare":0, + "liquidity":[{ + "liquidity":1234, + "tickLower":-123, + "tickUpper":124 + }], + "startTime":1234, + "txns":[{ + "action":5,"timeOffset":0,"x":0,"y":""}, + {"action":0,"ethAmount":1,"x":0,"y":""}, + {"action":5,"timeOffset":0,"x":0,"y":""}, + {"action":0,"ethAmount":2,"x":0,"y":""}, + {"action":5,"timeOffset":0,"x":0,"y":""}, + {"action":0,"ethAmount":4,"x":0,"y":""}, + {"action":2,"harbAmount":3000,"tax":10,"y":""}, + {"action":5,"timeOffset":0,"x":0,"y":""}, + {"action":4,"positionId":654321,"x":0,"y":""}, + {"action":2,"harbAmount":5000,"tax":20,"y":""}, + {"action":0,"ethAmount":8,"x":0,"y":""}, + {"action":5,"timeOffset":0,"x":0,"y":""}, + {"action":1,"harbAmount":20000,"x":0,"y":""}, + {"action":5,"timeOffset":0,"x":0,"y":""}, + {"action":4,"positionId":654321,"x":0,"y":""}, + {"action":2,"harbAmount":8000,"tax":29,"y":""} + ] +} +``` diff --git a/onchain/script/DeployScript.sol b/onchain/script/DeployScript.sol index 203da21..c29096b 100644 --- a/onchain/script/DeployScript.sol +++ b/onchain/script/DeployScript.sol @@ -6,57 +6,27 @@ import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; import "../src/Harberg.sol"; import "../src/Stake.sol"; +import "../src/Sentimenter.sol"; +import "../src/helpers/UniswapHelpers.sol"; import {LiquidityManager} from "../src/LiquidityManager.sol"; +import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; uint24 constant FEE = uint24(10_000); contract DeployScript is Script { + using UniswapHelpers for IUniswapV3Pool; + bool token0isWeth; address feeDest; address weth; address v3Factory; address twabc; - - function sqrt(uint256 y) internal pure returns (uint256 z) { - if (y > 3) { - z = y; - uint256 x = y / 2 + 1; - while (x < z) { - z = x; - x = (y / x + x) / 2; - } - } else if (y != 0) { - z = 1; - } - // z is now the integer square root of y, or the closest integer to the square root of y. - } - - function initializePoolFor1Cent(address _pool) public { - uint256 price; - if (token0isWeth) { - // ETH as token0, so we are setting the price of 1 ETH in terms of token1 (USD cent) - price = 3000 * 10**20; // 1 ETH = 3700 USD, scaled by 10^18 for precision - } else { - // Token (valued at 1 USD cent) as token0, ETH as token1 - // We invert the logic to represent the price of 1 token in terms of ETH - price = uint256(10**16) / 3700; // Adjust for 18 decimal places - } - - uint160 sqrtPriceX96 = uint160(sqrt(price) * 2**96 / 10**9); // Adjust sqrt value to 96-bit precision - - console.log(uint160(sqrt(3000 * 10**20) * 2**96 / 10**9)); - - // Initialize pool with the calculated sqrtPriceX96 - IUniswapV3Pool(_pool).initialize(sqrtPriceX96); - } - function run() public { string memory seedPhrase = vm.readFile(".secret"); uint256 privateKey = vm.deriveKey(seedPhrase, 0); vm.startBroadcast(privateKey); address sender = vm.addr(privateKey); - console.log(sender); TwabController tc; if (twabc == address(0)) { @@ -71,9 +41,12 @@ contract DeployScript is Script { harb.setStakingPool(address(stake)); IUniswapV3Factory factory = IUniswapV3Factory(v3Factory); address liquidityPool = factory.createPool(weth, address(harb), FEE); - initializePoolFor1Cent(liquidityPool); + IUniswapV3Pool(liquidityPool).initializePoolFor1Cent(token0isWeth); harb.setLiquidityPool(liquidityPool); - LiquidityManager liquidityManager = new LiquidityManager(v3Factory, weth, address(harb)); + Sentimenter sentimenter = new Sentimenter(); + bytes memory params = abi.encodeWithSignature("initialize(address,address)", address(harb),address(stake)); + ERC1967Proxy proxy = new ERC1967Proxy(address(sentimenter), params); + LiquidityManager liquidityManager = new LiquidityManager(v3Factory, weth, address(harb), address(proxy)); liquidityManager.setFeeDestination(feeDest); // note: this delayed initialization is not a security issue. harb.setLiquidityManager(address(liquidityManager)); diff --git a/onchain/src/LiquidityManager.sol b/onchain/src/LiquidityManager.sol index e6fc731..2c4732b 100644 --- a/onchain/src/LiquidityManager.sol +++ b/onchain/src/LiquidityManager.sol @@ -14,7 +14,7 @@ import {Math} from "@openzeppelin/utils/math/Math.sol"; import {ABDKMath64x64} from "@abdk/ABDKMath64x64.sol"; import "./interfaces/IWETH9.sol"; import {Harberg} from "./Harberg.sol"; - +import {Sentimenter} from "./Sentimenter.sol"; /** * @title LiquidityManager for Harberg Token on Uniswap V3 @@ -42,14 +42,6 @@ contract LiquidityManager { uint128 internal constant DISCOVERY_DEPTH = 200; // 500 // 500% // only working with UNI V3 1% fee tier pools uint24 internal constant FEE = uint24(10_000); - // ANCHOR_LIQ_SHARE is the mininum share of total ETH in control - // that will be left to put into anchor positon. - uint256 internal constant MIN_ANCHOR_LIQ_SHARE = 5; // 5 = 5% - uint256 internal constant MAX_ANCHOR_LIQ_SHARE = 25; - // virtual liabilities that are added to push the calculated floor price down artificially, - // creating a security margin for attacks on liquidity - uint256 internal constant MIN_CAPITAL_INEFFICIENCY = 70; - uint256 internal constant MAX_CAPITAL_INEFFICIENCY = 200; // used to double-check price with uni oracle uint32 internal constant PRICE_STABILITY_INTERVAL = 300; // 5 minutes in seconds int24 internal constant MAX_TICK_DEVIATION = 50; // how much is that? @@ -58,6 +50,7 @@ contract LiquidityManager { address private immutable factory; IWETH9 private immutable weth; Harberg private immutable harb; + Sentimenter private immutable sentimenter; IUniswapV3Pool private immutable pool; bool private immutable token0isWeth; PoolKey private poolKey; @@ -76,16 +69,12 @@ contract LiquidityManager { mapping(Stage => TokenPosition) public positions; // the address where liquidity fees will be sent address public feeDestination; - // the minimum share of ETH that will be put into the anchor - uint256 public anchorLiquidityShare; - // the higher the inefficiency, the more conservative the positioning of floor - uint256 public capitalInefficiency; error ZeroAddressInSetter(); error AddressAlreadySet(); - event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 capitalInefficiency, uint256 anchorLiquidityShare, int24 vwapTick); - event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 capitalInefficiency, uint256 anchorLiquidityShare, int24 vwapTick); + event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 sentiment, int24 vwapTick); + event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, uint256 sentiment, int24 vwapTick); /// @dev Function modifier to ensure that the caller is the feeDestination modifier onlyFeeDestination() { @@ -98,15 +87,14 @@ contract LiquidityManager { /// @param _WETH9 The address of the WETH contract for handling ETH in trades. /// @param _harb The address of the Harberg token contract. /// @dev Computes the Uniswap pool address for the Harberg-WETH pair and sets up the initial configuration for the liquidity manager. - constructor(address _factory, address _WETH9, address _harb) { + constructor(address _factory, address _WETH9, address _harb, address _sentimenter) { factory = _factory; weth = IWETH9(_WETH9); poolKey = PoolAddress.getPoolKey(_WETH9, _harb, FEE); pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); harb = Harberg(_harb); token0isWeth = _WETH9 < _harb; - anchorLiquidityShare = MAX_ANCHOR_LIQ_SHARE; - capitalInefficiency = MIN_CAPITAL_INEFFICIENCY; // starting at 95% fuzzer passes tests + sentimenter = Sentimenter(_sentimenter); } /// @notice Callback function that Uniswap V3 calls for liquidity actions requiring minting or burning of tokens. @@ -137,18 +125,6 @@ contract LiquidityManager { feeDestination = feeDestination_; } - function setAnchorLiquidityShare(uint256 anchorLiquidityShare_) external onlyFeeDestination { - require(anchorLiquidityShare_ >= MIN_ANCHOR_LIQ_SHARE, "anchor liquidity share too low."); - require(anchorLiquidityShare_ <= MAX_ANCHOR_LIQ_SHARE, "anchor liquidity share too high."); - anchorLiquidityShare = anchorLiquidityShare_; - } - - function setCapitalInfefficiency(uint256 capitalInefficiency_) external onlyFeeDestination { - require(capitalInefficiency_ >= MIN_CAPITAL_INEFFICIENCY, "capital inefficiency is too low."); - require(capitalInefficiency_ <= MAX_CAPITAL_INEFFICIENCY, "capital inefficiency is too high."); - capitalInefficiency = capitalInefficiency_; - } - function setMinStakeSupplyFraction(uint256 mssf_) external onlyFeeDestination { harb.setMinStakeSupplyFraction(mssf_); } @@ -231,15 +207,18 @@ contract LiquidityManager { /// @notice Internal function to set or adjust the floor, anchor, and discovery positions based on current market conditions and the manager's strategy. /// @param currentTick The current market tick. /// @dev Recalculates and realigns all liquidity positions according to the latest market data and strategic requirements. - function _set(int24 currentTick) internal { + function _set(int24 currentTick, uint256 sentiment) internal { // estimate the lower tick of the anchor int24 vwapTick; uint256 outstandingSupply = harb.outstandingSupply(); uint256 ethBalance = (address(this).balance + weth.balanceOf(address(this))); - uint256 floorEthBalance = ethBalance * (100 - anchorLiquidityShare) / 100; + // this enforces an floor liquidity share of 75% to 95 %; + uint256 floorEthBalance = (3 * ethBalance / 4) + (2 * sentiment * ethBalance / 10**19); if (outstandingSupply > 0) { - vwapTick = tickAtPrice(token0isWeth, outstandingSupply * capitalInefficiency / 100 , floorEthBalance); + // this enables a "capital inefficiency" of 70% to 170%; + uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * sentiment / 10**18); + vwapTick = tickAtPrice(token0isWeth, balancedCapital, floorEthBalance); } else { vwapTick = token0isWeth ? currentTick + ANCHOR_SPACING : currentTick - ANCHOR_SPACING; } @@ -311,15 +290,17 @@ contract LiquidityManager { uint256 vwapX96 = 0; uint256 requiredEthForBuyback = 0; if (cumulativeVolume > 0) { - vwapX96 = cumulativeVolumeWeightedPriceX96 * capitalInefficiency / 100 / cumulativeVolume; // in harb/eth + vwapX96 = cumulativeVolumeWeightedPriceX96 / cumulativeVolume; // in harb/eth + vwapX96 = (7 * vwapX96 / 10) + (vwapX96 * sentiment / 10**18); requiredEthForBuyback = outstandingSupply.mulDiv(vwapX96, (1 << 96)); } // make a new calculation of the vwapTick, having updated outstandingSupply if (floorEthBalance < requiredEthForBuyback) { // not enough ETH, find a lower price requiredEthForBuyback = floorEthBalance; - vwapTick = tickAtPrice(token0isWeth, outstandingSupply * capitalInefficiency / 100 , requiredEthForBuyback); - emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, capitalInefficiency, anchorLiquidityShare, vwapTick); + uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * sentiment / 10**18); + vwapTick = tickAtPrice(token0isWeth, balancedCapital , requiredEthForBuyback); + emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, sentiment, vwapTick); } else if (vwapX96 == 0) { requiredEthForBuyback = floorEthBalance; vwapTick = currentTick; @@ -328,7 +309,7 @@ contract LiquidityManager { vwapTick = tickAtPriceRatio(int128(int256(vwapX96 >> 32))); // convert to pool tick vwapTick = token0isWeth ? -vwapTick : vwapTick; - emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, capitalInefficiency, anchorLiquidityShare, vwapTick); + emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, sentiment, vwapTick); } // move floor below anchor, if needed if (token0isWeth) { @@ -451,7 +432,7 @@ contract LiquidityManager { /// @notice Adjusts liquidity positions in response to an increase or decrease in the Harberg token's price. /// @dev This function should be called when significant price movement is detected. It recalibrates the liquidity ranges to align with the new market conditions. - function recenter() external { + function recenter() external returns (bool isUp, uint256 sentiment) { if (recenterAccess != address(0)) { require(msg.sender == recenterAccess, "access denied"); } @@ -460,7 +441,7 @@ contract LiquidityManager { // check slippage with oracle require(_isPriceStable(currentTick), "price deviated from oracle"); - bool isUp = false; + isUp = false; // check how price moved if (positions[Stage.ANCHOR].liquidity > 0) { // get the anchor position @@ -484,8 +465,16 @@ contract LiquidityManager { if (isUp) { harb.setPreviousTotalSupply(harb.totalSupply()); } + + try sentimenter.getSentiment() returns (uint256 currentSentiment) { + sentiment = (currentSentiment > 10**18) ? 10**18 : currentSentiment; + } catch { + //sentiment = 10**18 / 2; + sentiment = 0; + } + // set new positions - _set(currentTick); + _set(currentTick, sentiment); } } diff --git a/onchain/src/Sentimenter.sol b/onchain/src/Sentimenter.sol new file mode 100644 index 0000000..4a45c00 --- /dev/null +++ b/onchain/src/Sentimenter.sol @@ -0,0 +1,79 @@ + +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.19; + +import {Harberg} from "./Harberg.sol"; +import {Stake} from "./Stake.sol"; +import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol"; +import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; + +contract Sentimenter is Initializable, UUPSUpgradeable { + + Harberg private harberg; + Stake private stake; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error UnauthorizedAccount(address account); + + function initialize(address _harberg, address _stake) initializer public { + _changeAdmin(msg.sender); + harberg = Harberg(_harberg); + stake = Stake(_stake); + } + /** + * @dev Throws if called by any account other than the admin. + */ + modifier onlyAdmin() { + _checkAdmin(); + _; + } + + + /** + * @dev Throws if the sender is not the admin. + */ + function _checkAdmin() internal view virtual { + if (_getAdmin() != msg.sender) { + revert UnauthorizedAccount(msg.sender); + } + } + function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {} + + function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked) public pure returns (uint256 sentimentValue) { + uint256 deltaS = 10**18 - percentageStaked; + + if (percentageStaked > 92 * 10**16) { + // Rapid drop for high percentageStaked values + uint256 penalty = (deltaS * deltaS * deltaS * averageTaxRate) / (20 * 10**48); + sentimentValue = penalty / 2; + } else { + // Linearly decreasing sentiment value with rising percentageStaked + uint256 baseSentiment = 10**18 - (percentageStaked * 10**18) / (92 * 10**16); // Decreases from 10**18 to 0 + + // Apply penalty based on averageTaxRate + if (averageTaxRate <= 10**16) { + sentimentValue = baseSentiment; // No penalty for low averageTaxRate + } else if (averageTaxRate <= 5 * 10**16) { + uint256 ratePenalty = (averageTaxRate - 10**16) * baseSentiment / (4 * 10**16); + sentimentValue = baseSentiment > ratePenalty ? baseSentiment - ratePenalty : 0; + } else { + sentimentValue = 10**18; // High averageTaxRate results in maximum sentiment value (low sentiment) + } + } + + return sentimentValue; + } + + + /// @notice Computes the staker sentiment based on the proportion of the authorized stake that is currently staked. + /// @return sentiment A number between 0 and 200 indicating the market sentiment. + function getSentiment() external returns (uint256 sentiment) { + uint256 percentageStaked = stake.getPercentageStaked(); + uint256 averageTaxRate = stake.getAverageTaxRate(); + sentiment = calculateSentiment(averageTaxRate, percentageStaked); + } + + +} diff --git a/onchain/src/Stake.sol b/onchain/src/Stake.sol index aa707a9..c0da882 100644 --- a/onchain/src/Stake.sol +++ b/onchain/src/Stake.sol @@ -60,7 +60,7 @@ contract Stake { address owner; uint32 creationTime; uint32 lastTaxTime; - uint32 taxRate; // e.g. value of 60 = 60% tax per year + uint32 taxRate; // index of TAX_RATES array } Harberg private immutable harberg; @@ -72,6 +72,9 @@ contract Stake { mapping(uint256 => StakingPosition) public positions; + // Array to keep track of total shares at each tax rate + uint256[] public totalSharesAtTaxRate; + /// @notice Initializes the stake contract with references to the Harberg contract and sets the initial position ID. /// @param _harberg Address of the Harberg contract which this Stake contract interacts with. /// @dev Sets up the total supply based on the decimals of the Harberg token plus a fixed offset. @@ -81,6 +84,8 @@ contract Stake { taxPool = Harberg(_harberg).TAX_POOL(); // start counting somewhere nextPositionId = 654321; + // Initialize totalSharesAtTaxRate array + totalSharesAtTaxRate = new uint256[](TAX_RATES.length); } function authorizedStake() private view returns (uint256) { @@ -104,12 +109,15 @@ contract Stake { if (assetsBefore - taxAmountDue > 0) { // if something left over, update storage uint256 shareAfterTax = assetsToShares(assetsBefore - taxAmountDue); - outstandingStake -= pos.share - shareAfterTax; + uint256 deltaShare = pos.share - shareAfterTax; + totalSharesAtTaxRate[pos.taxRate] -= deltaShare; + outstandingStake -= deltaShare; pos.share = shareAfterTax; pos.lastTaxTime = uint32(block.timestamp); emit PositionTaxPaid(positionId, pos.owner, taxAmountDue, shareAfterTax, pos.taxRate); } else { // if nothing left over, liquidate position + totalSharesAtTaxRate[pos.taxRate] -= pos.share; outstandingStake -= pos.share; emit PositionTaxPaid(positionId, pos.owner, taxAmountDue, 0, pos.taxRate); emit PositionRemoved(positionId, pos.owner, 0); @@ -122,6 +130,7 @@ contract Stake { /// @dev Internal function to close a staking position, transferring the remaining Harberg tokens back to the owner after tax payment. function _exitPosition(uint256 positionId, StakingPosition storage pos) private { + totalSharesAtTaxRate[pos.taxRate] -= pos.share; outstandingStake -= pos.share; address owner = pos.owner; uint256 assets = sharesToAssets(pos.share); @@ -137,6 +146,7 @@ contract Stake { require (sharesToTake < pos.share, "position too small"); uint256 assets = sharesToAssets(sharesToTake); pos.share -= sharesToTake; + totalSharesAtTaxRate[pos.taxRate] -= sharesToTake; outstandingStake -= sharesToTake; emit PositionShrunk(positionId, pos.owner, pos.share, assets); SafeERC20.safeTransfer(harberg, pos.owner, assets); @@ -252,6 +262,7 @@ contract Stake { sp.creationTime = uint32(block.timestamp); sp.taxRate = taxRate; + totalSharesAtTaxRate[taxRate] += sharesWanted; outstandingStake += sharesWanted; emit PositionCreated(positionId, sp.owner, assets, sp.share, sp.taxRate); } @@ -299,6 +310,8 @@ contract Stake { // to prevent snatch-and-change grieving attack, pay TAX_FLOOR_DURATION require(taxRate > pos.taxRate, "tax too low to snatch"); _payTax(positionId, pos, 0); + totalSharesAtTaxRate[pos.taxRate] -= pos.share; + totalSharesAtTaxRate[taxRate] += pos.share; pos.taxRate = taxRate; emit PositionRateHiked(positionId, pos.owner, taxRate); } @@ -345,5 +358,25 @@ contract Stake { amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE; } + /// @return averageTaxRate A number between 0 and 1e18 indicating the average tax rate. + function getAverageTaxRate() external view returns (uint256 averageTaxRate) { + + // Compute average tax rate weighted by shares + averageTaxRate = 0; + if (outstandingStake > 0) { + for (uint256 i = 0; i < TAX_RATES.length; i++) { + averageTaxRate += TAX_RATES[i] * totalSharesAtTaxRate[i]; + } + averageTaxRate = averageTaxRate / outstandingStake; + // normalize tax rate + averageTaxRate = averageTaxRate * 1e18 / TAX_RATES[TAX_RATES.length - 1]; + } + } + + /// @notice Computes the percentage of Harberg staked from outstanding Stake and authorized Stake. + /// @return percentageStaked A number between 0 and 1e18 indicating the percentage of Harberg supply staked. + function getPercentageStaked() external view returns (uint256 percentageStaked) { + percentageStaked = (outstandingStake * 1e18) / authorizedStake(); + } } diff --git a/onchain/src/helpers/UniswapHelpers.sol b/onchain/src/helpers/UniswapHelpers.sol new file mode 100644 index 0000000..0878aab --- /dev/null +++ b/onchain/src/helpers/UniswapHelpers.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; +import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; + +library UniswapHelpers { + + /** + * @notice Computes the integer square root of a given number. + * @param y The input value for which the square root is calculated. + * @return z The integer square root of the input. + */ + function sqrt(uint256 y) internal pure returns (uint256 z) { + if (y > 3) { + z = y; + uint256 x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } + + /** + * @notice Initializes a Uniswap V3 pool with the price of 1 cent per ETH or 1 ETH in terms of token price. + * @dev Uses the square root of the price to initialize the pool with proper scaling. + * @param _pool The address of the Uniswap V3 pool to initialize. + * @param token0isWeth A boolean indicating if WETH is token0 in the pool. + */ + function initializePoolFor1Cent(IUniswapV3Pool _pool, bool token0isWeth) internal { + uint256 price; + if (token0isWeth) { + // ETH as token0, so we are setting the price of 1 ETH in terms of token1 (USD cent) + price = 2400 * 10**20; // 1 ETH = 2400 USD, scaled by 10^18 for precision + } else { + // Token (valued at 1 USD cent) as token0, ETH as token1 + // We invert the logic to represent the price of 1 token in terms of ETH + price = uint256(10**16) / 2400; // Adjust for 18 decimal places + } + + // Calculate the square root of the price with scaling for Uniswap V3 initialization + uint160 sqrtPriceX96 = uint160(sqrt(price) * 2**96 / 10**9); + + // Initialize the pool with the calculated price + _pool.initialize(sqrtPriceX96); + } + + /** + * @notice Deploys the Uniswap V3 factory using the provided bytecode and returns the factory instance. + * @return factory The IUniswapV3Factory instance created by the deployment. + */ + function deployUniswapFactory() internal returns (IUniswapV3Factory factory) { + bytes memory factoryBytecode = hex"60a060405234801561001057600080fd5b503060601b608052600380546001600160a01b031916339081179091556040516000907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c908290a36101f4600081815260046020527ffb8cf1d12598d1a039dd1d106665851a96aadf67d0d9ed76fceea282119208b7805462ffffff1916600a90811790915560405190929160008051602061614b83398151915291a3610bb8600081815260046020527f72dffa9b822156d9cf4b0090fa0b656bcb9cc2b2c60eb6acfc20a34f54b31743805462ffffff1916603c90811790915560405190929160008051602061614b83398151915291a3612710600081815260046020527f8cc740d51daa94ff54f33bd779c2d20149f524c340519b49181be5a08615f829805462ffffff191660c890811790915560405190929160008051602061614b83398151915291a360805160601c615fd7610174600039806105515250615fd76000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063890357301161005b578063890357301461013b5780638a7c195f146101855780638da5cb5b146101b0578063a1671295146101b85761007d565b806313af4035146100825780631698ee82146100aa57806322afcccb14610102575b600080fd5b6100a86004803603602081101561009857600080fd5b50356001600160a01b03166101f4565b005b6100e6600480360360608110156100c057600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff16610267565b604080516001600160a01b039092168252519081900360200190f35b6101246004803603602081101561011857600080fd5b503562ffffff16610293565b6040805160029290920b8252519081900360200190f35b6101436102a8565b604080516001600160a01b0396871681529486166020860152929094168383015262ffffff16606083015260029290920b608082015290519081900360a00190f35b6100a86004803603604081101561019b57600080fd5b5062ffffff813516906020013560020b6102de565b6100e66103a1565b6100e6600480360360608110156101ce57600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff166103b0565b6003546001600160a01b0316331461020b57600080fd5b6003546040516001600160a01b038084169216907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c90600090a3600380546001600160a01b0319166001600160a01b0392909216919091179055565b60056020908152600093845260408085208252928452828420905282529020546001600160a01b031681565b60046020526000908152604090205460020b81565b600054600154600280546001600160a01b03938416939283169281169162ffffff600160a01b83041691600160b81b9004900b85565b6003546001600160a01b031633146102f557600080fd5b620f42408262ffffff161061030957600080fd5b60008160020b13801561032057506140008160020b125b61032957600080fd5b62ffffff8216600090815260046020526040902054600290810b900b1561034f57600080fd5b62ffffff828116600081815260046020526040808220805462ffffff1916600287900b958616179055517fc66a3fdf07232cdd185febcc6579d408c241b47ae2f9907d84be655141eeaecc9190a35050565b6003546001600160a01b031681565b60006103ba610546565b826001600160a01b0316846001600160a01b031614156103d957600080fd5b600080846001600160a01b0316866001600160a01b0316106103fc5784866103ff565b85855b90925090506001600160a01b03821661041757600080fd5b62ffffff8416600090815260046020526040902054600290810b9081900b61043e57600080fd5b6001600160a01b0383811660009081526005602090815260408083208685168452825280832062ffffff8a168452909152902054161561047d57600080fd5b61048a308484888561057d565b6001600160a01b03808516600081815260056020818152604080842089871680865290835281852062ffffff8e168087529084528286208054988a166001600160a01b0319998a1681179091558287529484528286208787528452828620818752845294829020805490971684179096558051600289900b815291820192909252815195995091947f783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b71189281900390910190a45050509392505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461057b57600080fd5b565b6040805160a0810182526001600160a01b03878116808352878216602080850182905292881684860181905262ffffff888116606080880182905260028a810b6080998a01819052600080546001600160a01b03199081169099178155600180548a1689179055825490981686177fffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffff16600160a01b8502177fffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffff16600160b81b91830b9095160293909317909255875180870194909452838801929092528281019190915285518083039091018152930193849052825192909101919091209091610686906106f5565b8190604051809103906000f59050801580156106a6573d6000803e3d6000fd5b50600080546001600160a01b0319908116909155600180549091169055600280547fffffffffffff00000000000000000000000000000000000000000000000000001690559695505050505050565b6158c8806107038339019056fe6101606040523480156200001257600080fd5b503060601b60805260408051630890357360e41b81529051600091339163890357309160048082019260a092909190829003018186803b1580156200005657600080fd5b505afa1580156200006b573d6000803e3d6000fd5b505050506040513d60a08110156200008257600080fd5b508051602080830151604084015160608086015160809096015160e896871b6001600160e81b0319166101005291811b6001600160601b031990811660e05292811b831660c0529390931b1660a052600282810b900b90921b610120529150620000f79082906200010f811b62002b8417901c565b60801b6001600160801b03191661014052506200017d565b60008082600281900b620d89e719816200012557fe5b05029050600083600281900b620d89e8816200013d57fe5b0502905060008460020b83830360020b816200015557fe5b0560010190508062ffffff166001600160801b038016816200017357fe5b0495945050505050565b60805160601c60a05160601c60c05160601c60e05160601c6101005160e81c6101205160e81c6101405160801c61567e6200024a60003980611fee5280614b5f5280614b96525080610c0052806128fd5280614bca5280614bfc525080610cef52806119cb5280611a0252806129455250806111c75280611a855280611ef4528061244452806129215280613e6b5250806108d252806112f55280611a545280611e8e52806123be5280613d2252508061207b528061227d52806128d9525080612bfb525061567e6000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c806370cf754a116100ee578063c45a015511610097578063ddca3f4311610071578063ddca3f4314610800578063f305839914610820578063f30dba9314610828578063f637731d146108aa576101ae565b8063c45a0155146107d1578063d0c93a7c146107d9578063d21220a7146107f8576101ae565b8063883bdbfd116100c8578063883bdbfd14610633578063a34123a71461073c578063a38807f214610776576101ae565b806370cf754a146105c65780638206a4d1146105ce57806385b66729146105f6576101ae565b80633850c7bd1161015b578063490e6cbc11610135578063490e6cbc146104705780634f1eb3d8146104fc578063514ea4bf1461054d5780635339c296146105a6576101ae565b80633850c7bd1461035b5780633c8a7d8d146103b45780634614131914610456576101ae565b80631ad8b03b1161018c5780631ad8b03b146102aa578063252c09d7146102e157806332148f6714610338576101ae565b80630dfe1681146101b3578063128acb08146101d75780631a68650214610286575b600080fd5b6101bb6108d0565b604080516001600160a01b039092168252519081900360200190f35b61026d600480360360a08110156101ed57600080fd5b6001600160a01b0382358116926020810135151592604082013592606083013516919081019060a08101608082013564010000000081111561022e57600080fd5b82018360208201111561024057600080fd5b8035906020019184600183028401116401000000008311171561026257600080fd5b5090925090506108f4565b6040805192835260208301919091528051918290030190f35b61028e6114ad565b604080516001600160801b039092168252519081900360200190f35b6102b26114bc565b60405180836001600160801b03168152602001826001600160801b031681526020019250505060405180910390f35b6102fe600480360360208110156102f757600080fd5b50356114d6565b6040805163ffffffff909516855260069390930b60208501526001600160a01b039091168383015215156060830152519081900360800190f35b6103596004803603602081101561034e57600080fd5b503561ffff1661151c565b005b610363611616565b604080516001600160a01b03909816885260029690960b602088015261ffff9485168787015292841660608701529216608085015260ff90911660a0840152151560c0830152519081900360e00190f35b61026d600480360360a08110156103ca57600080fd5b6001600160a01b03823516916020810135600290810b92604083013590910b916001600160801b036060820135169181019060a08101608082013564010000000081111561041757600080fd5b82018360208201111561042957600080fd5b8035906020019184600183028401116401000000008311171561044b57600080fd5b509092509050611666565b61045e611922565b60408051918252519081900360200190f35b6103596004803603608081101561048657600080fd5b6001600160a01b0382351691602081013591604082013591908101906080810160608201356401000000008111156104bd57600080fd5b8201836020820111156104cf57600080fd5b803590602001918460018302840111640100000000831117156104f157600080fd5b509092509050611928565b6102b2600480360360a081101561051257600080fd5b506001600160a01b03813516906020810135600290810b91604081013590910b906001600160801b0360608201358116916080013516611d83565b61056a6004803603602081101561056357600080fd5b5035611f9d565b604080516001600160801b0396871681526020810195909552848101939093529084166060840152909216608082015290519081900360a00190f35b61045e600480360360208110156105bc57600080fd5b503560010b611fda565b61028e611fec565b610359600480360360408110156105e457600080fd5b5060ff81358116916020013516612010565b6102b26004803603606081101561060c57600080fd5b506001600160a01b03813516906001600160801b036020820135811691604001351661220f565b6106a36004803603602081101561064957600080fd5b81019060208101813564010000000081111561066457600080fd5b82018360208201111561067657600080fd5b8035906020019184602083028401116401000000008311171561069857600080fd5b5090925090506124dc565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156106e75781810151838201526020016106cf565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561072657818101518382015260200161070e565b5050505090500194505050505060405180910390f35b61026d6004803603606081101561075257600080fd5b508035600290810b91602081013590910b90604001356001600160801b0316612569565b6107a06004803603604081101561078c57600080fd5b508035600290810b9160200135900b6126e0565b6040805160069490940b84526001600160a01b03909216602084015263ffffffff1682820152519081900360600190f35b6101bb6128d7565b6107e16128fb565b6040805160029290920b8252519081900360200190f35b6101bb61291f565b610808612943565b6040805162ffffff9092168252519081900360200190f35b61045e612967565b6108486004803603602081101561083e57600080fd5b503560020b61296d565b604080516001600160801b039099168952600f9790970b602089015287870195909552606087019390935260069190910b60808601526001600160a01b031660a085015263ffffffff1660c0840152151560e083015251908190036101000190f35b610359600480360360208110156108c057600080fd5b50356001600160a01b03166129db565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000806108ff612bf0565b85610936576040805162461bcd60e51b8152602060048201526002602482015261415360f01b604482015290519081900360640190fd5b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602083015261ffff600160b81b8204811693830193909352600160c81b810483166060830152600160d81b8104909216608082015260ff600160e81b8304811660a0830152600160f01b909204909116151560c082018190526109ef576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b87610a3a5780600001516001600160a01b0316866001600160a01b0316118015610a35575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038716105b610a6c565b80600001516001600160a01b0316866001600160a01b0316108015610a6c57506401000276a36001600160a01b038716115b610aa3576040805162461bcd60e51b815260206004820152600360248201526214d41360ea1b604482015290519081900360640190fd5b6000805460ff60f01b191681556040805160c08101909152808a610ad25760048460a0015160ff16901c610ae5565b60108460a0015160ff1681610ae357fe5b065b60ff1681526004546001600160801b03166020820152604001610b06612c27565b63ffffffff168152602001600060060b815260200160006001600160a01b031681526020016000151581525090506000808913905060006040518060e001604052808b81526020016000815260200185600001516001600160a01b03168152602001856020015160020b81526020018c610b8257600254610b86565b6001545b815260200160006001600160801b0316815260200184602001516001600160801b031681525090505b805115801590610bd55750886001600160a01b031681604001516001600160a01b031614155b15610f9f57610be261560e565b60408201516001600160a01b031681526060820151610c25906006907f00000000000000000000000000000000000000000000000000000000000000008f612c2b565b15156040830152600290810b810b60208301819052620d89e719910b1215610c5657620d89e7196020820152610c75565b6020810151620d89e860029190910b1315610c7557620d89e860208201525b610c828160200151612d6d565b6001600160a01b031660608201526040820151610d13908d610cbc578b6001600160a01b031683606001516001600160a01b031611610cd6565b8b6001600160a01b031683606001516001600160a01b0316105b610ce4578260600151610ce6565b8b5b60c085015185517f000000000000000000000000000000000000000000000000000000000000000061309f565b60c085015260a084015260808301526001600160a01b031660408301528215610d7557610d498160c00151826080015101613291565b825103825260a0810151610d6b90610d6090613291565b6020840151906132a7565b6020830152610db0565b610d828160a00151613291565b825101825260c08101516080820151610daa91610d9f9101613291565b6020840151906132c3565b60208301525b835160ff1615610df6576000846000015160ff168260c0015181610dd057fe5b60c0840180519290910491829003905260a0840180519091016001600160801b03169052505b60c08201516001600160801b031615610e3557610e298160c00151600160801b8460c001516001600160801b03166132d9565b60808301805190910190525b80606001516001600160a01b031682604001516001600160a01b03161415610f5e57806040015115610f35578360a00151610ebf57610e9d846040015160008760200151886040015188602001518a606001516008613389909695949392919063ffffffff16565b6001600160a01b03166080860152600690810b900b6060850152600160a08501525b6000610f0b82602001518e610ed657600154610edc565b84608001515b8f610eeb578560800151610eef565b6002545b608089015160608a015160408b0151600595949392919061351c565b90508c15610f17576000035b610f258360c00151826135ef565b6001600160801b031660c0840152505b8b610f44578060200151610f4d565b60018160200151035b600290810b900b6060830152610f99565b80600001516001600160a01b031682604001516001600160a01b031614610f9957610f8c82604001516136a5565b600290810b900b60608301525b50610baf565b836020015160020b816060015160020b1461107a57600080610fed86604001518660400151886020015188602001518a606001518b6080015160086139d1909695949392919063ffffffff16565b604085015160608601516000805461ffff60c81b1916600160c81b61ffff958616021761ffff60b81b1916600160b81b95909416949094029290921762ffffff60a01b1916600160a01b62ffffff60029490940b93909316929092029190911773ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116179055506110ac9050565b60408101516000805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039092169190911790555b8060c001516001600160801b031683602001516001600160801b0316146110f25760c0810151600480546001600160801b0319166001600160801b039092169190911790555b8a1561114257608081015160015560a08101516001600160801b03161561113d5760a0810151600380546001600160801b031981166001600160801b03918216909301169190911790555b611188565b608081015160025560a08101516001600160801b0316156111885760a0810151600380546001600160801b03808216600160801b92839004821690940116029190911790555b8115158b1515146111a157602081015181518b036111ae565b80600001518a0381602001515b90965094508a156112e75760008512156111f0576111f07f00000000000000000000000000000000000000000000000000000000000000008d87600003613b86565b60006111fa613cd4565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561127e57600080fd5b505af1158015611292573d6000803e3d6000fd5b5050505061129e613cd4565b6112a88289613e0d565b11156112e1576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b50611411565b600086121561131e5761131e7f00000000000000000000000000000000000000000000000000000000000000008d88600003613b86565b6000611328613e1d565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156113ac57600080fd5b505af11580156113c0573d6000803e3d6000fd5b505050506113cc613e1d565b6113d68288613e0d565b111561140f576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b505b60408082015160c083015160608085015184518b8152602081018b90526001600160a01b03948516818701526001600160801b039093169183019190915260020b60808201529151908e169133917fc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca679181900360a00190a350506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b6004546001600160801b031681565b6003546001600160801b0380821691600160801b90041682565b60088161ffff81106114e757600080fd5b015463ffffffff81169150640100000000810460060b90600160581b81046001600160a01b031690600160f81b900460ff1684565b600054600160f01b900460ff16611560576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611575612bf0565b60008054600160d81b900461ffff169061159160088385613eb5565b6000805461ffff808416600160d81b810261ffff60d81b19909316929092179092559192508316146115fe576040805161ffff80851682528316602082015281517fac49e518f90a358f652e4400164f05a5d8f7e35e7747279bc3a93dbf584e125a929181900390910190a15b50506000805460ff60f01b1916600160f01b17905550565b6000546001600160a01b03811690600160a01b810460020b9061ffff600160b81b8204811691600160c81b8104821691600160d81b8204169060ff600160e81b8204811691600160f01b90041687565b600080548190600160f01b900460ff166116ad576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b191690556001600160801b0385166116cd57600080fd5b60008061171b60405180608001604052808c6001600160a01b031681526020018b60020b81526020018a60020b81526020016117118a6001600160801b0316613f58565b600f0b9052613f69565b9250925050819350809250600080600086111561173d5761173a613cd4565b91505b841561174e5761174b613e1d565b90505b336001600160a01b031663d348799787878b8b6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117d057600080fd5b505af11580156117e4573d6000803e3d6000fd5b50505050600086111561183b576117f9613cd4565b6118038388613e0d565b111561183b576040805162461bcd60e51b815260206004820152600260248201526104d360f41b604482015290519081900360640190fd5b841561188b57611849613e1d565b6118538287613e0d565b111561188b576040805162461bcd60e51b81526020600482015260026024820152614d3160f01b604482015290519081900360640190fd5b8960020b8b60020b8d6001600160a01b03167f7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde338d8b8b60405180856001600160a01b03168152602001846001600160801b0316815260200183815260200182815260200194505050505060405180910390a450506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b60025481565b600054600160f01b900460ff1661196c576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611981612bf0565b6004546001600160801b0316806119c3576040805162461bcd60e51b81526020600482015260016024820152601360fa1b604482015290519081900360640190fd5b60006119f8867f000000000000000000000000000000000000000000000000000000000000000062ffffff16620f42406141a9565b90506000611a2f867f000000000000000000000000000000000000000000000000000000000000000062ffffff16620f42406141a9565b90506000611a3b613cd4565b90506000611a47613e1d565b90508815611a7a57611a7a7f00000000000000000000000000000000000000000000000000000000000000008b8b613b86565b8715611aab57611aab7f00000000000000000000000000000000000000000000000000000000000000008b8a613b86565b336001600160a01b031663e9cbafb085858a8a6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611b2d57600080fd5b505af1158015611b41573d6000803e3d6000fd5b505050506000611b4f613cd4565b90506000611b5b613e1d565b905081611b688588613e0d565b1115611ba0576040805162461bcd60e51b8152602060048201526002602482015261046360f41b604482015290519081900360640190fd5b80611bab8487613e0d565b1115611be3576040805162461bcd60e51b8152602060048201526002602482015261463160f01b604482015290519081900360640190fd5b8382038382038115611c725760008054600160e81b9004600f16908115611c16578160ff168481611c1057fe5b04611c19565b60005b90506001600160801b03811615611c4c57600380546001600160801b038082168401166001600160801b03199091161790555b611c66818503600160801b8d6001600160801b03166132d9565b60018054909101905550505b8015611cfd5760008054600160e81b900460041c600f16908115611ca2578160ff168381611c9c57fe5b04611ca5565b60005b90506001600160801b03811615611cd757600380546001600160801b03600160801b8083048216850182160291161790555b611cf1818403600160801b8d6001600160801b03166132d9565b60028054909101905550505b8d6001600160a01b0316336001600160a01b03167fbdbdb71d7860376ba52b25a5028beea23581364a40522f6bcfb86bb1f2dca6338f8f86866040518085815260200184815260200183815260200182815260200194505050505060405180910390a350506000805460ff60f01b1916600160f01b179055505050505050505050505050565b600080548190600160f01b900460ff16611dca576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19168155611de460073389896141e3565b60038101549091506001600160801b0390811690861611611e055784611e14565b60038101546001600160801b03165b60038201549093506001600160801b03600160801b909104811690851611611e3c5783611e52565b6003810154600160801b90046001600160801b03165b91506001600160801b03831615611eb7576003810180546001600160801b031981166001600160801b03918216869003821617909155611eb7907f0000000000000000000000000000000000000000000000000000000000000000908a908616613b86565b6001600160801b03821615611f1d576003810180546001600160801b03600160801b808304821686900382160291811691909117909155611f1d907f0000000000000000000000000000000000000000000000000000000000000000908a908516613b86565b604080516001600160a01b038a1681526001600160801b0380861660208301528416818301529051600288810b92908a900b9133917f70935338e69775456a85ddef226c395fb668b63fa0115f5f20610b388e6ca9c0919081900360600190a4506000805460ff60f01b1916600160f01b17905590969095509350505050565b60076020526000908152604090208054600182015460028301546003909301546001600160801b0392831693919281811691600160801b90041685565b60066020526000908152604090205481565b7f000000000000000000000000000000000000000000000000000000000000000081565b600054600160f01b900460ff16612054576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691638da5cb5b916004808301926020929190829003018186803b1580156120c157600080fd5b505afa1580156120d5573d6000803e3d6000fd5b505050506040513d60208110156120eb57600080fd5b50516001600160a01b0316331461210157600080fd5b60ff82161580612124575060048260ff16101580156121245750600a8260ff1611155b801561214e575060ff8116158061214e575060048160ff161015801561214e5750600a8160ff1611155b61215757600080fd5b60008054610ff0600484901b16840160ff908116600160e81b9081027fffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841617909355919004167f973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b1336010826040805160ff9390920683168252600f600486901c16602083015286831682820152918516606082015290519081900360800190a150506000805460ff60f01b1916600160f01b17905550565b600080548190600160f01b900460ff16612256576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691638da5cb5b916004808301926020929190829003018186803b1580156122c357600080fd5b505afa1580156122d7573d6000803e3d6000fd5b505050506040513d60208110156122ed57600080fd5b50516001600160a01b0316331461230357600080fd5b6003546001600160801b039081169085161161231f578361232c565b6003546001600160801b03165b6003549092506001600160801b03600160801b9091048116908416116123525782612366565b600354600160801b90046001600160801b03165b90506001600160801b038216156123e7576003546001600160801b038381169116141561239557600019909101905b600380546001600160801b031981166001600160801b039182168590038216179091556123e7907f00000000000000000000000000000000000000000000000000000000000000009087908516613b86565b6001600160801b0381161561246d576003546001600160801b03828116600160801b90920416141561241857600019015b600380546001600160801b03600160801b80830482168590038216029181169190911790915561246d907f00000000000000000000000000000000000000000000000000000000000000009087908416613b86565b604080516001600160801b0380851682528316602082015281516001600160a01b0388169233927f596b573906218d3411850b26a6b437d6c4522fdb43d2d2386263f86d50b8b151929081900390910190a36000805460ff60f01b1916600160f01b1790559094909350915050565b6060806124e7612bf0565b61255e6124f2612c27565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525054600454600896959450600160a01b820460020b935061ffff600160b81b8304811693506001600160801b0390911691600160c81b900416614247565b915091509250929050565b600080548190600160f01b900460ff166125b0576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916815560408051608081018252338152600288810b602083015287900b918101919091528190819061260990606081016125fc6001600160801b038a16613f58565b600003600f0b9052613f69565b925092509250816000039450806000039350600085118061262a5750600084115b15612669576003830180546001600160801b038082168089018216600160801b93849004831689019092169092029091176001600160801b0319161790555b604080516001600160801b0388168152602081018790528082018690529051600289810b92908b900b9133917f0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c919081900360600190a450506000805460ff60f01b1916600160f01b179055509094909350915050565b60008060006126ed612bf0565b6126f785856143a1565b600285810b810b60009081526005602052604080822087840b90930b825281206003830154600681900b9367010000000000000082046001600160a01b0316928492600160d81b810463ffffffff169284929091600160f81b900460ff168061275f57600080fd5b6003820154600681900b985067010000000000000081046001600160a01b03169650600160d81b810463ffffffff169450600160f81b900460ff16806127a457600080fd5b50506040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b810b6020840181905261ffff600160b81b8404811695850195909552600160c81b830485166060850152600160d81b8304909416608084015260ff600160e81b8304811660a0850152600160f01b909204909116151560c08301529093508e810b91900b1215905061284d575093909403965090039350900390506128d0565b8a60020b816020015160020b12156128c1576000612869612c27565b602083015160408401516004546060860151939450600093849361289f936008938893879392916001600160801b031690613389565b9a9003989098039b5050949096039290920396509091030392506128d0915050565b50949093039650039350900390505b9250925092565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b60015481565b60056020526000908152604090208054600182015460028301546003909301546001600160801b03831693600160801b909304600f0b9290600681900b9067010000000000000081046001600160a01b031690600160d81b810463ffffffff1690600160f81b900460ff1688565b6000546001600160a01b031615612a1e576040805162461bcd60e51b8152602060048201526002602482015261414960f01b604482015290519081900360640190fd5b6000612a29826136a5565b9050600080612a41612a39612c27565b60089061446a565b6040805160e0810182526001600160a01b038816808252600288810b6020808501829052600085870181905261ffff898116606088018190529089166080880181905260a08801839052600160c0909801979097528154600160f01b73ffffffffffffffffffffffffffffffffffffffff19909116871762ffffff60a01b1916600160a01b62ffffff9787900b9790971696909602959095177fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff16600160c81b9091021761ffff60d81b1916600160d81b909602959095177fff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1692909217909355835191825281019190915281519395509193507f98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c9592918290030190a150505050565b60008082600281900b620d89e71981612b9957fe5b05029050600083600281900b620d89e881612bb057fe5b0502905060008460020b83830360020b81612bc757fe5b0560010190508062ffffff166001600160801b03801681612be457fe5b0493505050505b919050565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614612c2557600080fd5b565b4290565b60008060008460020b8660020b81612c3f57fe5b05905060008660020b128015612c6657508460020b8660020b81612c5f57fe5b0760020b15155b15612c7057600019015b8315612ce557600080612c82836144b6565b600182810b810b600090815260208d9052604090205460ff83169190911b80016000190190811680151597509294509092509085612cc757888360ff16860302612cda565b88612cd1826144c8565b840360ff168603025b965050505050612d63565b600080612cf4836001016144b6565b91509150600060018260ff166001901b031990506000818b60008660010b60010b8152602001908152602001600020541690508060001415955085612d4657888360ff0360ff16866001010102612d5c565b8883612d5183614568565b0360ff168660010101025b9650505050505b5094509492505050565b60008060008360020b12612d84578260020b612d8c565b8260020b6000035b9050620d89e8811115612dca576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b600060018216612dde57600160801b612df0565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff1690506002821615612e24576ffff97272373d413259a46990580e213a0260801c5b6004821615612e43576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612e62576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612e81576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612ea0576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612ebf576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612ede576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612efe576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612f1e576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612f3e576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612f5e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612f7e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612f9e576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612fbe576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612fde576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612fff576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561301f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561303e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561305b576b048a170391f7dc42444e8fa20260801c5b60008460020b131561307657806000198161307257fe5b0490505b64010000000081061561308a57600161308d565b60005b60ff16602082901c0192505050919050565b60008080806001600160a01b03808916908a1610158187128015906131245760006130d88989620f42400362ffffff16620f42406132d9565b9050826130f1576130ec8c8c8c6001614652565b6130fe565b6130fe8b8d8c60016146cd565b955085811061310f578a965061311e565b61311b8c8b838661478a565b96505b5061316e565b8161313b576131368b8b8b60006146cd565b613148565b6131488a8c8b6000614652565b935083886000031061315c5789955061316e565b61316b8b8a8a600003856147d6565b95505b6001600160a01b038a81169087161482156131d15780801561318d5750815b6131a35761319e878d8c60016146cd565b6131a5565b855b95508080156131b2575081155b6131c8576131c3878d8c6000614652565b6131ca565b845b945061321b565b8080156131db5750815b6131f1576131ec8c888c6001614652565b6131f3565b855b9550808015613200575081155b613216576132118c888c60006146cd565b613218565b845b94505b8115801561322b57508860000385115b15613237578860000394505b81801561325657508a6001600160a01b0316876001600160a01b031614155b15613265578589039350613282565b61327f868962ffffff168a620f42400362ffffff166141a9565b93505b50505095509550955095915050565b6000600160ff1b82106132a357600080fd5b5090565b808203828113156000831215146132bd57600080fd5b92915050565b818101828112156000831215146132bd57600080fd5b600080806000198587098686029250828110908390030390508061330f576000841161330457600080fd5b508290049050613382565b80841161331b57600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150505b9392505050565b60008063ffffffff8716613430576000898661ffff1661ffff81106133aa57fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff16151560608301529092508a161461341c57613419818a8988614822565b90505b806020015181604001519250925050613510565b8688036000806134458c8c858c8c8c8c6148d2565b91509150816000015163ffffffff168363ffffffff161415613477578160200151826040015194509450505050613510565b805163ffffffff8481169116141561349f578060200151816040015194509450505050613510565b8151815160208085015190840151918390039286039163ffffffff80841692908516910360060b816134cd57fe5b05028460200151018263ffffffff168263ffffffff1686604001518660400151036001600160a01b031602816134ff57fe5b048560400151019650965050505050505b97509795505050505050565b600295860b860b60009081526020979097526040909620600181018054909503909455938301805490920390915560038201805463ffffffff600160d81b6001600160a01b036701000000000000008085048216909603169094027fffffffffff0000000000000000000000000000000000000000ffffffffffffff90921691909117600681810b90960390950b66ffffffffffffff1666ffffffffffffff199095169490941782810485169095039093160263ffffffff60d81b1990931692909217905554600160801b9004600f0b90565b60008082600f0b121561365457826001600160801b03168260000384039150816001600160801b03161061364f576040805162461bcd60e51b81526020600482015260026024820152614c5360f01b604482015290519081900360640190fd5b6132bd565b826001600160801b03168284019150816001600160801b031610156132bd576040805162461bcd60e51b81526020600482015260026024820152614c4160f01b604482015290519081900360640190fd5b60006401000276a36001600160a01b038316108015906136e1575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038316105b613716576040805162461bcd60e51b81526020600482015260016024820152602960f91b604482015290519081900360640190fd5b77ffffffffffffffffffffffffffffffffffffffff00000000602083901b166001600160801b03811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106137b757607f810383901c91506137c1565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b146139c257886001600160a01b03166139a682612d6d565b6001600160a01b031611156139bb57816139bd565b805b6139c4565b815b9998505050505050505050565b6000806000898961ffff1661ffff81106139e757fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff161515606083015290925089161415613a575788859250925050613510565b8461ffff168461ffff16118015613a7857506001850361ffff168961ffff16145b15613a8557839150613a89565b8491505b8161ffff168960010161ffff1681613a9d57fe5b069250613aac81898989614822565b8a8461ffff1661ffff8110613abd57fe5b825191018054602084015160408501516060909501511515600160f81b027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001600160a01b03909616600160581b027fff0000000000000000000000000000000000000000ffffffffffffffffffffff60069390930b66ffffffffffffff16640100000000026affffffffffffff000000001963ffffffff90971663ffffffff199095169490941795909516929092171692909217929092161790555097509795505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b60208310613c025780518252601f199092019160209182019101613be3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c64576040519150601f19603f3d011682016040523d82523d6000602084013e613c69565b606091505b5091509150818015613c97575080511580613c975750808060200190516020811015613c9457600080fd5b50515b613ccd576040805162461bcd60e51b81526020600482015260026024820152612a2360f11b604482015290519081900360640190fd5b5050505050565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001693919290918291908083835b60208310613d6d5780518252601f199092019160209182019101613d4e565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d8060008114613dcd576040519150601f19603f3d011682016040523d82523d6000602084013e613dd2565b606091505b5091509150818015613de657506020815110155b613def57600080fd5b808060200190516020811015613e0457600080fd5b50519250505090565b808201828110156132bd57600080fd5b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016939192909182919080838360208310613d6d5780518252601f199092019160209182019101613d4e565b6000808361ffff1611613ef3576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b8261ffff168261ffff1611613f09575081613382565b825b8261ffff168161ffff161015613f4f576001858261ffff1661ffff8110613f2e57fe5b01805463ffffffff191663ffffffff92909216919091179055600101613f0b565b50909392505050565b80600f81900b8114612beb57600080fd5b6000806000613f76612bf0565b613f88846020015185604001516143a1565b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602080840182905261ffff600160b81b8404811685870152600160c81b84048116606080870191909152600160d81b8504909116608086015260ff600160e81b8504811660a0870152600160f01b909404909316151560c08501528851908901519489015192890151939461402c9491939092909190614acf565b93508460600151600f0b6000146141a157846020015160020b816020015160020b12156140815761407a6140638660200151612d6d565b6140708760400151612d6d565b8760600151614c84565b92506141a1565b846040015160020b816020015160020b12156141775760045460408201516001600160801b03909116906140d3906140b7612c27565b60208501516060860151608087015160089493929187916139d1565b6000805461ffff60c81b1916600160c81b61ffff938416021761ffff60b81b1916600160b81b939092169290920217905581516040870151614123919061411990612d6d565b8860600151614c84565b93506141416141358760200151612d6d565b83516060890151614cc8565b92506141518187606001516135ef565b600480546001600160801b0319166001600160801b0392909216919091179055506141a1565b61419e6141878660200151612d6d565b6141948760400151612d6d565b8760600151614cc8565b91505b509193909250565b60006141b68484846132d9565b9050600082806141c257fe5b84860911156133825760001981106141d957600080fd5b6001019392505050565b6040805160609490941b6bffffffffffffffffffffffff1916602080860191909152600293840b60e890811b60348701529290930b90911b60378401528051808403601a018152603a90930181528251928201929092206000908152929052902090565b60608060008361ffff1611614287576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b865167ffffffffffffffff8111801561429f57600080fd5b506040519080825280602002602001820160405280156142c9578160200160208202803683370190505b509150865167ffffffffffffffff811180156142e457600080fd5b5060405190808252806020026020018201604052801561430e578160200160208202803683370190505b50905060005b87518110156143945761433f8a8a8a848151811061432e57fe5b60200260200101518a8a8a8a613389565b84838151811061434b57fe5b6020026020010184848151811061435e57fe5b60200260200101826001600160a01b03166001600160a01b03168152508260060b60060b81525050508080600101915050614314565b5097509795505050505050565b8060020b8260020b126143e1576040805162461bcd60e51b8152602060048201526003602482015262544c5560e81b604482015290519081900360640190fd5b620d89e719600283900b1215614424576040805162461bcd60e51b8152602060048201526003602482015262544c4d60e81b604482015290519081900360640190fd5b620d89e8600282900b1315614466576040805162461bcd60e51b815260206004820152600360248201526254554d60e81b604482015290519081900360640190fd5b5050565b6040805160808101825263ffffffff9283168082526000602083018190529282019290925260016060909101819052835463ffffffff1916909117909116600160f81b17909155908190565b60020b600881901d9161010090910790565b60008082116144d657600080fd5b600160801b82106144e957608091821c91015b68010000000000000000821061450157604091821c91015b640100000000821061451557602091821c91015b62010000821061452757601091821c91015b610100821061453857600891821c91015b6010821061454857600491821c91015b6004821061455857600291821c91015b60028210612beb57600101919050565b600080821161457657600080fd5b5060ff6001600160801b0382161561459157607f1901614599565b608082901c91505b67ffffffffffffffff8216156145b257603f19016145ba565b604082901c91505b63ffffffff8216156145cf57601f19016145d7565b602082901c91505b61ffff8216156145ea57600f19016145f2565b601082901c91505b60ff821615614604576007190161460c565b600882901c91505b600f82161561461e5760031901614626565b600482901c91505b60038216156146385760011901614640565b600282901c91505b6001821615612beb5760001901919050565b6000836001600160a01b0316856001600160a01b03161115614672579293925b8161469f5761469a836001600160801b03168686036001600160a01b0316600160601b6132d9565b6146c2565b6146c2836001600160801b03168686036001600160a01b0316600160601b6141a9565b90505b949350505050565b6000836001600160a01b0316856001600160a01b031611156146ed579293925b7bffffffffffffffffffffffffffffffff000000000000000000000000606084901b166001600160a01b03868603811690871661472957600080fd5b8361475957866001600160a01b031661474c8383896001600160a01b03166132d9565b8161475357fe5b0461477f565b61477f6147708383896001600160a01b03166141a9565b886001600160a01b0316614cf7565b979650505050505050565b600080856001600160a01b0316116147a157600080fd5b6000846001600160801b0316116147b757600080fd5b816147c95761469a8585856001614d02565b6146c28585856001614de3565b600080856001600160a01b0316116147ed57600080fd5b6000846001600160801b03161161480357600080fd5b816148155761469a8585856000614de3565b6146c28585856000614d02565b61482a61564a565b600085600001518503905060405180608001604052808663ffffffff1681526020018263ffffffff168660020b0288602001510160060b81526020016000856001600160801b03161161487e576001614880565b845b6001600160801b031673ffffffff00000000000000000000000000000000608085901b16816148ab57fe5b048860400151016001600160a01b0316815260200160011515815250915050949350505050565b6148da61564a565b6148e261564a565b888561ffff1661ffff81106148f357fe5b60408051608081018252919092015463ffffffff81168083526401000000008204600690810b810b900b6020840152600160581b82046001600160a01b031693830193909352600160f81b900460ff1615156060820152925061495890899089614ed8565b15614990578663ffffffff16826000015163ffffffff16141561497a57613510565b8161498783898988614822565b91509150613510565b888361ffff168660010161ffff16816149a557fe5b0661ffff1661ffff81106149b557fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b90910416151560608201819052909250614a6c57604080516080810182528a5463ffffffff811682526401000000008104600690810b810b900b6020830152600160581b81046001600160a01b031692820192909252600160f81b90910460ff161515606082015291505b614a7b88836000015189614ed8565b614ab2576040805162461bcd60e51b815260206004820152600360248201526213d31160ea1b604482015290519081900360640190fd5b614abf8989898887614f9b565b9150915097509795505050505050565b6000614ade60078787876141e3565b60015460025491925090600080600f87900b15614c24576000614aff612c27565b6000805460045492935090918291614b499160089186918591600160a01b810460020b9161ffff600160b81b83048116926001600160801b0390921691600160c81b900416613389565b9092509050614b8360058d8b8d8b8b87898b60007f000000000000000000000000000000000000000000000000000000000000000061513b565b9450614bba60058c8b8d8b8b87898b60017f000000000000000000000000000000000000000000000000000000000000000061513b565b93508415614bee57614bee60068d7f0000000000000000000000000000000000000000000000000000000000000000615325565b8315614c2057614c2060068c7f0000000000000000000000000000000000000000000000000000000000000000615325565b5050505b600080614c3660058c8c8b8a8a61538b565b9092509050614c47878a8484615437565b600089600f0b1215614c75578315614c6457614c6460058c6155cc565b8215614c7557614c7560058b6155cc565b50505050505095945050505050565b60008082600f0b12614caa57614ca5614ca085858560016146cd565b613291565b6146c5565b614cbd614ca085858560000360006146cd565b600003949350505050565b60008082600f0b12614ce457614ca5614ca08585856001614652565b614cbd614ca08585856000036000614652565b808204910615150190565b60008115614d755760006001600160a01b03841115614d3857614d3384600160601b876001600160801b03166132d9565b614d50565b6001600160801b038516606085901b81614d4e57fe5b045b9050614d6d614d686001600160a01b03881683613e0d565b6155f8565b9150506146c5565b60006001600160a01b03841115614da357614d9e84600160601b876001600160801b03166141a9565b614dba565b614dba606085901b6001600160801b038716614cf7565b905080866001600160a01b031611614dd157600080fd5b6001600160a01b0386160390506146c5565b600082614df15750836146c5565b7bffffffffffffffffffffffffffffffff000000000000000000000000606085901b168215614e91576001600160a01b03861684810290858281614e3157fe5b041415614e6257818101828110614e6057614e5683896001600160a01b0316836141a9565b93505050506146c5565b505b614e8882614e83878a6001600160a01b03168681614e7c57fe5b0490613e0d565b614cf7565b925050506146c5565b6001600160a01b03861684810290858281614ea857fe5b04148015614eb557508082115b614ebe57600080fd5b808203614e56614d68846001600160a01b038b16846141a9565b60008363ffffffff168363ffffffff1611158015614f0257508363ffffffff168263ffffffff1611155b15614f1e578163ffffffff168363ffffffff1611159050613382565b60008463ffffffff168463ffffffff1611614f46578363ffffffff1664010000000001614f4e565b8363ffffffff165b64ffffffffff16905060008563ffffffff168463ffffffff1611614f7f578363ffffffff1664010000000001614f87565b8363ffffffff165b64ffffffffff169091111595945050505050565b614fa361564a565b614fab61564a565b60008361ffff168560010161ffff1681614fc157fe5b0661ffff169050600060018561ffff16830103905060005b506002818301048961ffff87168281614fee57fe5b0661ffff8110614ffa57fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b9091041615156060820181905290955061506557806001019250614fd9565b898661ffff16826001018161507657fe5b0661ffff811061508257fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b909104161515606082015285519094506000906150ed908b908b614ed8565b905080801561510657506151068a8a8760000151614ed8565b15615111575061512e565b8061512157600182039250615128565b8160010193505b50614fd9565b5050509550959350505050565b60028a810b900b600090815260208c90526040812080546001600160801b031682615166828d6135ef565b9050846001600160801b0316816001600160801b031611156151b4576040805162461bcd60e51b81526020600482015260026024820152614c4f60f01b604482015290519081900360640190fd5b6001600160801b03828116159082161581141594501561528a578c60020b8e60020b1361525a57600183018b9055600283018a90556003830180547fffffffffff0000000000000000000000000000000000000000ffffffffffffff166701000000000000006001600160a01b038c16021766ffffffffffffff191666ffffffffffffff60068b900b161763ffffffff60d81b1916600160d81b63ffffffff8a16021790555b6003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555b82546001600160801b0319166001600160801b038216178355856152d35782546152ce906152c990600160801b9004600f90810b810b908f900b6132c3565b613f58565b6152f4565b82546152f4906152c990600160801b9004600f90810b810b908f900b6132a7565b8354600f9190910b6001600160801b03908116600160801b0291161790925550909c9b505050505050505050505050565b8060020b8260020b8161533457fe5b0760020b1561534257600080fd5b60008061535d8360020b8560020b8161535757fe5b056144b6565b600191820b820b60009081526020979097526040909620805460ff9097169190911b90951890945550505050565b600285810b80820b60009081526020899052604080822088850b850b83529082209193849391929184918291908a900b126153d1575050600182015460028301546153e4565b8360010154880391508360020154870390505b6000808b60020b8b60020b121561540657505060018301546002840154615419565b84600101548a0391508460020154890390505b92909803979097039b96909503949094039850939650505050505050565b6040805160a08101825285546001600160801b0390811682526001870154602083015260028701549282019290925260038601548083166060830152600160801b900490911660808201526000600f85900b6154d65781516001600160801b03166154ce576040805162461bcd60e51b815260206004820152600260248201526104e560f41b604482015290519081900360640190fd5b5080516154e5565b81516154e290866135ef565b90505b60006155098360200151860384600001516001600160801b0316600160801b6132d9565b9050600061552f8460400151860385600001516001600160801b0316600160801b6132d9565b905086600f0b6000146155565787546001600160801b0319166001600160801b0384161788555b60018801869055600288018590556001600160801b03821615158061558457506000816001600160801b0316115b156155c2576003880180546001600160801b031981166001600160801b039182168501821617808216600160801b9182900483168501909216021790555b5050505050505050565b600290810b810b6000908152602092909252604082208281556001810183905590810182905560030155565b806001600160a01b0381168114612beb57600080fd5b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160808101825260008082526020820181905291810182905260608101919091529056fea164736f6c6343000706000aa164736f6c6343000706000ac66a3fdf07232cdd185febcc6579d408c241b47ae2f9907d84be655141eeaecc"; + address factoryAddress = deployContract(factoryBytecode, ""); + factory = IUniswapV3Factory(factoryAddress); + } + + /** + * @notice Internal function to deploy a contract using its bytecode and constructor arguments. + * @param bytecode The contract bytecode to be deployed. + * @param constructorArgs The constructor arguments for the contract. + * @return addr The address of the deployed contract. + */ + function deployContract(bytes memory bytecode, bytes memory constructorArgs) internal returns (address addr) { + bytes memory fullCode = abi.encodePacked(bytecode, constructorArgs); + + // Deploy the contract with CREATE + assembly { + addr := create(0, add(fullCode, 0x20), mload(fullCode)) + if iszero(extcodesize(addr)) { + revert(0, 0) + } + } + } +} \ No newline at end of file diff --git a/onchain/test/LiquidityManager.t.sol b/onchain/test/LiquidityManager.t.sol index 650691b..698f553 100644 --- a/onchain/test/LiquidityManager.t.sol +++ b/onchain/test/LiquidityManager.t.sol @@ -4,17 +4,22 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "@aperture/uni-v3-lib/TickMath.sol"; import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol"; -import "../src/interfaces/IWETH9.sol"; import {WETH} from "solmate/tokens/WETH.sol"; import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; import {PoolAddress, PoolKey} from "@aperture/uni-v3-lib/PoolAddress.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; +import "../src/interfaces/IWETH9.sol"; import {Harberg} from "../src/Harberg.sol"; import {Stake, ExceededAvailableStake} from "../src/Stake.sol"; import {LiquidityManager} from "../src/LiquidityManager.sol"; - +import "../src/helpers/UniswapHelpers.sol"; +import {CSVHelper} from "./helpers/CSVHelper.sol"; +import {CSVManager} from "./helpers/CSVManager.sol"; +import {UniswapTestBase} from "./helpers/UniswapTestBase.sol"; +import "../src/Sentimenter.sol"; +import "../test/mocks/MockSentimenter.sol"; address constant TAX_POOL = address(2); // default fee of 1% @@ -27,18 +32,23 @@ contract Dummy { // This contract can be empty as it is only used to affect the nonce } -contract LiquidityManagerTest is Test { +contract LiquidityManagerTest is UniswapTestBase, CSVManager { + using UniswapHelpers for IUniswapV3Pool; + using CSVHelper for *; - IWETH9 weth; - Harberg harberg; IUniswapV3Factory factory; Stake stake; LiquidityManager lm; - IUniswapV3Pool pool; - bool token0isWeth; - address account = makeAddr("alice"); address feeDestination = makeAddr("fees"); - string csv; + + struct Response { + uint256 ethFloor; + uint256 ethAnchor; + uint256 ethDiscovery; + uint256 harbergFloor; + uint256 harbergAnchor; + uint256 harbergDiscovery; + } // Utility to deploy dummy contracts function deployDummies(uint count) internal { @@ -47,50 +57,8 @@ contract LiquidityManagerTest is Test { } } - - function sqrt(uint256 y) internal pure returns (uint256 z) { - if (y > 3) { - z = y; - uint256 x = y / 2 + 1; - while (x < z) { - z = x; - x = (y / x + x) / 2; - } - } else if (y != 0) { - z = 1; - } - // z is now the integer square root of y, or the closest integer to the square root of y. - } - - function initializePoolFor1Cent(address _pool) public { - uint256 price; - if (token0isWeth) { - // ETH as token0, so we are setting the price of 1 ETH in terms of token1 (USD cent) - price = 3000 * 10**20; // 1 ETH = 3700 USD, scaled by 10^18 for precision - } else { - // Token (valued at 1 USD cent) as token0, ETH as token1 - // We invert the logic to represent the price of 1 token in terms of ETH - price = uint256(10**16) / 3000; // Adjust for 18 decimal places - } - - uint160 sqrtPriceX96 = uint160(sqrt(price) * 2**96 / 10**9); // Adjust sqrt value to 96-bit precision - - // Initialize pool with the calculated sqrtPriceX96 - IUniswapV3Pool(_pool).initialize(sqrtPriceX96); - } - - function deployContract(bytes memory bytecode, bytes memory constructorArgs) internal returns (address addr) { - bytes memory deploymentData = abi.encodePacked(bytecode, constructorArgs); - assembly { - addr := create(0, add(deploymentData, 0x20), mload(deploymentData)) - } - require(addr != address(0), "Contract deployment failed"); - } - function setUpCustomToken0(bool token0shouldBeWeth) public { - bytes memory factoryBytecode = hex"60a060405234801561001057600080fd5b503060601b608052600380546001600160a01b031916339081179091556040516000907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c908290a36101f4600081815260046020527ffb8cf1d12598d1a039dd1d106665851a96aadf67d0d9ed76fceea282119208b7805462ffffff1916600a90811790915560405190929160008051602061614b83398151915291a3610bb8600081815260046020527f72dffa9b822156d9cf4b0090fa0b656bcb9cc2b2c60eb6acfc20a34f54b31743805462ffffff1916603c90811790915560405190929160008051602061614b83398151915291a3612710600081815260046020527f8cc740d51daa94ff54f33bd779c2d20149f524c340519b49181be5a08615f829805462ffffff191660c890811790915560405190929160008051602061614b83398151915291a360805160601c615fd7610174600039806105515250615fd76000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063890357301161005b578063890357301461013b5780638a7c195f146101855780638da5cb5b146101b0578063a1671295146101b85761007d565b806313af4035146100825780631698ee82146100aa57806322afcccb14610102575b600080fd5b6100a86004803603602081101561009857600080fd5b50356001600160a01b03166101f4565b005b6100e6600480360360608110156100c057600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff16610267565b604080516001600160a01b039092168252519081900360200190f35b6101246004803603602081101561011857600080fd5b503562ffffff16610293565b6040805160029290920b8252519081900360200190f35b6101436102a8565b604080516001600160a01b0396871681529486166020860152929094168383015262ffffff16606083015260029290920b608082015290519081900360a00190f35b6100a86004803603604081101561019b57600080fd5b5062ffffff813516906020013560020b6102de565b6100e66103a1565b6100e6600480360360608110156101ce57600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff166103b0565b6003546001600160a01b0316331461020b57600080fd5b6003546040516001600160a01b038084169216907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c90600090a3600380546001600160a01b0319166001600160a01b0392909216919091179055565b60056020908152600093845260408085208252928452828420905282529020546001600160a01b031681565b60046020526000908152604090205460020b81565b600054600154600280546001600160a01b03938416939283169281169162ffffff600160a01b83041691600160b81b9004900b85565b6003546001600160a01b031633146102f557600080fd5b620f42408262ffffff161061030957600080fd5b60008160020b13801561032057506140008160020b125b61032957600080fd5b62ffffff8216600090815260046020526040902054600290810b900b1561034f57600080fd5b62ffffff828116600081815260046020526040808220805462ffffff1916600287900b958616179055517fc66a3fdf07232cdd185febcc6579d408c241b47ae2f9907d84be655141eeaecc9190a35050565b6003546001600160a01b031681565b60006103ba610546565b826001600160a01b0316846001600160a01b031614156103d957600080fd5b600080846001600160a01b0316866001600160a01b0316106103fc5784866103ff565b85855b90925090506001600160a01b03821661041757600080fd5b62ffffff8416600090815260046020526040902054600290810b9081900b61043e57600080fd5b6001600160a01b0383811660009081526005602090815260408083208685168452825280832062ffffff8a168452909152902054161561047d57600080fd5b61048a308484888561057d565b6001600160a01b03808516600081815260056020818152604080842089871680865290835281852062ffffff8e168087529084528286208054988a166001600160a01b0319998a1681179091558287529484528286208787528452828620818752845294829020805490971684179096558051600289900b815291820192909252815195995091947f783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b71189281900390910190a45050509392505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461057b57600080fd5b565b6040805160a0810182526001600160a01b03878116808352878216602080850182905292881684860181905262ffffff888116606080880182905260028a810b6080998a01819052600080546001600160a01b03199081169099178155600180548a1689179055825490981686177fffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffff16600160a01b8502177fffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffff16600160b81b91830b9095160293909317909255875180870194909452838801929092528281019190915285518083039091018152930193849052825192909101919091209091610686906106f5565b8190604051809103906000f59050801580156106a6573d6000803e3d6000fd5b50600080546001600160a01b0319908116909155600180549091169055600280547fffffffffffff00000000000000000000000000000000000000000000000000001690559695505050505050565b6158c8806107038339019056fe6101606040523480156200001257600080fd5b503060601b60805260408051630890357360e41b81529051600091339163890357309160048082019260a092909190829003018186803b1580156200005657600080fd5b505afa1580156200006b573d6000803e3d6000fd5b505050506040513d60a08110156200008257600080fd5b508051602080830151604084015160608086015160809096015160e896871b6001600160e81b0319166101005291811b6001600160601b031990811660e05292811b831660c0529390931b1660a052600282810b900b90921b610120529150620000f79082906200010f811b62002b8417901c565b60801b6001600160801b03191661014052506200017d565b60008082600281900b620d89e719816200012557fe5b05029050600083600281900b620d89e8816200013d57fe5b0502905060008460020b83830360020b816200015557fe5b0560010190508062ffffff166001600160801b038016816200017357fe5b0495945050505050565b60805160601c60a05160601c60c05160601c60e05160601c6101005160e81c6101205160e81c6101405160801c61567e6200024a60003980611fee5280614b5f5280614b96525080610c0052806128fd5280614bca5280614bfc525080610cef52806119cb5280611a0252806129455250806111c75280611a855280611ef4528061244452806129215280613e6b5250806108d252806112f55280611a545280611e8e52806123be5280613d2252508061207b528061227d52806128d9525080612bfb525061567e6000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c806370cf754a116100ee578063c45a015511610097578063ddca3f4311610071578063ddca3f4314610800578063f305839914610820578063f30dba9314610828578063f637731d146108aa576101ae565b8063c45a0155146107d1578063d0c93a7c146107d9578063d21220a7146107f8576101ae565b8063883bdbfd116100c8578063883bdbfd14610633578063a34123a71461073c578063a38807f214610776576101ae565b806370cf754a146105c65780638206a4d1146105ce57806385b66729146105f6576101ae565b80633850c7bd1161015b578063490e6cbc11610135578063490e6cbc146104705780634f1eb3d8146104fc578063514ea4bf1461054d5780635339c296146105a6576101ae565b80633850c7bd1461035b5780633c8a7d8d146103b45780634614131914610456576101ae565b80631ad8b03b1161018c5780631ad8b03b146102aa578063252c09d7146102e157806332148f6714610338576101ae565b80630dfe1681146101b3578063128acb08146101d75780631a68650214610286575b600080fd5b6101bb6108d0565b604080516001600160a01b039092168252519081900360200190f35b61026d600480360360a08110156101ed57600080fd5b6001600160a01b0382358116926020810135151592604082013592606083013516919081019060a08101608082013564010000000081111561022e57600080fd5b82018360208201111561024057600080fd5b8035906020019184600183028401116401000000008311171561026257600080fd5b5090925090506108f4565b6040805192835260208301919091528051918290030190f35b61028e6114ad565b604080516001600160801b039092168252519081900360200190f35b6102b26114bc565b60405180836001600160801b03168152602001826001600160801b031681526020019250505060405180910390f35b6102fe600480360360208110156102f757600080fd5b50356114d6565b6040805163ffffffff909516855260069390930b60208501526001600160a01b039091168383015215156060830152519081900360800190f35b6103596004803603602081101561034e57600080fd5b503561ffff1661151c565b005b610363611616565b604080516001600160a01b03909816885260029690960b602088015261ffff9485168787015292841660608701529216608085015260ff90911660a0840152151560c0830152519081900360e00190f35b61026d600480360360a08110156103ca57600080fd5b6001600160a01b03823516916020810135600290810b92604083013590910b916001600160801b036060820135169181019060a08101608082013564010000000081111561041757600080fd5b82018360208201111561042957600080fd5b8035906020019184600183028401116401000000008311171561044b57600080fd5b509092509050611666565b61045e611922565b60408051918252519081900360200190f35b6103596004803603608081101561048657600080fd5b6001600160a01b0382351691602081013591604082013591908101906080810160608201356401000000008111156104bd57600080fd5b8201836020820111156104cf57600080fd5b803590602001918460018302840111640100000000831117156104f157600080fd5b509092509050611928565b6102b2600480360360a081101561051257600080fd5b506001600160a01b03813516906020810135600290810b91604081013590910b906001600160801b0360608201358116916080013516611d83565b61056a6004803603602081101561056357600080fd5b5035611f9d565b604080516001600160801b0396871681526020810195909552848101939093529084166060840152909216608082015290519081900360a00190f35b61045e600480360360208110156105bc57600080fd5b503560010b611fda565b61028e611fec565b610359600480360360408110156105e457600080fd5b5060ff81358116916020013516612010565b6102b26004803603606081101561060c57600080fd5b506001600160a01b03813516906001600160801b036020820135811691604001351661220f565b6106a36004803603602081101561064957600080fd5b81019060208101813564010000000081111561066457600080fd5b82018360208201111561067657600080fd5b8035906020019184602083028401116401000000008311171561069857600080fd5b5090925090506124dc565b604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156106e75781810151838201526020016106cf565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561072657818101518382015260200161070e565b5050505090500194505050505060405180910390f35b61026d6004803603606081101561075257600080fd5b508035600290810b91602081013590910b90604001356001600160801b0316612569565b6107a06004803603604081101561078c57600080fd5b508035600290810b9160200135900b6126e0565b6040805160069490940b84526001600160a01b03909216602084015263ffffffff1682820152519081900360600190f35b6101bb6128d7565b6107e16128fb565b6040805160029290920b8252519081900360200190f35b6101bb61291f565b610808612943565b6040805162ffffff9092168252519081900360200190f35b61045e612967565b6108486004803603602081101561083e57600080fd5b503560020b61296d565b604080516001600160801b039099168952600f9790970b602089015287870195909552606087019390935260069190910b60808601526001600160a01b031660a085015263ffffffff1660c0840152151560e083015251908190036101000190f35b610359600480360360208110156108c057600080fd5b50356001600160a01b03166129db565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000806108ff612bf0565b85610936576040805162461bcd60e51b8152602060048201526002602482015261415360f01b604482015290519081900360640190fd5b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602083015261ffff600160b81b8204811693830193909352600160c81b810483166060830152600160d81b8104909216608082015260ff600160e81b8304811660a0830152600160f01b909204909116151560c082018190526109ef576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b87610a3a5780600001516001600160a01b0316866001600160a01b0316118015610a35575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038716105b610a6c565b80600001516001600160a01b0316866001600160a01b0316108015610a6c57506401000276a36001600160a01b038716115b610aa3576040805162461bcd60e51b815260206004820152600360248201526214d41360ea1b604482015290519081900360640190fd5b6000805460ff60f01b191681556040805160c08101909152808a610ad25760048460a0015160ff16901c610ae5565b60108460a0015160ff1681610ae357fe5b065b60ff1681526004546001600160801b03166020820152604001610b06612c27565b63ffffffff168152602001600060060b815260200160006001600160a01b031681526020016000151581525090506000808913905060006040518060e001604052808b81526020016000815260200185600001516001600160a01b03168152602001856020015160020b81526020018c610b8257600254610b86565b6001545b815260200160006001600160801b0316815260200184602001516001600160801b031681525090505b805115801590610bd55750886001600160a01b031681604001516001600160a01b031614155b15610f9f57610be261560e565b60408201516001600160a01b031681526060820151610c25906006907f00000000000000000000000000000000000000000000000000000000000000008f612c2b565b15156040830152600290810b810b60208301819052620d89e719910b1215610c5657620d89e7196020820152610c75565b6020810151620d89e860029190910b1315610c7557620d89e860208201525b610c828160200151612d6d565b6001600160a01b031660608201526040820151610d13908d610cbc578b6001600160a01b031683606001516001600160a01b031611610cd6565b8b6001600160a01b031683606001516001600160a01b0316105b610ce4578260600151610ce6565b8b5b60c085015185517f000000000000000000000000000000000000000000000000000000000000000061309f565b60c085015260a084015260808301526001600160a01b031660408301528215610d7557610d498160c00151826080015101613291565b825103825260a0810151610d6b90610d6090613291565b6020840151906132a7565b6020830152610db0565b610d828160a00151613291565b825101825260c08101516080820151610daa91610d9f9101613291565b6020840151906132c3565b60208301525b835160ff1615610df6576000846000015160ff168260c0015181610dd057fe5b60c0840180519290910491829003905260a0840180519091016001600160801b03169052505b60c08201516001600160801b031615610e3557610e298160c00151600160801b8460c001516001600160801b03166132d9565b60808301805190910190525b80606001516001600160a01b031682604001516001600160a01b03161415610f5e57806040015115610f35578360a00151610ebf57610e9d846040015160008760200151886040015188602001518a606001516008613389909695949392919063ffffffff16565b6001600160a01b03166080860152600690810b900b6060850152600160a08501525b6000610f0b82602001518e610ed657600154610edc565b84608001515b8f610eeb578560800151610eef565b6002545b608089015160608a015160408b0151600595949392919061351c565b90508c15610f17576000035b610f258360c00151826135ef565b6001600160801b031660c0840152505b8b610f44578060200151610f4d565b60018160200151035b600290810b900b6060830152610f99565b80600001516001600160a01b031682604001516001600160a01b031614610f9957610f8c82604001516136a5565b600290810b900b60608301525b50610baf565b836020015160020b816060015160020b1461107a57600080610fed86604001518660400151886020015188602001518a606001518b6080015160086139d1909695949392919063ffffffff16565b604085015160608601516000805461ffff60c81b1916600160c81b61ffff958616021761ffff60b81b1916600160b81b95909416949094029290921762ffffff60a01b1916600160a01b62ffffff60029490940b93909316929092029190911773ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116179055506110ac9050565b60408101516000805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b039092169190911790555b8060c001516001600160801b031683602001516001600160801b0316146110f25760c0810151600480546001600160801b0319166001600160801b039092169190911790555b8a1561114257608081015160015560a08101516001600160801b03161561113d5760a0810151600380546001600160801b031981166001600160801b03918216909301169190911790555b611188565b608081015160025560a08101516001600160801b0316156111885760a0810151600380546001600160801b03808216600160801b92839004821690940116029190911790555b8115158b1515146111a157602081015181518b036111ae565b80600001518a0381602001515b90965094508a156112e75760008512156111f0576111f07f00000000000000000000000000000000000000000000000000000000000000008d87600003613b86565b60006111fa613cd4565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b15801561127e57600080fd5b505af1158015611292573d6000803e3d6000fd5b5050505061129e613cd4565b6112a88289613e0d565b11156112e1576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b50611411565b600086121561131e5761131e7f00000000000000000000000000000000000000000000000000000000000000008d88600003613b86565b6000611328613e1d565b9050336001600160a01b031663fa461e3388888c8c6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156113ac57600080fd5b505af11580156113c0573d6000803e3d6000fd5b505050506113cc613e1d565b6113d68288613e0d565b111561140f576040805162461bcd60e51b815260206004820152600360248201526249494160e81b604482015290519081900360640190fd5b505b60408082015160c083015160608085015184518b8152602081018b90526001600160a01b03948516818701526001600160801b039093169183019190915260020b60808201529151908e169133917fc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca679181900360a00190a350506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b6004546001600160801b031681565b6003546001600160801b0380821691600160801b90041682565b60088161ffff81106114e757600080fd5b015463ffffffff81169150640100000000810460060b90600160581b81046001600160a01b031690600160f81b900460ff1684565b600054600160f01b900460ff16611560576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611575612bf0565b60008054600160d81b900461ffff169061159160088385613eb5565b6000805461ffff808416600160d81b810261ffff60d81b19909316929092179092559192508316146115fe576040805161ffff80851682528316602082015281517fac49e518f90a358f652e4400164f05a5d8f7e35e7747279bc3a93dbf584e125a929181900390910190a15b50506000805460ff60f01b1916600160f01b17905550565b6000546001600160a01b03811690600160a01b810460020b9061ffff600160b81b8204811691600160c81b8104821691600160d81b8204169060ff600160e81b8204811691600160f01b90041687565b600080548190600160f01b900460ff166116ad576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b191690556001600160801b0385166116cd57600080fd5b60008061171b60405180608001604052808c6001600160a01b031681526020018b60020b81526020018a60020b81526020016117118a6001600160801b0316613f58565b600f0b9052613f69565b9250925050819350809250600080600086111561173d5761173a613cd4565b91505b841561174e5761174b613e1d565b90505b336001600160a01b031663d348799787878b8b6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b1580156117d057600080fd5b505af11580156117e4573d6000803e3d6000fd5b50505050600086111561183b576117f9613cd4565b6118038388613e0d565b111561183b576040805162461bcd60e51b815260206004820152600260248201526104d360f41b604482015290519081900360640190fd5b841561188b57611849613e1d565b6118538287613e0d565b111561188b576040805162461bcd60e51b81526020600482015260026024820152614d3160f01b604482015290519081900360640190fd5b8960020b8b60020b8d6001600160a01b03167f7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde338d8b8b60405180856001600160a01b03168152602001846001600160801b0316815260200183815260200182815260200194505050505060405180910390a450506000805460ff60f01b1916600160f01b17905550919890975095505050505050565b60025481565b600054600160f01b900460ff1661196c576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19169055611981612bf0565b6004546001600160801b0316806119c3576040805162461bcd60e51b81526020600482015260016024820152601360fa1b604482015290519081900360640190fd5b60006119f8867f000000000000000000000000000000000000000000000000000000000000000062ffffff16620f42406141a9565b90506000611a2f867f000000000000000000000000000000000000000000000000000000000000000062ffffff16620f42406141a9565b90506000611a3b613cd4565b90506000611a47613e1d565b90508815611a7a57611a7a7f00000000000000000000000000000000000000000000000000000000000000008b8b613b86565b8715611aab57611aab7f00000000000000000000000000000000000000000000000000000000000000008b8a613b86565b336001600160a01b031663e9cbafb085858a8a6040518563ffffffff1660e01b815260040180858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f82011690508083019250505095505050505050600060405180830381600087803b158015611b2d57600080fd5b505af1158015611b41573d6000803e3d6000fd5b505050506000611b4f613cd4565b90506000611b5b613e1d565b905081611b688588613e0d565b1115611ba0576040805162461bcd60e51b8152602060048201526002602482015261046360f41b604482015290519081900360640190fd5b80611bab8487613e0d565b1115611be3576040805162461bcd60e51b8152602060048201526002602482015261463160f01b604482015290519081900360640190fd5b8382038382038115611c725760008054600160e81b9004600f16908115611c16578160ff168481611c1057fe5b04611c19565b60005b90506001600160801b03811615611c4c57600380546001600160801b038082168401166001600160801b03199091161790555b611c66818503600160801b8d6001600160801b03166132d9565b60018054909101905550505b8015611cfd5760008054600160e81b900460041c600f16908115611ca2578160ff168381611c9c57fe5b04611ca5565b60005b90506001600160801b03811615611cd757600380546001600160801b03600160801b8083048216850182160291161790555b611cf1818403600160801b8d6001600160801b03166132d9565b60028054909101905550505b8d6001600160a01b0316336001600160a01b03167fbdbdb71d7860376ba52b25a5028beea23581364a40522f6bcfb86bb1f2dca6338f8f86866040518085815260200184815260200183815260200182815260200194505050505060405180910390a350506000805460ff60f01b1916600160f01b179055505050505050505050505050565b600080548190600160f01b900460ff16611dca576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b19168155611de460073389896141e3565b60038101549091506001600160801b0390811690861611611e055784611e14565b60038101546001600160801b03165b60038201549093506001600160801b03600160801b909104811690851611611e3c5783611e52565b6003810154600160801b90046001600160801b03165b91506001600160801b03831615611eb7576003810180546001600160801b031981166001600160801b03918216869003821617909155611eb7907f0000000000000000000000000000000000000000000000000000000000000000908a908616613b86565b6001600160801b03821615611f1d576003810180546001600160801b03600160801b808304821686900382160291811691909117909155611f1d907f0000000000000000000000000000000000000000000000000000000000000000908a908516613b86565b604080516001600160a01b038a1681526001600160801b0380861660208301528416818301529051600288810b92908a900b9133917f70935338e69775456a85ddef226c395fb668b63fa0115f5f20610b388e6ca9c0919081900360600190a4506000805460ff60f01b1916600160f01b17905590969095509350505050565b60076020526000908152604090208054600182015460028301546003909301546001600160801b0392831693919281811691600160801b90041685565b60066020526000908152604090205481565b7f000000000000000000000000000000000000000000000000000000000000000081565b600054600160f01b900460ff16612054576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691638da5cb5b916004808301926020929190829003018186803b1580156120c157600080fd5b505afa1580156120d5573d6000803e3d6000fd5b505050506040513d60208110156120eb57600080fd5b50516001600160a01b0316331461210157600080fd5b60ff82161580612124575060048260ff16101580156121245750600a8260ff1611155b801561214e575060ff8116158061214e575060048160ff161015801561214e5750600a8160ff1611155b61215757600080fd5b60008054610ff0600484901b16840160ff908116600160e81b9081027fffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841617909355919004167f973d8d92bb299f4af6ce49b52a8adb85ae46b9f214c4c4fc06ac77401237b1336010826040805160ff9390920683168252600f600486901c16602083015286831682820152918516606082015290519081900360800190a150506000805460ff60f01b1916600160f01b17905550565b600080548190600160f01b900460ff16612256576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916905560408051638da5cb5b60e01b815290516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691638da5cb5b916004808301926020929190829003018186803b1580156122c357600080fd5b505afa1580156122d7573d6000803e3d6000fd5b505050506040513d60208110156122ed57600080fd5b50516001600160a01b0316331461230357600080fd5b6003546001600160801b039081169085161161231f578361232c565b6003546001600160801b03165b6003549092506001600160801b03600160801b9091048116908416116123525782612366565b600354600160801b90046001600160801b03165b90506001600160801b038216156123e7576003546001600160801b038381169116141561239557600019909101905b600380546001600160801b031981166001600160801b039182168590038216179091556123e7907f00000000000000000000000000000000000000000000000000000000000000009087908516613b86565b6001600160801b0381161561246d576003546001600160801b03828116600160801b90920416141561241857600019015b600380546001600160801b03600160801b80830482168590038216029181169190911790915561246d907f00000000000000000000000000000000000000000000000000000000000000009087908416613b86565b604080516001600160801b0380851682528316602082015281516001600160a01b0388169233927f596b573906218d3411850b26a6b437d6c4522fdb43d2d2386263f86d50b8b151929081900390910190a36000805460ff60f01b1916600160f01b1790559094909350915050565b6060806124e7612bf0565b61255e6124f2612c27565b858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525054600454600896959450600160a01b820460020b935061ffff600160b81b8304811693506001600160801b0390911691600160c81b900416614247565b915091509250929050565b600080548190600160f01b900460ff166125b0576040805162461bcd60e51b81526020600482015260036024820152624c4f4b60e81b604482015290519081900360640190fd5b6000805460ff60f01b1916815560408051608081018252338152600288810b602083015287900b918101919091528190819061260990606081016125fc6001600160801b038a16613f58565b600003600f0b9052613f69565b925092509250816000039450806000039350600085118061262a5750600084115b15612669576003830180546001600160801b038082168089018216600160801b93849004831689019092169092029091176001600160801b0319161790555b604080516001600160801b0388168152602081018790528082018690529051600289810b92908b900b9133917f0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c919081900360600190a450506000805460ff60f01b1916600160f01b179055509094909350915050565b60008060006126ed612bf0565b6126f785856143a1565b600285810b810b60009081526005602052604080822087840b90930b825281206003830154600681900b9367010000000000000082046001600160a01b0316928492600160d81b810463ffffffff169284929091600160f81b900460ff168061275f57600080fd5b6003820154600681900b985067010000000000000081046001600160a01b03169650600160d81b810463ffffffff169450600160f81b900460ff16806127a457600080fd5b50506040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b810b6020840181905261ffff600160b81b8404811695850195909552600160c81b830485166060850152600160d81b8304909416608084015260ff600160e81b8304811660a0850152600160f01b909204909116151560c08301529093508e810b91900b1215905061284d575093909403965090039350900390506128d0565b8a60020b816020015160020b12156128c1576000612869612c27565b602083015160408401516004546060860151939450600093849361289f936008938893879392916001600160801b031690613389565b9a9003989098039b5050949096039290920396509091030392506128d0915050565b50949093039650039350900390505b9250925092565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b60015481565b60056020526000908152604090208054600182015460028301546003909301546001600160801b03831693600160801b909304600f0b9290600681900b9067010000000000000081046001600160a01b031690600160d81b810463ffffffff1690600160f81b900460ff1688565b6000546001600160a01b031615612a1e576040805162461bcd60e51b8152602060048201526002602482015261414960f01b604482015290519081900360640190fd5b6000612a29826136a5565b9050600080612a41612a39612c27565b60089061446a565b6040805160e0810182526001600160a01b038816808252600288810b6020808501829052600085870181905261ffff898116606088018190529089166080880181905260a08801839052600160c0909801979097528154600160f01b73ffffffffffffffffffffffffffffffffffffffff19909116871762ffffff60a01b1916600160a01b62ffffff9787900b9790971696909602959095177fffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffff16600160c81b9091021761ffff60d81b1916600160d81b909602959095177fff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1692909217909355835191825281019190915281519395509193507f98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c9592918290030190a150505050565b60008082600281900b620d89e71981612b9957fe5b05029050600083600281900b620d89e881612bb057fe5b0502905060008460020b83830360020b81612bc757fe5b0560010190508062ffffff166001600160801b03801681612be457fe5b0493505050505b919050565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614612c2557600080fd5b565b4290565b60008060008460020b8660020b81612c3f57fe5b05905060008660020b128015612c6657508460020b8660020b81612c5f57fe5b0760020b15155b15612c7057600019015b8315612ce557600080612c82836144b6565b600182810b810b600090815260208d9052604090205460ff83169190911b80016000190190811680151597509294509092509085612cc757888360ff16860302612cda565b88612cd1826144c8565b840360ff168603025b965050505050612d63565b600080612cf4836001016144b6565b91509150600060018260ff166001901b031990506000818b60008660010b60010b8152602001908152602001600020541690508060001415955085612d4657888360ff0360ff16866001010102612d5c565b8883612d5183614568565b0360ff168660010101025b9650505050505b5094509492505050565b60008060008360020b12612d84578260020b612d8c565b8260020b6000035b9050620d89e8811115612dca576040805162461bcd60e51b81526020600482015260016024820152601560fa1b604482015290519081900360640190fd5b600060018216612dde57600160801b612df0565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff1690506002821615612e24576ffff97272373d413259a46990580e213a0260801c5b6004821615612e43576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612e62576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b6010821615612e81576fffcb9843d60f6159c9db58835c9266440260801c5b6020821615612ea0576fff973b41fa98c081472e6896dfb254c00260801c5b6040821615612ebf576fff2ea16466c96a3843ec78b326b528610260801c5b6080821615612ede576ffe5dee046a99a2a811c461f1969c30530260801c5b610100821615612efe576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b610200821615612f1e576ff987a7253ac413176f2b074cf7815e540260801c5b610400821615612f3e576ff3392b0822b70005940c7a398e4b70f30260801c5b610800821615612f5e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b611000821615612f7e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b612000821615612f9e576fa9f746462d870fdf8a65dc1f90e061e50260801c5b614000821615612fbe576f70d869a156d2a1b890bb3df62baf32f70260801c5b618000821615612fde576f31be135f97d08fd981231505542fcfa60260801c5b62010000821615612fff576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561301f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561303e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561305b576b048a170391f7dc42444e8fa20260801c5b60008460020b131561307657806000198161307257fe5b0490505b64010000000081061561308a57600161308d565b60005b60ff16602082901c0192505050919050565b60008080806001600160a01b03808916908a1610158187128015906131245760006130d88989620f42400362ffffff16620f42406132d9565b9050826130f1576130ec8c8c8c6001614652565b6130fe565b6130fe8b8d8c60016146cd565b955085811061310f578a965061311e565b61311b8c8b838661478a565b96505b5061316e565b8161313b576131368b8b8b60006146cd565b613148565b6131488a8c8b6000614652565b935083886000031061315c5789955061316e565b61316b8b8a8a600003856147d6565b95505b6001600160a01b038a81169087161482156131d15780801561318d5750815b6131a35761319e878d8c60016146cd565b6131a5565b855b95508080156131b2575081155b6131c8576131c3878d8c6000614652565b6131ca565b845b945061321b565b8080156131db5750815b6131f1576131ec8c888c6001614652565b6131f3565b855b9550808015613200575081155b613216576132118c888c60006146cd565b613218565b845b94505b8115801561322b57508860000385115b15613237578860000394505b81801561325657508a6001600160a01b0316876001600160a01b031614155b15613265578589039350613282565b61327f868962ffffff168a620f42400362ffffff166141a9565b93505b50505095509550955095915050565b6000600160ff1b82106132a357600080fd5b5090565b808203828113156000831215146132bd57600080fd5b92915050565b818101828112156000831215146132bd57600080fd5b600080806000198587098686029250828110908390030390508061330f576000841161330457600080fd5b508290049050613382565b80841161331b57600080fd5b6000848688096000868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150505b9392505050565b60008063ffffffff8716613430576000898661ffff1661ffff81106133aa57fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff16151560608301529092508a161461341c57613419818a8988614822565b90505b806020015181604001519250925050613510565b8688036000806134458c8c858c8c8c8c6148d2565b91509150816000015163ffffffff168363ffffffff161415613477578160200151826040015194509450505050613510565b805163ffffffff8481169116141561349f578060200151816040015194509450505050613510565b8151815160208085015190840151918390039286039163ffffffff80841692908516910360060b816134cd57fe5b05028460200151018263ffffffff168263ffffffff1686604001518660400151036001600160a01b031602816134ff57fe5b048560400151019650965050505050505b97509795505050505050565b600295860b860b60009081526020979097526040909620600181018054909503909455938301805490920390915560038201805463ffffffff600160d81b6001600160a01b036701000000000000008085048216909603169094027fffffffffff0000000000000000000000000000000000000000ffffffffffffff90921691909117600681810b90960390950b66ffffffffffffff1666ffffffffffffff199095169490941782810485169095039093160263ffffffff60d81b1990931692909217905554600160801b9004600f0b90565b60008082600f0b121561365457826001600160801b03168260000384039150816001600160801b03161061364f576040805162461bcd60e51b81526020600482015260026024820152614c5360f01b604482015290519081900360640190fd5b6132bd565b826001600160801b03168284019150816001600160801b031610156132bd576040805162461bcd60e51b81526020600482015260026024820152614c4160f01b604482015290519081900360640190fd5b60006401000276a36001600160a01b038316108015906136e1575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038316105b613716576040805162461bcd60e51b81526020600482015260016024820152602960f91b604482015290519081900360640190fd5b77ffffffffffffffffffffffffffffffffffffffff00000000602083901b166001600160801b03811160071b81811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c60ff8111600390811b91821c600f811160021b90811c918211600190811b92831c979088119617909417909217179091171717608081106137b757607f810383901c91506137c1565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b146139c257886001600160a01b03166139a682612d6d565b6001600160a01b031611156139bb57816139bd565b805b6139c4565b815b9998505050505050505050565b6000806000898961ffff1661ffff81106139e757fe5b60408051608081018252919092015463ffffffff8082168084526401000000008304600690810b810b900b6020850152600160581b83046001600160a01b031694840194909452600160f81b90910460ff161515606083015290925089161415613a575788859250925050613510565b8461ffff168461ffff16118015613a7857506001850361ffff168961ffff16145b15613a8557839150613a89565b8491505b8161ffff168960010161ffff1681613a9d57fe5b069250613aac81898989614822565b8a8461ffff1661ffff8110613abd57fe5b825191018054602084015160408501516060909501511515600160f81b027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001600160a01b03909616600160581b027fff0000000000000000000000000000000000000000ffffffffffffffffffffff60069390930b66ffffffffffffff16640100000000026affffffffffffff000000001963ffffffff90971663ffffffff199095169490941795909516929092171692909217929092161790555097509795505050505050565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b031663a9059cbb60e01b1781529251825160009485949389169392918291908083835b60208310613c025780518252601f199092019160209182019101613be3565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613c64576040519150601f19603f3d011682016040523d82523d6000602084013e613c69565b606091505b5091509150818015613c97575080511580613c975750808060200190516020811015613c9457600080fd5b50515b613ccd576040805162461bcd60e51b81526020600482015260026024820152612a2360f11b604482015290519081900360640190fd5b5050505050565b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001693919290918291908083835b60208310613d6d5780518252601f199092019160209182019101613d4e565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d8060008114613dcd576040519150601f19603f3d011682016040523d82523d6000602084013e613dd2565b606091505b5091509150818015613de657506020815110155b613def57600080fd5b808060200190516020811015613e0457600080fd5b50519250505090565b808201828110156132bd57600080fd5b604080513060248083019190915282518083039091018152604490910182526020810180516001600160e01b03166370a0823160e01b17815291518151600093849384936001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016939192909182919080838360208310613d6d5780518252601f199092019160209182019101613d4e565b6000808361ffff1611613ef3576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b8261ffff168261ffff1611613f09575081613382565b825b8261ffff168161ffff161015613f4f576001858261ffff1661ffff8110613f2e57fe5b01805463ffffffff191663ffffffff92909216919091179055600101613f0b565b50909392505050565b80600f81900b8114612beb57600080fd5b6000806000613f76612bf0565b613f88846020015185604001516143a1565b6040805160e0810182526000546001600160a01b0381168252600160a01b8104600290810b810b900b602080840182905261ffff600160b81b8404811685870152600160c81b84048116606080870191909152600160d81b8504909116608086015260ff600160e81b8504811660a0870152600160f01b909404909316151560c08501528851908901519489015192890151939461402c9491939092909190614acf565b93508460600151600f0b6000146141a157846020015160020b816020015160020b12156140815761407a6140638660200151612d6d565b6140708760400151612d6d565b8760600151614c84565b92506141a1565b846040015160020b816020015160020b12156141775760045460408201516001600160801b03909116906140d3906140b7612c27565b60208501516060860151608087015160089493929187916139d1565b6000805461ffff60c81b1916600160c81b61ffff938416021761ffff60b81b1916600160b81b939092169290920217905581516040870151614123919061411990612d6d565b8860600151614c84565b93506141416141358760200151612d6d565b83516060890151614cc8565b92506141518187606001516135ef565b600480546001600160801b0319166001600160801b0392909216919091179055506141a1565b61419e6141878660200151612d6d565b6141948760400151612d6d565b8760600151614cc8565b91505b509193909250565b60006141b68484846132d9565b9050600082806141c257fe5b84860911156133825760001981106141d957600080fd5b6001019392505050565b6040805160609490941b6bffffffffffffffffffffffff1916602080860191909152600293840b60e890811b60348701529290930b90911b60378401528051808403601a018152603a90930181528251928201929092206000908152929052902090565b60608060008361ffff1611614287576040805162461bcd60e51b81526020600482015260016024820152604960f81b604482015290519081900360640190fd5b865167ffffffffffffffff8111801561429f57600080fd5b506040519080825280602002602001820160405280156142c9578160200160208202803683370190505b509150865167ffffffffffffffff811180156142e457600080fd5b5060405190808252806020026020018201604052801561430e578160200160208202803683370190505b50905060005b87518110156143945761433f8a8a8a848151811061432e57fe5b60200260200101518a8a8a8a613389565b84838151811061434b57fe5b6020026020010184848151811061435e57fe5b60200260200101826001600160a01b03166001600160a01b03168152508260060b60060b81525050508080600101915050614314565b5097509795505050505050565b8060020b8260020b126143e1576040805162461bcd60e51b8152602060048201526003602482015262544c5560e81b604482015290519081900360640190fd5b620d89e719600283900b1215614424576040805162461bcd60e51b8152602060048201526003602482015262544c4d60e81b604482015290519081900360640190fd5b620d89e8600282900b1315614466576040805162461bcd60e51b815260206004820152600360248201526254554d60e81b604482015290519081900360640190fd5b5050565b6040805160808101825263ffffffff9283168082526000602083018190529282019290925260016060909101819052835463ffffffff1916909117909116600160f81b17909155908190565b60020b600881901d9161010090910790565b60008082116144d657600080fd5b600160801b82106144e957608091821c91015b68010000000000000000821061450157604091821c91015b640100000000821061451557602091821c91015b62010000821061452757601091821c91015b610100821061453857600891821c91015b6010821061454857600491821c91015b6004821061455857600291821c91015b60028210612beb57600101919050565b600080821161457657600080fd5b5060ff6001600160801b0382161561459157607f1901614599565b608082901c91505b67ffffffffffffffff8216156145b257603f19016145ba565b604082901c91505b63ffffffff8216156145cf57601f19016145d7565b602082901c91505b61ffff8216156145ea57600f19016145f2565b601082901c91505b60ff821615614604576007190161460c565b600882901c91505b600f82161561461e5760031901614626565b600482901c91505b60038216156146385760011901614640565b600282901c91505b6001821615612beb5760001901919050565b6000836001600160a01b0316856001600160a01b03161115614672579293925b8161469f5761469a836001600160801b03168686036001600160a01b0316600160601b6132d9565b6146c2565b6146c2836001600160801b03168686036001600160a01b0316600160601b6141a9565b90505b949350505050565b6000836001600160a01b0316856001600160a01b031611156146ed579293925b7bffffffffffffffffffffffffffffffff000000000000000000000000606084901b166001600160a01b03868603811690871661472957600080fd5b8361475957866001600160a01b031661474c8383896001600160a01b03166132d9565b8161475357fe5b0461477f565b61477f6147708383896001600160a01b03166141a9565b886001600160a01b0316614cf7565b979650505050505050565b600080856001600160a01b0316116147a157600080fd5b6000846001600160801b0316116147b757600080fd5b816147c95761469a8585856001614d02565b6146c28585856001614de3565b600080856001600160a01b0316116147ed57600080fd5b6000846001600160801b03161161480357600080fd5b816148155761469a8585856000614de3565b6146c28585856000614d02565b61482a61564a565b600085600001518503905060405180608001604052808663ffffffff1681526020018263ffffffff168660020b0288602001510160060b81526020016000856001600160801b03161161487e576001614880565b845b6001600160801b031673ffffffff00000000000000000000000000000000608085901b16816148ab57fe5b048860400151016001600160a01b0316815260200160011515815250915050949350505050565b6148da61564a565b6148e261564a565b888561ffff1661ffff81106148f357fe5b60408051608081018252919092015463ffffffff81168083526401000000008204600690810b810b900b6020840152600160581b82046001600160a01b031693830193909352600160f81b900460ff1615156060820152925061495890899089614ed8565b15614990578663ffffffff16826000015163ffffffff16141561497a57613510565b8161498783898988614822565b91509150613510565b888361ffff168660010161ffff16816149a557fe5b0661ffff1661ffff81106149b557fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b90910416151560608201819052909250614a6c57604080516080810182528a5463ffffffff811682526401000000008104600690810b810b900b6020830152600160581b81046001600160a01b031692820192909252600160f81b90910460ff161515606082015291505b614a7b88836000015189614ed8565b614ab2576040805162461bcd60e51b815260206004820152600360248201526213d31160ea1b604482015290519081900360640190fd5b614abf8989898887614f9b565b9150915097509795505050505050565b6000614ade60078787876141e3565b60015460025491925090600080600f87900b15614c24576000614aff612c27565b6000805460045492935090918291614b499160089186918591600160a01b810460020b9161ffff600160b81b83048116926001600160801b0390921691600160c81b900416613389565b9092509050614b8360058d8b8d8b8b87898b60007f000000000000000000000000000000000000000000000000000000000000000061513b565b9450614bba60058c8b8d8b8b87898b60017f000000000000000000000000000000000000000000000000000000000000000061513b565b93508415614bee57614bee60068d7f0000000000000000000000000000000000000000000000000000000000000000615325565b8315614c2057614c2060068c7f0000000000000000000000000000000000000000000000000000000000000000615325565b5050505b600080614c3660058c8c8b8a8a61538b565b9092509050614c47878a8484615437565b600089600f0b1215614c75578315614c6457614c6460058c6155cc565b8215614c7557614c7560058b6155cc565b50505050505095945050505050565b60008082600f0b12614caa57614ca5614ca085858560016146cd565b613291565b6146c5565b614cbd614ca085858560000360006146cd565b600003949350505050565b60008082600f0b12614ce457614ca5614ca08585856001614652565b614cbd614ca08585856000036000614652565b808204910615150190565b60008115614d755760006001600160a01b03841115614d3857614d3384600160601b876001600160801b03166132d9565b614d50565b6001600160801b038516606085901b81614d4e57fe5b045b9050614d6d614d686001600160a01b03881683613e0d565b6155f8565b9150506146c5565b60006001600160a01b03841115614da357614d9e84600160601b876001600160801b03166141a9565b614dba565b614dba606085901b6001600160801b038716614cf7565b905080866001600160a01b031611614dd157600080fd5b6001600160a01b0386160390506146c5565b600082614df15750836146c5565b7bffffffffffffffffffffffffffffffff000000000000000000000000606085901b168215614e91576001600160a01b03861684810290858281614e3157fe5b041415614e6257818101828110614e6057614e5683896001600160a01b0316836141a9565b93505050506146c5565b505b614e8882614e83878a6001600160a01b03168681614e7c57fe5b0490613e0d565b614cf7565b925050506146c5565b6001600160a01b03861684810290858281614ea857fe5b04148015614eb557508082115b614ebe57600080fd5b808203614e56614d68846001600160a01b038b16846141a9565b60008363ffffffff168363ffffffff1611158015614f0257508363ffffffff168263ffffffff1611155b15614f1e578163ffffffff168363ffffffff1611159050613382565b60008463ffffffff168463ffffffff1611614f46578363ffffffff1664010000000001614f4e565b8363ffffffff165b64ffffffffff16905060008563ffffffff168463ffffffff1611614f7f578363ffffffff1664010000000001614f87565b8363ffffffff165b64ffffffffff169091111595945050505050565b614fa361564a565b614fab61564a565b60008361ffff168560010161ffff1681614fc157fe5b0661ffff169050600060018561ffff16830103905060005b506002818301048961ffff87168281614fee57fe5b0661ffff8110614ffa57fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b9091041615156060820181905290955061506557806001019250614fd9565b898661ffff16826001018161507657fe5b0661ffff811061508257fe5b60408051608081018252929091015463ffffffff811683526401000000008104600690810b810b900b60208401526001600160a01b03600160581b8204169183019190915260ff600160f81b909104161515606082015285519094506000906150ed908b908b614ed8565b905080801561510657506151068a8a8760000151614ed8565b15615111575061512e565b8061512157600182039250615128565b8160010193505b50614fd9565b5050509550959350505050565b60028a810b900b600090815260208c90526040812080546001600160801b031682615166828d6135ef565b9050846001600160801b0316816001600160801b031611156151b4576040805162461bcd60e51b81526020600482015260026024820152614c4f60f01b604482015290519081900360640190fd5b6001600160801b03828116159082161581141594501561528a578c60020b8e60020b1361525a57600183018b9055600283018a90556003830180547fffffffffff0000000000000000000000000000000000000000ffffffffffffff166701000000000000006001600160a01b038c16021766ffffffffffffff191666ffffffffffffff60068b900b161763ffffffff60d81b1916600160d81b63ffffffff8a16021790555b6003830180547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b1790555b82546001600160801b0319166001600160801b038216178355856152d35782546152ce906152c990600160801b9004600f90810b810b908f900b6132c3565b613f58565b6152f4565b82546152f4906152c990600160801b9004600f90810b810b908f900b6132a7565b8354600f9190910b6001600160801b03908116600160801b0291161790925550909c9b505050505050505050505050565b8060020b8260020b8161533457fe5b0760020b1561534257600080fd5b60008061535d8360020b8560020b8161535757fe5b056144b6565b600191820b820b60009081526020979097526040909620805460ff9097169190911b90951890945550505050565b600285810b80820b60009081526020899052604080822088850b850b83529082209193849391929184918291908a900b126153d1575050600182015460028301546153e4565b8360010154880391508360020154870390505b6000808b60020b8b60020b121561540657505060018301546002840154615419565b84600101548a0391508460020154890390505b92909803979097039b96909503949094039850939650505050505050565b6040805160a08101825285546001600160801b0390811682526001870154602083015260028701549282019290925260038601548083166060830152600160801b900490911660808201526000600f85900b6154d65781516001600160801b03166154ce576040805162461bcd60e51b815260206004820152600260248201526104e560f41b604482015290519081900360640190fd5b5080516154e5565b81516154e290866135ef565b90505b60006155098360200151860384600001516001600160801b0316600160801b6132d9565b9050600061552f8460400151860385600001516001600160801b0316600160801b6132d9565b905086600f0b6000146155565787546001600160801b0319166001600160801b0384161788555b60018801869055600288018590556001600160801b03821615158061558457506000816001600160801b0316115b156155c2576003880180546001600160801b031981166001600160801b039182168501821617808216600160801b9182900483168501909216021790555b5050505050505050565b600290810b810b6000908152602092909252604082208281556001810183905590810182905560030155565b806001600160a01b0381168114612beb57600080fd5b6040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915290565b6040805160808101825260008082526020820181905291810182905260608101919091529056fea164736f6c6343000706000aa164736f6c6343000706000ac66a3fdf07232cdd185febcc6579d408c241b47ae2f9907d84be655141eeaecc"; - address factoryAddress = deployContract(factoryBytecode, ""); - factory = IUniswapV3Factory(factoryAddress); + factory = UniswapHelpers.deployUniswapFactory(); TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp)); @@ -120,255 +88,140 @@ contract LiquidityManagerTest is Test { pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE)); token0isWeth = address(weth) < address(harberg); - initializePoolFor1Cent(address(pool)); + pool.initializePoolFor1Cent(token0isWeth); stake = new Stake(address(harberg)); harberg.setStakingPool(address(stake)); - lm = new LiquidityManager(factoryAddress, address(weth), address(harberg)); + Sentimenter senti = Sentimenter(address(new MockSentimenter())); + senti.initialize(address(harberg), address(stake)); + lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(senti)); lm.setFeeDestination(feeDestination); vm.prank(feeDestination); - lm.setCapitalInfefficiency(95); harberg.setLiquidityManager(address(lm)); harberg.setLiquidityPool(address(pool)); vm.deal(address(lm), 10 ether); - createCSVHeader(); + initializePositionsCSV(); // Set up the CSV header } - function slide(bool last) internal { + function recenter(bool last) internal { // have some time pass to record prices in uni oracle uint256 timeBefore = block.timestamp; vm.warp(timeBefore + (60 * 60 * 5)); - try lm.recenter() { + try lm.recenter() returns (bool isUp, uint256 sentiment) { - // Check liquidity positions after slide - (uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbergFloor, uint256 harbergAnchor, uint256 harbergDiscovery) = checkLiquidityPositionsAfter("slide"); - assertGt(ethFloor, ethAnchor, "slide - Floor should hold more ETH than Anchor"); - assertGt(harbergDiscovery, harbergAnchor * 5, "slide - Discovery should hold more HARB than Anchor"); - assertEq(harbergFloor, 0, "slide - Floor should have no HARB"); - assertEq(ethDiscovery, 0, "slide - Discovery should have no ETH"); - } catch Error(string memory reason) { - if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) { - console.log("slide failed on amplitude"); - } else { - if (!last) { - revert(reason); // Rethrow the error if it's not the expected message - } - } - } + // Check liquidity positions after slide + Response memory rsp; + rsp = checkLiquidity(isUp ? "shift" : "slide", sentiment); + assertGt(rsp.ethFloor, rsp.ethAnchor, "slide - Floor should hold more ETH than Anchor"); + assertGt(rsp.harbergDiscovery, rsp.harbergAnchor * 5, "slide - Discovery should hold more HARB than Anchor"); + assertEq(rsp.harbergFloor, 0, "slide - Floor should have no HARB"); + assertEq(rsp.ethDiscovery, 0, "slide - Discovery should have no ETH"); + + } catch Error(string memory reason) { + if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) { + console.log("slide failed on amplitude"); + } else { + if (!last) { + revert(reason); // Rethrow the error if it's not the expected message + } + } + } } - function shift() internal { - // have some time pass to record prices in uni oracle - uint256 timeBefore = block.timestamp; - vm.warp(timeBefore + (60 * 60 * 5)); - - try lm.recenter() { - // Check liquidity positions after shift - (uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbergFloor, uint256 harbergAnchor, uint256 harbergDiscovery) = checkLiquidityPositionsAfter("shift"); - assertGt(ethFloor, ethAnchor, "shift - Floor should hold more ETH than Anchor"); - assertGt(harbergDiscovery, harbergAnchor * 5, "shift - Discovery should hold more HARB than Anchor"); - assertEq(harbergFloor, 0, "shift - Floor should have no HARB"); - assertEq(ethDiscovery, 0, "shift - Discovery should have no ETH"); - } catch Error(string memory reason) { - if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) { - console.log("shift failed on amplitude"); - } else { - revert(reason); // Rethrow the error if it's not the expected message - } - } - } - - function getBalancesPool(LiquidityManager.Stage s) internal view returns (int24 currentTick, int24 tickLower, int24 tickUpper, uint256 ethAmount, uint256 harbergAmount) { - (,tickLower, tickUpper) = lm.positions(s); - (uint128 liquidity, , , ,) = pool.positions(keccak256(abi.encodePacked(address(lm), tickLower, tickUpper))); + (,tickLower, tickUpper) = lm.positions(s); + (uint128 liquidity, , , ,) = pool.positions(keccak256(abi.encodePacked(address(lm), tickLower, tickUpper))); - // Fetch the current price from the pool - uint160 sqrtPriceX96; - (sqrtPriceX96, currentTick, , , , , ) = pool.slot0(); - uint160 sqrtPriceAX96 = TickMath.getSqrtRatioAtTick(tickLower); - uint160 sqrtPriceBX96 = TickMath.getSqrtRatioAtTick(tickUpper); + // Fetch the current price from the pool + uint160 sqrtPriceX96; + (sqrtPriceX96, currentTick, , , , , ) = pool.slot0(); + uint160 sqrtPriceAX96 = TickMath.getSqrtRatioAtTick(tickLower); + uint160 sqrtPriceBX96 = TickMath.getSqrtRatioAtTick(tickUpper); - // Calculate amounts based on the current tick position relative to provided ticks - if (token0isWeth) { - if (currentTick < tickLower) { - // Current price is below the lower bound of the liquidity position - ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); - harbergAmount = 0; // All liquidity is in token0 (ETH) - } else if (currentTick > tickUpper) { - // Current price is above the upper bound of the liquidity position - ethAmount = 0; // All liquidity is in token1 (HARB) - harbergAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); - } else { - // Current price is within the bounds of the liquidity position - ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); - harbergAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); - } - } else { - if (currentTick < tickLower) { - // Current price is below the lower bound of the liquidity position - harbergAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); - ethAmount = 0; // All liquidity is in token1 (ETH) - } else if (currentTick > tickUpper) { - // Current price is above the upper bound of the liquidity position - harbergAmount = 0; // All liquidity is in token0 (HARB) - ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); - } else { - // Current price is within the bounds of the liquidity position - harbergAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); - ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); - } - } + // Calculate amounts based on the current tick position relative to provided ticks + if (token0isWeth) { + if (currentTick < tickLower) { + // Current price is below the lower bound of the liquidity position + ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); + harbergAmount = 0; // All liquidity is in token0 (ETH) + } else if (currentTick > tickUpper) { + // Current price is above the upper bound of the liquidity position + ethAmount = 0; // All liquidity is in token1 (HARB) + harbergAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); + } else { + // Current price is within the bounds of the liquidity position + ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); + harbergAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); + } + } else { + if (currentTick < tickLower) { + // Current price is below the lower bound of the liquidity position + harbergAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); + ethAmount = 0; // All liquidity is in token1 (ETH) + } else if (currentTick > tickUpper) { + // Current price is above the upper bound of the liquidity position + harbergAmount = 0; // All liquidity is in token0 (HARB) + ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); + } else { + // Current price is within the bounds of the liquidity position + harbergAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); + ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); + } + } } - // csv = "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb"; - function checkLiquidityPositionsAfter(string memory eventName) internal returns (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbergFloor, uint harbergAnchor, uint harbergDiscovery) { - int24 currentTick; - int24 tickLower; - int24 tickUpper; - (currentTick, tickLower, tickUpper, ethFloor, harbergFloor) = getBalancesPool(LiquidityManager.Stage.FLOOR); - string memory floorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethFloor), ",", uintToStr(harbergFloor), ",")); + function checkLiquidity(string memory eventName, uint256 sentiment) internal returns (Response memory) { + Response memory rsp; + int24 currentTick; + string memory floorData; + string memory anchorData; + string memory discoveryData; + { + int24 tickLower; + int24 tickUpper; + uint256 eth; + uint256 harb; + { + (currentTick, tickLower, tickUpper, eth, harb) = getBalancesPool(LiquidityManager.Stage.FLOOR); + floorData = string(abi.encodePacked(CSVHelper.intToStr(tickLower), ",", CSVHelper.intToStr(tickUpper), ",", CSVHelper.uintToStr(eth), ",", CSVHelper.uintToStr(harb), ",")); + rsp.ethFloor = eth; + rsp.harbergFloor = harb; + } + { + (,tickLower, tickUpper, eth, harb) = getBalancesPool(LiquidityManager.Stage.ANCHOR); + anchorData = string(abi.encodePacked(CSVHelper.intToStr(tickLower), ",", CSVHelper.intToStr(tickUpper), ",", CSVHelper.uintToStr(eth), ",", CSVHelper.uintToStr(harb), ",")); + rsp.ethAnchor = eth; + rsp.harbergAnchor = harb; + } + { + (,tickLower, tickUpper, eth, harb) = getBalancesPool(LiquidityManager.Stage.DISCOVERY); + discoveryData = string(abi.encodePacked(CSVHelper.intToStr(tickLower), ",", CSVHelper.intToStr(tickUpper), ",", CSVHelper.uintToStr(eth), ",", CSVHelper.uintToStr(harb), ",")); + rsp.ethDiscovery = eth; + rsp.harbergDiscovery = harb; + } + } - (,tickLower, tickUpper, ethAnchor, harbergAnchor) = getBalancesPool(LiquidityManager.Stage.ANCHOR); - string memory anchorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethAnchor), ",", uintToStr(harbergAnchor), ",")); - - (,tickLower, tickUpper, ethDiscovery, harbergDiscovery) = getBalancesPool(LiquidityManager.Stage.DISCOVERY); - string memory discoveryData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethDiscovery), ",", uintToStr(harbergDiscovery), ",")); - - csv = string(abi.encodePacked(csv, "\n", eventName, ",", intToStr(currentTick), ",", floorData, anchorData, discoveryData)); - } - - // Helper function to calculate absolute value of integer - function abs(int x) internal pure returns (uint) { - return x >= 0 ? uint(x) : uint(-x); + string memory newRow = string(abi.encodePacked(eventName, ",", CSVHelper.intToStr(currentTick), ",", CSVHelper.uintToStr(sentiment / 1e12), ",", floorData, anchorData, discoveryData)); + appendCSVRow(newRow); // Append the new row to the CSV + return rsp; } function buy(uint256 amountEth) internal { performSwap(amountEth, true); - checkLiquidityPositionsAfter(string.concat("buy ", uintToStr(amountEth))); + checkLiquidity(string.concat("buy ", CSVHelper.uintToStr(amountEth)), 0); } function sell(uint256 amountHarb) internal { performSwap(amountHarb, false); - checkLiquidityPositionsAfter(string.concat("sell ", uintToStr(amountHarb))); - } - - function performSwap(uint256 amount, bool isBuy) internal { - uint160 limit; - if (isBuy) { - vm.startPrank(account); - //vm.deal(account, amount); - //weth.deposit{value: account.balance}(); - weth.transfer(address(this), amount); - vm.stopPrank(); - limit = token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1; - } else { - // vm.startPrank(address(lm)); - // harberg.mint(amount); - // harberg.transfer(address(account), amount); - // vm.stopPrank(); - vm.prank(account); - harberg.approve(address(this), amount); - limit = !token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1; - } - pool.swap( - account, - isBuy ? token0isWeth : !token0isWeth, - int256(amount), - limit, - abi.encode(account, int256(amount), isBuy) - ); - } - - /*////////////////////////////////////////////////////////////// - CALLBACKS - //////////////////////////////////////////////////////////////*/ - function uniswapV3SwapCallback( - int256 amount0Delta, - int256 amount1Delta, - bytes calldata _data - ) - external - { - require(amount0Delta > 0 || amount1Delta > 0); - - (address seller, , bool isBuy) = abi.decode(_data, (address, uint256, bool)); - - (, uint256 amountToPay) = amount0Delta > 0 - ? (!token0isWeth, uint256(amount0Delta)) - : (token0isWeth, uint256(amount1Delta)); - if (isBuy) { - weth.transfer(msg.sender, amountToPay); - return; - } - - require(harberg.transferFrom(seller, msg.sender, amountToPay), "reason 3"); + checkLiquidity(string.concat("sell ", CSVHelper.uintToStr(amountHarb)), 0); } receive() external payable {} - // Helper function to convert uint to string - function uintToStr(uint256 _i) internal pure returns (string memory _uintAsString) { - if (_i == 0) { - return "0"; - } - uint256 j = _i; - uint256 len; - while (j != 0) { - len++; - j /= 10; - } - bytes memory bstr = new bytes(len); - uint256 k = len; - while (_i != 0) { - k = k-1; - uint8 temp = (48 + uint8(_i - _i / 10 * 10)); - bytes1 b1 = bytes1(temp); - bstr[k] = b1; - _i /= 10; - } - return string(bstr); - } - - // Helper function to convert int to string - function intToStr(int256 _i) internal pure returns (string memory) { - if (_i == 0) { - return "0"; - } - bool negative = _i < 0; - uint256 j = uint256(negative ? -_i : _i); - uint256 len; - while (j != 0) { - len++; - j /= 10; - } - if (negative) { - len++; - } - bytes memory bstr = new bytes(len); - uint256 k = len; - uint256 l = uint256(negative ? -_i : _i); - while (l != 0) { - k = k-1; - uint8 temp = (48 + uint8(l - l / 10 * 10)); - bytes1 b1 = bytes1(temp); - bstr[k] = b1; - l /= 10; - } - if (negative) { - bstr[0] = '-'; - } - return string(bstr); - } - - function createCSVHeader() public { - csv = "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb"; - } function writeCsv() public { - string memory path = "./out/positions.csv"; - vm.writeFile(path, csv); + writeCSVToFile("./out/positions.csv"); // Write CSV to file } function testHandleCumulativeOverflow() public { @@ -378,7 +231,7 @@ contract LiquidityManagerTest is Test { weth.deposit{value: 201 ether}(); // Setup initial liquidity - slide(false); + recenter(false); vm.store( address(lm), @@ -400,7 +253,7 @@ contract LiquidityManagerTest is Test { buy(25 ether); - shift(); + recenter(false); cumulativeVolumeWeightedPriceX96 = lm.cumulativeVolumeWeightedPriceX96(); uint256 cumulativeVolume = lm.cumulativeVolume(); @@ -413,61 +266,6 @@ contract LiquidityManagerTest is Test { assertTrue(calculatedPrice > 0 && calculatedPrice < 10**40, "Calculated price after wrap-around is not within a reasonable range"); } - // function testLiquidityPositions() public { - // setUpCustomToken0(false); - - // // Setup initial liquidity - // slide(); - - // // Initial checks of liquidity positions - // (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbergFloor, uint harbergAnchor, uint harbergDiscovery) = checkLiquidityPositionsAfter("slide"); - - // // Assertions to verify initial setup - // assertGt(ethFloor, 9 ether, "Floor should have initial ETH"); - // assertGt(ethAnchor, 0.8 ether, "Anchor should have initial ETH"); - // assertEq(ethDiscovery, 0, "Discovery should not have ETH"); - // assertEq(harbergFloor, 0, "Floor should have no HARB"); - // assertGt(harbergAnchor, 0, "Anchor should have HARB"); - // assertGt(harbergDiscovery, 0, "Discovery should have HARB"); - - // // Introduce large buy to push into discovery - // buy(3 ether); - - // // Check liquidity positions after buy - // (, ethAnchor, ethDiscovery, , , ) = checkLiquidityPositionsAfter("buy 3"); - // assertGt(ethAnchor, 2 ether, "Anchor should have more ETH"); - // assertGt(ethDiscovery, 2.2 ether, "Discovery should have ETH"); - - // shift(); - - // // Check liquidity positions after shift - // (ethFloor, ethAnchor, ethDiscovery, harbergFloor, , ) = checkLiquidityPositionsAfter("shift"); - // assertGt(ethFloor, 11.5 ether, "Floor should have more ETH"); - // assertGt(ethAnchor, 1.4 ether, "Anchor should have more ETH"); - // assertEq(ethDiscovery, 0, "Discovery should not have ETH after shift"); - // assertEq(harbergFloor, 0, "Floor should have no HARB"); - - // // Simulate large sell to push price down to floor - // sell(6 * 10**23); - - // // Check liquidity positions after sell - // (ethFloor, ethAnchor, ethDiscovery, harbergFloor, harbergAnchor, harbergDiscovery) = checkLiquidityPositionsAfter("sell 600000"); - // assertGt(ethFloor, 0, "Floor should still have ETH after manipulation"); - // assertGt(harbergDiscovery, 0, "Discovery should have increased HARB after buys"); - - // slide(); - - // // Check liquidity positions after slide - // (ethFloor, ethAnchor, ethDiscovery, harbergFloor, harbergAnchor, harbergDiscovery) = checkLiquidityPositionsAfter("slide"); - - // // Ensure liquidity positions are as expected - // assertGt(ethFloor, 0, "Floor should still have ETH after manipulation"); - // assertEq(harbergFloor, 0, "Floor should have no HARB"); - // assertEq(ethDiscovery, 0, "Discovery should not have ETH after slide"); - // assertGt(harbergDiscovery, 0, "Discovery should have increased HARB after buys"); - // writeCsv(); - // } - // function testScenarioBuyAll() public { // setUpCustomToken0(false); // vm.deal(account, 300 ether); @@ -477,17 +275,17 @@ contract LiquidityManagerTest is Test { // uint256 traderBalanceBefore = weth.balanceOf(account); // // Setup initial liquidity - // slide(false); + // recenter(false); // buy(200 ether); - // shift(); + // recenter(false); // //revert(); // sell(harberg.balanceOf(account)); - // slide(true); + // recenter(true); // writeCsv(); @@ -505,39 +303,39 @@ contract LiquidityManagerTest is Test { // uint256 traderBalanceBefore = weth.balanceOf(account); // // Setup initial liquidity - // slide(false); + // recenter(false); // buy(25 ether); - // shift(); + // recenter(false); // buy(45 ether); - // shift(); + // recenter(false); // buy(80 ether); - // shift(); + // recenter(false); // buy(120 ether); - // shift(); + // recenter(false); // sell(harberg.balanceOf(account) / 4); - // slide(true); + // recenter(true); // sell(harberg.balanceOf(account) / 4); - // slide(true); + // recenter(true); // sell(harberg.balanceOf(account) / 4); - // slide(true); + // recenter(true); // sell(harberg.balanceOf(account)); - // slide(true); + // recenter(true); // writeCsv(); // uint256 traderBalanceAfter = weth.balanceOf(account); @@ -554,13 +352,13 @@ contract LiquidityManagerTest is Test { vm.assume(amounts.length >= numActions); setUpCustomToken0(numActions % 2 == 0 ? true : false); - vm.deal(account, 100 ether); + vm.deal(account, 20 ether); vm.prank(account); - weth.deposit{value: 100 ether}(); + weth.deposit{value: 20 ether}(); // Setup initial liquidity - slide(false); + recenter(false); uint256 traderBalanceBefore = weth.balanceOf(account); uint8 f = 0; @@ -583,25 +381,17 @@ contract LiquidityManagerTest is Test { } } + (, int24 currentTick, , , , , ) = pool.slot0(); + if (currentTick < -887270) { + // buy(1000000000000000); + sell(100000000000000); + } + if (currentTick > 887270) { + buy(1000000000000000); + // sell(100000000000000); + } if (f >= frequency) { - (, int24 currentTick, , , , , ) = pool.slot0(); - (, int24 tickLower, int24 tickUpper) = lm.positions(LiquidityManager.Stage.ANCHOR); - int24 midTick = token0isWeth ? tickLower + ANCHOR_SPACING : tickUpper - ANCHOR_SPACING; - if (currentTick < midTick) { - // Current tick is below the midpoint, so call slide() - if (token0isWeth) { - shift(); - } else { - slide(false); - } - } else if (currentTick > midTick) { - // Current tick is above the midpoint, so call shift() - if (token0isWeth) { - slide(false); - } else { - shift(); - } - } + recenter(false); f = 0; } else { f++; @@ -611,7 +401,7 @@ contract LiquidityManagerTest is Test { // Simulate large sell to push price down to floor sell(harberg.balanceOf(account)); - slide(true); + recenter(true); uint256 traderBalanceAfter = weth.balanceOf(account); diff --git a/onchain/test/Sentimenter.t.sol b/onchain/test/Sentimenter.t.sol new file mode 100644 index 0000000..571f60b --- /dev/null +++ b/onchain/test/Sentimenter.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; +import "../src/Harberg.sol"; +import {TooMuchSnatch, Stake} from "../src/Stake.sol"; +import "../src/Sentimenter.sol"; +import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; +import {MockSentimenter} from "./mocks/MockSentimenter.sol"; + +contract SentimenterTest is Test { + TwabController tc; + Harberg harberg; + Stake stake; + Sentimenter sentimenter; + address liquidityPool; + address liquidityManager; + address taxPool; + + function setUp() public { + tc = new TwabController(60 * 60, uint32(block.timestamp)); + harberg = new Harberg("HARB", "HARB", tc); + taxPool = harberg.TAX_POOL(); + stake = new Stake(address(harberg)); + harberg.setStakingPool(address(stake)); + liquidityPool = makeAddr("liquidityPool"); + harberg.setLiquidityPool(liquidityPool); + liquidityManager = makeAddr("liquidityManager"); + harberg.setLiquidityManager(liquidityManager); + // deploy upgradeable tuner contract + Sentimenter _sentimenter = new Sentimenter(); + bytes memory params = abi.encodeWithSignature("initialize(address,address)", address(harberg),address(stake)); + ERC1967Proxy proxy = new ERC1967Proxy(address(_sentimenter), params); + sentimenter = Sentimenter(address(proxy)); + } + + function doSnatch(address staker, uint256 amount, uint32 taxRate) private returns (uint256 positionId) { + vm.startPrank(staker); + harberg.approve(address(stake), amount); + uint256[] memory empty; + positionId = stake.snatch(amount, staker, taxRate, empty); + vm.stopPrank(); + } + + function testSentiment() public { + uint256 smallstake = 0.3e17; + uint256 stakeOneThird = 1 ether; + uint256 stakeTwoThird = 2 ether; + address staker = makeAddr("staker"); + + // Mint and distribute tokens + vm.startPrank(liquidityManager); + // mint all the tokens we will need in the test + harberg.mint((smallstake + stakeOneThird + stakeTwoThird) * 5); + // send 20% of that to staker + harberg.transfer(staker, (smallstake + stakeOneThird + stakeTwoThird) * 2); + vm.stopPrank(); + + // Setup initial stakers + uint256 positionId1 = doSnatch(staker, smallstake, 0); + + uint256 sentiment; + sentiment = sentimenter.getSentiment(); + // 0.99 - horrible sentiment + assertApproxEqRel(sentiment, 9.9e17, 1e16); + + vm.prank(staker); + stake.exitPosition(positionId1); + uint256 positionId2 = doSnatch(staker, stakeOneThird, 2); + + sentiment = sentimenter.getSentiment(); + + // 0.64 - depressive sentiment + assertApproxEqRel(sentiment, 6.4e17, 1e16); + + vm.prank(staker); + stake.exitPosition(positionId2); + positionId1 = doSnatch(staker, stakeOneThird, 10); + positionId2 = doSnatch(staker, stakeTwoThird, 11); + + sentiment = sentimenter.getSentiment(); + + // 0.00018 - feaking good sentiment + assertApproxEqRel(sentiment, 1.8e14, 1e17); + + vm.startPrank(staker); + stake.exitPosition(positionId1); + stake.exitPosition(positionId2); + vm.stopPrank(); + positionId1 = doSnatch(staker, stakeOneThird, 29); + positionId2 = doSnatch(staker, stakeTwoThird, 29); + + sentiment = sentimenter.getSentiment(); + // 0.024 - pretty good sentiment + assertApproxEqRel(sentiment, 2.4e16, 2e16); + + vm.startPrank(staker); + stake.exitPosition(positionId1); + stake.exitPosition(positionId2); + vm.stopPrank(); + positionId2 = doSnatch(staker, stakeTwoThird, 15); + + sentiment = sentimenter.getSentiment(); + + // 0.17 - positive sentiment + assertApproxEqRel(sentiment, 1.7e17, 2e16); + + vm.startPrank(staker); + stake.exitPosition(positionId2); + vm.stopPrank(); + + positionId1 = doSnatch(staker, stakeOneThird, 15); + + sentiment = sentimenter.getSentiment(); + + // 0.4 - OK sentiment + assertApproxEqRel(sentiment, 3.9e17, 2e16); + } + + function testContractUpgrade() public { + address newSent = address(new MockSentimenter()); + sentimenter.upgradeTo(newSent); + uint256 sentiment = sentimenter.getSentiment(); + assertEq(sentiment, 1234567890123456789, "should have been upgraded"); + } +} diff --git a/onchain/test/Simulations.t.sol b/onchain/test/Simulations.t.sol new file mode 100644 index 0000000..33a344f --- /dev/null +++ b/onchain/test/Simulations.t.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "@aperture/uni-v3-lib/TickMath.sol"; +import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol"; +import "../src/interfaces/IWETH9.sol"; +import {WETH} from "solmate/tokens/WETH.sol"; +import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; +import {PoolAddress, PoolKey} from "@aperture/uni-v3-lib/PoolAddress.sol"; +import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; +import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; +import {Harberg} from "../src/Harberg.sol"; +import "../src/helpers/UniswapHelpers.sol"; +import {Stake, ExceededAvailableStake} from "../src/Stake.sol"; +import {LiquidityManager} from "../src/LiquidityManager.sol"; +import {UniswapTestBase} from "./helpers/UniswapTestBase.sol"; +import {CSVHelper} from "./helpers/CSVHelper.sol"; +import {CSVManager} from "./helpers/CSVManager.sol"; +import "../src/Sentimenter.sol"; + +address constant TAX_POOL = address(2); +// default fee of 1% +uint24 constant FEE = uint24(10_000); + +contract SimulationsTest is UniswapTestBase, CSVManager { + using UniswapHelpers for IUniswapV3Pool; + using CSVHelper for *; + + IUniswapV3Factory factory; + Stake stakingPool; + LiquidityManager lm; + address feeDestination = makeAddr("fees"); + uint256 supplyOnRecenter; + uint256 timeOnRecenter; + int256 supplyChange; + + struct Position { + uint256 liquidity; + int32 tickLower; + int32 tickUpper; + } + + enum ActionType { + Buy, + Sell, + Snatch, + Unstake, + PayTax, + Recenter, + Mint, + Burn + } + + struct Action { + uint256 kind; // buy, sell, snatch, unstake, paytax, recenter, mint, burn + uint256 amount1; // x , x , x , x , x , x , , + uint256 amount2; // , , x , , , , , + string position; // , , x , , , , , + } + + struct Scenario { + uint256 VWAP; + uint256 comEthBal; + uint256 comHarbBal; + uint256 comStakeShare; + Position[] liquidity; // the positions are floor, anchor, liquidity, [comPos1, comPos2 ...] + uint256 time; + Action[] txns; + } + + function setUp() public { + factory = UniswapHelpers.deployUniswapFactory(); + + weth = IWETH9(address(new WETH())); + TwabController tc = new TwabController(60 * 60, uint32(block.timestamp)); + harberg = new Harberg("Harberg", "HRB", tc); + pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE)); + + token0isWeth = address(weth) < address(harberg); + pool.initializePoolFor1Cent(token0isWeth); + + stakingPool = new Stake(address(harberg)); + harberg.setStakingPool(address(stakingPool)); + Sentimenter senti = new Sentimenter(); + senti.initialize(address(harberg), address(stakingPool)); + lm = new LiquidityManager(address(factory), address(weth), address(harberg), address(senti)); + lm.setFeeDestination(feeDestination); + vm.prank(feeDestination); + harberg.setLiquidityManager(address(lm)); + harberg.setLiquidityPool(address(pool)); + vm.deal(address(lm), 1 ether); + timeOnRecenter = block.timestamp; + initializeTimeSeriesCSV(); + } + + function buy(uint256 amountEth) internal { + performSwap(amountEth, true); + } + + function sell(uint256 amountHarb) internal { + performSwap(amountHarb, false); + } + + receive() external payable {} + + + function writeCsv() public { + writeCSVToFile("./out/timeSeries.csv"); // Write CSV to file + } + + function recenter() internal { + // have some time pass to record prices in uni oracle + uint256 timeBefore = block.timestamp; + vm.warp(timeBefore + 5 minutes); + + // uint256 + // uint256 supplyDelta = currentSupply - supplyOnRecenter; + // uint256 timeDelta = block.timestamp - timeOnRecenter; + // uint256 growthPerYear = supplyDelta * 52 weeks / timeDelta; + // // console.log("supplyOnLastRecenter"); + // // console.log(supplyOnRecenter); + // // console.log("currentSupply"); + // // console.log(currentSupply); + // uint256 growthPercentage = growthPerYear * 100 >= currentSupply ? 101 : growthPerYear * 100 / currentSupply; + // supplyOnRecenter = currentSupply; + + timeOnRecenter = block.timestamp; + uint256 supplyBefore = harberg.totalSupply(); + lm.recenter(); + supplyChange = int256(harberg.totalSupply()) - int256(supplyBefore); + // TODO: update supplyChangeOnLastRecenter + + // have some time pass to record prices in uni oracle + timeBefore = block.timestamp; + vm.warp(timeBefore + 5 minutes); + } + + function getPriceInHarb(uint160 sqrtPriceX96) internal returns (uint256 price) { + uint256 sqrtPrice = uint256(sqrtPriceX96); + + if (token0isWeth) { + // WETH is token0, price = (sqrtPriceX96 / 2^96)^2 + price = (sqrtPrice * sqrtPrice) / (1 << 192); + } else { + // WETH is token1, price = (2^96 / sqrtPriceX96)^2 + price = ((1 << 192) * 1e18) / (sqrtPrice * sqrtPrice); + } + } + + function recordState() internal { + + uint160 sqrtPriceX96; + uint256 outstandingStake = stakingPool.outstandingStake(); + + (sqrtPriceX96, , , , , , ) = pool.slot0(); + + string memory newRow = string(abi.encodePacked(CSVHelper.uintToStr(block.timestamp), + ",", CSVHelper.uintToStr(getPriceInHarb(sqrtPriceX96)), + ",", CSVHelper.uintToStr(harberg.totalSupply() / 1e18), + ",", CSVHelper.intToStr(supplyChange / 1e18), + ",", CSVHelper.uintToStr(outstandingStake * 500 / 1e25) + )); + if (outstandingStake > 0) { + uint256 sentiment; + uint256 avgTaxRate; + //(sentiment, avgTaxRate) = stakingPool.getSentiment(); + newRow = string.concat(newRow, + ",", CSVHelper.uintToStr(avgTaxRate), + ",", CSVHelper.uintToStr(sentiment), + ",", CSVHelper.uintToStr(harberg.sumTaxCollected() / 1e18) + ); + } else { + newRow = string.concat(newRow, ", 0, 100, 95, 25, 0"); + } + appendCSVRow(newRow); // Append the new row to the CSV + } + + function stake(uint256 harbAmount, uint32 taxRateIndex) internal returns (uint256) { + vm.startPrank(account); + harberg.approve(address(stakingPool), harbAmount); + uint256[] memory empty; + uint256 posId = stakingPool.snatch(harbAmount, account, taxRateIndex, empty); + vm.stopPrank(); + return posId; + } + + function unstake(uint256 positionId) internal { + vm.prank(account); + stakingPool.exitPosition(positionId); + } + + function handleAction(Action memory action) internal { + if (action.kind == uint256(ActionType.Buy)) { + buy(action.amount1 * 10**18); + } else if (action.kind == uint256(ActionType.Sell)) { + sell(action.amount1 * 10**18); + } else if (action.kind == uint256(ActionType.Snatch)) { + stake(action.amount1 ** 10**18, uint32(action.amount2)); + } else if (action.kind == uint256(ActionType.Unstake)) { + unstake(action.amount1); + } else if (action.kind == uint256(ActionType.PayTax)) { + stakingPool.payTax(action.amount1); + } else if (action.kind == uint256(ActionType.Recenter)) { + uint256 timeBefore = block.timestamp; + vm.warp(timeBefore + action.amount1); + recenter(); + } + } + + function testGeneration() public { + string memory json = vm.readFile("out/scenarios.json"); + bytes memory data = vm.parseJson(json); + Scenario memory scenario = abi.decode(data, (Scenario)); + + vm.deal(account, scenario.comEthBal * 10**18); + vm.prank(account); + weth.deposit{value: address(account).balance}(); + + for (uint256 i = 0; i < scenario.txns.length; i++) { + handleAction(scenario.txns[i]); + recordState(); + } + + //writeCsv(); + + } + + +} diff --git a/onchain/test/Stake.t.sol b/onchain/test/Stake.t.sol index 329cb2f..9dcfccc 100644 --- a/onchain/test/Stake.t.sol +++ b/onchain/test/Stake.t.sol @@ -31,6 +31,20 @@ contract StakeTest is Test { harberg.setLiquidityManager(liquidityManager); } + function assertPosition(uint256 positionId, uint256 expectedShares, uint32 expectedTaxRate) private view { + (uint256 shares, , , , uint32 taxRate) = stakingPool.positions(positionId); + assertEq(shares, expectedShares, "Incorrect share amount for new position"); + assertEq(taxRate, expectedTaxRate, "Incorrect tax rate for new position"); + } + + function verifyPositionShrunkOrRemoved(uint256 positionId, uint256 initialStake) private view { + (uint256 remainingShare, , , , ) = stakingPool.positions(positionId); + uint256 expectedInitialShares = stakingPool.assetsToShares(initialStake); + bool positionRemoved = remainingShare == 0; + bool positionShrunk = remainingShare < expectedInitialShares; + + assertTrue(positionRemoved || positionShrunk, "Position was not correctly shrunk or removed"); + } function testBasicStaking() public { // Setup @@ -122,8 +136,8 @@ contract StakeTest is Test { vm.stopPrank(); // Setup initial stakers - uint256 positionId1 = setupStaker(firstStaker, initialStake1, 1); - uint256 positionId2 = setupStaker(secondStaker, initialStake2, 5); + uint256 positionId1 = doSnatch(firstStaker, initialStake1, 1); + uint256 positionId2 = doSnatch(secondStaker, initialStake2, 5); // Snatch setup vm.startPrank(newStaker); @@ -146,7 +160,7 @@ contract StakeTest is Test { vm.stopPrank(); } - function setupStaker(address staker, uint256 amount, uint32 taxRate) private returns (uint256 positionId) { + function doSnatch(address staker, uint256 amount, uint32 taxRate) private returns (uint256 positionId) { vm.startPrank(staker); harberg.approve(address(stakingPool), amount); uint256[] memory empty; @@ -154,21 +168,97 @@ contract StakeTest is Test { vm.stopPrank(); } - function assertPosition(uint256 positionId, uint256 expectedShares, uint32 expectedTaxRate) private view { - (uint256 shares, , , , uint32 taxRate) = stakingPool.positions(positionId); - assertEq(shares, expectedShares, "Incorrect share amount for new position"); - assertEq(taxRate, expectedTaxRate, "Incorrect tax rate for new position"); + function bp(uint256 val) internal pure returns (uint256) { + return val / 1e15; } - function verifyPositionShrunkOrRemoved(uint256 positionId, uint256 initialStake) private view { - (uint256 remainingShare, , , , ) = stakingPool.positions(positionId); - uint256 expectedInitialShares = stakingPool.assetsToShares(initialStake); - bool positionRemoved = remainingShare == 0; - bool positionShrunk = remainingShare < expectedInitialShares; - - assertTrue(positionRemoved || positionShrunk, "Position was not correctly shrunk or removed"); + function denormTR(uint256 normalizedTaxRate) internal pure returns (uint256) { + return normalizedTaxRate * 97; } + function testAvgTaxRateAndPercentageStaked() public { + uint256 smallstake = 0.3e17; + uint256 stakeOneThird = 1 ether; + uint256 stakeTwoThird = 2 ether; + address staker = makeAddr("staker"); + + // Mint and distribute tokens + vm.startPrank(liquidityManager); + // mint all the tokens we will need in the test + harberg.mint((smallstake + stakeOneThird + stakeTwoThird) * 5); + // send 20% of that to staker + harberg.transfer(staker, (smallstake + stakeOneThird + stakeTwoThird) * 2); + vm.stopPrank(); + + // Setup initial stakers + uint256 positionId1 = doSnatch(staker, smallstake, 0); + + uint256 avgTaxRate; + uint256 percentageStaked; + avgTaxRate = stakingPool.getAverageTaxRate(); + percentageStaked = stakingPool.getPercentageStaked(); + + // let this be about 10 basis points of tax rate + assertApproxEqRel(bp(denormTR(avgTaxRate)), 10, 1e17); + assertApproxEqRel(bp(percentageStaked), 10, 1e17); + + vm.prank(staker); + stakingPool.exitPosition(positionId1); + uint256 positionId2 = doSnatch(staker, stakeOneThird, 2); + + avgTaxRate = stakingPool.getAverageTaxRate(); + percentageStaked = stakingPool.getPercentageStaked(); + + assertApproxEqRel(bp(denormTR(avgTaxRate)), 50, 1e17); + assertApproxEqRel(bp(percentageStaked), 300, 1e17); + + vm.prank(staker); + stakingPool.exitPosition(positionId2); + positionId1 = doSnatch(staker, stakeOneThird, 10); + positionId2 = doSnatch(staker, stakeTwoThird, 11); + + avgTaxRate = stakingPool.getAverageTaxRate(); + percentageStaked = stakingPool.getPercentageStaked(); + + assertApproxEqRel(bp(denormTR(avgTaxRate)), 730, 1e17); + assertApproxEqRel(bp(percentageStaked), 1000, 1e17); + + vm.startPrank(staker); + stakingPool.exitPosition(positionId1); + stakingPool.exitPosition(positionId2); + vm.stopPrank(); + positionId1 = doSnatch(staker, stakeOneThird, 29); + positionId2 = doSnatch(staker, stakeTwoThird, 29); + + avgTaxRate = stakingPool.getAverageTaxRate(); + assertApproxEqRel(bp(denormTR(avgTaxRate)), 97000, 1e17); + + vm.startPrank(staker); + stakingPool.exitPosition(positionId1); + stakingPool.exitPosition(positionId2); + vm.stopPrank(); + positionId2 = doSnatch(staker, stakeTwoThird, 15); + + avgTaxRate = stakingPool.getAverageTaxRate(); + percentageStaked = stakingPool.getPercentageStaked(); + + assertApproxEqRel(bp(denormTR(avgTaxRate)), 2500, 1e17); + assertApproxEqRel(bp(percentageStaked), 660, 1e17); + + vm.startPrank(staker); + stakingPool.exitPosition(positionId2); + vm.stopPrank(); + + positionId1 = doSnatch(staker, stakeOneThird, 15); + + avgTaxRate = stakingPool.getAverageTaxRate(); + percentageStaked = stakingPool.getPercentageStaked(); + + assertApproxEqRel(bp(denormTR(avgTaxRate)), 2500, 1e17); + assertApproxEqRel(bp(percentageStaked), 330, 1e17); + } + + function testRevert_SharesTooLow() public { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); @@ -195,7 +285,7 @@ contract StakeTest is Test { harberg.transfer(newStaker, 1 ether); vm.stopPrank(); - uint256 positionId = setupStaker(existingStaker, 1 ether, 5); // Existing staker with tax rate 5 + uint256 positionId = doSnatch(existingStaker, 1 ether, 5); // Existing staker with tax rate 5 vm.startPrank(newStaker); harberg.transfer(newStaker, 1 ether); @@ -219,7 +309,7 @@ contract StakeTest is Test { harberg.transfer(ambitiousStaker, 1 ether); vm.stopPrank(); - uint256 positionId = setupStaker(staker, 2 ether, 10); + uint256 positionId = doSnatch(staker, 2 ether, 10); vm.startPrank(ambitiousStaker); harberg.approve(address(stakingPool), 1 ether); diff --git a/onchain/test/helpers/CSVHelper.sol b/onchain/test/helpers/CSVHelper.sol new file mode 100644 index 0000000..92a8dd0 --- /dev/null +++ b/onchain/test/helpers/CSVHelper.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/** + * @title CSVHelper + * @dev Library for managing CSV data in Solidity, including converting values to strings and writing CSV data. + */ +library CSVHelper { + /** + * @notice Creates a standard CSV header for liquidity position data. + * @return The CSV header as a string. + */ + function createPositionsHeader() internal pure returns (string memory) { + return "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb"; + } + + function createTimeSeriesHeader() internal pure returns (string memory) { + return "time, price, harbTotalSupply, supplyChange, stakeOutstandingShares, avgTaxRate, sentiment, taxCollected"; + } + + /** + * @notice Appends new CSV data to the existing CSV string. + * @param csv The current CSV string. + * @param newRow The new row to append. + * @return The updated CSV string. + */ + function appendRow(string memory csv, string memory newRow) internal pure returns (string memory) { + return string(abi.encodePacked(csv, "\n", newRow)); + } + + /** + * @notice Converts a `uint256` to a string. + * @param _i The integer to convert. + * @return The string representation of the integer. + */ + function uintToStr(uint256 _i) internal pure returns (string memory) { + if (_i == 0) { + return "0"; + } + uint256 j = _i; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len; + while (_i != 0) { + k = k - 1; + uint8 temp = (48 + uint8(_i - _i / 10 * 10)); + bstr[k] = bytes1(temp); + _i /= 10; + } + return string(bstr); + } + + /** + * @notice Converts an `int256` to a string. + * @param _i The integer to convert. + * @return The string representation of the integer. + */ + function intToStr(int256 _i) internal pure returns (string memory) { + if (_i == 0) { + return "0"; + } + bool negative = _i < 0; + uint256 absValue = uint256(negative ? -_i : _i); + uint256 len; + uint256 j = absValue; + while (j != 0) { + len++; + j /= 10; + } + if (negative) { + len++; // Increase length for the minus sign. + } + bytes memory bstr = new bytes(len); + uint256 k = len; + while (absValue != 0) { + k = k - 1; + uint8 temp = (48 + uint8(absValue - absValue / 10 * 10)); + bstr[k] = bytes1(temp); + absValue /= 10; + } + if (negative) { + bstr[0] = '-'; + } + return string(bstr); + } +} diff --git a/onchain/test/helpers/CSVManager.sol b/onchain/test/helpers/CSVManager.sol new file mode 100644 index 0000000..eddb149 --- /dev/null +++ b/onchain/test/helpers/CSVManager.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "./CSVHelper.sol"; + +/** + * @title CSVManager + * @dev An abstract contract that manages CSV creation, row appending, and writing to file. + * Contracts that inherit this contract can use the CSV functionality without managing the CSV string directly. + */ +abstract contract CSVManager is Test { + using CSVHelper for *; + + string internal csv; + + /** + * @notice Creates the header for the CSV file. + * This function should be called in the `setUp` function of derived contracts. + */ + function initializePositionsCSV() internal { + csv = CSVHelper.createPositionsHeader(); + } + + function initializeTimeSeriesCSV() internal { + csv = CSVHelper.createTimeSeriesHeader(); + } + + /** + * @notice Appends a new row to the CSV string. + * @param newRow The new row to append. + */ + function appendCSVRow(string memory newRow) internal { + csv = CSVHelper.appendRow(csv, newRow); + } + + /** + * @notice Writes the CSV string to a file. + * @param filePath The path where the CSV file will be saved. + */ + function writeCSVToFile(string memory filePath) internal { + vm.writeFile(filePath, csv); + } +} diff --git a/onchain/test/helpers/UniswapTestBase.sol b/onchain/test/helpers/UniswapTestBase.sol new file mode 100644 index 0000000..381282b --- /dev/null +++ b/onchain/test/helpers/UniswapTestBase.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; +import {TickMath} from "@aperture/uni-v3-lib/TickMath.sol"; +import "../../src/interfaces/IWETH9.sol"; +import {Harberg} from "../../src/Harberg.sol"; + + +/** + * @title UniswapTestBase + * @dev Base contract for Uniswap V3 testing, providing reusable swap logic. + */ +abstract contract UniswapTestBase is Test { + address account = makeAddr("alice"); + IUniswapV3Pool public pool; + IWETH9 public weth; + Harberg public harberg; + bool public token0isWeth; + + /** + * @dev Performs a swap in the Uniswap V3 pool. + * @param amount The amount to swap. + * @param isBuy True if buying WETH, false if selling. + */ + function performSwap(uint256 amount, bool isBuy) internal { + uint160 limit; + // Determine the swap direction + bool zeroForOne = isBuy ? token0isWeth : !token0isWeth; + + if (isBuy) { + vm.prank(account); + weth.transfer(address(this), amount); + } else { + vm.prank(account); + harberg.approve(address(this), amount); + } + + // Set the sqrtPriceLimitX96 based on the swap direction + if (zeroForOne) { + // Swapping token0 for token1 + // sqrtPriceLimitX96 must be less than current price but greater than MIN_SQRT_RATIO + limit = TickMath.MIN_SQRT_RATIO + 1; + } else { + // Swapping token1 for token0 + // sqrtPriceLimitX96 must be greater than current price but less than MAX_SQRT_RATIO + limit = TickMath.MAX_SQRT_RATIO - 1; + } + + pool.swap( + account, + zeroForOne, + int256(amount), + limit, + abi.encode(account, int256(amount), isBuy) + ); + } + + /** + * @dev The Uniswap V3 swap callback. + */ + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata _data + ) external { + require(amount0Delta > 0 || amount1Delta > 0); + + (address seller, , bool isBuy) = abi.decode(_data, (address, uint256, bool)); + + (, uint256 amountToPay) = amount0Delta > 0 + ? (!token0isWeth, uint256(amount0Delta)) + : (token0isWeth, uint256(amount1Delta)); + if (isBuy) { + weth.transfer(msg.sender, amountToPay); + } else { + require(harberg.transferFrom(seller, msg.sender, amountToPay), "Transfer failed"); + } + } +} diff --git a/onchain/test/mocks/MockSentimenter.sol b/onchain/test/mocks/MockSentimenter.sol new file mode 100644 index 0000000..3e59553 --- /dev/null +++ b/onchain/test/mocks/MockSentimenter.sol @@ -0,0 +1,59 @@ + +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.19; + +import {Harberg} from "../../src/Harberg.sol"; +import {Stake} from "../../src/Stake.sol"; +import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol"; +import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; + +contract MockSentimenter is Initializable, UUPSUpgradeable { + + Harberg private harberg; + Stake private stake; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error UnauthorizedAccount(address account); + + function initialize(address _harberg, address _stake) initializer public { + _changeAdmin(msg.sender); + harberg = Harberg(_harberg); + stake = Stake(_stake); + } + /** + * @dev Throws if called by any account other than the admin. + */ + modifier onlyAdmin() { + _checkAdmin(); + _; + } + + + /** + * @dev Throws if the sender is not the admin. + */ + function _checkAdmin() internal view virtual { + if (_getAdmin() != msg.sender) { + revert UnauthorizedAccount(msg.sender); + } + } + function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {} + + function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked) public pure returns (uint256 sentimentValue) { + + return 0; + } + + + /// @notice Computes the staker sentiment based on the proportion of the authorized stake that is currently staked. + /// @return sentiment A number between 0 and 200 indicating the market sentiment. + function getSentiment() external returns (uint256 sentiment) { + uint256 percentageStaked = stake.getPercentageStaked(); + uint256 averageTaxRate = stake.getAverageTaxRate(); + sentiment = calculateSentiment(averageTaxRate, percentageStaked); + } + + +}