From a37fb4aa642897d77236ea3fba80e22676fa0e38 Mon Sep 17 00:00:00 2001 From: Ruslan Akhtariev Date: Sun, 24 Nov 2024 20:00:01 -0800 Subject: [PATCH] finish donate with a basic test case --- foundry.toml | 4 + src/ClvrExecutor.sol | 57 ---------- src/ClvrHook.sol | 160 ++++++++++++++++++--------- src/ClvrModel.sol | 19 ++++ test/ClvrHook.t.sol | 131 ++++++++++++++++++++-- test/ClvrModel.t.sol | 16 +-- test/utils/Fixtures.sol | 150 ++++++++++++------------- test/utils/forks/DeployPermit2.sol | 30 +++++ test/utils/forks/Permit2Bytecode.sol | 7 ++ 9 files changed, 377 insertions(+), 197 deletions(-) delete mode 100644 src/ClvrExecutor.sol create mode 100644 test/utils/forks/DeployPermit2.sol create mode 100644 test/utils/forks/Permit2Bytecode.sol diff --git a/foundry.toml b/foundry.toml index 5107865..046d71c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,10 @@ src = "src" out = "out" libs = ["lib"] +ffi = true +fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] +solc_version = "0.8.26" +evm_version = "cancun" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options remappings = [ diff --git a/src/ClvrExecutor.sol b/src/ClvrExecutor.sol deleted file mode 100644 index ce3c721..0000000 --- a/src/ClvrExecutor.sol +++ /dev/null @@ -1,57 +0,0 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.24; - -// import { ISwapRouter } from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; -// import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -// import { ClvrIntentPool } from "./ClvrIntentPool.sol"; -// import { ClvrLibrary } from "./ClvrLibrary.sol"; - -// contract ClvrExecutor { -// ClvrIntentPool private intentPool; -// ISwapRouter private swapRouter; - -// constructor(address intentPool_, address swapRouter_) { -// intentPool = ClvrIntentPool(intentPool_); -// swapRouter = ISwapRouter(swapRouter_); -// } - -// /** -// * @notice Execute a batch of intents -// * @param statusQuoPrice The starting price TODO: get this in contract -// * @param poolKey The identifier for the pool through which the intents are being executed -// * @param intents The intents to execute -// */ -// function executeBatch(uint256 statusQuoPrice, bytes32 poolKey, ClvrLibrary.CLVRIntent[] memory intents) public { -// // uint256[] memory volumes = new uint256[](intents.length); -// for (uint256 i = 0; i < intents.length; i++) { -// bytes32 intentHash = keccak256(abi.encode(intents[i])); - -// bytes4 verification = intentPool.intentExists(poolKey, intentHash); - -// if (verification != ClvrLibrary.MAGICVALUE) { -// revert("Invalid Intent Passed to Executor"); -// } -// } -// } - -// function executeIntent(ClvrLibrary.CLVRIntent memory intent) private { -// IERC20(intent.tokenIn).transferFrom(intent.creator, address(this), intent.amountIn); -// IERC20(intent.tokenIn).approve(address(swapRouter), intent.amountIn); - -// uint256 amountOut = swapRouter.exactInputSingle(ISwapRouter.ExactInputSingleParams({ -// tokenIn: intent.tokenIn, -// tokenOut: intent.tokenOut, -// fee: intent.fee, -// recipient: intent.recipient, -// deadline: block.timestamp, -// amountIn: intent.amountIn, -// amountOutMinimum: 0, -// sqrtPriceLimitX96: 0 -// })); - -// IERC20(intent.tokenOut).transfer(intent.recipient, amountOut); - -// // emit IntentExecuted(intent.creator, intent.tokenIn, intent.tokenOut, intent.recipient, intent.fee, intent.amountIn, amountOut); -// } -// } diff --git a/src/ClvrHook.sol b/src/ClvrHook.sol index e25249b..be7a0a6 100644 --- a/src/ClvrHook.sol +++ b/src/ClvrHook.sol @@ -7,23 +7,32 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary, toBeforeSwapDelta } from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; +import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { ClvrIntentPool } from "./ClvrIntentPool.sol"; import { ClvrModel } from "./ClvrModel.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {console} from "forge-std/console.sol"; contract ClvrHook is BaseHook { using SafeCast for *; using StateLibrary for IPoolManager; + using BalanceDeltaLibrary for BalanceDelta; + using TransientStateLibrary for IPoolManager; + using CurrencySettler for Currency; struct SwapParamsExtended { + address sender; address recepient; IPoolManager.SwapParams params; } @@ -33,8 +42,12 @@ contract ClvrHook is BaseHook { mapping(PoolId => SwapParamsExtended[]) public swapParams; ClvrModel private model; + PoolSwapTest swapRouter; - constructor(IPoolManager _manager) BaseHook(_manager) {} + constructor(IPoolManager _manager, PoolSwapTest _swapRouter) BaseHook(_manager) { + model = new ClvrModel(0, 0); + swapRouter = _swapRouter; + } function getHookPermissions() public @@ -43,44 +56,56 @@ contract ClvrHook is BaseHook { override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: false, - afterInitialize: false, - beforeAddLiquidity: false, - afterAddLiquidity: false, - beforeRemoveLiquidity: false, - afterRemoveLiquidity: false, - beforeSwap: true, - afterSwap: false, - beforeDonate: true, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); + return + Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: false, + beforeDonate: true, + afterDonate: false, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); } - function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata data) - external - override - returns (bytes4, BeforeSwapDelta, uint24) - { - require(params.amountSpecified < 0, "Clvr Pools only work with exact input swaps"); + function beforeSwap( + address, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + bytes calldata data + ) external override returns (bytes4, BeforeSwapDelta, uint24) { + require( + params.amountSpecified < 0, + "Clvr Pools only work with exact input swaps" + ); PoolId poolId = PoolIdLibrary.toId(key); address recepient = abi.decode(data, (address)); - if (recepient == BATCH) { // TODO: make sure this can't easily be called + if (recepient == BATCH) { + // TODO: make sure this can't easily be called // perform the swap right away - return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + return ( + BaseHook.beforeSwap.selector, + BeforeSwapDeltaLibrary.ZERO_DELTA, + 0 + ); } + // the hook takes the exact amount of the input currency to itself Currency input = params.zeroForOne ? key.currency0 : key.currency1; - uint256 amountTaken = uint256(params.amountSpecified); + uint256 amountTaken = uint256(-params.amountSpecified); poolManager.mint(address(this), input.toId(), amountTaken); SwapParamsExtended memory paramsE = SwapParamsExtended({ + sender: tx.origin, recepient: recepient, params: params }); @@ -88,14 +113,20 @@ contract ClvrHook is BaseHook { // Store the swap params swapParams[poolId].push(paramsE); - return (BaseHook.beforeSwap.selector, toBeforeSwapDelta(amountTaken.toInt128(), 0), 0); + return ( + BaseHook.beforeSwap.selector, + toBeforeSwapDelta(amountTaken.toInt128(), 0), + 0 + ); } - function beforeDonate(address, PoolKey calldata key, uint256, uint256, bytes calldata) - external - override - returns (bytes4) - { + function beforeDonate( + address, + PoolKey calldata key, + uint256, + uint256, + bytes calldata + ) external override returns (bytes4) { PoolId poolId = key.toId(); SwapParamsExtended[] memory params = swapParams[poolId]; @@ -103,27 +134,56 @@ contract ClvrHook is BaseHook { return BaseHook.beforeDonate.selector; } - (uint160 sqrtPriceX96, , ,) = poolManager.getSlot0(poolId); - uint256 decimals = 10 ** ERC20(Currency.unwrap(key.currency1)).decimals(); - uint256 sqrtPrice = (sqrtPriceX96 * decimals) / 2 ** 96; // get sqrtPrice with decimals of token - uint256 currentPrice = sqrtPrice ** 2 / decimals; - - params = model.clvrReorder(currentPrice, params, currentPrice * 1e18, 1e18/currentPrice); // currentPrice hack to simulate token amounts - - for (uint256 i = 0; i < params.length; i++) { - poolManager.swap( - key, - params[i].params, - abi.encode(BATCH) - ); + uint256 currentPrice = getCurrentPrice(key); + + params = model.clvrReorder( + currentPrice, + params, + currentPrice * 1e18, + 1e18 / currentPrice + ); // currentPrice hack to simulate token amounts + + for (uint256 i = 1; i < params.length; i++) { + // PoolSwapTest.TestSettings memory testSettings = + // PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + // swapRouter.swap(key, params[i].params, testSettings, abi.encode(BATCH)); + poolManager.swap(key, params[i].params, abi.encode(BATCH)); + + int256 delta0 = poolManager.currencyDelta(address(this), key.currency0); + int256 delta1 = poolManager.currencyDelta(address(this), key.currency1); + + // console.log(delta0); + // console.log(delta1); + + if (delta0 < 0) { + key.currency0.settle(poolManager, address(this), uint256(-delta0), true); + } + + if (delta1 < 0) { + key.currency1.settle(poolManager, address(this), uint256(-delta1), true); + } + + if (delta0 > 0) { + key.currency0.take(poolManager, params[i].recepient, uint256(delta0), false); + } + + if (delta1 > 0) { + key.currency1.take(poolManager, params[i].recepient, uint256(delta1), false); + } } return BaseHook.beforeDonate.selector; } - // function sqrtPriceX96ToPrice(uint160 sqrtPriceX96) internal pure returns (uint256) { - // uint256 priceX96 = uint256(sqrtPriceX96).mul(uint256(sqrtPriceX96)).mul(1e18) >> (96 * 2); - // return priceX96; - // } + function getCurrentPrice(PoolKey calldata key) view internal returns (uint256) { + (uint160 sqrtPriceX96, , , ) = poolManager.getSlot0(key.toId()); + uint256 decimals = 10 ** + ERC20(Currency.unwrap(key.currency1)).decimals(); + uint256 sqrtPrice = (sqrtPriceX96 * decimals) / 2 ** 96; // get sqrtPrice with decimals of token + return sqrtPrice ** 2 / decimals; + } + function _unlockCallback( + bytes calldata data + ) internal override returns (bytes memory) {} } \ No newline at end of file diff --git a/src/ClvrModel.sol b/src/ClvrModel.sol index 4e72e7e..06e006f 100644 --- a/src/ClvrModel.sol +++ b/src/ClvrModel.sol @@ -5,6 +5,8 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import { ClvrLn } from "./ClvrLn.sol"; import { ClvrHook } from "./ClvrHook.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + import { console } from "forge-std/console.sol"; @@ -33,16 +35,21 @@ contract ClvrModel { uint256 reserve_x, uint256 reserve_y ) public returns (ClvrHook.SwapParamsExtended[] memory) { + o = addMockTrade(o); + + // console.log("ENTERED FUNCTION"); set_reserve_x(reserve_x); set_reserve_y(reserve_y); int128 lnP0 = p0.lnU256().toInt128(); for (uint256 i = 1; i < o.length; i++) { + // console.log("ENTERED LOOP"); uint256 candidateIndex = i; int128 unsquaredCandidateValue = lnP0 - P(o, i).lnU256().toInt128(); int256 candidateValue = unsquaredCandidateValue ** 2 / 1e18; for (uint256 j = i + 1; j < o.length; j++) { + // console.log("ENTERED INNER LOOP"); swap(o, i, j); int256 unsquaredValue = lnP0 - P(o, i).lnU256().toInt128(); @@ -64,6 +71,18 @@ contract ClvrModel { return o; } + // adds a mock trade as a first entry of th array + function addMockTrade(ClvrHook.SwapParamsExtended[] memory o) internal pure returns(ClvrHook.SwapParamsExtended[] memory) { + ClvrHook.SwapParamsExtended memory mock = ClvrHook.SwapParamsExtended(address(0), address(0), IPoolManager.SwapParams(false, 0, 0)); + ClvrHook.SwapParamsExtended[] memory newO = new ClvrHook.SwapParamsExtended[](o.length + 1); + for (uint256 i = 0; i < o.length; i++) { + newO[i] = o[i]; + } + newO[o.length] = o[0]; + newO[0] = mock; + return newO; + } + function swap(ClvrHook.SwapParamsExtended[] memory o, uint256 i1, uint256 i2) internal pure { ClvrHook.SwapParamsExtended memory temp = o[i1]; o[i1] = o[i2]; diff --git a/test/ClvrHook.t.sol b/test/ClvrHook.t.sol index 36a1202..e5e1d9e 100644 --- a/test/ClvrHook.t.sol +++ b/test/ClvrHook.t.sol @@ -6,8 +6,6 @@ import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionMa import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; - - import {Test} from "forge-std/Test.sol"; import {console} from "forge-std/console.sol"; @@ -25,36 +23,155 @@ import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; +import {Fixtures} from "./utils/Fixtures.sol"; import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol"; -contract ClvrHookTest is Test, Deployers { +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ClvrHookTest is Test, Deployers, Fixtures { + using EasyPosm for IPositionManager; using StateLibrary for IPoolManager; PoolId poolId; ClvrHook hook; + address[5] users; + + uint256 tokenId; + int24 tickLower; + int24 tickUpper; + function setUp() public { deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); + deployAndApprovePosm(manager); + address flags = address( uint160( - Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_DONATE_FLAG + Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG ) ^ (0x4444 << 144) // Namespace the hook to avoid collisions ); - bytes memory constructorArgs = abi.encode(manager); + bytes memory constructorArgs = abi.encode(manager, swapRouter); deployCodeTo("ClvrHook.sol:ClvrHook", constructorArgs, flags); hook = ClvrHook(flags); + deal(address(this), 200 ether); + key = PoolKey(currency0, currency1, 3000, 60, IHooks(hook)); poolId = key.toId(); manager.initialize(key, SQRT_PRICE_1_1); + + // Provide full-range liquidity to the pool + tickLower = TickMath.minUsableTick(key.tickSpacing); + tickUpper = TickMath.maxUsableTick(key.tickSpacing); + + deal(address(this), 200 ether); + + (uint256 amount0, uint256 amount1) = LiquidityAmounts + .getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(100e18) + ); + + (tokenId, ) = posm.mint( + key, + tickLower, + tickUpper, + 100e18, + amount0 + 1, + amount1 + 1, + address(this), + block.timestamp, + "" + ); + + initUsers(); + } + + function initUsers() internal { + users[0] = makeAddr("Alice"); + users[1] = makeAddr("Bob"); + users[2] = makeAddr("Carol"); + users[3] = makeAddr("3"); + users[4] = makeAddr("4"); + } + + function dealCurrencyToUsers() internal { + for (uint256 i = 0; i < users.length; i++) { + deal(Currency.unwrap(currency0), users[i], 100e18); + deal(Currency.unwrap(currency1), users[i], 100e18); + } } - function testHook() public { - console.log("Hello, World!"); + function approveSwapRouter() internal { + for (uint256 i = 0; i < 5; i++) { + vm.startPrank(users[i]); + ERC20(Currency.unwrap(currency0)).approve(address(swapRouter), type(uint256).max); + ERC20(Currency.unwrap(currency1)).approve(address(swapRouter), type(uint256).max); + vm.stopPrank(); + } + } + + function approveHook() internal { + for (uint256 i = 0; i < 5; i++) { + vm.startPrank(users[i]); + ERC20(Currency.unwrap(currency0)).approve(address(hook), type(uint256).max); + ERC20(Currency.unwrap(currency1)).approve(address(hook), type(uint256).max); + vm.stopPrank(); + } + } + + // function approveHookForUsers() public { + + function testHookSanity() public { + dealCurrencyToUsers(); + approveSwapRouter(); + approveHook(); + + address sender = users[0]; + bytes memory hookData = abi.encode(sender); + + int256 amount = -1e18; + bool zeroForOne = true; + // console.log(currency0.balanceOf(address(this))); + // console.log(currency1.balanceOf(address(this))); + // console.log(); + + // console.log(currency0.balanceOf(sender)); + // console.log(currency1.balanceOf(sender)); + + uint256 c0balance = currency0.balanceOf(sender); + uint256 c1balance = currency1.balanceOf(sender); + + vm.startPrank(sender, sender); + swap(key, zeroForOne, amount, hookData); + vm.stopPrank(); + + require(currency0.balanceOf(sender) + uint256(-amount) == c0balance, "Currency0 balance should be decreased by amount"); + require(currency1.balanceOf(sender) == c1balance, "Currency1 balance should not be changed"); + + // console.log(currency0.balanceOf(address(this))); + // console.log(currency1.balanceOf(address(this))); + // console.log(); + + // console.log(currency1.balanceOf(sender)); + // console.log(currency0.balanceOf(sender)); + // console.log(currency1.balanceOf(address(hook))); + + // console.log(currency1.balanceOf(sender)); + donateRouter.donate(key, 0, 0, ""); + + require(currency1.balanceOf(sender) > c1balance, "Swap should have increased currency1 balance"); + + // console.log(currency1.balanceOf(sender)); + // console.log(currency0.balanceOf(sender)); + // console.log(currency1.balanceOf(address(hook))); } } \ No newline at end of file diff --git a/test/ClvrModel.t.sol b/test/ClvrModel.t.sol index be54bc1..c6ca392 100644 --- a/test/ClvrModel.t.sol +++ b/test/ClvrModel.t.sol @@ -24,16 +24,16 @@ contract ClvrModelTest is Test { */ function testModel() public { ClvrHook.SwapParamsExtended[] memory o = new ClvrHook.SwapParamsExtended[](4); - o[0] = ClvrHook.SwapParamsExtended(address(0), IPoolManager.SwapParams(false, 0, 0)); // first one is mock (address = 0) - o[3] = ClvrHook.SwapParamsExtended(address(1), IPoolManager.SwapParams(BUY, -10e18, 0)); - o[1] = ClvrHook.SwapParamsExtended(address(1), IPoolManager.SwapParams(SELL, -5e18, 0)); - o[2] = ClvrHook.SwapParamsExtended(address(1), IPoolManager.SwapParams(SELL, -2e18, 0)); + o[0] = ClvrHook.SwapParamsExtended(address(0), address(0), IPoolManager.SwapParams(false, 0, 0)); // first one is mock (address = 0) + o[3] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(BUY, -10e18, 0)); + o[1] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -5e18, 0)); + o[2] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -2e18, 0)); ClvrHook.SwapParamsExtended[] memory expected = new ClvrHook.SwapParamsExtended[](4); - expected[0] = ClvrHook.SwapParamsExtended(address(0), IPoolManager.SwapParams(false, 0, 0)); - expected[1] = ClvrHook.SwapParamsExtended(address(1), IPoolManager.SwapParams(SELL, -2e18, 0)); - expected[2] = ClvrHook.SwapParamsExtended(address(1), IPoolManager.SwapParams(SELL, -5e18, 0)); - expected[3] = ClvrHook.SwapParamsExtended(address(1), IPoolManager.SwapParams(BUY, -10e18, 0)); + expected[0] = ClvrHook.SwapParamsExtended(address(0), address(0), IPoolManager.SwapParams(false, 0, 0)); + expected[1] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -2e18, 0)); + expected[2] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -5e18, 0)); + expected[3] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(BUY, -10e18, 0)); uint256 gas = gasleft(); diff --git a/test/utils/Fixtures.sol b/test/utils/Fixtures.sol index 2056274..4c351e3 100644 --- a/test/utils/Fixtures.sol +++ b/test/utils/Fixtures.sol @@ -1,88 +1,88 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.24; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; -// import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -// import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -// import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -// import {PositionManager} from "v4-periphery/src/PositionManager.sol"; -// import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol"; -// import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -// import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -// import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -// import {IERC20} from "forge-std/interfaces/IERC20.sol"; -// import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; -// import {DeployPermit2} from "./forks/DeployPermit2.sol"; -// import {IERC721Permit_v4} from "v4-periphery/src/interfaces/IERC721Permit_v4.sol"; -// import {IEIP712_v4} from "v4-periphery/src/interfaces/IEIP712_v4.sol"; -// import {ERC721PermitHash} from "v4-periphery/src/libraries/ERC721PermitHash.sol"; -// import {IPositionDescriptor} from "v4-periphery/src/interfaces/IPositionDescriptor.sol"; -// import {IWETH9} from "v4-periphery/src/interfaces/external/IWETH9.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PositionManager} from "v4-periphery/src/PositionManager.sol"; +import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {DeployPermit2} from "./forks/DeployPermit2.sol"; +import {IERC721Permit_v4} from "v4-periphery/src/interfaces/IERC721Permit_v4.sol"; +import {IEIP712_v4} from "v4-periphery/src/interfaces/IEIP712_v4.sol"; +import {ERC721PermitHash} from "v4-periphery/src/libraries/ERC721PermitHash.sol"; +import {IPositionDescriptor} from "v4-periphery/src/interfaces/IPositionDescriptor.sol"; +import {IWETH9} from "v4-periphery/src/interfaces/external/IWETH9.sol"; -// /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. -// contract Fixtures is Deployers, DeployPermit2 { -// uint256 constant STARTING_USER_BALANCE = 10_000_000 ether; -// uint256 constant MAX_SLIPPAGE_ADD_LIQUIDITY = type(uint256).max; -// uint256 constant MAX_SLIPPAGE_REMOVE_LIQUIDITY = 0; +/// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. +contract Fixtures is Deployers, DeployPermit2 { + uint256 constant STARTING_USER_BALANCE = 10_000_000 ether; + uint256 constant MAX_SLIPPAGE_ADD_LIQUIDITY = type(uint256).max; + uint256 constant MAX_SLIPPAGE_REMOVE_LIQUIDITY = 0; -// IPositionManager posm; + IPositionManager posm; -// function deployAndApprovePosm(IPoolManager poolManager) public { -// deployPosm(poolManager); -// approvePosm(); -// } + function deployAndApprovePosm(IPoolManager poolManager) public { + deployPosm(poolManager); + approvePosm(); + } -// function deployPosm(IPoolManager poolManager) internal { -// // We use vm.etch to prevent having to use via-ir in this repository. -// etchPermit2(); -// posm = IPositionManager(new PositionManager(poolManager, permit2, 300_000, IPositionDescriptor(address(0)), IWETH9(address(0)))); -// } + function deployPosm(IPoolManager poolManager) internal { + // We use vm.etch to prevent having to use via-ir in this repository. + etchPermit2(); + posm = IPositionManager(new PositionManager(poolManager, permit2, 300_000, IPositionDescriptor(address(0)), IWETH9(address(0)))); + } -// function seedBalance(address to) internal { -// IERC20(Currency.unwrap(currency0)).transfer(to, STARTING_USER_BALANCE); -// IERC20(Currency.unwrap(currency1)).transfer(to, STARTING_USER_BALANCE); -// } + function seedBalance(address to) internal { + IERC20(Currency.unwrap(currency0)).transfer(to, STARTING_USER_BALANCE); + IERC20(Currency.unwrap(currency1)).transfer(to, STARTING_USER_BALANCE); + } -// function approvePosm() internal { -// approvePosmCurrency(currency0); -// approvePosmCurrency(currency1); -// } + function approvePosm() internal { + approvePosmCurrency(currency0); + approvePosmCurrency(currency1); + } -// function approvePosmCurrency(Currency currency) internal { -// // Because POSM uses permit2, we must execute 2 permits/approvals. -// // 1. First, the caller must approve permit2 on the token. -// IERC20(Currency.unwrap(currency)).approve(address(permit2), type(uint256).max); -// // 2. Then, the caller must approve POSM as a spender of permit2. TODO: This could also be a signature. -// permit2.approve(Currency.unwrap(currency), address(posm), type(uint160).max, type(uint48).max); -// } + function approvePosmCurrency(Currency currency) internal { + // Because POSM uses permit2, we must execute 2 permits/approvals. + // 1. First, the caller must approve permit2 on the token. + IERC20(Currency.unwrap(currency)).approve(address(permit2), type(uint256).max); + // 2. Then, the caller must approve POSM as a spender of permit2. TODO: This could also be a signature. + permit2.approve(Currency.unwrap(currency), address(posm), type(uint160).max, type(uint48).max); + } -// // Does the same approvals as approvePosm, but for a specific address. -// function approvePosmFor(address addr) internal { -// vm.startPrank(addr); -// approvePosm(); -// vm.stopPrank(); -// } + // Does the same approvals as approvePosm, but for a specific address. + function approvePosmFor(address addr) internal { + vm.startPrank(addr); + approvePosm(); + vm.stopPrank(); + } -// function permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { -// bytes32 digest = getDigest(operator, tokenId, 1, block.timestamp + 1); + function permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { + bytes32 digest = getDigest(operator, tokenId, 1, block.timestamp + 1); -// (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); -// bytes memory signature = abi.encodePacked(r, s, v); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); -// vm.prank(operator); -// IERC721Permit_v4(address(posm)).permit(operator, tokenId, block.timestamp + 1, nonce, signature); -// } + vm.prank(operator); + IERC721Permit_v4(address(posm)).permit(operator, tokenId, block.timestamp + 1, nonce, signature); + } -// function getDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) -// internal -// view -// returns (bytes32 digest) -// { -// digest = keccak256( -// abi.encodePacked( -// "\x19\x01", -// IEIP712_v4(address(posm)).DOMAIN_SEPARATOR(), -// keccak256(abi.encode(ERC721PermitHash.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) -// ) -// ); -// } -// } \ No newline at end of file + function getDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + internal + view + returns (bytes32 digest) + { + digest = keccak256( + abi.encodePacked( + "\x19\x01", + IEIP712_v4(address(posm)).DOMAIN_SEPARATOR(), + keccak256(abi.encode(ERC721PermitHash.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) + ) + ); + } +} \ No newline at end of file diff --git a/test/utils/forks/DeployPermit2.sol b/test/utils/forks/DeployPermit2.sol new file mode 100644 index 0000000..0255081 --- /dev/null +++ b/test/utils/forks/DeployPermit2.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {CommonBase} from "forge-std/Base.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +import {Permit2Bytecode} from "./Permit2Bytecode.sol"; + +/// @notice helper to deploy permit2 from precompiled bytecode. To be used in foundry tests and scripts +/// @dev useful if testing externally against permit2 and want to avoid +/// recompiling entirely and requiring viaIR compilation +/// a fork of DeployPermit2 from the permit2 repository +contract DeployPermit2 is CommonBase, Permit2Bytecode { + IAllowanceTransfer permit2 = IAllowanceTransfer(address(0x000000000022D473030F116dDEE9F6B43aC78BA3)); + + /// @notice Deploys permit2 with vm.etch, to be used in foundry tests + function etchPermit2() public returns (IAllowanceTransfer) { + vm.etch(address(permit2), PERMIT2_BYTECODE); + return permit2; + } + + /// @notice Deploys permit2 with anvil_setCode, to be used in foundry scripts against anvil + function anvilPermit2() public returns (IAllowanceTransfer) { + vm.rpc( + "anvil_setCode", + string.concat('["', vm.toString(address(permit2)), '","', vm.toString(PERMIT2_BYTECODE), '"]') + ); + return permit2; + } +} \ No newline at end of file diff --git a/test/utils/forks/Permit2Bytecode.sol b/test/utils/forks/Permit2Bytecode.sol new file mode 100644 index 0000000..fa1eba0 --- /dev/null +++ b/test/utils/forks/Permit2Bytecode.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract Permit2Bytecode { + bytes constant PERMIT2_BYTECODE = + hex"6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f0000000000000000000000000000000000000000000000000000000000007a6903611b69577fd5a17abc3865df5c1400c0299bd4ce2eefc8114aec5f9d3dded1745783e57b9890565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"; +} \ No newline at end of file