From 50e7be5cef2f4acf0464dea668c359df4b94f3c6 Mon Sep 17 00:00:00 2001 From: prosperring <162565078+prosperring@users.noreply.github.com> Date: Wed, 8 May 2024 21:09:03 +0300 Subject: [PATCH 01/10] bug: update README with new repository structure (#628) Resolves Issue Uniswap#627 --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e74aa63f5..f60939444 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,12 @@ The fee values, or callback logic, may be updated by the hooks dependent on thei ## Repository Structure -All contracts are held within the `v4-core/contracts` folder. +All contracts are held within the `v4-core/src` folder. -Note that helper contracts used by tests are held in the `v4-core/contracts/test` subfolder within the contracts folder. Any new test helper contracts should be added here, but all foundry tests are in the `v4-core/test/foundry-tests` folder. +Note that helper contracts used by tests are held in the `v4-core/src/test` subfolder within the `src` folder. Any new test helper contracts should be added here, but all foundry tests are in the `v4-core/test` folder. ```markdown -contracts/ +src/ ----interfaces/ | IPoolManager.sol | ... @@ -56,10 +56,12 @@ contracts/ | Pool.sol | ... ----test +----PoolManager.sol ... -PoolManager.sol test/ -----foundry-tests/ +----libraries/ + | Position.t.sol + | Pool.t.sol ``` ## Local deployment and Usage From 241e025bfb45e031aaac8b3d59a12f98c7559612 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Thu, 9 May 2024 15:58:57 -0400 Subject: [PATCH 02/10] Add salt to modifyLiquidity params (#608) * add salt poc and tests * update gas test * fmt * gas snaps * use lastCallGas * update * add .snap * use external * use external * use overloaded func * use isolate * assertEq, update gas snaps * use scratch space, add multi router test * undo lib/forge-std * comments: * update gas --- ...o already existing position with salt.snap | 1 + .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ...new liquidity to a position with salt.snap | 1 + .forge-snapshots/donate gas with 1 token.snap | 2 +- .../donate gas with 2 tokens.snap | 2 +- .../erc20 collect protocol fees.snap | 2 +- .../native collect protocol fees.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- ...wap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../swap with lp fee and protocol fee.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/PoolManager.sol | 11 +- src/interfaces/IPoolManager.sol | 6 +- src/libraries/Pool.sol | 4 +- src/libraries/Position.sol | 10 +- src/test/PoolNestedActionsTest.sol | 4 +- src/test/SkipCallsTestHook.sol | 2 +- test/ModifyLiquidity.t.sol | 186 ++++++++++++++++++ test/PoolManager.t.sol | 4 +- test/Sync.t.sol | 2 +- test/libraries/Hooks.t.sol | 14 +- test/libraries/Pool.t.sol | 3 +- test/libraries/Position.t.sol | 6 +- test/utils/Deployers.sol | 4 +- 39 files changed, 252 insertions(+), 54 deletions(-) create mode 100644 .forge-snapshots/add liquidity to already existing position with salt.snap create mode 100644 .forge-snapshots/create new liquidity to a position with salt.snap create mode 100644 test/ModifyLiquidity.t.sol diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap new file mode 100644 index 000000000..d48dd491a --- /dev/null +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -0,0 +1 @@ +146357 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index a54c413f5..449ae03c2 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -278788 \ No newline at end of file +279342 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 36e97649b..97f85c617 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -136156 \ No newline at end of file +136584 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index df3667106..e63c54318 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -145947 \ No newline at end of file +146333 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap new file mode 100644 index 000000000..bd6fabdec --- /dev/null +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -0,0 +1 @@ +294885 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 1 token.snap b/.forge-snapshots/donate gas with 1 token.snap index d03053ee3..89ae48f28 100644 --- a/.forge-snapshots/donate gas with 1 token.snap +++ b/.forge-snapshots/donate gas with 1 token.snap @@ -1 +1 @@ -108839 \ No newline at end of file +108818 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 2 tokens.snap b/.forge-snapshots/donate gas with 2 tokens.snap index f99b4be6f..c5366c4b3 100644 --- a/.forge-snapshots/donate gas with 2 tokens.snap +++ b/.forge-snapshots/donate gas with 2 tokens.snap @@ -1 +1 @@ -149324 \ No newline at end of file +149260 \ No newline at end of file diff --git a/.forge-snapshots/erc20 collect protocol fees.snap b/.forge-snapshots/erc20 collect protocol fees.snap index 9d85816b6..975b23465 100644 --- a/.forge-snapshots/erc20 collect protocol fees.snap +++ b/.forge-snapshots/erc20 collect protocol fees.snap @@ -1 +1 @@ -57332 \ No newline at end of file +57354 \ No newline at end of file diff --git a/.forge-snapshots/native collect protocol fees.snap b/.forge-snapshots/native collect protocol fees.snap index f704d0c34..86d096ded 100644 --- a/.forge-snapshots/native collect protocol fees.snap +++ b/.forge-snapshots/native collect protocol fees.snap @@ -1 +1 @@ -59565 \ No newline at end of file +59587 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index ea6b47a93..1f2f82624 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22548 \ No newline at end of file +22710 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 4a890fee8..9b7f305da 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -115868 \ No newline at end of file +116427 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index ab9f1f9e9..12c0317b0 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -113155 \ No newline at end of file +113605 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index c11cc8c15..c67fcb47d 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -115856 \ No newline at end of file +116415 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index bc2fb8634..b63e5c735 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -130538 \ No newline at end of file +130629 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 2cdead364..44ba60386 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -148889 \ No newline at end of file +148937 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index aae292123..4f51848d5 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -113433 \ No newline at end of file +113524 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index b01815060..9f50530b3 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -124803 \ No newline at end of file +124851 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 1ab75f9b4..5df1673f6 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -136807 \ No newline at end of file +136898 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 2634c653d..84508f65d 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -125922 \ No newline at end of file +126013 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index 1a060b5e4..60e6f7a84 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -148001 \ No newline at end of file +147983 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index a4154b13b..73b0bfbf5 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -164904 \ No newline at end of file +164886 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 858c46065..7c98774e1 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -224124 \ No newline at end of file +224212 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index a74236b3b..e1eba0bc6 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -149105 \ No newline at end of file +149153 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 05f7243a9..23c7648dd 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -124815 \ No newline at end of file +124863 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index 5d31b2e86..be735dedf 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -181394 \ No newline at end of file +181496 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 1c9388d2b..222663c68 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -159641 \ No newline at end of file +159743 \ No newline at end of file diff --git a/src/PoolManager.sol b/src/PoolManager.sol index 050ca55a8..0e1d1d78a 100644 --- a/src/PoolManager.sol +++ b/src/PoolManager.sol @@ -116,22 +116,22 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim } /// @inheritdoc IPoolManager - function getLiquidity(PoolId id, address _owner, int24 tickLower, int24 tickUpper) + function getLiquidity(PoolId id, address _owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view override returns (uint128 liquidity) { - return pools[id].positions.get(_owner, tickLower, tickUpper).liquidity; + return pools[id].positions.get(_owner, tickLower, tickUpper, salt).liquidity; } - function getPosition(PoolId id, address _owner, int24 tickLower, int24 tickUpper) + function getPosition(PoolId id, address _owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view override returns (Position.Info memory position) { - return pools[id].positions.get(_owner, tickLower, tickUpper); + return pools[id].positions.get(_owner, tickLower, tickUpper, salt); } /// @inheritdoc IPoolManager @@ -239,7 +239,8 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim tickLower: params.tickLower, tickUpper: params.tickUpper, liquidityDelta: params.liquidityDelta.toInt128(), - tickSpacing: key.tickSpacing + tickSpacing: key.tickSpacing, + salt: params.salt }) ); diff --git a/src/interfaces/IPoolManager.sol b/src/interfaces/IPoolManager.sol index 1ba47b813..0755288b9 100644 --- a/src/interfaces/IPoolManager.sol +++ b/src/interfaces/IPoolManager.sol @@ -97,7 +97,7 @@ interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload { function getLiquidity(PoolId id) external view returns (uint128 liquidity); /// @notice Get the current value of liquidity for the specified pool and position - function getLiquidity(PoolId id, address owner, int24 tickLower, int24 tickUpper) + function getLiquidity(PoolId id, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view returns (uint128 liquidity); @@ -115,7 +115,7 @@ interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload { returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1); /// @notice Get the position struct for a specified pool and position - function getPosition(PoolId id, address owner, int24 tickLower, int24 tickUpper) + function getPosition(PoolId id, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view returns (Position.Info memory position); @@ -152,6 +152,8 @@ interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload { int24 tickUpper; // how to modify the liquidity int256 liquidityDelta; + // a value to set if you want unique liquidity positions at the same range + bytes32 salt; } /// @notice Modify the liquidity for the given pool diff --git a/src/libraries/Pool.sol b/src/libraries/Pool.sol index e7b6420c0..d61f17525 100644 --- a/src/libraries/Pool.sol +++ b/src/libraries/Pool.sol @@ -143,6 +143,8 @@ library Pool { int128 liquidityDelta; // the spacing between ticks int24 tickSpacing; + // used to distinguish positions of the same owner, at the same tick range + bytes32 salt; } struct ModifyLiquidityState { @@ -200,7 +202,7 @@ library Pool { (state.feeGrowthInside0X128, state.feeGrowthInside1X128) = getFeeGrowthInside(self, tickLower, tickUpper); - Position.Info storage position = self.positions.get(params.owner, tickLower, tickUpper); + Position.Info storage position = self.positions.get(params.owner, tickLower, tickUpper, params.salt); (feesOwed0, feesOwed1) = position.update(liquidityDelta, state.feeGrowthInside0X128, state.feeGrowthInside1X128); diff --git a/src/libraries/Position.sol b/src/libraries/Position.sol index 3b08fbd69..13563b63f 100644 --- a/src/libraries/Position.sol +++ b/src/libraries/Position.sol @@ -26,20 +26,24 @@ library Position { /// @param owner The address of the position owner /// @param tickLower The lower tick boundary of the position /// @param tickUpper The upper tick boundary of the position + /// @param salt A unique value to differentiate between multiple positions in the same range /// @return position The position info struct of the given owners' position - function get(mapping(bytes32 => Info) storage self, address owner, int24 tickLower, int24 tickUpper) + function get(mapping(bytes32 => Info) storage self, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) internal view returns (Info storage position) { - // positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper)) + // positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt)) bytes32 positionKey; + /// @solidity memory-safe-assembly assembly { + mstore(0x26, salt) // [0x26, 0x46) mstore(0x06, tickUpper) // [0x23, 0x26) mstore(0x03, tickLower) // [0x20, 0x23) mstore(0, owner) // [0x0c, 0x20) - positionKey := keccak256(0x0c, 0x1a) + positionKey := keccak256(0x0c, 0x3a) // len is 58 bytes + mstore(0x26, 0) // rewrite 0x26 to 0 } position = self[positionKey]; } diff --git a/src/test/PoolNestedActionsTest.sol b/src/test/PoolNestedActionsTest.sol index 07fbdd081..7cba77aa8 100644 --- a/src/test/PoolNestedActionsTest.sol +++ b/src/test/PoolNestedActionsTest.sol @@ -68,10 +68,10 @@ contract NestedActionExecutor is Test, PoolTestBase { error KeyNotSet(); IPoolManager.ModifyLiquidityParams internal ADD_LIQ_PARAMS = - IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18}); + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); IPoolManager.ModifyLiquidityParams internal REMOVE_LIQ_PARAMS = - IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18}); + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); IPoolManager.SwapParams internal SWAP_PARAMS = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: Constants.SQRT_RATIO_1_2}); diff --git a/src/test/SkipCallsTestHook.sol b/src/test/SkipCallsTestHook.sol index c98a7975c..3c85dac19 100644 --- a/src/test/SkipCallsTestHook.sol +++ b/src/test/SkipCallsTestHook.sol @@ -176,7 +176,7 @@ contract SkipCallsTestHook is BaseTestHooks, Test { ) public { // first hook needs to add liquidity for itself IPoolManager.ModifyLiquidityParams memory newParams = - IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18}); + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); IPoolManager(manager).modifyLiquidity(key, newParams, hookData); // hook removes liquidity IPoolManager(manager).modifyLiquidity(key, params, hookData); diff --git a/test/ModifyLiquidity.t.sol b/test/ModifyLiquidity.t.sol new file mode 100644 index 000000000..5bbbef4df --- /dev/null +++ b/test/ModifyLiquidity.t.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Deployers} from "./utils/Deployers.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {PoolKey} from "src/types/PoolKey.sol"; +import {IPoolManager} from "src/interfaces/IPoolManager.sol"; +import {IHooks} from "src/interfaces/IHooks.sol"; +import {Position} from "src/libraries/Position.sol"; +import {PoolId} from "src/types/PoolId.sol"; +import {PoolModifyLiquidityTest} from "../src/test/PoolModifyLiquidityTest.sol"; +import {Constants} from "./utils/Constants.sol"; +import {Currency} from "src/types/Currency.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; + +contract ModifyLiquidityTest is Test, Deployers, GasSnapshot { + PoolKey simpleKey; // vanilla pool key + PoolId simplePoolId; // id for vanilla pool key + + bytes32 SALT = hex"CAFF"; + + IPoolManager.ModifyLiquidityParams public LIQ_PARAM_NO_SALT = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); + + IPoolManager.ModifyLiquidityParams public LIQ_PARAM_SALT = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: SALT}); + + function setUp() public { + deployFreshManagerAndRouters(); + deployMintAndApprove2Currencies(); + (simpleKey, simplePoolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_RATIO_1_1, ZERO_BYTES); + } + + function test_modifyLiquidity_samePosition_zeroSalt_isUpdated() public { + Position.Info memory position = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_NO_SALT.tickLower, LIQ_PARAM_NO_SALT.tickUpper, 0 + ); + assertEq(position.liquidity, 0); + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_NO_SALT, ZERO_BYTES); + position = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_NO_SALT.tickLower, LIQ_PARAM_NO_SALT.tickUpper, 0 + ); + + assertEq(position.liquidity, uint128(uint256(LIQ_PARAM_NO_SALT.liquidityDelta))); + + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_NO_SALT, ZERO_BYTES); + Position.Info memory updated = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_NO_SALT.tickLower, LIQ_PARAM_NO_SALT.tickUpper, 0 + ); + assertEq(updated.liquidity, position.liquidity + uint128(uint256(LIQ_PARAM_NO_SALT.liquidityDelta))); + } + + function test_modifyLiquidity_samePosition_withSalt_isUpdated() public { + Position.Info memory position = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + assertEq(position.liquidity, 0); + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + position = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + assertEq(position.liquidity, uint128(uint256(LIQ_PARAM_SALT.liquidityDelta))); + + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + Position.Info memory updated = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + assertEq(updated.liquidity, position.liquidity + uint128(uint256(LIQ_PARAM_SALT.liquidityDelta))); + } + + function test_modifyLiquidity_sameTicks_withDifferentSalt_isNotUpdated() public { + Position.Info memory positionNoSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_NO_SALT.tickLower, LIQ_PARAM_NO_SALT.tickUpper, 0 + ); + + Position.Info memory positionSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + assertEq(positionNoSalt.liquidity, 0); + assertEq(positionSalt.liquidity, 0); + + // Modify the liquidity with the salt. + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + + positionNoSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_NO_SALT.tickLower, LIQ_PARAM_NO_SALT.tickUpper, 0 + ); + + positionSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + + assertEq(positionNoSalt.liquidity, 0); // This position does not have liquidity. + assertEq(positionSalt.liquidity, uint128(uint256(LIQ_PARAM_SALT.liquidityDelta))); // This position does. + + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_NO_SALT, ZERO_BYTES); // Now the positions should have the same liquidity. + + positionNoSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_NO_SALT.tickLower, LIQ_PARAM_NO_SALT.tickUpper, 0 + ); + + positionSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + + // positionSalt should still only have the original liquidity deposited to it + assertEq(positionSalt.liquidity, uint128(uint256(LIQ_PARAM_SALT.liquidityDelta))); + assertEq(positionNoSalt.liquidity, positionSalt.liquidity); + + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + Position.Info memory updatedWithSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + Position.Info memory updatedNoSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_NO_SALT.tickLower, LIQ_PARAM_NO_SALT.tickUpper, 0 + ); + + assertEq(updatedWithSalt.liquidity, positionSalt.liquidity + uint128(uint256(LIQ_PARAM_SALT.liquidityDelta))); + assertGt(updatedWithSalt.liquidity, updatedNoSalt.liquidity); + assertEq(updatedNoSalt.liquidity, positionNoSalt.liquidity); + } + + function test_modifyLiquidity_sameSalt_differentLiquidityRouters_doNotEditSamePosition() public { + // Set up new router. + PoolModifyLiquidityTest modifyLiquidityRouter2 = new PoolModifyLiquidityTest(manager); + + MockERC20(Currency.unwrap(currency0)).approve(address(modifyLiquidityRouter2), Constants.MAX_UINT256); + MockERC20(Currency.unwrap(currency1)).approve(address(modifyLiquidityRouter2), Constants.MAX_UINT256); + + IPoolManager.ModifyLiquidityParams memory LIQ_PARAM_SALT_2 = + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 2e18, salt: SALT}); + + // Get the uninitialized positions and assert they have no liquidity. + Position.Info memory positionSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + + Position.Info memory positionSalt2 = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter2), LIQ_PARAM_SALT_2.tickLower, LIQ_PARAM_SALT_2.tickUpper, SALT + ); + + assertEq(positionSalt.liquidity, 0); + assertEq(positionSalt2.liquidity, 0); + + // Modify the liquidity with the salt with the first router. + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + + Position.Info memory updatedPositionSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + + Position.Info memory updatedPositionSalt2 = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter2), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + + // Assert only the liquidity from the first router is updated. + assertEq(updatedPositionSalt.liquidity, uint128(uint256(LIQ_PARAM_SALT.liquidityDelta))); + assertEq(updatedPositionSalt2.liquidity, 0); + + // Modify the liquidity with the second router. + modifyLiquidityRouter2.modifyLiquidity(simpleKey, LIQ_PARAM_SALT_2, ZERO_BYTES); + + updatedPositionSalt2 = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter2), LIQ_PARAM_SALT_2.tickLower, LIQ_PARAM_SALT_2.tickUpper, SALT + ); + updatedPositionSalt = manager.getPosition( + simplePoolId, address(modifyLiquidityRouter), LIQ_PARAM_SALT.tickLower, LIQ_PARAM_SALT.tickUpper, SALT + ); + + // Assert only the liquidity from the second router is updated. + assertEq(updatedPositionSalt2.liquidity, uint128(uint256(LIQ_PARAM_SALT_2.liquidityDelta))); + assertEq(updatedPositionSalt.liquidity, uint128(uint256(LIQ_PARAM_SALT.liquidityDelta))); + } + + function test_gas_modifyLiquidity_newPosition() public { + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + snapLastCall("create new liquidity to a position with salt"); + } + + function test_gas_modifyLiquidity_updateSamePosition_withSalt() public { + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(simpleKey, LIQ_PARAM_SALT, ZERO_BYTES); + snapLastCall("add liquidity to already existing position with salt"); + } +} diff --git a/test/PoolManager.t.sol b/test/PoolManager.t.sol index a2fa50ce4..7d8fcce8b 100644 --- a/test/PoolManager.t.sol +++ b/test/PoolManager.t.sol @@ -1164,7 +1164,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { // manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); // // populate feeGrowthGlobalX128 struct w/ modify + swap - // modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(-120, 120, 5 ether)); + // modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(-120, 120, 5 ether, 0)); // swapRouter.swap( // key, // IPoolManager.SwapParams(false, 1 ether, TickMath.MAX_SQRT_RATIO - 1), @@ -1193,7 +1193,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_getPosition() public view { Position.Info memory managerPosition = - manager.getPosition(key.toId(), address(modifyLiquidityRouter), -120, 120); + manager.getPosition(key.toId(), address(modifyLiquidityRouter), -120, 120, 0); assert(LIQ_PARAMS.liquidityDelta > 0); assertEq(managerPosition.liquidity, uint128(uint256(LIQ_PARAMS.liquidityDelta))); } diff --git a/test/Sync.t.sol b/test/Sync.t.sol index 7c4305cc2..fe18abf53 100644 --- a/test/Sync.t.sol +++ b/test/Sync.t.sol @@ -80,7 +80,7 @@ contract SyncTest is Test, Deployers, GasSnapshot { // Sync has not been called. vm.expectRevert(Reserves.ReservesMustBeSynced.selector); manager.getReserves(currency2); - modifyLiquidityRouter.modifyLiquidity(key2, IPoolManager.ModifyLiquidityParams(-60, 60, 100), new bytes(0)); + modifyLiquidityRouter.modifyLiquidity(key2, IPoolManager.ModifyLiquidityParams(-60, 60, 100, 0), new bytes(0)); (uint256 balanceCurrency2) = currency2.balanceOf(address(manager)); assertEq(manager.getReserves(currency2), balanceCurrency2); } diff --git a/test/libraries/Hooks.t.sol b/test/libraries/Hooks.t.sol index 1c4acdb9b..a64992673 100644 --- a/test/libraries/Hooks.t.sol +++ b/test/libraries/Hooks.t.sol @@ -67,11 +67,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { function test_beforeAfterAddLiquidity_beforeAfterRemoveLiquidity_succeedsWithHook() public { MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); - modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 1e18), new bytes(111)); + modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 1e18, 0), new bytes(111)); assertEq(mockHooks.beforeAddLiquidityData(), new bytes(111)); assertEq(mockHooks.afterAddLiquidityData(), new bytes(111)); - modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, -1e18), new bytes(222)); + modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, -1e18, 0), new bytes(222)); assertEq(mockHooks.beforeRemoveLiquidityData(), new bytes(222)); assertEq(mockHooks.afterRemoveLiquidityData(), new bytes(222)); } @@ -79,7 +79,7 @@ contract HooksTest is Test, Deployers, GasSnapshot { function test_beforeAfterAddLiquidity_calledWithPositiveLiquidityDelta() public { MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); - modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 100), new bytes(111)); + modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 100, 0), new bytes(111)); assertEq(mockHooks.beforeAddLiquidityData(), new bytes(111)); assertEq(mockHooks.afterAddLiquidityData(), new bytes(111)); } @@ -87,11 +87,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { function test_beforeAfterRemoveLiquidity_calledWithZeroLiquidityDelta() public { MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); - modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 1e18), new bytes(111)); + modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 1e18, 0), new bytes(111)); assertEq(mockHooks.beforeAddLiquidityData(), new bytes(111)); assertEq(mockHooks.afterAddLiquidityData(), new bytes(111)); - modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 0), new bytes(222)); + modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 0, 0), new bytes(222)); assertEq(mockHooks.beforeAddLiquidityData(), new bytes(111)); assertEq(mockHooks.afterAddLiquidityData(), new bytes(111)); assertEq(mockHooks.beforeRemoveLiquidityData(), new bytes(222)); @@ -99,10 +99,10 @@ contract HooksTest is Test, Deployers, GasSnapshot { } function test_beforeAfterRemoveLiquidity_calledWithPositiveLiquidityDelta() public { - modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 1e18), new bytes(111)); + modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, 1e18, 0), new bytes(111)); MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); - modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, -1e18), new bytes(111)); + modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(0, 60, -1e18, 0), new bytes(111)); assertEq(mockHooks.beforeRemoveLiquidityData(), new bytes(111)); assertEq(mockHooks.afterRemoveLiquidityData(), new bytes(111)); } diff --git a/test/libraries/Pool.t.sol b/test/libraries/Pool.t.sol index 25f9bd7f2..187bf1312 100644 --- a/test/libraries/Pool.t.sol +++ b/test/libraries/Pool.t.sol @@ -110,7 +110,8 @@ contract PoolTest is Test { tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, - tickSpacing: 60 + tickSpacing: 60, + salt: 0 }) ); Pool.Slot0 memory slot0 = state.slot0; diff --git a/test/libraries/Position.t.sol b/test/libraries/Position.t.sol index 66c09ce43..6a87bff4e 100644 --- a/test/libraries/Position.t.sol +++ b/test/libraries/Position.t.sol @@ -9,10 +9,10 @@ contract PositionTest is Test { mapping(bytes32 => Position.Info) internal positions; - function test_get_fuzz(address owner, int24 tickLower, int24 tickUpper) public view { - bytes32 positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper)); + function test_get_fuzz(address owner, int24 tickLower, int24 tickUpper, bytes32 salt) public view { + bytes32 positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt)); Position.Info storage expectedPosition = positions[positionKey]; - Position.Info storage position = positions.get(owner, tickLower, tickUpper); + Position.Info storage position = positions.get(owner, tickLower, tickUpper, salt); bytes32 expectedPositionSlot; bytes32 positionSlot; assembly ("memory-safe") { diff --git a/test/utils/Deployers.sol b/test/utils/Deployers.sol index d0d4d8f8e..996af80f3 100644 --- a/test/utils/Deployers.sol +++ b/test/utils/Deployers.sol @@ -46,9 +46,9 @@ contract Deployers { uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1; IPoolManager.ModifyLiquidityParams public LIQ_PARAMS = - IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18}); + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); IPoolManager.ModifyLiquidityParams public REMOVE_LIQ_PARAMS = - IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18}); + IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); // Global variables Currency internal currency0; From 80b75259c9501d51046ca6e53704257495b59cf9 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Thu, 9 May 2024 17:57:36 -0400 Subject: [PATCH 03/10] Hooks return deltas (#482) * Remove access lock * remove current hook * Remove nested locking * Locker library tests * Rename empty lock test * Test skeleton * Test for a nested swap * Tests for nested function calls * snapshots * Separate libs, rename error * Remove lock caller * merge errors * Flipping the sign of deltas * update comments * refactor hook callsite * linting * first draft of swap impl * Compiling and tests passing * first test passing * Add todo * other TODO * reduce calls to account delta * check delta modification in callsite * fix error and compiler warnings * skeleton for modify position * Test * Add liquidity check in modify router * add liq test * linting * remove unnecessary read of result * move delta flip to sqrtprice lib * remove duplicate test * amountSpecified matches deltas (#491) Co-authored-by: hensha256 * common var, and custom error * refactor of hook callsites and perms * PR comments, clean up swap comments * Allow noop on the pool * Update src/PoolManager.sol Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> * correct delta taken from user * PR comments * liq to liquidity * refactor logic into hook contract * move all delta settling to the end * Factor out swap logic into helper * small refactor mdoify liq * amountToSwap variable * more tests * beginning of fuzz work * full fuzz test of beforeSwap return * rename * split comments on multiple lines * Update src/libraries/Hooks.sol Co-authored-by: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> * refactor processing hook deltas * remove console imports * nit PR comments * remove unneeded check * refactor logic into hook contract * modifyLiquidity to match swap * remove unneeded check * PR comments * fix fuzz test * remove extra check and add comments * correct gas snaps * Natspec updates * natspec * PR comments * comment protocol fee clearer * PR comments --------- Co-authored-by: Emily Williams Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> Co-authored-by: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> --- ...o already existing position with salt.snap | 2 +- .forge-snapshots/addLiquidity CA fee.snap | 1 + .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ...new liquidity to a position with salt.snap | 2 +- .forge-snapshots/donate gas with 1 token.snap | 2 +- .../donate gas with 2 tokens.snap | 2 +- .forge-snapshots/initialize.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity CA fee.snap | 1 + .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap CA custom curve + swap noop.snap | 1 + .../swap CA fee on unspecified.snap | 1 + ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- ...wap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../swap with lp fee and protocol fee.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/PoolManager.sol | 75 +-- src/interfaces/IHooks.sol | 12 +- src/interfaces/IPoolManager.sol | 15 +- src/libraries/CurrencySettleTake.sol | 6 +- src/libraries/Hooks.sol | 150 ++++-- src/libraries/Pool.sol | 46 +- src/test/BaseTestHooks.sol | 8 +- src/test/CustomCurveHook.sol | 80 ++++ src/test/DeltaReturningHook.sol | 92 ++++ src/test/DynamicFeesTestHook.sol | 4 +- src/test/EmptyTestHooks.sol | 24 +- src/test/FeeTakingHook.sol | 88 ++++ src/test/MockHooks.sol | 18 +- src/test/PoolModifyLiquidityTest.sol | 14 + src/test/PoolNestedActionsTest.sol | 16 +- src/test/PoolSwapTest.sol | 8 +- src/test/SkipCallsTestHook.sol | 18 +- src/types/BalanceDelta.sol | 8 +- test/DynamicFees.t.sol | 23 +- test/PoolManager.t.sol | 427 +++++++++++++++--- test/SkipCallsTestHook.t.sol | 25 +- test/libraries/Hooks.t.sol | 296 +++++++++--- test/libraries/Pool.t.sol | 41 +- test/utils/Deployers.sol | 12 +- test/utils/NestedActions.t.sol | 4 +- 54 files changed, 1225 insertions(+), 339 deletions(-) create mode 100644 .forge-snapshots/addLiquidity CA fee.snap create mode 100644 .forge-snapshots/removeLiquidity CA fee.snap create mode 100644 .forge-snapshots/swap CA custom curve + swap noop.snap create mode 100644 .forge-snapshots/swap CA fee on unspecified.snap create mode 100644 src/test/CustomCurveHook.sol create mode 100644 src/test/DeltaReturningHook.sol create mode 100644 src/test/FeeTakingHook.sol diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index d48dd491a..72bdeb87c 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -146357 \ No newline at end of file +151456 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap new file mode 100644 index 000000000..b07e289ff --- /dev/null +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -0,0 +1 @@ +329809 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 449ae03c2..5f8cc2368 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -279342 \ No newline at end of file +284524 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 97f85c617..4c5ad9d9e 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -136584 \ No newline at end of file +141632 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index e63c54318..a90703535 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -146333 \ No newline at end of file +151432 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index bd6fabdec..f21c7a844 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -294885 \ No newline at end of file +299984 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 1 token.snap b/.forge-snapshots/donate gas with 1 token.snap index 89ae48f28..ba015f0c8 100644 --- a/.forge-snapshots/donate gas with 1 token.snap +++ b/.forge-snapshots/donate gas with 1 token.snap @@ -1 +1 @@ -108818 \ No newline at end of file +108879 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 2 tokens.snap b/.forge-snapshots/donate gas with 2 tokens.snap index c5366c4b3..a988246a9 100644 --- a/.forge-snapshots/donate gas with 2 tokens.snap +++ b/.forge-snapshots/donate gas with 2 tokens.snap @@ -1 +1 @@ -149260 \ No newline at end of file +149374 \ No newline at end of file diff --git a/.forge-snapshots/initialize.snap b/.forge-snapshots/initialize.snap index e2b0523e4..811a2ae0c 100644 --- a/.forge-snapshots/initialize.snap +++ b/.forge-snapshots/initialize.snap @@ -1 +1 @@ -61933 \ No newline at end of file +62245 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 1f2f82624..5b1d58da4 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -22710 \ No newline at end of file +23627 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap new file mode 100644 index 000000000..4c9438ea5 --- /dev/null +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -0,0 +1 @@ +185379 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 9b7f305da..0c5ecb089 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -116427 \ No newline at end of file +121413 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 12c0317b0..671b430ad 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -113605 \ No newline at end of file +118188 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index c67fcb47d..efdeffd0f 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -116415 \ No newline at end of file +121401 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index b63e5c735..096d7a870 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -130629 \ No newline at end of file +131137 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 44ba60386..6aff0f3a3 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -148937 \ No newline at end of file +149496 \ No newline at end of file diff --git a/.forge-snapshots/swap CA custom curve + swap noop.snap b/.forge-snapshots/swap CA custom curve + swap noop.snap new file mode 100644 index 000000000..7ff2ada5b --- /dev/null +++ b/.forge-snapshots/swap CA custom curve + swap noop.snap @@ -0,0 +1 @@ +139532 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap new file mode 100644 index 000000000..e036c50b1 --- /dev/null +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -0,0 +1 @@ +185012 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index 4f51848d5..b61a66610 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -113524 \ No newline at end of file +114032 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 9f50530b3..6944aef44 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -124851 \ No newline at end of file +125410 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 5df1673f6..08df3eac1 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -136898 \ No newline at end of file +137415 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index 84508f65d..d00043654 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -126013 \ No newline at end of file +126492 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index 60e6f7a84..3e4401fe3 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -147983 \ No newline at end of file +148527 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 73b0bfbf5..608305d88 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -164886 \ No newline at end of file +165445 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index 7c98774e1..c9ba8fd96 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -224212 \ No newline at end of file +225236 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index e1eba0bc6..dff261c55 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -149153 \ No newline at end of file +149712 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 23c7648dd..81c4e9eb1 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -124863 \ No newline at end of file +125422 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index be735dedf..f3266fd4a 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -181496 \ No newline at end of file +182041 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 222663c68..9dbfcd03f 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -159743 \ No newline at end of file +160355 \ No newline at end of file diff --git a/src/PoolManager.sol b/src/PoolManager.sol index 0e1d1d78a..8b01de5bd 100644 --- a/src/PoolManager.sol +++ b/src/PoolManager.sol @@ -16,7 +16,7 @@ import {IUnlockCallback} from "./interfaces/callback/IUnlockCallback.sol"; import {ProtocolFees} from "./ProtocolFees.sol"; import {ERC6909Claims} from "./ERC6909Claims.sol"; import {PoolId, PoolIdLibrary} from "./types/PoolId.sol"; -import {BalanceDelta, BalanceDeltaLibrary} from "./types/BalanceDelta.sol"; +import {BalanceDelta, BalanceDeltaLibrary, toBalanceDelta} from "./types/BalanceDelta.sol"; import {Lock} from "./libraries/Lock.sol"; import {CurrencyDelta} from "./libraries/CurrencyDelta.sol"; import {NonZeroDeltaCount} from "./libraries/NonZeroDeltaCount.sol"; @@ -197,10 +197,10 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim currency.setReserves(balance); } - function _accountDelta(Currency currency, int128 delta) internal { + function _accountDelta(Currency currency, int128 delta, address target) internal { if (delta == 0) return; - int256 current = currency.getDelta(msg.sender); + int256 current = currency.getDelta(target); int256 next = current + delta; if (next == 0) { @@ -209,13 +209,13 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim NonZeroDeltaCount.increment(); } - currency.setDelta(msg.sender, next); + currency.setDelta(target, next); } /// @dev Accumulates a balance change to a map of currency to balance changes - function _accountPoolBalanceDelta(PoolKey memory key, BalanceDelta delta) internal { - _accountDelta(key.currency0, delta.amount0()); - _accountDelta(key.currency1, delta.amount1()); + function _accountPoolBalanceDelta(PoolKey memory key, BalanceDelta delta, address target) internal { + _accountDelta(key.currency0, delta.amount0(), target); + _accountDelta(key.currency1, delta.amount1(), target); } function _checkPoolInitialized(PoolId id) internal view { @@ -227,13 +227,14 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params, bytes calldata hookData - ) external override onlyWhenUnlocked returns (BalanceDelta delta, BalanceDelta feeDelta) { + ) external override onlyWhenUnlocked returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) { PoolId id = key.toId(); _checkPoolInitialized(id); key.hooks.beforeModifyLiquidity(key, params, hookData); - (delta, feeDelta) = pools[id].modifyLiquidity( + BalanceDelta principalDelta; + (principalDelta, feesAccrued) = pools[id].modifyLiquidity( Pool.ModifyLiquidityParams({ owner: msg.sender, tickLower: params.tickLower, @@ -244,11 +245,17 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim }) ); - _accountPoolBalanceDelta(key, delta + feeDelta); + callerDelta = principalDelta + feesAccrued; emit ModifyLiquidity(id, msg.sender, params.tickLower, params.tickUpper, params.liquidityDelta); - key.hooks.afterModifyLiquidity(key, params, delta, hookData); + // if the hook doesnt have the flag to be able to return deltas, hookDelta will always be 0. + BalanceDelta hookDelta; + (callerDelta, hookDelta) = key.hooks.afterModifyLiquidity(key, params, callerDelta, hookData); + + if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) _accountPoolBalanceDelta(key, hookDelta, address(key.hooks)); + + _accountPoolBalanceDelta(key, callerDelta, msg.sender); } /// @inheritdoc IPoolManager @@ -256,35 +263,48 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim external override onlyWhenUnlocked - returns (BalanceDelta) + returns (BalanceDelta swapDelta) { + if (params.amountSpecified == 0) revert SwapAmountCannotBeZero(); + PoolId id = key.toId(); _checkPoolInitialized(id); - key.hooks.beforeSwap(key, params, hookData); + (int256 amountToSwap, int128 hookDeltaSpecified) = key.hooks.beforeSwap(key, params, hookData); - (BalanceDelta delta, uint256 feeForProtocol, uint24 swapFee, Pool.SwapState memory state) = pools[id].swap( + // execute swap, account protocol fees, and emit swap event + swapDelta = _swap( + id, Pool.SwapParams({ tickSpacing: key.tickSpacing, zeroForOne: params.zeroForOne, - amountSpecified: params.amountSpecified, + amountSpecified: amountToSwap, sqrtPriceLimitX96: params.sqrtPriceLimitX96 - }) + }), + params.zeroForOne ? key.currency0 : key.currency1 // input token ); - _accountPoolBalanceDelta(key, delta); + BalanceDelta hookDelta; + (swapDelta, hookDelta) = key.hooks.afterSwap(key, params, swapDelta, hookData, hookDeltaSpecified); + + // if the hook doesnt have the flag to be able to return deltas, hookDelta will always be 0 + if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) _accountPoolBalanceDelta(key, hookDelta, address(key.hooks)); + + _accountPoolBalanceDelta(key, swapDelta, msg.sender); + } + + // Internal swap function to execute a swap, take protocol fees on input token, and emit the swap event + function _swap(PoolId id, Pool.SwapParams memory params, Currency inputCurrency) internal returns (BalanceDelta) { + (BalanceDelta delta, uint256 feeForProtocol, uint24 swapFee, Pool.SwapState memory state) = + pools[id].swap(params); // The fee is on the input currency. - if (feeForProtocol > 0) { - _updateProtocolFees(params.zeroForOne ? key.currency0 : key.currency1, feeForProtocol); - } + if (feeForProtocol > 0) _updateProtocolFees(inputCurrency, feeForProtocol); emit Swap( id, msg.sender, delta.amount0(), delta.amount1(), state.sqrtPriceX96, state.liquidity, state.tick, swapFee ); - key.hooks.afterSwap(key, params, delta, hookData); - return delta; } @@ -302,7 +322,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim delta = pools[id].donate(amount0, amount1); - _accountPoolBalanceDelta(key, delta); + _accountPoolBalanceDelta(key, delta, msg.sender); key.hooks.afterDonate(key, amount0, amount1, hookData); } @@ -311,7 +331,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim function take(Currency currency, address to, uint256 amount) external override onlyWhenUnlocked { unchecked { // subtraction must be safe - _accountDelta(currency, -(amount.toInt128())); + _accountDelta(currency, -(amount.toInt128()), msg.sender); currency.transfer(to, amount); } } @@ -326,21 +346,22 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim uint256 reservesNow = sync(currency); paid = reservesNow - reservesBefore; } - _accountDelta(currency, paid.toInt128()); + + _accountDelta(currency, paid.toInt128(), msg.sender); } /// @inheritdoc IPoolManager function mint(address to, uint256 id, uint256 amount) external override onlyWhenUnlocked { unchecked { // subtraction must be safe - _accountDelta(CurrencyLibrary.fromId(id), -(amount.toInt128())); + _accountDelta(CurrencyLibrary.fromId(id), -(amount.toInt128()), msg.sender); _mint(to, id, amount); } } /// @inheritdoc IPoolManager function burn(address from, uint256 id, uint256 amount) external override onlyWhenUnlocked { - _accountDelta(CurrencyLibrary.fromId(id), amount.toInt128()); + _accountDelta(CurrencyLibrary.fromId(id), amount.toInt128(), msg.sender); _burnFrom(from, id, amount); } diff --git a/src/interfaces/IHooks.sol b/src/interfaces/IHooks.sol index 66f20bf31..34efe9da7 100644 --- a/src/interfaces/IHooks.sol +++ b/src/interfaces/IHooks.sol @@ -54,13 +54,14 @@ interface IHooks { /// @param params The parameters for adding liquidity /// @param hookData Arbitrary data handed into the PoolManager by the liquidty provider to be be passed on to the hook /// @return bytes4 The function selector for the hook + /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency function afterAddLiquidity( address sender, PoolKey calldata key, IPoolManager.ModifyLiquidityParams calldata params, BalanceDelta delta, bytes calldata hookData - ) external returns (bytes4); + ) external returns (bytes4, BalanceDelta); /// @notice The hook called before liquidity is removed /// @param sender The initial msg.sender for the remove liquidity call @@ -81,13 +82,14 @@ interface IHooks { /// @param params The parameters for removing liquidity /// @param hookData Arbitrary data handed into the PoolManager by the liquidty provider to be be passed on to the hook /// @return bytes4 The function selector for the hook + /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency function afterRemoveLiquidity( address sender, PoolKey calldata key, IPoolManager.ModifyLiquidityParams calldata params, BalanceDelta delta, bytes calldata hookData - ) external returns (bytes4); + ) external returns (bytes4, BalanceDelta); /// @notice The hook called before a swap /// @param sender The initial msg.sender for the swap call @@ -95,12 +97,13 @@ interface IHooks { /// @param params The parameters for the swap /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook /// @return bytes4 The function selector for the hook + /// @return int256 The hook's delta in specified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency function beforeSwap( address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata hookData - ) external returns (bytes4); + ) external returns (bytes4, int128); /// @notice The hook called after a swap /// @param sender The initial msg.sender for the swap call @@ -109,13 +112,14 @@ interface IHooks { /// @param delta The amount owed to the caller (positive) or owed to the pool (negative) /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook /// @return bytes4 The function selector for the hook + /// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency function afterSwap( address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, BalanceDelta delta, bytes calldata hookData - ) external returns (bytes4); + ) external returns (bytes4, int128); /// @notice The hook called before donate /// @param sender The initial msg.sender for the donate call diff --git a/src/interfaces/IPoolManager.sol b/src/interfaces/IPoolManager.sol index 0755288b9..04138aa65 100644 --- a/src/interfaces/IPoolManager.sol +++ b/src/interfaces/IPoolManager.sol @@ -27,6 +27,7 @@ interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload { /// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow error TickSpacingTooLarge(); + /// @notice Pools must have a positive non-zero tickSpacing passed to #initialize error TickSpacingTooSmall(); @@ -37,6 +38,9 @@ interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload { /// or on a pool that does not have a dynamic swap fee. error UnauthorizedDynamicLPFeeUpdate(); + /// @notice Thrown when trying to swap amount of 0 + error SwapAmountCannotBeZero(); + ///@notice Thrown when native currency is passed to a non native settlement error NonZeroNativeValue(); @@ -161,8 +165,8 @@ interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload { /// @param key The pool to modify liquidity in /// @param params The parameters for modifying the liquidity /// @param hookData Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)` - /// @return delta The balance delta of the liquidity change - /// @return feeDelta The balance delta of the fees generated in the liquidity range + /// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal and fee deltas. + /// @return feeDelta The balance delta of the fees generated in the liquidity range. Returned for informational purposes. function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData) external returns (BalanceDelta, BalanceDelta); @@ -174,6 +178,13 @@ interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload { } /// @notice Swap against the given pool + /// @param key The pool to swap in + /// @param params The parameters for swapping + /// @param hookData Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)` + /// @return swapDelta The balance delta of the address swapping + /// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified. + /// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG + /// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta. function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData) external returns (BalanceDelta); diff --git a/src/libraries/CurrencySettleTake.sol b/src/libraries/CurrencySettleTake.sol index 8fcc957a8..74aef26e2 100644 --- a/src/libraries/CurrencySettleTake.sol +++ b/src/libraries/CurrencySettleTake.sol @@ -23,7 +23,11 @@ library CurrencySettleTake { manager.settle{value: amount}(currency); } else { manager.sync(currency); - IERC20Minimal(Currency.unwrap(currency)).transferFrom(payer, address(manager), amount); + if (payer != address(this)) { + IERC20Minimal(Currency.unwrap(currency)).transferFrom(payer, address(manager), amount); + } else { + IERC20Minimal(Currency.unwrap(currency)).transfer(address(manager), amount); + } manager.settle(currency); } } diff --git a/src/libraries/Hooks.sol b/src/libraries/Hooks.sol index 6d83b9152..a6cefc69a 100644 --- a/src/libraries/Hooks.sol +++ b/src/libraries/Hooks.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.24; import {PoolKey} from "../types/PoolKey.sol"; import {IHooks} from "../interfaces/IHooks.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; import {LPFeeLibrary} from "./LPFeeLibrary.sol"; -import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; /// @notice V4 decides whether to invoke specific hooks by inspecting the leading bits of the address that @@ -14,18 +15,28 @@ import {IPoolManager} from "../interfaces/IPoolManager.sol"; library Hooks { using LPFeeLibrary for uint24; using Hooks for IHooks; + using SafeCast for int256; uint256 internal constant BEFORE_INITIALIZE_FLAG = 1 << 159; uint256 internal constant AFTER_INITIALIZE_FLAG = 1 << 158; + uint256 internal constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 157; uint256 internal constant AFTER_ADD_LIQUIDITY_FLAG = 1 << 156; + uint256 internal constant BEFORE_REMOVE_LIQUIDITY_FLAG = 1 << 155; uint256 internal constant AFTER_REMOVE_LIQUIDITY_FLAG = 1 << 154; + uint256 internal constant BEFORE_SWAP_FLAG = 1 << 153; uint256 internal constant AFTER_SWAP_FLAG = 1 << 152; + uint256 internal constant BEFORE_DONATE_FLAG = 1 << 151; uint256 internal constant AFTER_DONATE_FLAG = 1 << 150; + uint256 internal constant BEFORE_SWAP_RETURNS_DELTA_FLAG = 1 << 149; + uint256 internal constant AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 148; + uint256 internal constant AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 147; + uint256 internal constant AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 146; + struct Permissions { bool beforeInitialize; bool afterInitialize; @@ -37,6 +48,10 @@ library Hooks { bool afterSwap; bool beforeDonate; bool afterDonate; + bool beforeSwapReturnDelta; + bool afterSwapReturnDelta; + bool afterAddLiquidityReturnDelta; + bool afterRemoveLiquidityReturnDelta; } /// @notice Thrown if the address will not lead to the specified hook calls being called @@ -49,6 +64,9 @@ library Hooks { /// @notice thrown when a hook call fails error FailedHookCall(); + /// @notice The hook's delta changed the swap from exactIn to exactOut or vice versa + error HookDeltaExceedsSwapAmount(); + /// @notice Utility function intended to be used in hook constructors to ensure /// the deployed hooks address causes the intended hooks to be called /// @param permissions The hooks that are intended to be called @@ -65,6 +83,11 @@ library Hooks { || permissions.afterSwap != self.hasPermission(AFTER_SWAP_FLAG) || permissions.beforeDonate != self.hasPermission(BEFORE_DONATE_FLAG) || permissions.afterDonate != self.hasPermission(AFTER_DONATE_FLAG) + || permissions.beforeSwapReturnDelta != self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG) + || permissions.afterSwapReturnDelta != self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG) + || permissions.afterAddLiquidityReturnDelta != self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + || permissions.afterRemoveLiquidityReturnDelta + != self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) ) { revert HookAddressNotValid(address(self)); } @@ -72,35 +95,54 @@ library Hooks { /// @notice Ensures that the hook address includes at least one hook flag or dynamic fees, or is the 0 address /// @param self The hook to verify + /// @return bool True if the hook address is valid function isValidHookAddress(IHooks self, uint24 fee) internal pure returns (bool) { + // The hook can only have a flag to return a hook delta on an action if it also has the corresponding action flag + if (!self.hasPermission(BEFORE_SWAP_FLAG) && self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) return false; + if (!self.hasPermission(AFTER_SWAP_FLAG) && self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG)) return false; + if (!self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG) && self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)) + { + return false; + } + if ( + !self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG) + && self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) return false; + // If there is no hook contract set, then fee cannot be dynamic // If a hook contract is set, it must have at least 1 flag set, or have a dynamic fee return address(self) == address(0) ? !fee.isDynamicFee() - : (uint160(address(self)) >= AFTER_DONATE_FLAG || fee.isDynamicFee()); + : (uint160(address(self)) >= AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG || fee.isDynamicFee()); } - /// @notice performs a hook call using the given calldata on the given hook - /// @return expectedSelector The selector that the hook is expected to return - /// @return selector The selector that the hook actually returned - function _callHook(IHooks self, bytes memory data) private returns (bytes4 expectedSelector, bytes4 selector) { + /// @notice performs a hook call using the given calldata on the given hook that doesnt return a delta + /// @return result The complete data returned by the hook + function callHook(IHooks self, bytes memory data) internal returns (bytes memory result) { + bool success; + (success, result) = address(self).call(data); + if (!success) _revert(result); + + bytes4 expectedSelector; + bytes4 selector; assembly { expectedSelector := mload(add(data, 0x20)) + selector := mload(add(result, 0x20)) } - (bool success, bytes memory result) = address(self).call(data); - if (!success) _revert(result); - - selector = abi.decode(result, (bytes4)); + if (selector != expectedSelector) revert InvalidHookResponse(); } /// @notice performs a hook call using the given calldata on the given hook - function callHook(IHooks self, bytes memory data) internal { - (bytes4 expectedSelector, bytes4 selector) = _callHook(self, data); + /// @return delta The delta returned by the hook + function callHookWithReturnDelta(IHooks self, bytes memory data, bool parseReturn) + internal + returns (int256 delta) + { + bytes memory result = callHook(self, data); - if (selector != expectedSelector) { - revert InvalidHookResponse(); - } + if (!parseReturn) return 0; + (, delta) = abi.decode(result, (bytes4, int256)); } /// @notice modifier to prevent calling a hook if they initiated the action @@ -157,25 +199,56 @@ library Hooks { IPoolManager.ModifyLiquidityParams memory params, BalanceDelta delta, bytes calldata hookData - ) internal noSelfCall(self) { - if (params.liquidityDelta > 0 && self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)) { - self.callHook( - abi.encodeWithSelector(IHooks.afterAddLiquidity.selector, msg.sender, key, params, delta, hookData) - ); - } else if (params.liquidityDelta <= 0 && self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)) { - self.callHook( - abi.encodeWithSelector(IHooks.afterRemoveLiquidity.selector, msg.sender, key, params, delta, hookData) - ); + ) internal returns (BalanceDelta callerDelta, BalanceDelta hookDelta) { + if (msg.sender == address(self)) return (delta, BalanceDeltaLibrary.ZERO_DELTA); + + callerDelta = delta; + if (params.liquidityDelta > 0) { + if (self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)) { + hookDelta = BalanceDelta.wrap( + self.callHookWithReturnDelta( + abi.encodeWithSelector( + IHooks.afterAddLiquidity.selector, msg.sender, key, params, delta, hookData + ), + self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + ) + ); + callerDelta = callerDelta - hookDelta; + } + } else { + if (self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)) { + hookDelta = BalanceDelta.wrap( + self.callHookWithReturnDelta( + abi.encodeWithSelector( + IHooks.afterRemoveLiquidity.selector, msg.sender, key, params, delta, hookData + ), + self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) + ); + callerDelta = callerDelta - hookDelta; + } } } /// @notice calls beforeSwap hook if permissioned and validates return value function beforeSwap(IHooks self, PoolKey memory key, IPoolManager.SwapParams memory params, bytes calldata hookData) internal - noSelfCall(self) + returns (int256 amountToSwap, int128 hookDeltaSpecified) { + amountToSwap = params.amountSpecified; + if (msg.sender == address(self)) return (amountToSwap, hookDeltaSpecified); if (self.hasPermission(BEFORE_SWAP_FLAG)) { - self.callHook(abi.encodeWithSelector(IHooks.beforeSwap.selector, msg.sender, key, params, hookData)); + hookDeltaSpecified = self.callHookWithReturnDelta( + abi.encodeWithSelector(IHooks.beforeSwap.selector, msg.sender, key, params, hookData), + self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG) + ).toInt128(); + + // Update the swap amount according to the hook's return, and check that the swap type doesnt change (exact input/output) + if (hookDeltaSpecified != 0) { + bool exactInput = amountToSwap < 0; + amountToSwap += hookDeltaSpecified; + if (exactInput ? amountToSwap > 0 : amountToSwap < 0) revert HookDeltaExceedsSwapAmount(); + } } } @@ -184,11 +257,28 @@ library Hooks { IHooks self, PoolKey memory key, IPoolManager.SwapParams memory params, - BalanceDelta delta, - bytes calldata hookData - ) internal noSelfCall(self) { + BalanceDelta swapDelta, + bytes calldata hookData, + int128 hookDeltaSpecified + ) internal returns (BalanceDelta swapperDelta, BalanceDelta hookDelta) { + if (msg.sender == address(self)) return (swapDelta, BalanceDeltaLibrary.ZERO_DELTA); + + int128 hookDeltaUnspecified; + swapperDelta = swapDelta; if (self.hasPermission(AFTER_SWAP_FLAG)) { - self.callHook(abi.encodeWithSelector(IHooks.afterSwap.selector, msg.sender, key, params, delta, hookData)); + hookDeltaUnspecified = self.callHookWithReturnDelta( + abi.encodeWithSelector(IHooks.afterSwap.selector, msg.sender, key, params, swapDelta, hookData), + self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG) + ).toInt128(); + } + + if (hookDeltaUnspecified != 0 || hookDeltaSpecified != 0) { + hookDelta = (params.amountSpecified < 0 == params.zeroForOne) + ? toBalanceDelta(hookDeltaSpecified, hookDeltaUnspecified) + : toBalanceDelta(hookDeltaUnspecified, hookDeltaSpecified); + + // the caller has to pay for (or receive) the hook's delta + swapperDelta = swapDelta - hookDelta; } } diff --git a/src/libraries/Pool.sol b/src/libraries/Pool.sol index d61f17525..ea5f6af07 100644 --- a/src/libraries/Pool.sol +++ b/src/libraries/Pool.sol @@ -9,7 +9,7 @@ import {FixedPoint128} from "./FixedPoint128.sol"; import {TickMath} from "./TickMath.sol"; import {SqrtPriceMath} from "./SqrtPriceMath.sol"; import {SwapMath} from "./SwapMath.sol"; -import {BalanceDelta, toBalanceDelta} from "../types/BalanceDelta.sol"; +import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; import {ProtocolFeeLibrary} from "./ProtocolFeeLibrary.sol"; import {LiquidityMath} from "./LiquidityMath.sol"; import {LPFeeLibrary} from "./LPFeeLibrary.sol"; @@ -48,9 +48,6 @@ library Pool { /// @notice Thrown when trying to interact with a non-initialized pool error PoolNotInitialized(); - /// @notice Thrown when trying to swap amount of 0 - error SwapAmountCannotBeZero(); - /// @notice Thrown when sqrtPriceLimitX96 on a swap has already exceeded its limit /// @param sqrtPriceCurrentX96 The invalid, already surpassed sqrtPriceLimitX96 /// @param sqrtPriceLimitX96 The surpassed price limit @@ -307,33 +304,15 @@ library Pool { internal returns (BalanceDelta result, uint256 feeForProtocol, uint24 swapFee, SwapState memory state) { - if (params.amountSpecified == 0) revert SwapAmountCannotBeZero(); - Slot0 memory slot0Start = self.slot0; bool zeroForOne = params.zeroForOne; - if (zeroForOne) { - if (params.sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96) { - revert PriceLimitAlreadyExceeded(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96); - } - if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) { - revert PriceLimitOutOfBounds(params.sqrtPriceLimitX96); - } - } else { - if (params.sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96) { - revert PriceLimitAlreadyExceeded(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96); - } - if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) { - revert PriceLimitOutOfBounds(params.sqrtPriceLimitX96); - } - } + bool exactInput = params.amountSpecified < 0; SwapCache memory cache = SwapCache({ liquidityStart: self.liquidity, protocolFee: zeroForOne ? slot0Start.protocolFee.getZeroForOneFee() : slot0Start.protocolFee.getOneForZeroFee() }); - bool exactInput = params.amountSpecified < 0; - state.amountSpecifiedRemaining = params.amountSpecified; state.amountCalculated = 0; state.sqrtPriceX96 = slot0Start.sqrtPriceX96; @@ -341,7 +320,6 @@ library Pool { state.feeGrowthGlobalX128 = zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128; state.liquidity = cache.liquidityStart; - StepComputations memory step; swapFee = cache.protocolFee == 0 ? slot0Start.lpFee : uint24(cache.protocolFee).calculateSwapFee(slot0Start.lpFee); @@ -349,6 +327,26 @@ library Pool { revert InvalidFeeForExactOut(); } + if (params.amountSpecified == 0) return (BalanceDeltaLibrary.ZERO_DELTA, 0, swapFee, state); + + if (zeroForOne) { + if (params.sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96) { + revert PriceLimitAlreadyExceeded(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96); + } + if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) { + revert PriceLimitOutOfBounds(params.sqrtPriceLimitX96); + } + } else { + if (params.sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96) { + revert PriceLimitAlreadyExceeded(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96); + } + if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) { + revert PriceLimitOutOfBounds(params.sqrtPriceLimitX96); + } + } + + StepComputations memory step; + // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != params.sqrtPriceLimitX96) { step.sqrtPriceStartX96 = state.sqrtPriceX96; diff --git a/src/test/BaseTestHooks.sol b/src/test/BaseTestHooks.sol index a57750158..be791a6e7 100644 --- a/src/test/BaseTestHooks.sol +++ b/src/test/BaseTestHooks.sol @@ -43,7 +43,7 @@ contract BaseTestHooks is IHooks { IPoolManager.ModifyLiquidityParams calldata, /* params **/ BalanceDelta, /* delta **/ bytes calldata /* hookData **/ - ) external virtual returns (bytes4) { + ) external virtual returns (bytes4, BalanceDelta) { revert HookNotImplemented(); } @@ -62,7 +62,7 @@ contract BaseTestHooks is IHooks { IPoolManager.ModifyLiquidityParams calldata, /* params **/ BalanceDelta, /* delta **/ bytes calldata /* hookData **/ - ) external virtual returns (bytes4) { + ) external virtual returns (bytes4, BalanceDelta) { revert HookNotImplemented(); } @@ -71,7 +71,7 @@ contract BaseTestHooks is IHooks { PoolKey calldata, /* key **/ IPoolManager.SwapParams calldata, /* params **/ bytes calldata /* hookData **/ - ) external virtual returns (bytes4) { + ) external virtual returns (bytes4, int128) { revert HookNotImplemented(); } @@ -81,7 +81,7 @@ contract BaseTestHooks is IHooks { IPoolManager.SwapParams calldata, /* params **/ BalanceDelta, /* delta **/ bytes calldata /* hookData **/ - ) external virtual returns (bytes4) { + ) external virtual returns (bytes4, int128) { revert HookNotImplemented(); } diff --git a/src/test/CustomCurveHook.sol b/src/test/CustomCurveHook.sol new file mode 100644 index 000000000..ab7f2725e --- /dev/null +++ b/src/test/CustomCurveHook.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, toBalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {CurrencySettleTake} from "../libraries/CurrencySettleTake.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; +import {CurrencyLibrary, Currency} from "../types/Currency.sol"; + +contract CustomCurveHook is BaseTestHooks { + using Hooks for IHooks; + using CurrencyLibrary for Currency; + using CurrencySettleTake for Currency; + + error AddLiquidityDirectToHook(); + + IPoolManager immutable manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + modifier onlyPoolManager() { + require(msg.sender == address(manager)); + _; + } + + function beforeSwap( + address, /* sender **/ + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, int128) { + (Currency inputCurrency, Currency outputCurrency, uint256 amount) = _getInputOutputAndAmount(key, params); + + // this "custom curve" is a line, 1-1 + // take the full input amount, and give the full output amount + manager.take(inputCurrency, address(this), amount); + outputCurrency.settle(manager, address(this), amount, false); + + // return -amountSpecified to no-op the concentrated liquidity swap + return (IHooks.beforeSwap.selector, int128(-params.amountSpecified)); + } + + function afterSwap( + address, /* sender **/ + PoolKey calldata, /* key **/ + IPoolManager.SwapParams calldata params, + BalanceDelta, /* delta **/ + bytes calldata /* hookData **/ + ) external view override onlyPoolManager returns (bytes4, int128) { + return (IHooks.afterSwap.selector, int128(params.amountSpecified)); + } + + function afterAddLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + IPoolManager.ModifyLiquidityParams calldata, /* params **/ + BalanceDelta, /* delta **/ + bytes calldata /* hookData **/ + ) external view override onlyPoolManager returns (bytes4, BalanceDelta) { + revert AddLiquidityDirectToHook(); + } + + function _getInputOutputAndAmount(PoolKey calldata key, IPoolManager.SwapParams calldata params) + internal + pure + returns (Currency input, Currency output, uint256 amount) + { + (input, output) = params.zeroForOne ? (key.currency0, key.currency1) : (key.currency1, key.currency0); + + amount = params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified); + } +} diff --git a/src/test/DeltaReturningHook.sol b/src/test/DeltaReturningHook.sol new file mode 100644 index 000000000..9a093caf6 --- /dev/null +++ b/src/test/DeltaReturningHook.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {CurrencySettleTake} from "../libraries/CurrencySettleTake.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, toBalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; +import {CurrencyLibrary, Currency} from "../types/Currency.sol"; + +contract DeltaReturningHook is BaseTestHooks { + using Hooks for IHooks; + using CurrencyLibrary for Currency; + using CurrencySettleTake for Currency; + + IPoolManager immutable manager; + + int128 deltaSpecified; + int128 deltaUnspecified; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + modifier onlyPoolManager() { + require(msg.sender == address(manager)); + _; + } + + function setDeltaSpecified(int128 delta) external { + deltaSpecified = delta; + } + + function setDeltaUnspecified(int128 delta) external { + deltaUnspecified = delta; + } + + function beforeSwap( + address, /* sender **/ + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, int128) { + (Currency specifiedCurrency,) = _sortCurrencies(key, params); + _settleOrTake(specifiedCurrency, deltaSpecified); + + return (IHooks.beforeSwap.selector, deltaSpecified); + } + + function afterSwap( + address, /* sender **/ + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + BalanceDelta, /* delta **/ + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, int128) { + (, Currency unspecifiedCurrency) = _sortCurrencies(key, params); + _settleOrTake(unspecifiedCurrency, deltaUnspecified); + + return (IHooks.afterSwap.selector, deltaUnspecified); + } + + function _sortCurrencies(PoolKey calldata key, IPoolManager.SwapParams calldata params) + internal + pure + returns (Currency specified, Currency unspecified) + { + (specified, unspecified) = (params.zeroForOne == (params.amountSpecified < 0)) + ? (key.currency0, key.currency1) + : (key.currency1, key.currency0); + } + + function _settleOrTake(Currency currency, int128 delta) internal { + // positive amount means positive delta for the hook, so it can take + // negative it should settle + if (delta > 0) { + currency.take(manager, address(this), uint128(delta), false); + } else { + uint256 amount = uint256(-int256(delta)); + if (currency.isNative()) { + manager.settle{value: amount}(currency); + } else { + currency.settle(manager, address(this), amount, false); + } + } + } +} diff --git a/src/test/DynamicFeesTestHook.sol b/src/test/DynamicFeesTestHook.sol index ecc1b459b..c6355d4b9 100644 --- a/src/test/DynamicFeesTestHook.sol +++ b/src/test/DynamicFeesTestHook.sol @@ -30,10 +30,10 @@ contract DynamicFeesTestHook is BaseTestHooks { function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override - returns (bytes4) + returns (bytes4, int128) { manager.updateDynamicLPFee(key, fee); - return IHooks.beforeSwap.selector; + return (IHooks.beforeSwap.selector, 0); } function forcePoolFeeUpdate(PoolKey calldata _key, uint24 _fee) external { diff --git a/src/test/EmptyTestHooks.sol b/src/test/EmptyTestHooks.sol index c5c3e419a..0b3ed4128 100644 --- a/src/test/EmptyTestHooks.sol +++ b/src/test/EmptyTestHooks.sol @@ -5,7 +5,7 @@ import {Hooks} from "../libraries/Hooks.sol"; import {IHooks} from "../interfaces/IHooks.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {PoolKey} from "../types/PoolKey.sol"; -import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; contract EmptyTestHooks is IHooks { using Hooks for IHooks; @@ -22,7 +22,11 @@ contract EmptyTestHooks is IHooks { beforeSwap: true, afterSwap: true, beforeDonate: true, - afterDonate: true + afterDonate: true, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: true, + afterAddLiquidityReturnDelta: true, + afterRemoveLiquidityReturnDelta: true }) ); } @@ -60,8 +64,8 @@ contract EmptyTestHooks is IHooks { IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata - ) external pure override returns (bytes4) { - return IHooks.afterAddLiquidity.selector; + ) external pure override returns (bytes4, BalanceDelta) { + return (IHooks.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); } function beforeRemoveLiquidity( @@ -79,26 +83,26 @@ contract EmptyTestHooks is IHooks { IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata - ) external pure override returns (bytes4) { - return IHooks.afterRemoveLiquidity.selector; + ) external pure override returns (bytes4, BalanceDelta) { + return (IHooks.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); } function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) external pure override - returns (bytes4) + returns (bytes4, int128) { - return IHooks.beforeSwap.selector; + return (IHooks.beforeSwap.selector, 0); } function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) external pure override - returns (bytes4) + returns (bytes4, int128) { - return IHooks.afterSwap.selector; + return (IHooks.afterSwap.selector, 0); } function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) diff --git a/src/test/FeeTakingHook.sol b/src/test/FeeTakingHook.sol new file mode 100644 index 000000000..f955482d4 --- /dev/null +++ b/src/test/FeeTakingHook.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, toBalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; + +contract FeeTakingHook is BaseTestHooks { + using Hooks for IHooks; + using SafeCast for uint256; + using SafeCast for int128; + + IPoolManager immutable manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + modifier onlyPoolManager() { + require(msg.sender == address(manager)); + _; + } + + uint128 public constant LIQUIDITY_FEE = 543; // 543/10000 = 5.43% + uint128 public constant SWAP_FEE_BIPS = 123; // 123/10000 = 1.23% + uint128 public constant TOTAL_BIPS = 10000; + + function afterSwap( + address, /* sender **/ + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + BalanceDelta delta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, int128) { + // fee will be in the unspecified token of the swap + bool specifiedTokenIs0 = (params.amountSpecified < 0 == params.zeroForOne); + (Currency feeCurrency, int128 swapAmount) = + (specifiedTokenIs0) ? (key.currency1, delta.amount1()) : (key.currency0, delta.amount0()); + // if fee is on output, get the absolute output amount + if (swapAmount < 0) swapAmount = -swapAmount; + + uint256 feeAmount = uint128(swapAmount) * SWAP_FEE_BIPS / TOTAL_BIPS; + manager.take(feeCurrency, address(this), feeAmount); + + return (IHooks.afterSwap.selector, feeAmount.toInt128()); + } + + function afterRemoveLiquidity( + address, /* sender **/ + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata, /* params **/ + BalanceDelta delta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + assert(delta.amount0() >= 0 && delta.amount1() >= 0); + + uint128 feeAmount0 = uint128(delta.amount0()) * LIQUIDITY_FEE / TOTAL_BIPS; + uint128 feeAmount1 = uint128(delta.amount1()) * LIQUIDITY_FEE / TOTAL_BIPS; + + manager.take(key.currency0, address(this), feeAmount0); + manager.take(key.currency1, address(this), feeAmount1); + + return (IHooks.afterRemoveLiquidity.selector, toBalanceDelta(int128(feeAmount0), int128(feeAmount1))); + } + + function afterAddLiquidity( + address, /* sender **/ + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata, /* params **/ + BalanceDelta delta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + assert(delta.amount0() <= 0 && delta.amount1() <= 0); + + uint128 feeAmount0 = uint128(-delta.amount0()) * LIQUIDITY_FEE / TOTAL_BIPS; + uint128 feeAmount1 = uint128(-delta.amount1()) * LIQUIDITY_FEE / TOTAL_BIPS; + + manager.take(key.currency0, address(this), feeAmount0); + manager.take(key.currency1, address(this), feeAmount1); + + return (IHooks.afterAddLiquidity.selector, toBalanceDelta(int128(feeAmount0), int128(feeAmount1))); + } +} diff --git a/src/test/MockHooks.sol b/src/test/MockHooks.sol index 1da0f160f..e7dc57f09 100644 --- a/src/test/MockHooks.sol +++ b/src/test/MockHooks.sol @@ -5,7 +5,7 @@ import {Hooks} from "../libraries/Hooks.sol"; import {IHooks} from "../interfaces/IHooks.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {PoolKey} from "../types/PoolKey.sol"; -import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; import {PoolId, PoolIdLibrary} from "../types/PoolId.sol"; contract MockHooks is IHooks { @@ -64,10 +64,10 @@ contract MockHooks is IHooks { IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata hookData - ) external override returns (bytes4) { + ) external override returns (bytes4, BalanceDelta) { afterAddLiquidityData = hookData; bytes4 selector = MockHooks.afterAddLiquidity.selector; - return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], BalanceDeltaLibrary.ZERO_DELTA); } function beforeRemoveLiquidity( @@ -87,20 +87,20 @@ contract MockHooks is IHooks { IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata hookData - ) external override returns (bytes4) { + ) external override returns (bytes4, BalanceDelta) { afterRemoveLiquidityData = hookData; bytes4 selector = MockHooks.afterRemoveLiquidity.selector; - return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], BalanceDeltaLibrary.ZERO_DELTA); } function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata hookData) external override - returns (bytes4) + returns (bytes4, int128) { beforeSwapData = hookData; bytes4 selector = MockHooks.beforeSwap.selector; - return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0); } function afterSwap( @@ -109,10 +109,10 @@ contract MockHooks is IHooks { IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata hookData - ) external override returns (bytes4) { + ) external override returns (bytes4, int128) { afterSwapData = hookData; bytes4 selector = MockHooks.afterSwap.selector; - return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0); } function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata hookData) diff --git a/src/test/PoolModifyLiquidityTest.sol b/src/test/PoolModifyLiquidityTest.sol index c3c41ff42..fe290272b 100644 --- a/src/test/PoolModifyLiquidityTest.sol +++ b/src/test/PoolModifyLiquidityTest.sol @@ -5,6 +5,7 @@ import {CurrencyLibrary, Currency} from "../types/Currency.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {BalanceDelta} from "../types/BalanceDelta.sol"; import {PoolKey} from "../types/PoolKey.sol"; +import {PoolIdLibrary} from "../types/PoolId.sol"; import {PoolTestBase} from "./PoolTestBase.sol"; import {IHooks} from "../interfaces/IHooks.sol"; import {Hooks} from "../libraries/Hooks.sol"; @@ -16,6 +17,7 @@ contract PoolModifyLiquidityTest is PoolTestBase { using CurrencySettleTake for Currency; using Hooks for IHooks; using LPFeeLibrary for uint24; + using PoolIdLibrary for PoolKey; constructor(IPoolManager _manager) PoolTestBase(_manager) {} @@ -59,11 +61,23 @@ contract PoolModifyLiquidityTest is PoolTestBase { CallbackData memory data = abi.decode(rawData, (CallbackData)); + uint128 liquidityBefore = manager.getPosition( + data.key.toId(), address(this), data.params.tickLower, data.params.tickUpper, data.params.salt + ).liquidity; + (BalanceDelta delta,) = manager.modifyLiquidity(data.key, data.params, data.hookData); + uint128 liquidityAfter = manager.getPosition( + data.key.toId(), address(this), data.params.tickLower, data.params.tickUpper, data.params.salt + ).liquidity; + (,, int256 delta0) = _fetchBalances(data.key.currency0, data.sender, address(this)); (,, int256 delta1) = _fetchBalances(data.key.currency1, data.sender, address(this)); + require( + int128(liquidityBefore) + data.params.liquidityDelta == int128(liquidityAfter), "liquidity change incorrect" + ); + if (data.params.liquidityDelta < 0) { assert(delta0 > 0 || delta1 > 0); assert(!(delta0 < 0 || delta1 < 0)); diff --git a/src/test/PoolNestedActionsTest.sol b/src/test/PoolNestedActionsTest.sol index 7cba77aa8..6ecbe4251 100644 --- a/src/test/PoolNestedActionsTest.sol +++ b/src/test/PoolNestedActionsTest.sol @@ -17,8 +17,8 @@ enum Action { NESTED_EXECUTOR_UNLOCK, SWAP_AND_SETTLE, DONATE_AND_SETTLE, - ADD_LIQ_AND_SETTLE, - REMOVE_LIQ_AND_SETTLE, + ADD_LIQUIDITY_AND_SETTLE, + REMOVE_LIQUIDITY_AND_SETTLE, INITIALIZE } @@ -67,10 +67,10 @@ contract NestedActionExecutor is Test, PoolTestBase { error KeyNotSet(); - IPoolManager.ModifyLiquidityParams internal ADD_LIQ_PARAMS = + IPoolManager.ModifyLiquidityParams internal ADD_LIQUIDITY_PARAMS = IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); - IPoolManager.ModifyLiquidityParams internal REMOVE_LIQ_PARAMS = + IPoolManager.ModifyLiquidityParams internal REMOVE_LIQUIDITY_PARAMS = IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); IPoolManager.SwapParams internal SWAP_PARAMS = @@ -93,8 +93,8 @@ contract NestedActionExecutor is Test, PoolTestBase { Action action = actions[i]; if (action == Action.NESTED_EXECUTOR_UNLOCK) _nestedUnlock(); else if (action == Action.SWAP_AND_SETTLE) _swap(msg.sender); - else if (action == Action.ADD_LIQ_AND_SETTLE) _addLiquidity(msg.sender); - else if (action == Action.REMOVE_LIQ_AND_SETTLE) _removeLiquidity(msg.sender); + else if (action == Action.ADD_LIQUIDITY_AND_SETTLE) _addLiquidity(msg.sender); + else if (action == Action.REMOVE_LIQUIDITY_AND_SETTLE) _removeLiquidity(msg.sender); else if (action == Action.DONATE_AND_SETTLE) _donate(msg.sender); else if (action == Action.INITIALIZE) _initialize(); } @@ -143,7 +143,7 @@ contract NestedActionExecutor is Test, PoolTestBase { (,, int256 deltaThisBefore0) = _fetchBalances(key.currency0, user, address(this)); (,, int256 deltaThisBefore1) = _fetchBalances(key.currency1, user, address(this)); - (BalanceDelta delta,) = manager.modifyLiquidity(key, ADD_LIQ_PARAMS, ""); + (BalanceDelta delta,) = manager.modifyLiquidity(key, ADD_LIQUIDITY_PARAMS, ""); (,, int256 deltaCallerAfter0) = _fetchBalances(key.currency0, user, caller); (,, int256 deltaCallerAfter1) = _fetchBalances(key.currency1, user, caller); @@ -168,7 +168,7 @@ contract NestedActionExecutor is Test, PoolTestBase { (,, int256 deltaThisBefore0) = _fetchBalances(key.currency0, user, address(this)); (,, int256 deltaThisBefore1) = _fetchBalances(key.currency1, user, address(this)); - (BalanceDelta delta,) = manager.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ""); + (BalanceDelta delta,) = manager.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ""); (,, int256 deltaCallerAfter0) = _fetchBalances(key.currency0, user, caller); (,, int256 deltaCallerAfter1) = _fetchBalances(key.currency1, user, caller); diff --git a/src/test/PoolSwapTest.sol b/src/test/PoolSwapTest.sol index 46a70d4c1..5bb773e8b 100644 --- a/src/test/PoolSwapTest.sol +++ b/src/test/PoolSwapTest.sol @@ -71,10 +71,12 @@ contract PoolSwapTest is PoolTestBase { deltaAfter0 >= data.params.amountSpecified, "deltaAfter0 is not greater than or equal to data.params.amountSpecified" ); + require(delta.amount0() == deltaAfter0, "delta.amount0() is not equal to deltaAfter0"); require(deltaAfter1 >= 0, "deltaAfter1 is not greater than or equal to 0"); } else { // exact output, 0 for 1 - require(deltaAfter0 < 0, "deltaAfter0 is not less than zero"); + require(deltaAfter0 <= 0, "deltaAfter0 is not less than zero"); + require(delta.amount1() == deltaAfter1, "delta.amount1() is not equal to deltaAfter1"); require( deltaAfter1 <= data.params.amountSpecified, "deltaAfter1 is not less than or equal to data.params.amountSpecified" @@ -87,10 +89,12 @@ contract PoolSwapTest is PoolTestBase { deltaAfter1 >= data.params.amountSpecified, "deltaAfter1 is not greater than or equal to data.params.amountSpecified" ); + require(delta.amount1() == deltaAfter1, "delta.amount1() is not equal to deltaAfter1"); require(deltaAfter0 >= 0, "deltaAfter0 is not greater than or equal to 0"); } else { // exact output, 1 for 0 - require(deltaAfter1 < 0, "deltaAfter1 is not less than 0"); + require(deltaAfter1 <= 0, "deltaAfter1 is not less than 0"); + require(delta.amount0() == deltaAfter0, "delta.amount0() is not equal to deltaAfter0"); require( deltaAfter0 <= data.params.amountSpecified, "deltaAfter0 is not less than or equal to data.params.amountSpecified" diff --git a/src/test/SkipCallsTestHook.sol b/src/test/SkipCallsTestHook.sol index 3c85dac19..7d0a8f2c9 100644 --- a/src/test/SkipCallsTestHook.sol +++ b/src/test/SkipCallsTestHook.sol @@ -6,7 +6,7 @@ import {BaseTestHooks} from "./BaseTestHooks.sol"; import {IHooks} from "../interfaces/IHooks.sol"; import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {PoolKey} from "../types/PoolKey.sol"; -import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; import {PoolId, PoolIdLibrary} from "../types/PoolId.sol"; import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; import {CurrencyLibrary, Currency} from "../types/Currency.sol"; @@ -64,10 +64,10 @@ contract SkipCallsTestHook is BaseTestHooks, Test { IPoolManager.ModifyLiquidityParams calldata params, BalanceDelta, bytes calldata hookData - ) external override returns (bytes4) { + ) external override returns (bytes4, BalanceDelta) { counter++; _addLiquidity(key, params, hookData); - return IHooks.afterAddLiquidity.selector; + return (IHooks.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); } function beforeRemoveLiquidity( @@ -87,20 +87,20 @@ contract SkipCallsTestHook is BaseTestHooks, Test { IPoolManager.ModifyLiquidityParams calldata params, BalanceDelta, bytes calldata hookData - ) external override returns (bytes4) { + ) external override returns (bytes4, BalanceDelta) { counter++; _removeLiquidity(key, params, hookData); - return IHooks.afterRemoveLiquidity.selector; + return (IHooks.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata hookData) external override - returns (bytes4) + returns (bytes4, int128) { counter++; _swap(key, params, hookData); - return IHooks.beforeSwap.selector; + return (IHooks.beforeSwap.selector, 0); } function afterSwap( @@ -109,10 +109,10 @@ contract SkipCallsTestHook is BaseTestHooks, Test { IPoolManager.SwapParams calldata params, BalanceDelta, bytes calldata hookData - ) external override returns (bytes4) { + ) external override returns (bytes4, int128) { counter++; _swap(key, params, hookData); - return IHooks.afterSwap.selector; + return (IHooks.afterSwap.selector, 0); } function beforeDonate(address, PoolKey calldata key, uint256 amt0, uint256 amt1, bytes calldata hookData) diff --git a/src/types/BalanceDelta.sol b/src/types/BalanceDelta.sol index 49ae295e0..b807ce410 100644 --- a/src/types/BalanceDelta.sol +++ b/src/types/BalanceDelta.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; type BalanceDelta is int256; -using {add as +, sub as -, eq as ==} for BalanceDelta global; +using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global; using BalanceDeltaLibrary for BalanceDelta global; function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) { @@ -25,7 +25,13 @@ function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) { return a.amount0() == b.amount0() && a.amount1() == b.amount1(); } +function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) { + return a.amount0() != b.amount0() || a.amount1() != b.amount1(); +} + library BalanceDeltaLibrary { + BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0); + function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) { /// @solidity memory-safe-assembly assembly { diff --git a/test/DynamicFees.t.sol b/test/DynamicFees.t.sol index 2223ee288..97e7f67fc 100644 --- a/test/DynamicFees.t.sol +++ b/test/DynamicFees.t.sol @@ -25,26 +25,15 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { DynamicFeesTestHook dynamicFeesHooks = DynamicFeesTestHook( address( - uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) - & uint160( - ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG - & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.AFTER_SWAP_FLAG - & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG - ) + uint160( + uint256(type(uint160).max) & clearAllHookPermisssionsMask | Hooks.BEFORE_SWAP_FLAG + | Hooks.AFTER_INITIALIZE_FLAG + ) ) ); - DynamicFeesTestHook dynamicFeesNoHooks = DynamicFeesTestHook( - address( - uint160(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF) - & uint160( - ~Hooks.BEFORE_INITIALIZE_FLAG & ~Hooks.AFTER_INITIALIZE_FLAG & ~Hooks.BEFORE_ADD_LIQUIDITY_FLAG - & ~Hooks.AFTER_ADD_LIQUIDITY_FLAG & ~Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG - & ~Hooks.AFTER_REMOVE_LIQUIDITY_FLAG & ~Hooks.BEFORE_SWAP_FLAG & ~Hooks.AFTER_SWAP_FLAG - & ~Hooks.BEFORE_DONATE_FLAG & ~Hooks.AFTER_DONATE_FLAG - ) - ) - ); + DynamicFeesTestHook dynamicFeesNoHooks = + DynamicFeesTestHook(address(uint160(uint256(type(uint160).max) & clearAllHookPermisssionsMask))); event Swap( PoolId indexed poolId, diff --git a/test/PoolManager.t.sol b/test/PoolManager.t.sol index 7d8fcce8b..8376fab56 100644 --- a/test/PoolManager.t.sol +++ b/test/PoolManager.t.sol @@ -8,6 +8,9 @@ import {IPoolManager} from "../src/interfaces/IPoolManager.sol"; import {IProtocolFees} from "../src/interfaces/IProtocolFees.sol"; import {IProtocolFeeController} from "../src/interfaces/IProtocolFeeController.sol"; import {PoolManager} from "../src/PoolManager.sol"; +import {FeeTakingHook} from "../src/test/FeeTakingHook.sol"; +import {CustomCurveHook} from "../src/test/CustomCurveHook.sol"; +import {DeltaReturningHook} from "../src/test/DeltaReturningHook.sol"; import {TickMath} from "../src/libraries/TickMath.sol"; import {Pool} from "../src/libraries/Pool.sol"; import {Deployers} from "./utils/Deployers.sol"; @@ -38,6 +41,8 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; using LPFeeLibrary for uint24; using CurrencyLibrary for Currency; + using SafeCast for uint256; + using SafeCast for uint128; using ProtocolFeeLibrary for uint24; event UnlockCallback(); @@ -95,22 +100,22 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_addLiquidity_failsIfNotInitialized() public { vm.expectRevert(Pool.PoolNotInitialized.selector); - modifyLiquidityRouter.modifyLiquidity(uninitializedKey, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(uninitializedKey, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_addLiquidity_failsIfLocked() public { vm.expectRevert(IPoolManager.ManagerLocked.selector); - manager.modifyLiquidity(uninitializedKey, LIQ_PARAMS, ZERO_BYTES); + manager.modifyLiquidity(uninitializedKey, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_removeLiquidity_failsIfNotInitialized() public { vm.expectRevert(Pool.PoolNotInitialized.selector); - modifyLiquidityRouter.modifyLiquidity(uninitializedKey, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(uninitializedKey, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); } function test_removeLiquidity_failsIfLocked() public { vm.expectRevert(IPoolManager.ManagerLocked.selector); - manager.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + manager.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); } function test_addLiquidity_succeedsIfInitialized(uint160 sqrtPriceX96) public { @@ -120,12 +125,12 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { emit ModifyLiquidity( key.toId(), address(modifyLiquidityRouter), - LIQ_PARAMS.tickLower, - LIQ_PARAMS.tickUpper, - LIQ_PARAMS.liquidityDelta + LIQUIDITY_PARAMS.tickLower, + LIQUIDITY_PARAMS.tickUpper, + LIQUIDITY_PARAMS.liquidityDelta ); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_removeLiquidity_succeedsIfInitialized(uint160 sqrtPriceX96) public { @@ -135,12 +140,76 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { emit ModifyLiquidity( key.toId(), address(modifyLiquidityRouter), - REMOVE_LIQ_PARAMS.tickLower, - REMOVE_LIQ_PARAMS.tickUpper, - REMOVE_LIQ_PARAMS.liquidityDelta + REMOVE_LIQUIDITY_PARAMS.tickLower, + REMOVE_LIQUIDITY_PARAMS.tickUpper, + REMOVE_LIQUIDITY_PARAMS.liquidityDelta ); - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); + } + + function test_addLiquidity_withFeeTakingHook() public { + address hookAddr = + address(uint160(Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + FeeTakingHook impl = new FeeTakingHook(manager); + vm.etch(hookAddr, address(impl).code); + + (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + + uint256 balanceBefore0 = currency0.balanceOf(address(this)); + uint256 balanceBefore1 = currency1.balanceOf(address(this)); + uint256 hookBalanceBefore0 = currency0.balanceOf(hookAddr); + uint256 hookBalanceBefore1 = currency1.balanceOf(hookAddr); + uint256 managerBalanceBefore0 = currency0.balanceOf(address(manager)); + uint256 managerBalanceBefore1 = currency1.balanceOf(address(manager)); + + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + snapLastCall("addLiquidity CA fee"); + + uint256 hookGain0 = currency0.balanceOf(hookAddr) - hookBalanceBefore0; + uint256 hookGain1 = currency1.balanceOf(hookAddr) - hookBalanceBefore1; + uint256 thisLoss0 = balanceBefore0 - currency0.balanceOf(address(this)); + uint256 thisLoss1 = balanceBefore1 - currency1.balanceOf(address(this)); + uint256 managerGain0 = currency0.balanceOf(address(manager)) - managerBalanceBefore0; + uint256 managerGain1 = currency1.balanceOf(address(manager)) - managerBalanceBefore1; + + // Assert that the hook got 5.43% of the added liquidity + assertEq(hookGain0, managerGain0 * 543 / 10000, "hook amount 0"); + assertEq(hookGain1, managerGain1 * 543 / 10000, "hook amount 1"); + assertEq(thisLoss0 - hookGain0, managerGain0, "manager amount 0"); + assertEq(thisLoss1 - hookGain1, managerGain1, "manager amount 1"); + } + + function test_removeLiquidity_withFeeTakingHook() public { + address hookAddr = + address(uint160(Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); + FeeTakingHook impl = new FeeTakingHook(manager); + vm.etch(hookAddr, address(impl).code); + + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + + uint256 balanceBefore0 = currency0.balanceOf(address(this)); + uint256 balanceBefore1 = currency1.balanceOf(address(this)); + uint256 hookBalanceBefore0 = currency0.balanceOf(hookAddr); + uint256 hookBalanceBefore1 = currency1.balanceOf(hookAddr); + uint256 managerBalanceBefore0 = currency0.balanceOf(address(manager)); + uint256 managerBalanceBefore1 = currency1.balanceOf(address(manager)); + + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); + snapLastCall("removeLiquidity CA fee"); + + uint256 hookGain0 = currency0.balanceOf(hookAddr) - hookBalanceBefore0; + uint256 hookGain1 = currency1.balanceOf(hookAddr) - hookBalanceBefore1; + uint256 thisGain0 = currency0.balanceOf(address(this)) - balanceBefore0; + uint256 thisGain1 = currency1.balanceOf(address(this)) - balanceBefore1; + uint256 managerLoss0 = managerBalanceBefore0 - currency0.balanceOf(address(manager)); + uint256 managerLoss1 = managerBalanceBefore1 - currency1.balanceOf(address(manager)); + + // Assert that the hook got 5.43% of the withdrawn liquidity + assertEq(hookGain0, managerLoss0 * 543 / 10000, "hook amount 0"); + assertEq(hookGain1, managerLoss1 * 543 / 10000, "hook amount 1"); + assertEq(thisGain0 + hookGain0, managerLoss0, "manager amount 0"); + assertEq(thisGain1 + hookGain1, managerLoss1, "manager amount 1"); } function test_addLiquidity_succeedsForNativeTokensIfInitialized(uint160 sqrtPriceX96) public { @@ -150,12 +219,12 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { emit ModifyLiquidity( nativeKey.toId(), address(modifyLiquidityRouter), - LIQ_PARAMS.tickLower, - LIQ_PARAMS.tickUpper, - LIQ_PARAMS.liquidityDelta + LIQUIDITY_PARAMS.tickLower, + LIQUIDITY_PARAMS.tickUpper, + LIQUIDITY_PARAMS.liquidityDelta ); - modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_removeLiquidity_succeedsForNativeTokensIfInitialized(uint160 sqrtPriceX96) public { @@ -165,12 +234,12 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { emit ModifyLiquidity( nativeKey.toId(), address(modifyLiquidityRouter), - REMOVE_LIQ_PARAMS.tickLower, - REMOVE_LIQ_PARAMS.tickUpper, - REMOVE_LIQ_PARAMS.liquidityDelta + REMOVE_LIQUIDITY_PARAMS.tickLower, + REMOVE_LIQUIDITY_PARAMS.tickUpper, + REMOVE_LIQUIDITY_PARAMS.liquidityDelta ); - modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); } function test_addLiquidity_succeedsWithHooksIfInitialized(uint160 sqrtPriceX96) public { @@ -188,12 +257,13 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { (key,) = initPool(currency0, currency1, IHooks(mockAddr), 3000, sqrtPriceX96, ZERO_BYTES); - BalanceDelta balanceDelta = modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + BalanceDelta balanceDelta = modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); bytes32 beforeSelector = MockHooks.beforeAddLiquidity.selector; - bytes memory beforeParams = abi.encode(address(modifyLiquidityRouter), key, LIQ_PARAMS, ZERO_BYTES); + bytes memory beforeParams = abi.encode(address(modifyLiquidityRouter), key, LIQUIDITY_PARAMS, ZERO_BYTES); bytes32 afterSelector = MockHooks.afterAddLiquidity.selector; - bytes memory afterParams = abi.encode(address(modifyLiquidityRouter), key, LIQ_PARAMS, balanceDelta, ZERO_BYTES); + bytes memory afterParams = + abi.encode(address(modifyLiquidityRouter), key, LIQUIDITY_PARAMS, balanceDelta, ZERO_BYTES); assertEq(MockContract(mockAddr).timesCalledSelector(beforeSelector), 1); assertTrue(MockContract(mockAddr).calledWithSelector(beforeSelector, beforeParams)); @@ -215,14 +285,14 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { MockContract(mockAddr).setImplementation(hookAddr); (key,) = initPool(currency0, currency1, IHooks(mockAddr), 3000, sqrtPriceX96, ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); - BalanceDelta balanceDelta = modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + BalanceDelta balanceDelta = modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); bytes32 beforeSelector = MockHooks.beforeRemoveLiquidity.selector; - bytes memory beforeParams = abi.encode(address(modifyLiquidityRouter), key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + bytes memory beforeParams = abi.encode(address(modifyLiquidityRouter), key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); bytes32 afterSelector = MockHooks.afterRemoveLiquidity.selector; bytes memory afterParams = - abi.encode(address(modifyLiquidityRouter), key, REMOVE_LIQ_PARAMS, balanceDelta, ZERO_BYTES); + abi.encode(address(modifyLiquidityRouter), key, REMOVE_LIQUIDITY_PARAMS, balanceDelta, ZERO_BYTES); assertEq(MockContract(mockAddr).timesCalledSelector(beforeSelector), 1); assertTrue(MockContract(mockAddr).calledWithSelector(beforeSelector, beforeParams)); @@ -244,12 +314,12 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { // Fails at beforeAddLiquidity hook. vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); // Fail at afterAddLiquidity hook. mockHooks.setReturnValue(mockHooks.beforeAddLiquidity.selector, mockHooks.beforeAddLiquidity.selector); vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_removeLiquidity_failsWithIncorrectSelectors() public { @@ -260,19 +330,19 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { MockHooks mockHooks = MockHooks(hookAddr); (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeRemoveLiquidity.selector, bytes4(0xdeadbeef)); mockHooks.setReturnValue(mockHooks.afterRemoveLiquidity.selector, bytes4(0xdeadbeef)); // Fails at beforeRemoveLiquidity hook. vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); // Fail at afterRemoveLiquidity hook. mockHooks.setReturnValue(mockHooks.beforeRemoveLiquidity.selector, mockHooks.beforeRemoveLiquidity.selector); vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); } function test_addLiquidity_succeedsWithCorrectSelectors() public { @@ -291,12 +361,12 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { emit ModifyLiquidity( key.toId(), address(modifyLiquidityRouter), - LIQ_PARAMS.tickLower, - LIQ_PARAMS.tickUpper, - LIQ_PARAMS.liquidityDelta + LIQUIDITY_PARAMS.tickLower, + LIQUIDITY_PARAMS.tickUpper, + LIQUIDITY_PARAMS.liquidityDelta ); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_removeLiquidity_succeedsWithCorrectSelectors() public { @@ -307,7 +377,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { MockHooks mockHooks = MockHooks(hookAddr); (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeRemoveLiquidity.selector, mockHooks.beforeRemoveLiquidity.selector); mockHooks.setReturnValue(mockHooks.afterRemoveLiquidity.selector, mockHooks.afterRemoveLiquidity.selector); @@ -316,12 +386,12 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { emit ModifyLiquidity( key.toId(), address(modifyLiquidityRouter), - REMOVE_LIQ_PARAMS.tickLower, - REMOVE_LIQ_PARAMS.tickUpper, - REMOVE_LIQ_PARAMS.liquidityDelta + REMOVE_LIQUIDITY_PARAMS.tickLower, + REMOVE_LIQUIDITY_PARAMS.tickUpper, + REMOVE_LIQUIDITY_PARAMS.liquidityDelta ); - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); } function test_addLiquidity_6909() public { @@ -340,7 +410,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { manager.setOperator(address(modifyLiquidityRouter), true); // add liquidity with 6909: settleUsingBurn=true, takeClaims=true (unused) - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES, true, true); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES, true, true); assertLt(manager.balanceOf(address(this), currency0.toId()), 10_000e18); assertLt(manager.balanceOf(address(this), currency1.toId()), 10_000e18); @@ -355,7 +425,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_removeLiquidity_6909() public { - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); assertEq(manager.balanceOf(address(this), currency0.toId()), 0); assertEq(manager.balanceOf(address(this), currency1.toId()), 0); @@ -366,7 +436,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { uint256 currency1PMBalanceBefore = currency1.balanceOf(address(manager)); // remove liquidity as 6909: settleUsingBurn=true (unused), takeClaims=true - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES, true, true); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES, true, true); assertTrue(manager.balanceOf(address(this), currency0.toId()) > 0); assertTrue(manager.balanceOf(address(this), currency1.toId()) > 0); @@ -381,22 +451,22 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_addLiquidity_gas() public { - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); snapLastCall("addLiquidity"); } function test_removeLiquidity_gas() public { - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); snapLastCall("removeLiquidity"); } function test_addLiquidity_withNative_gas() public { - modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, LIQUIDITY_PARAMS, ZERO_BYTES); snapLastCall("addLiquidity with native token"); } function test_removeLiquidity_withNative_gas() public { - modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity{value: 1 ether}(nativeKey, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); snapLastCall("removeLiquidity with native token"); } @@ -408,7 +478,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { (key,) = initPool(currency0, currency1, mockHooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); snapLastCall("addLiquidity with empty hook"); } @@ -419,9 +489,9 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { MockHooks mockHooks = MockHooks(hookEmptyAddr); (key,) = initPool(currency0, currency1, mockHooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); snapLastCall("removeLiquidity with empty hook"); } @@ -708,7 +778,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { assertEq(erc6909Balance, 71); } - function test_swap_againstLiq_gas() public { + function test_swap_againstLiquidity_gas() public { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); @@ -738,6 +808,245 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { snapLastCall("swap against liquidity with native token"); } + function test_swap_afterSwapFeeOnUnspecified_exactInput() public { + address hookAddr = address(uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + FeeTakingHook impl = new FeeTakingHook(manager); + vm.etch(hookAddr, address(impl).code); + + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + + uint256 balanceBefore0 = currency0.balanceOf(address(this)); + uint256 balanceBefore1 = currency1.balanceOf(address(this)); + + uint256 amountToSwap = 1000; + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: -int256(amountToSwap), + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + snapLastCall("swap CA fee on unspecified"); + + // input is 1000 for output of 998 with this much liquidity available + // plus a fee of 1.23% on unspecified (output) => (998*123)/10000 = 12 + assertEq(currency0.balanceOf(address(this)), balanceBefore0 - amountToSwap, "amount 0"); + assertEq(currency1.balanceOf(address(this)), balanceBefore1 + (998 - 12), "amount 1"); + } + + function test_swap_afterSwapFeeOnUnspecified_exactOutput() public { + address hookAddr = address(uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + FeeTakingHook impl = new FeeTakingHook(manager); + vm.etch(hookAddr, address(impl).code); + + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + + uint256 balanceBefore0 = currency0.balanceOf(address(this)); + uint256 balanceBefore1 = currency1.balanceOf(address(this)); + + uint256 amountToSwap = 1000; + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: int256(amountToSwap), + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + + // input is 1002 for output of 1000 with this much liquidity available + // plus a fee of 1.23% on unspecified (input) => (1002*123)/10000 = 12 + assertEq(currency0.balanceOf(address(this)), balanceBefore0 - 1002 - 12, "amount 0"); + assertEq(currency1.balanceOf(address(this)), balanceBefore1 + amountToSwap, "amount 1"); + } + + function test_swap_beforeSwapNoOpsSwap_exactInput() public { + address hookAddr = address( + uint160( + Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG | Hooks.BEFORE_SWAP_FLAG + | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG + ) + ); + CustomCurveHook impl = new CustomCurveHook(manager); + vm.etch(hookAddr, address(impl).code); + + (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + // add liquidity by sending tokens straight into the contract + key.currency0.transfer(hookAddr, 10e18); + key.currency1.transfer(hookAddr, 10e18); + + uint256 balanceBefore0 = currency0.balanceOf(address(this)); + uint256 balanceBefore1 = currency1.balanceOf(address(this)); + + uint256 amountToSwap = 123456; + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: -int256(amountToSwap), + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + snapLastCall("swap CA custom curve + swap noop"); + + // the custom curve hook is 1-1 linear + assertEq(currency0.balanceOf(address(this)), balanceBefore0 - amountToSwap, "amount 0"); + assertEq(currency1.balanceOf(address(this)), balanceBefore1 + amountToSwap, "amount 1"); + } + + function test_swap_beforeSwapNoOpsSwap_exactOutput() public { + address hookAddr = address( + uint160( + Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG | Hooks.BEFORE_SWAP_FLAG + | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG + ) + ); + CustomCurveHook impl = new CustomCurveHook(manager); + vm.etch(hookAddr, address(impl).code); + + (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + // add liquidity by sending tokens straight into the contract + key.currency0.transfer(hookAddr, 10e18); + key.currency1.transfer(hookAddr, 10e18); + + uint256 balanceBefore0 = currency0.balanceOf(address(this)); + uint256 balanceBefore1 = currency1.balanceOf(address(this)); + + uint256 amountToSwap = 123456; + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: int256(amountToSwap), + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + + // the custom curve hook is 1-1 linear + assertEq(currency0.balanceOf(address(this)), balanceBefore0 - amountToSwap, "amount 0"); + assertEq(currency1.balanceOf(address(this)), balanceBefore1 + amountToSwap, "amount 1"); + } + + // maximum available liquidity in each direction for the pool in fuzz_swap_beforeSwapReturnsDelta + int128 maxPossibleIn_fuzz_test = -6018336102428409; + int128 maxPossibleOut_fuzz_test = 5981737760509662; + + function test_fuzz_swap_beforeSwapReturnsDelta(int128 hookDeltaSpecified, int256 amountSpecified, bool zeroForOne) + public + { + // ------------------------ SETUP ------------------------ + Currency specifiedCurrency; + bool isExactIn; + address hookAddr = address(uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + + // stack too deep management + { + // setup the hook and the pool + + DeltaReturningHook impl = new DeltaReturningHook(manager); + vm.etch(hookAddr, address(impl).code); + + // initialize the pool and give the hook tokens to pay into swaps + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + key.currency0.transfer(hookAddr, type(uint128).max); + key.currency1.transfer(hookAddr, type(uint128).max); + + // bound amount specified to be a fair amount less than the amount of liquidity we have + amountSpecified = int128(bound(amountSpecified, -3e11, 3e11)); + isExactIn = amountSpecified < 0; + specifiedCurrency = (isExactIn == zeroForOne) ? key.currency0 : key.currency1; + + // bound delta in specified to not take more than the reserves available, nor be the minimum int to + // stop the hook reverting on take/settle + uint128 reservesOfSpecified = uint128(specifiedCurrency.balanceOf(address(manager))); + hookDeltaSpecified = int128(bound(hookDeltaSpecified, type(int128).min + 1, int128(reservesOfSpecified))); + DeltaReturningHook(hookAddr).setDeltaSpecified(hookDeltaSpecified); + } + + // setup swap variables + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: (zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT) + }); + + // ------------------------ FUZZING CASES ------------------------ + // with an amount specified of 0: the trade reverts + if (amountSpecified == 0) { + vm.expectRevert(IPoolManager.SwapAmountCannotBeZero.selector); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + + // trade is exact input of n:, the hook cannot TAKE (+ve hookDeltaSpecified) more than n in input + // otherwise the user would have to send more than n in input + } else if (isExactIn && (hookDeltaSpecified > -amountSpecified)) { + vm.expectRevert(Hooks.HookDeltaExceedsSwapAmount.selector); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + + // exact output of n: the hook cannot GIVE (-ve hookDeltaSpecified) more than n in output + // otherwise the user would receive more than n in output + } else if (!isExactIn && (amountSpecified < -hookDeltaSpecified)) { + vm.expectRevert(Hooks.HookDeltaExceedsSwapAmount.selector); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + + // successful swaps ! + } else { + uint256 balanceThisBefore = specifiedCurrency.balanceOf(address(this)); + uint256 balanceHookBefore = specifiedCurrency.balanceOf(hookAddr); + uint256 balanceManagerBefore = specifiedCurrency.balanceOf(address(manager)); + + BalanceDelta delta = swapRouter.swap(key, params, testSettings, ZERO_BYTES); + int128 deltaSpecified = (zeroForOne == isExactIn) ? delta.amount0() : delta.amount1(); + + // in all cases the hook gets what they took, and the user gets the swap's output delta (checked more below) + assertEq( + balanceHookBefore.toInt256() + hookDeltaSpecified, + specifiedCurrency.balanceOf(hookAddr).toInt256(), + "hook balance change incorrect" + ); + assertEq( + balanceThisBefore.toInt256() + deltaSpecified, + specifiedCurrency.balanceOf(address(this)).toInt256(), + "swapper balance change incorrect" + ); + + // exact input, where there arent enough input reserves available to pay swap and hook + // note: all 3 values are negative, so we use < + if (isExactIn && (hookDeltaSpecified + amountSpecified < maxPossibleIn_fuzz_test)) { + // the hook will have taken hookDeltaSpecified of the maxPossibleIn + assertEq(deltaSpecified, maxPossibleIn_fuzz_test - hookDeltaSpecified, "deltaSpecified exact input"); + // the manager received all possible input tokens + assertEq( + balanceManagerBefore.toInt256() - maxPossibleIn_fuzz_test, + specifiedCurrency.balanceOf(address(manager)).toInt256(), + "manager balance change exact input" + ); + + // exact output, where there isnt enough output reserves available to pay swap and hook + } else if (!isExactIn && (hookDeltaSpecified + amountSpecified > maxPossibleOut_fuzz_test)) { + // the hook will have taken hookDeltaSpecified of the maxPossibleOut + assertEq(deltaSpecified, maxPossibleOut_fuzz_test - hookDeltaSpecified, "deltaSpecified exact output"); + // the manager sent out all possible output tokens + assertEq( + balanceManagerBefore.toInt256() - maxPossibleOut_fuzz_test, + specifiedCurrency.balanceOf(address(manager)).toInt256(), + "manager balance change exact output" + ); + + // enough reserves were available, so the user got what they desired + } else { + assertEq(deltaSpecified, amountSpecified, "deltaSpecified not amountSpecified"); + assertEq( + balanceManagerBefore.toInt256() - amountSpecified - hookDeltaSpecified, + specifiedCurrency.balanceOf(address(manager)).toInt256(), + "manager balance change not" + ); + } + } + } + function test_swap_accruesProtocolFees(uint16 protocolFee0, uint16 protocolFee1, int256 amountSpecified) public { protocolFee0 = uint16(bound(protocolFee0, 0, 1000)); protocolFee1 = uint16(bound(protocolFee1, 0, 1000)); @@ -752,21 +1061,21 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { assertEq(slot0ProtocolFee, protocolFee); // Add liquidity - Fees dont accrue for positive liquidity delta. - IPoolManager.ModifyLiquidityParams memory params = LIQ_PARAMS; + IPoolManager.ModifyLiquidityParams memory params = LIQUIDITY_PARAMS; modifyLiquidityRouter.modifyLiquidity(key, params, ZERO_BYTES); assertEq(manager.protocolFeesAccrued(currency0), 0); assertEq(manager.protocolFeesAccrued(currency1), 0); // Remove liquidity - Fees dont accrue for negative liquidity delta. - params.liquidityDelta = -LIQ_PARAMS.liquidityDelta; + params.liquidityDelta = -LIQUIDITY_PARAMS.liquidityDelta; modifyLiquidityRouter.modifyLiquidity(key, params, ZERO_BYTES); assertEq(manager.protocolFeesAccrued(currency0), 0); assertEq(manager.protocolFeesAccrued(currency1), 0); // Now re-add the liquidity to test swap - params.liquidityDelta = LIQ_PARAMS.liquidityDelta; + params.liquidityDelta = LIQUIDITY_PARAMS.liquidityDelta; modifyLiquidityRouter.modifyLiquidity(key, params, ZERO_BYTES); IPoolManager.SwapParams memory swapParams = @@ -779,7 +1088,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_donate_failsIfNotInitialized() public { - vm.expectRevert(abi.encodeWithSelector(Pool.PoolNotInitialized.selector)); + vm.expectRevert(Pool.PoolNotInitialized.selector); donateRouter.donate(uninitializedKey, 100, 100, ZERO_BYTES); } @@ -793,7 +1102,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { (key,) = initPool(currency0, currency1, IHooks(address(0)), 100, sqrtPriceX96, ZERO_BYTES); - vm.expectRevert(abi.encodeWithSelector(Pool.NoLiquidityToReceiveFees.selector)); + vm.expectRevert(Pool.NoLiquidityToReceiveFees.selector); donateRouter.donate(key, 100, 100, ZERO_BYTES); } @@ -1194,8 +1503,8 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_getPosition() public view { Position.Info memory managerPosition = manager.getPosition(key.toId(), address(modifyLiquidityRouter), -120, 120, 0); - assert(LIQ_PARAMS.liquidityDelta > 0); - assertEq(managerPosition.liquidity, uint128(uint256(LIQ_PARAMS.liquidityDelta))); + assert(LIQUIDITY_PARAMS.liquidityDelta > 0); + assertEq(managerPosition.liquidity, uint128(uint256(LIQUIDITY_PARAMS.liquidityDelta))); } function supportsInterface(bytes4) external pure returns (bool) { diff --git a/test/SkipCallsTestHook.t.sol b/test/SkipCallsTestHook.t.sol index 7ec9f2c6a..7922fc772 100644 --- a/test/SkipCallsTestHook.t.sol +++ b/test/SkipCallsTestHook.t.sol @@ -26,13 +26,6 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); - uint160 clearAllHookPermisssionsMask; - uint256 hookPermissionCount = 10; - - function setUp() public { - clearAllHookPermisssionsMask = ~uint160(0) >> hookPermissionCount; - } - function deploy(SkipCallsTestHook skipCallsTestHook) private { SkipCallsTestHook impl = new SkipCallsTestHook(); vm.etch(address(skipCallsTestHook), address(impl).code); @@ -48,7 +41,7 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { function approveAndAddLiquidity(SkipCallsTestHook skipCallsTestHook) private { MockERC20(Currency.unwrap(key.currency0)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); MockERC20(Currency.unwrap(key.currency1)).approve(address(skipCallsTestHook), Constants.MAX_UINT256); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, abi.encode(address(this))); } function test_beforeInitialize_skipIfCalledByHook() public { @@ -83,7 +76,7 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { approveAndAddLiquidity(skipCallsTestHook); assertEq(skipCallsTestHook.counter(), 1); // adds liquidity again and increments counter - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, abi.encode(address(this))); assertEq(skipCallsTestHook.counter(), 2); } @@ -99,7 +92,7 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { approveAndAddLiquidity(skipCallsTestHook); assertEq(skipCallsTestHook.counter(), 1); // adds liquidity and increments counter again - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, abi.encode(address(this))); assertEq(skipCallsTestHook.counter(), 2); } @@ -113,12 +106,12 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { assertEq(skipCallsTestHook.counter(), 0); // removes liquidity and increments counter - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, abi.encode(address(this))); assertEq(skipCallsTestHook.counter(), 1); // adds liquidity again - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, abi.encode(address(this))); // removes liquidity again and increments counter - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, abi.encode(address(this))); assertEq(skipCallsTestHook.counter(), 2); } @@ -132,12 +125,12 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { assertEq(skipCallsTestHook.counter(), 0); // removes liquidity and increments counter - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, abi.encode(address(this))); assertEq(skipCallsTestHook.counter(), 1); // adds liquidity again - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, abi.encode(address(this))); // removes liquidity again and increments counter - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, abi.encode(address(this))); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, abi.encode(address(this))); assertEq(skipCallsTestHook.counter(), 2); } diff --git a/test/libraries/Hooks.t.sol b/test/libraries/Hooks.t.sol index a64992673..a988e601c 100644 --- a/test/libraries/Hooks.t.sol +++ b/test/libraries/Hooks.t.sol @@ -25,17 +25,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; using Hooks for IHooks; - /// 1111 1111 1100 - address payable ALL_HOOKS_ADDRESS = payable(0xFfC0000000000000000000000000000000000000); + /// 1111 1111 1111 1100 + address payable ALL_HOOKS_ADDRESS = payable(0xFffC000000000000000000000000000000000000); MockHooks mockHooks; - // Update this value when you add a new hook flag. And then update all appropriate asserts. - uint256 hookPermissionCount = 10; - uint256 clearAllHookPermisssionsMask; - function setUp() public { - clearAllHookPermisssionsMask = uint256(~uint160(0) >> (hookPermissionCount)); - MockHooks impl = new MockHooks(); vm.etch(ALL_HOOKS_ADDRESS, address(impl).code); mockHooks = MockHooks(ALL_HOOKS_ADDRESS); @@ -112,16 +106,16 @@ contract HooksTest is Test, Deployers, GasSnapshot { MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_beforeRemoveLiquidity_invalidReturn() public { mockHooks.setReturnValue(mockHooks.beforeRemoveLiquidity.selector, bytes4(0xdeadbeef)); MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); } function test_afterAddLiquidity_invalidReturn() public { @@ -129,16 +123,16 @@ contract HooksTest is Test, Deployers, GasSnapshot { MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); } function test_afterRemoveLiquidity_invalidReturn() public { mockHooks.setReturnValue(mockHooks.afterRemoveLiquidity.selector, bytes4(0xdeadbeef)); MockERC20(Currency.unwrap(key.currency0)).mint(address(this), 1e18); MockERC20(Currency.unwrap(key.currency0)).approve(address(modifyLiquidityRouter), 1e18); - modifyLiquidityRouter.modifyLiquidity(key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); vm.expectRevert(Hooks.InvalidHookResponse.selector); - modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); } function test_swap_succeedsWithHook() public { @@ -194,7 +188,7 @@ contract HooksTest is Test, Deployers, GasSnapshot { } // hook validation - function test_ValidateHookAddress_noHooks(uint160 addr) public view { + function test_validateHookPermissions_noHooks(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(preAddr)); @@ -210,7 +204,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -223,9 +221,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeInitialize(uint160 addr) public view { + function test_validateHookPermissions_beforeInitialize(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_INITIALIZE_FLAG))); @@ -241,7 +243,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertTrue(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -254,9 +260,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_afterInitialize(uint160 addr) public view { + function test_validateHookPermissions_afterInitialize(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_INITIALIZE_FLAG))); @@ -272,7 +282,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -285,9 +299,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeAndAfterInitialize(uint160 addr) public view { + function test_validateHookPermissions_beforeAndAfterInitialize(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG))); Hooks.validateHookPermissions( @@ -302,7 +320,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertTrue(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -315,9 +337,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeAddLiquidity(uint160 addr) public view { + function test_validateHookPermissions_beforeAddLiquidity(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_ADD_LIQUIDITY_FLAG))); Hooks.validateHookPermissions( @@ -332,7 +358,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -345,9 +375,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_afterAddLiquidity(uint160 addr) public view { + function test_validateHookPermissions_afterAddLiquidity(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_ADD_LIQUIDITY_FLAG))); Hooks.validateHookPermissions( @@ -362,7 +396,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -375,9 +413,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeAndAfterAddLiquidity(uint160 addr) public view { + function test_validateHookPermissions_beforeAndAfterAddLiquidity(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG))); @@ -393,7 +435,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -406,9 +452,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeRemoveLiquidity(uint160 addr) public view { + function test_validateHookPermissions_beforeRemoveLiquidity(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG))); Hooks.validateHookPermissions( @@ -423,7 +473,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -436,9 +490,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_afterRemoveLiquidity(uint160 addr) public view { + function test_validateHookPermissions_afterRemoveLiquidity(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG))); Hooks.validateHookPermissions( @@ -453,7 +511,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -466,9 +528,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeAfterRemoveLiquidity(uint160 addr) public view { + function test_validateHookPermissions_beforeAfterRemoveLiquidity(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG))); @@ -484,7 +550,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -497,9 +567,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeInitializeAfterAddLiquidity(uint160 addr) public view { + function test_validateHookPermissions_beforeInitializeAfterAddLiquidity(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG))); @@ -515,7 +589,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertTrue(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -528,9 +606,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeSwap(uint160 addr) public view { + function test_validateHookPermissions_beforeSwap(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_SWAP_FLAG))); Hooks.validateHookPermissions( @@ -545,7 +627,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -558,9 +644,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_afterSwap(uint160 addr) public view { + function test_validateHookPermissions_afterSwap(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_SWAP_FLAG))); Hooks.validateHookPermissions( @@ -575,7 +665,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: true, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -588,9 +682,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertTrue(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeAndAfterSwap(uint160 addr) public view { + function test_validateHookPermissions_beforeAndAfterSwap(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG))); Hooks.validateHookPermissions( @@ -605,7 +703,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: true, afterSwap: true, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -618,9 +720,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertTrue(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeDonate(uint160 addr) public view { + function test_validateHookPermissions_beforeDonate(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_DONATE_FLAG))); Hooks.validateHookPermissions( @@ -635,7 +741,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: true, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -648,9 +758,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertTrue(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_afterDonate(uint160 addr) public view { + function test_validateHookPermissions_afterDonate(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_DONATE_FLAG))); Hooks.validateHookPermissions( @@ -665,7 +779,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: true + afterDonate: true, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -678,9 +796,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertTrue(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_beforeAndAfterDonate(uint160 addr) public view { + function test_validateHookPermissions_beforeAndAfterDonate(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG))); Hooks.validateHookPermissions( @@ -695,7 +817,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: true, - afterDonate: true + afterDonate: true, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); assertFalse(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -708,9 +834,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertTrue(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertTrue(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertFalse(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_allHooks(uint160 addr) public view { + function test_validateHookPermissions_allHooks(uint160 addr) public view { uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); uint160 allHookBitsFlipped = (~uint160(0)) << uint160((160 - hookPermissionCount)); IHooks hookAddr = IHooks(address(uint160(preAddr) | allHookBitsFlipped)); @@ -726,7 +856,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: true, afterSwap: true, beforeDonate: true, - afterDonate: true + afterDonate: true, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: true, + afterAddLiquidityReturnDelta: true, + afterRemoveLiquidityReturnDelta: true }) ); assertTrue(hookAddr.hasPermission(Hooks.BEFORE_INITIALIZE_FLAG)); @@ -739,11 +873,16 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertTrue(hookAddr.hasPermission(Hooks.AFTER_SWAP_FLAG)); assertTrue(hookAddr.hasPermission(Hooks.BEFORE_DONATE_FLAG)); assertTrue(hookAddr.hasPermission(Hooks.AFTER_DONATE_FLAG)); + assertTrue(hookAddr.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG)); + assertTrue(hookAddr.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG)); + assertTrue(hookAddr.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)); + assertTrue(hookAddr.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)); } - function test_validateHookAddress_failsAllHooks(uint152 addr, uint8 mask) public { + function test_validateHookPermissions_failsAllHooks(uint152 addr, uint16 mask) public { uint160 preAddr = uint160(uint256(addr)); - vm.assume(mask != 0xff8); + mask = mask & 0xfffc; // the last 7 bits are all 0, we just want a 14 bit mask + vm.assume(mask != 0xfffc); // we want any combination except all hooks IHooks hookAddr = IHooks(address(uint160(preAddr) | (uint160(mask) << 151))); vm.expectRevert(abi.encodeWithSelector(Hooks.HookAddressNotValid.selector, (address(hookAddr)))); Hooks.validateHookPermissions( @@ -753,19 +892,23 @@ contract HooksTest is Test, Deployers, GasSnapshot { afterInitialize: true, beforeAddLiquidity: true, afterAddLiquidity: true, - beforeRemoveLiquidity: false, - afterRemoveLiquidity: false, + beforeRemoveLiquidity: true, + afterRemoveLiquidity: true, beforeSwap: true, afterSwap: true, beforeDonate: true, - afterDonate: true + afterDonate: true, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: true, + afterAddLiquidityReturnDelta: true, + afterRemoveLiquidityReturnDelta: true }) ); } - function test_validateHookAddress_failsNoHooks(uint160 addr, uint16 mask) public { + function test_validateHookPermissions_failsNoHooks(uint160 addr, uint16 mask) public { uint160 preAddr = addr & uint160(0x007ffffFfffffffffFffffFFfFFFFFFffFFfFFff); - mask = mask & 0xff80; // the last 7 bits are all 0, we just want a 9 bit mask + mask = mask & 0xfffc; // the last 7 bits are all 0, we just want a 14 bit mask vm.assume(mask != 0); // we want any combination except no hooks IHooks hookAddr = IHooks(address(preAddr | (uint160(mask) << 144))); vm.expectRevert(abi.encodeWithSelector(Hooks.HookAddressNotValid.selector, (address(hookAddr)))); @@ -781,7 +924,11 @@ contract HooksTest is Test, Deployers, GasSnapshot { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }) ); } @@ -792,22 +939,41 @@ contract HooksTest is Test, Deployers, GasSnapshot { snapEnd(); } - function test_isValidHookAddress_anyFlags() public pure { + function test_isValidHookAddress_valid_anyFlags() public pure { assertTrue(Hooks.isValidHookAddress(IHooks(0x8000000000000000000000000000000000000000), 3000)); assertTrue(Hooks.isValidHookAddress(IHooks(0x4000000000000000000000000000000000000000), 3000)); assertTrue(Hooks.isValidHookAddress(IHooks(0x2000000000000000000000000000000000000000), 3000)); assertTrue(Hooks.isValidHookAddress(IHooks(0x1000000000000000000000000000000000000000), 3000)); assertTrue(Hooks.isValidHookAddress(IHooks(0x0800000000000000000000000000000000000000), 3000)); + assertTrue(Hooks.isValidHookAddress(IHooks(0x0400000000000000000000000000000000000000), 3000)); assertTrue(Hooks.isValidHookAddress(IHooks(0x0200000000000000000000000000000000000000), 3000)); assertTrue(Hooks.isValidHookAddress(IHooks(0x0100000000000000000000000000000000000000), 3000)); - assertTrue(Hooks.isValidHookAddress(IHooks(0xf09840a85d5Af5bF1d1762f925bdaDdC4201f984), 3000)); + assertTrue(Hooks.isValidHookAddress(IHooks(0x0080000000000000000000000000000000000000), 3000)); + assertTrue(Hooks.isValidHookAddress(IHooks(0x0040000000000000000000000000000000000000), 3000)); + assertTrue(Hooks.isValidHookAddress(IHooks(0xf00040A85D5af5bf1d1762f925BDAddc4201f984), 3000)); } - function testIsValidHookAddress_zeroAddress() public pure { + function testIsValidHookAddress_valid_zeroAddressFixedFee() public pure { assertTrue(Hooks.isValidHookAddress(IHooks(address(0)), 3000)); } - function test_isValidIfDynamicFee() public pure { + function testIsValidHookAddress_invalid_zeroAddressWithDynamicFee() public pure { + assertFalse(Hooks.isValidHookAddress(IHooks(address(0)), LPFeeLibrary.DYNAMIC_FEE_FLAG)); + } + + function testIsValidHookAddress_invalid_returnsDeltaWithoutHookFlag(uint160 addr) public view { + uint160 preAddr = uint160(uint256(addr) & clearAllHookPermisssionsMask); + IHooks hookAddr = IHooks(address(uint160(preAddr | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG))); + assertFalse(Hooks.isValidHookAddress(hookAddr, 3000)); + hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG))); + assertFalse(Hooks.isValidHookAddress(hookAddr, 3000)); + hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG))); + assertFalse(Hooks.isValidHookAddress(hookAddr, 3000)); + hookAddr = IHooks(address(uint160(preAddr | Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG))); + assertFalse(Hooks.isValidHookAddress(hookAddr, 3000)); + } + + function test_isValidHookAddress_valid_noFlagsWithDynamicFee() public pure { assertTrue( Hooks.isValidHookAddress(IHooks(0x0000000000000000000000000000000000000001), LPFeeLibrary.DYNAMIC_FEE_FLAG) ); @@ -819,9 +985,9 @@ contract HooksTest is Test, Deployers, GasSnapshot { assertTrue(Hooks.isValidHookAddress(IHooks(0x8000000000000000000000000000000000000000), 3000)); } - function test_invalidIfNoFlags() public pure { + function test_isValidHookAddress_invalid_noFlagsNoDynamicFee() public pure { assertFalse(Hooks.isValidHookAddress(IHooks(0x0000000000000000000000000000000000000001), 3000)); - assertFalse(Hooks.isValidHookAddress(IHooks(0x0020000000000000000000000000000000000001), 3000)); - assertFalse(Hooks.isValidHookAddress(IHooks(0x003840a85d5Af5Bf1d1762F925BDADDc4201f984), 3000)); + assertFalse(Hooks.isValidHookAddress(IHooks(0x0001000000000000000000000000000000000001), 3000)); + assertFalse(Hooks.isValidHookAddress(IHooks(0x000340A85D5AF5bf1D1762F925BdaddC4201f984), 3000)); } } diff --git a/test/libraries/Pool.t.sol b/test/libraries/Pool.t.sol index 187bf1312..4919f68cb 100644 --- a/test/libraries/Pool.t.sol +++ b/test/libraries/Pool.t.sol @@ -10,6 +10,7 @@ import {TickMath} from "src/libraries/TickMath.sol"; import {TickBitmap} from "src/libraries/TickBitmap.sol"; import {LiquidityAmounts} from "test/utils/LiquidityAmounts.sol"; import {Constants} from "test/utils/Constants.sol"; +import {BalanceDelta} from "src/types/BalanceDelta.sol"; import {SafeCast} from "src/libraries/SafeCast.sol"; import {ProtocolFeeLibrary} from "src/libraries/ProtocolFeeLibrary.sol"; import {LPFeeLibrary} from "src/libraries/LPFeeLibrary.sol"; @@ -22,12 +23,12 @@ contract PoolTest is Test { uint24 constant MAX_PROTOCOL_FEE = ProtocolFeeLibrary.MAX_PROTOCOL_FEE; // 0.1% uint24 constant MAX_LP_FEE = LPFeeLibrary.MAX_LP_FEE; // 100% - function testPoolInitialize(uint160 sqrtPriceX96, uint24 protocolFee, uint24 dynamicFee) public { + function testPoolInitialize(uint160 sqrtPriceX96, uint24 protocolFee, uint24 swapFee) public { if (sqrtPriceX96 < TickMath.MIN_SQRT_RATIO || sqrtPriceX96 >= TickMath.MAX_SQRT_RATIO) { vm.expectRevert(TickMath.InvalidSqrtRatio.selector); - state.initialize(sqrtPriceX96, protocolFee, dynamicFee); + state.initialize(sqrtPriceX96, protocolFee, swapFee); } else { - state.initialize(sqrtPriceX96, protocolFee, dynamicFee); + state.initialize(sqrtPriceX96, protocolFee, swapFee); assertEq(state.slot0.sqrtPriceX96, sqrtPriceX96); assertEq(state.slot0.protocolFee, protocolFee); assertEq(state.slot0.tick, TickMath.getTickAtSqrtRatio(sqrtPriceX96)); @@ -116,40 +117,44 @@ contract PoolTest is Test { ); Pool.Slot0 memory slot0 = state.slot0; - if (params.amountSpecified == 0) { - vm.expectRevert(Pool.SwapAmountCannotBeZero.selector); - } else if (params.zeroForOne) { + if (params.amountSpecified > 0 && lpFee == MAX_LP_FEE) { + vm.expectRevert(Pool.InvalidFeeForExactOut.selector); + state.swap(params); + } else if (params.zeroForOne && params.amountSpecified != 0) { if (params.sqrtPriceLimitX96 >= slot0.sqrtPriceX96) { vm.expectRevert( abi.encodeWithSelector( Pool.PriceLimitAlreadyExceeded.selector, slot0.sqrtPriceX96, params.sqrtPriceLimitX96 ) ); + state.swap(params); } else if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) { vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); + state.swap(params); } - } else if (!params.zeroForOne) { + } else if (!params.zeroForOne && params.amountSpecified != 0) { if (params.sqrtPriceLimitX96 <= slot0.sqrtPriceX96) { vm.expectRevert( abi.encodeWithSelector( Pool.PriceLimitAlreadyExceeded.selector, slot0.sqrtPriceX96, params.sqrtPriceLimitX96 ) ); + state.swap(params); } else if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) { vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); + state.swap(params); } - } else if (params.amountSpecified > 0) { - if (lpFee == MAX_LP_FEE) { - vm.expectRevert(Pool.InvalidFeeForExactOut.selector); - } - } - - state.swap(params); - - if (params.zeroForOne) { - assertGe(state.slot0.sqrtPriceX96, params.sqrtPriceLimitX96); } else { - assertLe(state.slot0.sqrtPriceX96, params.sqrtPriceLimitX96); + uint160 sqrtPriceBefore = state.slot0.sqrtPriceX96; + state.swap(params); + + if (params.amountSpecified == 0) { + assertEq(sqrtPriceBefore, state.slot0.sqrtPriceX96, "amountSpecified == 0"); + } else if (params.zeroForOne) { + assertGe(state.slot0.sqrtPriceX96, params.sqrtPriceLimitX96, "zeroForOne"); + } else { + assertLe(state.slot0.sqrtPriceX96, params.sqrtPriceLimitX96, "oneForZero"); + } } } } diff --git a/test/utils/Deployers.sol b/test/utils/Deployers.sol index 996af80f3..45a23c5ad 100644 --- a/test/utils/Deployers.sol +++ b/test/utils/Deployers.sol @@ -45,9 +45,9 @@ contract Deployers { uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1; uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1; - IPoolManager.ModifyLiquidityParams public LIQ_PARAMS = + IPoolManager.ModifyLiquidityParams public LIQUIDITY_PARAMS = IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); - IPoolManager.ModifyLiquidityParams public REMOVE_LIQ_PARAMS = + IPoolManager.ModifyLiquidityParams public REMOVE_LIQUIDITY_PARAMS = IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); // Global variables @@ -73,6 +73,10 @@ contract Deployers { PoolKey uninitializedKey; PoolKey uninitializedNativeKey; + // Update this value when you add a new hook flag. + uint256 hookPermissionCount = 14; + uint256 clearAllHookPermisssionsMask = uint256(~uint160(0) >> (hookPermissionCount)); + modifier noIsolate() { if (msg.sender != address(this)) { (bool success,) = address(this).call(msg.data); @@ -169,7 +173,7 @@ contract Deployers { bytes memory initData ) internal returns (PoolKey memory _key, PoolId id) { (_key, id) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96, initData); - modifyLiquidityRouter.modifyLiquidity{value: msg.value}(_key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity{value: msg.value}(_key, LIQUIDITY_PARAMS, ZERO_BYTES); } function initPoolAndAddLiquidityETH( @@ -182,7 +186,7 @@ contract Deployers { uint256 msgValue ) internal returns (PoolKey memory _key, PoolId id) { (_key, id) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96, initData); - modifyLiquidityRouter.modifyLiquidity{value: msgValue}(_key, LIQ_PARAMS, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity{value: msgValue}(_key, LIQUIDITY_PARAMS, ZERO_BYTES); } // Deploys the manager, all test routers, and sets up 2 pools: with and without native diff --git a/test/utils/NestedActions.t.sol b/test/utils/NestedActions.t.sol index 78eee0836..90c3bdbb0 100644 --- a/test/utils/NestedActions.t.sol +++ b/test/utils/NestedActions.t.sol @@ -22,12 +22,12 @@ contract NestedActions is Test, Deployers, GasSnapshot { } function test_nestedAddLiquidity() public { - actions = [Action.ADD_LIQ_AND_SETTLE]; + actions = [Action.ADD_LIQUIDITY_AND_SETTLE]; nestedActionRouter.unlock(abi.encode(actions)); } function test_nestedRemoveLiquidity() public { - actions = [Action.ADD_LIQ_AND_SETTLE, Action.REMOVE_LIQ_AND_SETTLE]; + actions = [Action.ADD_LIQUIDITY_AND_SETTLE, Action.REMOVE_LIQUIDITY_AND_SETTLE]; nestedActionRouter.unlock(abi.encode(actions)); } From e09f003bf8e3410f8729a08aada29bcfd463f7be Mon Sep 17 00:00:00 2001 From: 0x57 Date: Sat, 11 May 2024 00:27:26 +0800 Subject: [PATCH 04/10] [Chore] Fix IHooks Return Type in comments (#638) --- src/interfaces/IHooks.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IHooks.sol b/src/interfaces/IHooks.sol index 34efe9da7..2e64acb3e 100644 --- a/src/interfaces/IHooks.sol +++ b/src/interfaces/IHooks.sol @@ -97,7 +97,7 @@ interface IHooks { /// @param params The parameters for the swap /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook /// @return bytes4 The function selector for the hook - /// @return int256 The hook's delta in specified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency + /// @return int128 The hook's delta in specified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency function beforeSwap( address sender, PoolKey calldata key, From d0700ceb251afa48df8cc26d593fe04ee5e6b775 Mon Sep 17 00:00:00 2001 From: marktoda Date: Fri, 10 May 2024 14:36:16 -0400 Subject: [PATCH 05/10] feat: lower fuzz runs for PRs (#640) * feat: lower fuzz runs for PRs * feat: update to 10k --- .../workflows/{tests.yml => tests-merge.yml} | 3 +- .github/workflows/tests-pr.yml | 40 +++++++++++++++++++ foundry.toml | 3 ++ 3 files changed, 44 insertions(+), 2 deletions(-) rename .github/workflows/{tests.yml => tests-merge.yml} (96%) create mode 100644 .github/workflows/tests-pr.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests-merge.yml similarity index 96% rename from .github/workflows/tests.yml rename to .github/workflows/tests-merge.yml index f545e2908..7ef603ea8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests-merge.yml @@ -4,7 +4,6 @@ on: push: branches: - main - pull_request: jobs: forge-tests: @@ -40,4 +39,4 @@ jobs: env: FOUNDRY_PROFILE: ci FORGE_SNAPSHOT_CHECK: true - + diff --git a/.github/workflows/tests-pr.yml b/.github/workflows/tests-pr.yml new file mode 100644 index 000000000..d06191042 --- /dev/null +++ b/.github/workflows/tests-pr.yml @@ -0,0 +1,40 @@ +name: Tests + +on: + pull_request: + +jobs: + forge-tests: + name: Forge Tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache-dependency-path: './test/js-scripts' + cache: 'yarn' + + - run: yarn + working-directory: ./test/js-scripts + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Build + run: forge build + env: + FOUNDRY_PROFILE: pr + + - name: Run tests + run: forge test --isolate -vvv + env: + FOUNDRY_PROFILE: pr + FORGE_SNAPSHOT_CHECK: true + diff --git a/foundry.toml b/foundry.toml index c329f4627..1bb3f28e1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,6 +10,9 @@ evm_version = "cancun" runs = 1000 seed = "0x4444" +[profile.pr.fuzz] +runs = 10000 + [profile.ci.fuzz] runs = 100000 From 8b4473e2a3215063f6761e7da94e008cdcc75c3d Mon Sep 17 00:00:00 2001 From: diana Date: Fri, 10 May 2024 19:00:49 -0400 Subject: [PATCH 06/10] Sqrt ratio -> sqrt price (#629) * sqrt price update tests * statement coverage * test * change ratio to price * re-add test that was deleted * fix name --- src/libraries/Pool.sol | 18 +-- src/libraries/SqrtPriceMath.sol | 50 +++---- src/libraries/SwapMath.sol | 48 +++---- src/libraries/TickMath.sol | 92 ++++++------- src/test/PoolNestedActionsTest.sol | 6 +- src/test/SqrtPriceMathTest.sol | 79 ----------- src/test/TickMathEchidnaTest.sol | 18 +-- src/test/TickMathTest.sol | 24 ++-- test/DynamicFees.t.sol | 38 +++--- test/ModifyLiquidity.t.sol | 2 +- test/PoolManager.gas.spec.ts | 6 +- test/PoolManager.t.sol | 126 +++++++++--------- test/PoolManagerInitialize.t.sol | 44 +++--- test/SkipCallsTestHook.t.sol | 4 +- test/Sync.t.sol | 4 +- test/js-scripts/package.json | 4 +- ...rtRatioAtTick.ts => getSqrtPriceAtTick.ts} | 0 ...ckAtSqrtRatio.ts => getTickAtSqrtPrice.ts} | 6 +- test/libraries/Hooks.t.sol | 14 +- test/libraries/Pool.t.sol | 14 +- test/libraries/Position.t.sol | 4 +- test/libraries/SqrtPriceMath.t.sol | 81 ++++++----- test/libraries/SwapMath.t.sol | 46 +++---- test/libraries/TickMath.t.sol | 124 ++++++++--------- test/utils/AmountHelpers.sol | 4 +- test/utils/Constants.sol | 12 +- test/utils/Deployers.sol | 18 +-- test/utils/LiquidityAmounts.sol | 96 ++++++------- 28 files changed, 455 insertions(+), 527 deletions(-) delete mode 100644 src/test/SqrtPriceMathTest.sol rename test/js-scripts/src/{getSqrtRatioAtTick.ts => getSqrtPriceAtTick.ts} (100%) rename test/js-scripts/src/{getTickAtSqrtRatio.ts => getTickAtSqrtPrice.ts} (62%) diff --git a/src/libraries/Pool.sol b/src/libraries/Pool.sol index ea5f6af07..89e97d34a 100644 --- a/src/libraries/Pool.sol +++ b/src/libraries/Pool.sol @@ -113,7 +113,7 @@ library Pool { { if (self.slot0.sqrtPriceX96 != 0) revert PoolAlreadyInitialized(); - tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); + tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96); self.slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick, protocolFee: protocolFee, lpFee: lpFee}); } @@ -222,15 +222,15 @@ library Pool { // right, when we'll need _more_ currency0 (it's becoming more valuable) so user must provide it delta = toBalanceDelta( SqrtPriceMath.getAmount0Delta( - TickMath.getSqrtRatioAtTick(tickLower), TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta + TickMath.getSqrtPriceAtTick(tickLower), TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta ).toInt128(), 0 ); } else if (tick < tickUpper) { delta = toBalanceDelta( - SqrtPriceMath.getAmount0Delta(sqrtPriceX96, TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta) + SqrtPriceMath.getAmount0Delta(sqrtPriceX96, TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta) .toInt128(), - SqrtPriceMath.getAmount1Delta(TickMath.getSqrtRatioAtTick(tickLower), sqrtPriceX96, liquidityDelta) + SqrtPriceMath.getAmount1Delta(TickMath.getSqrtPriceAtTick(tickLower), sqrtPriceX96, liquidityDelta) .toInt128() ); @@ -241,7 +241,7 @@ library Pool { delta = toBalanceDelta( 0, SqrtPriceMath.getAmount1Delta( - TickMath.getSqrtRatioAtTick(tickLower), TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta + TickMath.getSqrtPriceAtTick(tickLower), TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta ).toInt128() ); } @@ -333,14 +333,14 @@ library Pool { if (params.sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96) { revert PriceLimitAlreadyExceeded(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96); } - if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) { + if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_PRICE) { revert PriceLimitOutOfBounds(params.sqrtPriceLimitX96); } } else { if (params.sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96) { revert PriceLimitAlreadyExceeded(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96); } - if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) { + if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_PRICE) { revert PriceLimitOutOfBounds(params.sqrtPriceLimitX96); } } @@ -362,7 +362,7 @@ library Pool { } // get the price for the next tick - step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + step.sqrtPriceNextX96 = TickMath.getSqrtPriceAtTick(step.tickNext); // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( @@ -439,7 +439,7 @@ library Pool { } } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved - state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + state.tick = TickMath.getTickAtSqrtPrice(state.sqrtPriceX96); } } diff --git a/src/libraries/SqrtPriceMath.sol b/src/libraries/SqrtPriceMath.sol index 0839eb73c..71262ffa2 100644 --- a/src/libraries/SqrtPriceMath.sol +++ b/src/libraries/SqrtPriceMath.sol @@ -127,7 +127,7 @@ library SqrtPriceMath { /// @param sqrtPX96 The starting price before accounting for the output amount /// @param liquidity The amount of usable liquidity /// @param amountOut How much of currency0, or currency1, is being swapped out - /// @param zeroForOne Whether the amount out is currency0 or currency1 + /// @param zeroForOne Whether the amount out is currency1 or currency0 /// @return sqrtQX96 The price after removing the output amount of currency0 or currency1 function getNextSqrtPriceFromOutput(uint160 sqrtPX96, uint128 liquidity, uint256 amountOut, bool zeroForOne) internal @@ -145,80 +145,80 @@ library SqrtPriceMath { /// @notice Gets the amount0 delta between two prices /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) - /// @param sqrtRatioAX96 A sqrt price - /// @param sqrtRatioBX96 Another sqrt price + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price /// @param liquidity The amount of usable liquidity /// @param roundUp Whether to round the amount up or down /// @return amount0 Amount of currency0 required to cover a position of size liquidity between the two passed prices - function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + function getAmount0Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity, bool roundUp) internal pure returns (uint256 amount0) { unchecked { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; - uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + uint256 numerator2 = sqrtPriceBX96 - sqrtPriceAX96; - if (sqrtRatioAX96 == 0) revert InvalidPrice(); + if (sqrtPriceAX96 == 0) revert InvalidPrice(); return roundUp - ? UnsafeMath.divRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), sqrtRatioAX96) - : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + ? UnsafeMath.divRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtPriceBX96), sqrtPriceAX96) + : FullMath.mulDiv(numerator1, numerator2, sqrtPriceBX96) / sqrtPriceAX96; } } /// @notice Gets the amount1 delta between two prices /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) - /// @param sqrtRatioAX96 A sqrt price - /// @param sqrtRatioBX96 Another sqrt price + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price /// @param liquidity The amount of usable liquidity /// @param roundUp Whether to round the amount up, or down /// @return amount1 Amount of currency1 required to cover a position of size liquidity between the two passed prices - function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity, bool roundUp) + function getAmount1Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity, bool roundUp) internal pure returns (uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); return roundUp - ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) - : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + ? FullMath.mulDivRoundingUp(liquidity, sqrtPriceBX96 - sqrtPriceAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtPriceBX96 - sqrtPriceAX96, FixedPoint96.Q96); } /// @notice Helper that gets signed currency0 delta - /// @param sqrtRatioAX96 A sqrt price - /// @param sqrtRatioBX96 Another sqrt price + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price /// @param liquidity The change in liquidity for which to compute the amount0 delta /// @return amount0 Amount of currency0 corresponding to the passed liquidityDelta between the two prices - function getAmount0Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + function getAmount0Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, int128 liquidity) internal pure returns (int256 amount0) { unchecked { return liquidity < 0 - ? getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() - : -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + ? getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() + : -getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); } } /// @notice Helper that gets signed currency1 delta - /// @param sqrtRatioAX96 A sqrt price - /// @param sqrtRatioBX96 Another sqrt price + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price /// @param liquidity The change in liquidity for which to compute the amount1 delta /// @return amount1 Amount of currency1 corresponding to the passed liquidityDelta between the two prices - function getAmount1Delta(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, int128 liquidity) + function getAmount1Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, int128 liquidity) internal pure returns (int256 amount1) { unchecked { return liquidity < 0 - ? getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() - : -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + ? getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() + : -getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); } } } diff --git a/src/libraries/SwapMath.sol b/src/libraries/SwapMath.sol index de8e672f8..90214e293 100644 --- a/src/libraries/SwapMath.sol +++ b/src/libraries/SwapMath.sol @@ -8,69 +8,69 @@ import {SqrtPriceMath} from "./SqrtPriceMath.sol"; /// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. library SwapMath { /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap - /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive - /// @param sqrtRatioCurrentX96 The current sqrt price of the pool - /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred + /// @dev If the swap's amountSpecified is negative, the combined fee and input amount will never exceed the absolute value of the remaining amount. + /// @param sqrtPriceCurrentX96 The current sqrt price of the pool + /// @param sqrtPriceTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred /// @param liquidity The usable liquidity /// @param amountRemaining How much input or output amount is remaining to be swapped in/out /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip - /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target + /// @return sqrtPriceNextX96 The price after swapping the amount in/out, not to exceed the price target /// @return amountIn The amount to be swapped in, of either currency0 or currency1, based on the direction of the swap /// @return amountOut The amount to be received, of either currency0 or currency1, based on the direction of the swap /// @return feeAmount The amount of input that will be taken as a fee function computeSwapStep( - uint160 sqrtRatioCurrentX96, - uint160 sqrtRatioTargetX96, + uint160 sqrtPriceCurrentX96, + uint160 sqrtPriceTargetX96, uint128 liquidity, int256 amountRemaining, uint24 feePips - ) internal pure returns (uint160 sqrtRatioNextX96, uint256 amountIn, uint256 amountOut, uint256 feeAmount) { + ) internal pure returns (uint160 sqrtPriceNextX96, uint256 amountIn, uint256 amountOut, uint256 feeAmount) { unchecked { - bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; + bool zeroForOne = sqrtPriceCurrentX96 >= sqrtPriceTargetX96; bool exactIn = amountRemaining < 0; if (exactIn) { uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(-amountRemaining), 1e6 - feePips, 1e6); amountIn = zeroForOne - ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) - : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true); + ? SqrtPriceMath.getAmount0Delta(sqrtPriceTargetX96, sqrtPriceCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtPriceCurrentX96, sqrtPriceTargetX96, liquidity, true); if (amountRemainingLessFee >= amountIn) { - sqrtRatioNextX96 = sqrtRatioTargetX96; + sqrtPriceNextX96 = sqrtPriceTargetX96; } else { - sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( - sqrtRatioCurrentX96, liquidity, amountRemainingLessFee, zeroForOne + sqrtPriceNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtPriceCurrentX96, liquidity, amountRemainingLessFee, zeroForOne ); } } else { amountOut = zeroForOne - ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) - : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false); + ? SqrtPriceMath.getAmount1Delta(sqrtPriceTargetX96, sqrtPriceCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtPriceCurrentX96, sqrtPriceTargetX96, liquidity, false); if (uint256(amountRemaining) >= amountOut) { - sqrtRatioNextX96 = sqrtRatioTargetX96; + sqrtPriceNextX96 = sqrtPriceTargetX96; } else { - sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( - sqrtRatioCurrentX96, liquidity, uint256(amountRemaining), zeroForOne + sqrtPriceNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtPriceCurrentX96, liquidity, uint256(amountRemaining), zeroForOne ); } } - bool max = sqrtRatioTargetX96 == sqrtRatioNextX96; + bool max = sqrtPriceTargetX96 == sqrtPriceNextX96; // get the input/output amounts if (zeroForOne) { amountIn = max && exactIn ? amountIn - : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true); + : SqrtPriceMath.getAmount0Delta(sqrtPriceNextX96, sqrtPriceCurrentX96, liquidity, true); amountOut = max && !exactIn ? amountOut - : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false); + : SqrtPriceMath.getAmount1Delta(sqrtPriceNextX96, sqrtPriceCurrentX96, liquidity, false); } else { amountIn = max && exactIn ? amountIn - : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true); + : SqrtPriceMath.getAmount1Delta(sqrtPriceCurrentX96, sqrtPriceNextX96, liquidity, true); amountOut = max && !exactIn ? amountOut - : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false); + : SqrtPriceMath.getAmount0Delta(sqrtPriceCurrentX96, sqrtPriceNextX96, liquidity, false); } // cap the output amount to not exceed the remaining output amount @@ -78,7 +78,7 @@ library SwapMath { amountOut = uint256(amountRemaining); } - if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { + if (exactIn && sqrtPriceNextX96 != sqrtPriceTargetX96) { // we didn't reach the target, so take the remainder of the maximum input as fee feeAmount = uint256(-amountRemaining) - amountIn; } else { diff --git a/src/libraries/TickMath.sol b/src/libraries/TickMath.sol index 4c9b95b94..4628870b5 100644 --- a/src/libraries/TickMath.sol +++ b/src/libraries/TickMath.sol @@ -5,14 +5,14 @@ pragma solidity ^0.8.20; /// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports /// prices between 2**-128 and 2**128 library TickMath { - /// @notice Thrown when the tick passed to #getSqrtRatioAtTick is not between MIN_TICK and MAX_TICK + /// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK error InvalidTick(); - /// @notice Thrown when the ratio passed to #getTickAtSqrtRatio does not correspond to a price between MIN_TICK and MAX_TICK - error InvalidSqrtRatio(); + /// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK + error InvalidSqrtPrice(); - /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + /// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128 int24 internal constant MIN_TICK = -887272; - /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + /// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128 int24 internal constant MAX_TICK = -MIN_TICK; /// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767] @@ -20,10 +20,10 @@ library TickMath { /// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767] int24 internal constant MAX_TICK_SPACING = type(int16).max; - /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) - uint160 internal constant MIN_SQRT_RATIO = 4295128739; - /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) - uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + /// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_PRICE = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342; /// @notice Given a tickSpacing, compute the maximum usable tick function maxUsableTick(int24 tickSpacing) internal pure returns (int24) { @@ -42,56 +42,56 @@ library TickMath { /// @notice Calculates sqrt(1.0001^tick) * 2^96 /// @dev Throws if |tick| > max tick /// @param tick The input tick for the above formula - /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (currency1/currency0) + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0) /// at the given tick - function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { unchecked { uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); if (absTick > uint256(int256(MAX_TICK))) revert InvalidTick(); - uint256 ratio = + uint256 price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; - if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; - if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; - if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; - if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; - if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; - if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; - if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; - if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; - if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; - if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; - if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; - if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; - if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; - if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; - if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; - if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; - if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; - if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; - if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; - - if (tick > 0) ratio = type(uint256).max / ratio; + if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) price = type(uint256).max / price; // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. // we then downcast because we know the result always fits within 160 bits due to our tick input constraint - // we round up in the division so getTickAtSqrtRatio of the output price is always consistent - sqrtPriceX96 = uint160((ratio >> 32) + (ratio & ((1 << 32) - 1) == 0 ? 0 : 1)); + // we round up in the division so getTickAtSqrtPrice of the output price is always consistent + sqrtPriceX96 = uint160((price >> 32) + (price & ((1 << 32) - 1) == 0 ? 0 : 1)); } } - /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio - /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// @notice Calculates the greatest tick value such that getPriceAtTick(tick) <= price + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getPriceAtTick may /// ever return. - /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 - /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio - function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + /// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the price is less than or equal to the input price + function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) { unchecked { // second inequality must be < because the price can never reach the price at the max tick - if (sqrtPriceX96 < MIN_SQRT_RATIO || sqrtPriceX96 >= MAX_SQRT_RATIO) revert InvalidSqrtRatio(); - uint256 ratio = uint256(sqrtPriceX96) << 32; + if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice(); + uint256 price = uint256(sqrtPriceX96) << 32; - uint256 r = ratio; + uint256 r = price; uint256 msb = 0; assembly { @@ -134,8 +134,8 @@ library TickMath { msb := or(msb, f) } - if (msb >= 128) r = ratio >> (msb - 127); - else r = ratio << (127 - msb); + if (msb >= 128) r = price >> (msb - 127); + else r = price << (127 - msb); int256 log_2 = (int256(msb) - 128) << 64; @@ -228,7 +228,7 @@ library TickMath { int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); - tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; + tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; } } } diff --git a/src/test/PoolNestedActionsTest.sol b/src/test/PoolNestedActionsTest.sol index 6ecbe4251..d7e065162 100644 --- a/src/test/PoolNestedActionsTest.sol +++ b/src/test/PoolNestedActionsTest.sol @@ -74,7 +74,7 @@ contract NestedActionExecutor is Test, PoolTestBase { IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); IPoolManager.SwapParams internal SWAP_PARAMS = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: Constants.SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: Constants.SQRT_PRICE_1_2}); uint256 internal DONATE_AMOUNT0 = 12345e6; uint256 internal DONATE_AMOUNT1 = 98765e4; @@ -217,9 +217,9 @@ contract NestedActionExecutor is Test, PoolTestBase { PoolId id = key.toId(); (uint256 price,,,) = manager.getSlot0(id); assertEq(price, 0); - manager.initialize(key, Constants.SQRT_RATIO_1_2, Constants.ZERO_BYTES); + manager.initialize(key, Constants.SQRT_PRICE_1_2, Constants.ZERO_BYTES); (price,,,) = manager.getSlot0(id); - assertEq(price, Constants.SQRT_RATIO_1_2); + assertEq(price, Constants.SQRT_PRICE_1_2); } // This will never actually be used - its just to allow us to use the PoolTestBase helper contact diff --git a/src/test/SqrtPriceMathTest.sol b/src/test/SqrtPriceMathTest.sol deleted file mode 100644 index 7247b373e..000000000 --- a/src/test/SqrtPriceMathTest.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; - -import {SqrtPriceMath} from "../libraries/SqrtPriceMath.sol"; - -contract SqrtPriceMathTest { - function getNextSqrtPriceFromInput(uint160 sqrtP, uint128 liquidity, uint256 amountIn, bool zeroForOne) - external - pure - returns (uint160 sqrtQ) - { - return SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); - } - - function getGasCostOfGetNextSqrtPriceFromInput(uint160 sqrtP, uint128 liquidity, uint256 amountIn, bool zeroForOne) - external - view - returns (uint256) - { - uint256 gasBefore = gasleft(); - SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); - return gasBefore - gasleft(); - } - - function getNextSqrtPriceFromOutput(uint160 sqrtP, uint128 liquidity, uint256 amountOut, bool zeroForOne) - external - pure - returns (uint160 sqrtQ) - { - return SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); - } - - function getGasCostOfGetNextSqrtPriceFromOutput( - uint160 sqrtP, - uint128 liquidity, - uint256 amountOut, - bool zeroForOne - ) external view returns (uint256) { - uint256 gasBefore = gasleft(); - SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); - return gasBefore - gasleft(); - } - - function getAmount0Delta(uint160 sqrtLower, uint160 sqrtUpper, uint128 liquidity, bool roundUp) - external - pure - returns (uint256 amount0) - { - return SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); - } - - function getAmount1Delta(uint160 sqrtLower, uint160 sqrtUpper, uint128 liquidity, bool roundUp) - external - pure - returns (uint256 amount1) - { - return SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); - } - - function getGasCostOfGetAmount0Delta(uint160 sqrtLower, uint160 sqrtUpper, uint128 liquidity, bool roundUp) - external - view - returns (uint256) - { - uint256 gasBefore = gasleft(); - SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); - return gasBefore - gasleft(); - } - - function getGasCostOfGetAmount1Delta(uint160 sqrtLower, uint160 sqrtUpper, uint128 liquidity, bool roundUp) - external - view - returns (uint256) - { - uint256 gasBefore = gasleft(); - SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); - return gasBefore - gasleft(); - } -} diff --git a/src/test/TickMathEchidnaTest.sol b/src/test/TickMathEchidnaTest.sol index df963ae2c..d224cf2a7 100644 --- a/src/test/TickMathEchidnaTest.sol +++ b/src/test/TickMathEchidnaTest.sol @@ -5,17 +5,17 @@ import {TickMath} from "../libraries/TickMath.sol"; contract TickMathEchidnaTest { // uniqueness and increasing order - function checkGetSqrtRatioAtTickInvariants(int24 tick) external pure { - uint160 ratio = TickMath.getSqrtRatioAtTick(tick); - assert(TickMath.getSqrtRatioAtTick(tick - 1) < ratio && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); - assert(ratio >= TickMath.MIN_SQRT_RATIO); - assert(ratio <= TickMath.MAX_SQRT_RATIO); + function checkGetSqrtPriceAtTickInvariants(int24 tick) external pure { + uint160 price = TickMath.getSqrtPriceAtTick(tick); + assert(TickMath.getSqrtPriceAtTick(tick - 1) < price && price < TickMath.getSqrtPriceAtTick(tick + 1)); + assert(price >= TickMath.MIN_SQRT_PRICE); + assert(price <= TickMath.MAX_SQRT_PRICE); } - // the ratio is always between the returned tick and the returned tick+1 - function checkGetTickAtSqrtRatioInvariants(uint160 ratio) external pure { - int24 tick = TickMath.getTickAtSqrtRatio(ratio); - assert(ratio >= TickMath.getSqrtRatioAtTick(tick) && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + // the price is always between the returned tick and the returned tick+1 + function checkGetTickAtSqrtPriceInvariants(uint160 price) external pure { + int24 tick = TickMath.getTickAtSqrtPrice(price); + assert(price >= TickMath.getSqrtPriceAtTick(tick) && price < TickMath.getSqrtPriceAtTick(tick + 1)); assert(tick >= TickMath.MIN_TICK); assert(tick < TickMath.MAX_TICK); } diff --git a/src/test/TickMathTest.sol b/src/test/TickMathTest.sol index 44b519e39..ea0a42af4 100644 --- a/src/test/TickMathTest.sol +++ b/src/test/TickMathTest.sol @@ -4,32 +4,32 @@ pragma solidity ^0.8.20; import {TickMath} from "../libraries/TickMath.sol"; contract TickMathTest { - function getSqrtRatioAtTick(int24 tick) external pure returns (uint160) { - return TickMath.getSqrtRatioAtTick(tick); + function getSqrtPriceAtTick(int24 tick) external pure returns (uint160) { + return TickMath.getSqrtPriceAtTick(tick); } - function getGasCostOfGetSqrtRatioAtTick(int24 tick) external view returns (uint256) { + function getGasCostOfGetSqrtPriceAtTick(int24 tick) external view returns (uint256) { uint256 gasBefore = gasleft(); - TickMath.getSqrtRatioAtTick(tick); + TickMath.getSqrtPriceAtTick(tick); return gasBefore - gasleft(); } - function getTickAtSqrtRatio(uint160 sqrtPriceX96) external pure returns (int24) { - return TickMath.getTickAtSqrtRatio(sqrtPriceX96); + function getTickAtSqrtPrice(uint160 sqrtPriceX96) external pure returns (int24) { + return TickMath.getTickAtSqrtPrice(sqrtPriceX96); } - function getGasCostOfGetTickAtSqrtRatio(uint160 sqrtPriceX96) external view returns (uint256) { + function getGasCostOfGetTickAtSqrtPrice(uint160 sqrtPriceX96) external view returns (uint256) { uint256 gasBefore = gasleft(); - TickMath.getTickAtSqrtRatio(sqrtPriceX96); + TickMath.getTickAtSqrtPrice(sqrtPriceX96); return gasBefore - gasleft(); } - function MIN_SQRT_RATIO() external pure returns (uint160) { - return TickMath.MIN_SQRT_RATIO; + function MIN_SQRT_PRICE() external pure returns (uint160) { + return TickMath.MIN_SQRT_PRICE; } - function MAX_SQRT_RATIO() external pure returns (uint160) { - return TickMath.MAX_SQRT_RATIO; + function MAX_SQRT_PRICE() external pure returns (uint160) { + return TickMath.MAX_SQRT_PRICE; } function MIN_TICK() external pure returns (int24) { diff --git a/test/DynamicFees.t.sol b/test/DynamicFees.t.sol index 97e7f67fc..6d72a3b86 100644 --- a/test/DynamicFees.t.sol +++ b/test/DynamicFees.t.sol @@ -61,7 +61,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { currency1, IHooks(address(dynamicFeesHooks)), LPFeeLibrary.DYNAMIC_FEE_FLAG, - SQRT_RATIO_1_1, + SQRT_PRICE_1_1, ZERO_BYTES ); } @@ -71,7 +71,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { dynamicFeesHooks.setFee(1000001); vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); } function test_initialize_initializesFeeTo0() public { @@ -80,7 +80,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { // this fee is not fetched as theres no afterInitialize hook dynamicFeesNoHooks.setFee(1000000); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); assertEq(_fetchPoolLPFee(key), 0); } @@ -88,7 +88,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { key.tickSpacing = 30; dynamicFeesHooks.setFee(123); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); assertEq(_fetchPoolLPFee(key), 123); } @@ -103,7 +103,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { // afterInitialize will try to update the fee, and fail vm.expectRevert(IPoolManager.UnauthorizedDynamicLPFeeUpdate.selector); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); } function test_updateDynamicLPFee_beforeSwap_failsWithTooLargeFee() public { @@ -112,7 +112,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { dynamicFeesHooks.setFee(1000001); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -126,7 +126,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { dynamicFeesHooks.setFee(123); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -145,12 +145,12 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { dynamicFeesHooks.setFee(1000000); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); vm.expectEmit(true, true, true, true, address(manager)); - emit Swap(key.toId(), address(swapRouter), -100, 0, SQRT_RATIO_1_1, 1e18, -1, 1000000); + emit Swap(key.toId(), address(swapRouter), -100, 0, SQRT_PRICE_1_1, 1e18, -1, 1000000); swapRouter.swap(key, params, testSettings, ZERO_BYTES); @@ -163,7 +163,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { dynamicFeesHooks.setFee(500000); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -181,7 +181,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { dynamicFeesHooks.setFee(500000); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -199,7 +199,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { dynamicFeesHooks.setFee(1000000); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -216,7 +216,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { manager.setProtocolFee(key, 1000); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -241,12 +241,12 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { manager.setProtocolFee(key, 1000); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -1000, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -1000, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); vm.expectEmit(true, true, true, true, address(manager)); - emit Swap(key.toId(), address(swapRouter), -1000, 0, SQRT_RATIO_1_1, 1e18, -1, 1000000); + emit Swap(key.toId(), address(swapRouter), -1000, 0, SQRT_PRICE_1_1, 1e18, -1, 1000000); swapRouter.swap(key, params, testSettings, ZERO_BYTES); @@ -263,7 +263,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { manager.setProtocolFee(key, 1000); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -294,7 +294,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ zeroForOne: true, amountSpecified: amountSpecified, - sqrtPriceLimitX96: SQRT_RATIO_1_2 + sqrtPriceLimitX96: SQRT_PRICE_1_2 }); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -307,7 +307,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { function test_swap_withDynamicFee_gas() public { (key,) = initPoolAndAddLiquidity( - currency0, currency1, dynamicFeesNoHooks, LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_RATIO_1_1, ZERO_BYTES + currency0, currency1, dynamicFeesNoHooks, LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1, ZERO_BYTES ); assertEq(_fetchPoolLPFee(key), 0); @@ -315,7 +315,7 @@ contract TestDynamicFees is Test, Deployers, GasSnapshot { assertEq(_fetchPoolLPFee(key), 123); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); diff --git a/test/ModifyLiquidity.t.sol b/test/ModifyLiquidity.t.sol index 5bbbef4df..b0007ec00 100644 --- a/test/ModifyLiquidity.t.sol +++ b/test/ModifyLiquidity.t.sol @@ -29,7 +29,7 @@ contract ModifyLiquidityTest is Test, Deployers, GasSnapshot { function setUp() public { deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); - (simpleKey, simplePoolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (simpleKey, simplePoolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); } function test_modifyLiquidity_samePosition_zeroSalt_isUpdated() public { diff --git a/test/PoolManager.gas.spec.ts b/test/PoolManager.gas.spec.ts index e2bc5d8d4..42691f862 100644 --- a/test/PoolManager.gas.spec.ts +++ b/test/PoolManager.gas.spec.ts @@ -19,7 +19,7 @@ // getMaxTick, // MaxUint128, // SwapToPriceFunction, -// MIN_SQRT_RATIO, +// MIN_SQRT_PRICE, // getPoolId, // } from './shared/utilities' // @@ -85,7 +85,7 @@ // { // zeroForOne: true, // amountSpecified: amount, -// sqrtPriceLimitX96: sqrtPriceLimitX96 ?? MIN_SQRT_RATIO.add(1), +// sqrtPriceLimitX96: sqrtPriceLimitX96 ?? MIN_SQRT_PRICE.add(1), // }, // { // withdrawTokens: true, @@ -374,7 +374,7 @@ // { // zeroForOne: true, // amountSpecified: amount, -// sqrtPriceLimitX96: sqrtPriceLimitX96 ?? MIN_SQRT_RATIO.add(1), +// sqrtPriceLimitX96: sqrtPriceLimitX96 ?? MIN_SQRT_PRICE.add(1), // }, // { // withdrawTokens: true, diff --git a/test/PoolManager.t.sol b/test/PoolManager.t.sol index 8376fab56..8df6ddbcf 100644 --- a/test/PoolManager.t.sol +++ b/test/PoolManager.t.sol @@ -119,7 +119,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_addLiquidity_succeedsIfInitialized(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); vm.expectEmit(true, true, true, true); emit ModifyLiquidity( @@ -134,7 +134,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_removeLiquidity_succeedsIfInitialized(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); vm.expectEmit(true, true, true, true); emit ModifyLiquidity( @@ -154,7 +154,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { FeeTakingHook impl = new FeeTakingHook(manager); vm.etch(hookAddr, address(impl).code); - (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_PRICE_1_1, ZERO_BYTES); uint256 balanceBefore0 = currency0.balanceOf(address(this)); uint256 balanceBefore1 = currency1.balanceOf(address(this)); @@ -186,7 +186,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { FeeTakingHook impl = new FeeTakingHook(manager); vm.etch(hookAddr, address(impl).code); - (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_PRICE_1_1, ZERO_BYTES); uint256 balanceBefore0 = currency0.balanceOf(address(this)); uint256 balanceBefore1 = currency1.balanceOf(address(this)); @@ -213,7 +213,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_addLiquidity_succeedsForNativeTokensIfInitialized(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); vm.expectEmit(true, true, true, true); emit ModifyLiquidity( @@ -228,7 +228,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_removeLiquidity_succeedsForNativeTokensIfInitialized(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); vm.expectEmit(true, true, true, true); emit ModifyLiquidity( @@ -243,7 +243,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_addLiquidity_succeedsWithHooksIfInitialized(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); address payable mockAddr = payable(address(uint160(Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG))); @@ -272,7 +272,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_removeLiquidity_succeedsWithHooksIfInitialized(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); address payable mockAddr = payable(address(uint160(Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG))); @@ -307,7 +307,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeAddLiquidity.selector, bytes4(0xdeadbeef)); mockHooks.setReturnValue(mockHooks.afterAddLiquidity.selector, bytes4(0xdeadbeef)); @@ -329,7 +329,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeRemoveLiquidity.selector, bytes4(0xdeadbeef)); @@ -352,7 +352,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeAddLiquidity.selector, mockHooks.beforeAddLiquidity.selector); mockHooks.setReturnValue(mockHooks.afterAddLiquidity.selector, mockHooks.afterAddLiquidity.selector); @@ -376,7 +376,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeRemoveLiquidity.selector, mockHooks.beforeRemoveLiquidity.selector); @@ -476,7 +476,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookEmptyAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookEmptyAddr); - (key,) = initPool(currency0, currency1, mockHooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, mockHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); snapLastCall("addLiquidity with empty hook"); @@ -488,7 +488,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookEmptyAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookEmptyAddr); - (key,) = initPool(currency0, currency1, mockHooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, mockHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); @@ -496,7 +496,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_swap_failsIfNotInitialized(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); key.fee = 100; IPoolManager.SwapParams memory params = @@ -511,7 +511,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_succeedsIfInitialized() public { IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -526,7 +526,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_failsIfLocked() public { IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); vm.expectRevert(IPoolManager.ManagerLocked.selector); manager.swap(key, swapParams, ZERO_BYTES); @@ -534,7 +534,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_succeedsWithNativeTokensIfInitialized() public { IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -564,10 +564,10 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { MockContract(mockAddr).setImplementation(hookAddr); - (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(mockAddr), 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(mockAddr), 3000, SQRT_PRICE_1_1, ZERO_BYTES); IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -593,10 +593,10 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -621,10 +621,10 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -10, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -10, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -640,7 +640,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_gas() public { IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -651,7 +651,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_withNative_gas() public { IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -667,10 +667,10 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookEmptyAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookEmptyAddr); - (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -678,7 +678,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { swapRouter.swap(key, swapParams, testSettings, ZERO_BYTES); swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_4}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_4}); testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); swapRouter.swap(key, swapParams, testSettings, ZERO_BYTES); @@ -687,7 +687,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_mint6909IfOutputNotTaken_gas() public { IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -703,7 +703,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_mint6909IfNativeOutputNotTaken_gas() public { IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: false, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_2_1}); + IPoolManager.SwapParams({zeroForOne: false, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_2_1}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -719,7 +719,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_burn6909AsInput_gas() public { IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -735,7 +735,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { manager.setOperator(address(swapRouter), true); // swap from currency1 to currency0 again, using 6909s as input tokens - params = IPoolManager.SwapParams({zeroForOne: false, amountSpecified: 25, sqrtPriceLimitX96: SQRT_RATIO_4_1}); + params = IPoolManager.SwapParams({zeroForOne: false, amountSpecified: 25, sqrtPriceLimitX96: SQRT_PRICE_4_1}); testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: true}); vm.expectEmit(); @@ -749,7 +749,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_burnNative6909AsInput_gas() public { IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: false, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_2_1}); + IPoolManager.SwapParams({zeroForOne: false, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_2_1}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: true, settleUsingBurn: false}); @@ -765,7 +765,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { manager.setOperator(address(swapRouter), true); // swap from currency0 to currency1, using 6909s as input tokens - params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 25, sqrtPriceLimitX96: SQRT_RATIO_1_4}); + params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 25, sqrtPriceLimitX96: SQRT_PRICE_1_4}); testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: true}); vm.expectEmit(); @@ -780,14 +780,14 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_againstLiquidity_gas() public { IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); swapRouter.swap(key, params, testSettings, ZERO_BYTES); - params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_4}); + params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_4}); swapRouter.swap(key, params, testSettings, ZERO_BYTES); snapLastCall("swap against liquidity"); @@ -795,14 +795,14 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_swap_againstLiqWithNative_gas() public { IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); swapRouter.swap{value: 1 ether}(nativeKey, params, testSettings, ZERO_BYTES); - params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_4}); + params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_4}); swapRouter.swap{value: 1 ether}(nativeKey, params, testSettings, ZERO_BYTES); snapLastCall("swap against liquidity with native token"); @@ -813,7 +813,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { FeeTakingHook impl = new FeeTakingHook(manager); vm.etch(hookAddr, address(impl).code); - (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_PRICE_1_1, ZERO_BYTES); uint256 balanceBefore0 = currency0.balanceOf(address(this)); uint256 balanceBefore1 = currency1.balanceOf(address(this)); @@ -824,7 +824,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ zeroForOne: true, amountSpecified: -int256(amountToSwap), - sqrtPriceLimitX96: SQRT_RATIO_1_2 + sqrtPriceLimitX96: SQRT_PRICE_1_2 }); swapRouter.swap(key, params, testSettings, ZERO_BYTES); snapLastCall("swap CA fee on unspecified"); @@ -840,7 +840,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { FeeTakingHook impl = new FeeTakingHook(manager); vm.etch(hookAddr, address(impl).code); - (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_PRICE_1_1, ZERO_BYTES); uint256 balanceBefore0 = currency0.balanceOf(address(this)); uint256 balanceBefore1 = currency1.balanceOf(address(this)); @@ -851,7 +851,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ zeroForOne: true, amountSpecified: int256(amountToSwap), - sqrtPriceLimitX96: SQRT_RATIO_1_2 + sqrtPriceLimitX96: SQRT_PRICE_1_2 }); swapRouter.swap(key, params, testSettings, ZERO_BYTES); @@ -871,7 +871,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { CustomCurveHook impl = new CustomCurveHook(manager); vm.etch(hookAddr, address(impl).code); - (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_PRICE_1_1, ZERO_BYTES); // add liquidity by sending tokens straight into the contract key.currency0.transfer(hookAddr, 10e18); key.currency1.transfer(hookAddr, 10e18); @@ -885,7 +885,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ zeroForOne: true, amountSpecified: -int256(amountToSwap), - sqrtPriceLimitX96: SQRT_RATIO_1_2 + sqrtPriceLimitX96: SQRT_PRICE_1_2 }); swapRouter.swap(key, params, testSettings, ZERO_BYTES); snapLastCall("swap CA custom curve + swap noop"); @@ -905,7 +905,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { CustomCurveHook impl = new CustomCurveHook(manager); vm.etch(hookAddr, address(impl).code); - (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, IHooks(hookAddr), 100, SQRT_PRICE_1_1, ZERO_BYTES); // add liquidity by sending tokens straight into the contract key.currency0.transfer(hookAddr, 10e18); key.currency1.transfer(hookAddr, 10e18); @@ -919,7 +919,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ zeroForOne: true, amountSpecified: int256(amountToSwap), - sqrtPriceLimitX96: SQRT_RATIO_1_2 + sqrtPriceLimitX96: SQRT_PRICE_1_2 }); swapRouter.swap(key, params, testSettings, ZERO_BYTES); @@ -948,7 +948,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); // initialize the pool and give the hook tokens to pay into swaps - (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddr), 100, SQRT_PRICE_1_1, ZERO_BYTES); key.currency0.transfer(hookAddr, type(uint128).max); key.currency1.transfer(hookAddr, type(uint128).max); @@ -1079,7 +1079,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { modifyLiquidityRouter.modifyLiquidity(key, params, ZERO_BYTES); IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams(false, amountSpecified, TickMath.MAX_SQRT_RATIO - 1); + IPoolManager.SwapParams(false, amountSpecified, TickMath.MAX_SQRT_PRICE - 1); BalanceDelta delta = swapRouter.swap(key, swapParams, PoolSwapTest.TestSettings(false, false), ZERO_BYTES); uint256 expectedProtocolFee = uint256(uint128(-delta.amount1())) * protocolFee1 / ProtocolFeeLibrary.PIPS_DENOMINATOR; @@ -1098,7 +1098,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { } function test_donate_failsIfNoLiquidity(uint160 sqrtPriceX96) public { - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); (key,) = initPool(currency0, currency1, IHooks(address(0)), 100, sqrtPriceX96, ZERO_BYTES); @@ -1139,7 +1139,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeDonate.selector, bytes4(0xdeadbeef)); mockHooks.setReturnValue(mockHooks.afterDonate.selector, bytes4(0xdeadbeef)); @@ -1161,7 +1161,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { vm.etch(hookAddr, address(impl).code); MockHooks mockHooks = MockHooks(hookAddr); - (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, mockHooks, 100, SQRT_PRICE_1_1, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeDonate.selector, mockHooks.beforeDonate.selector); mockHooks.setReturnValue(mockHooks.afterDonate.selector, mockHooks.afterDonate.selector); @@ -1194,7 +1194,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { (currency0Invalid ? currency0 : invalidCurrency), IHooks(address(0)), 3000, - SQRT_RATIO_1_1, + SQRT_PRICE_1_1, ZERO_BYTES ); @@ -1281,7 +1281,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { function test_collectProtocolFees_initializesWithProtocolFeeIfCalled() public { feeController.setProtocolFeeForPool(uninitializedKey.toId(), MAX_PROTOCOL_FEE_BOTH_TOKENS); - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); (,, uint24 slot0ProtocolFee,) = manager.getSlot0(uninitializedKey.toId()); assertEq(slot0ProtocolFee, MAX_PROTOCOL_FEE_BOTH_TOKENS); } @@ -1302,7 +1302,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { swapRouter.swap( key, - IPoolManager.SwapParams(true, -10000, SQRT_RATIO_1_2), + IPoolManager.SwapParams(true, -10000, SQRT_PRICE_1_2), PoolSwapTest.TestSettings(false, false), ZERO_BYTES ); @@ -1328,7 +1328,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { swapRouter.swap( key, - IPoolManager.SwapParams(true, 10000, SQRT_RATIO_1_2), + IPoolManager.SwapParams(true, 10000, SQRT_PRICE_1_2), PoolSwapTest.TestSettings(false, false), ZERO_BYTES ); @@ -1353,7 +1353,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { swapRouter.swap( key, - IPoolManager.SwapParams(false, -10000, TickMath.MAX_SQRT_RATIO - 1), + IPoolManager.SwapParams(false, -10000, TickMath.MAX_SQRT_PRICE - 1), PoolSwapTest.TestSettings(false, false), ZERO_BYTES ); @@ -1379,7 +1379,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { swapRouter.swap{value: 10000}( nativeKey, - IPoolManager.SwapParams(true, -10000, SQRT_RATIO_1_2), + IPoolManager.SwapParams(true, -10000, SQRT_PRICE_1_2), PoolSwapTest.TestSettings(false, false), ZERO_BYTES ); @@ -1406,7 +1406,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { swapRouter.swap{value: 10000}( nativeKey, - IPoolManager.SwapParams(true, -10000, SQRT_RATIO_1_2), + IPoolManager.SwapParams(true, -10000, SQRT_PRICE_1_2), PoolSwapTest.TestSettings(false, false), ZERO_BYTES ); @@ -1446,7 +1446,7 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { // hooks: IHooks(address(0)), // tickSpacing: 10 // }); - // manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + // manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); // PoolId poolId = key.toId(); // bytes32 slot0Bytes = manager.extsload(keccak256(abi.encode(poolId, POOL_SLOT))); @@ -1470,18 +1470,18 @@ contract PoolManagerTest is Test, Deployers, GasSnapshot { // hooks: IHooks(address(0)), // tickSpacing: 10 // }); - // manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + // manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); // // populate feeGrowthGlobalX128 struct w/ modify + swap // modifyLiquidityRouter.modifyLiquidity(key, IPoolManager.ModifyLiquidityParams(-120, 120, 5 ether, 0)); // swapRouter.swap( // key, - // IPoolManager.SwapParams(false, 1 ether, TickMath.MAX_SQRT_RATIO - 1), + // IPoolManager.SwapParams(false, 1 ether, TickMath.MAX_SQRT_PRICE - 1), // PoolSwapTest.TestSettings(true, true) // ); // swapRouter.swap( // key, - // IPoolManager.SwapParams(true, 5 ether, TickMath.MIN_SQRT_RATIO + 1), + // IPoolManager.SwapParams(true, 5 ether, TickMath.MIN_SQRT_PRICE + 1), // PoolSwapTest.TestSettings(true, true) // ); diff --git a/test/PoolManagerInitialize.t.sol b/test/PoolManagerInitialize.t.sol index c5935bb03..1669aa57b 100644 --- a/test/PoolManagerInitialize.t.sol +++ b/test/PoolManagerInitialize.t.sol @@ -53,7 +53,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_fuzz_initialize(PoolKey memory key0, uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); // tested in Hooks.t.sol key0.hooks = IHooks(Constants.ADDRESS_ZERO); @@ -88,7 +88,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_forNativeTokens(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); uninitializedKey.currency0 = CurrencyLibrary.NATIVE; vm.expectEmit(true, true, true, true); @@ -106,12 +106,12 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { manager.getSlot0(uninitializedKey.toId()); assertEq(slot0SqrtPriceX96, sqrtPriceX96); assertEq(slot0ProtocolFee, 0); - assertEq(slot0Tick, TickMath.getTickAtSqrtRatio(sqrtPriceX96)); + assertEq(slot0Tick, TickMath.getTickAtSqrtPrice(sqrtPriceX96)); } function test_initialize_succeedsWithHooks(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); address payable mockAddr = payable(address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG))); address payable hookAddr = payable(Constants.MOCK_HOOKS); @@ -142,7 +142,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_succeedsWithMaxTickSpacing(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); uninitializedKey.tickSpacing = manager.MAX_TICK_SPACING(); @@ -161,7 +161,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_succeedsWithEmptyHooks(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); address hookEmptyAddr = Constants.EMPTY_HOOKS; @@ -178,7 +178,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_revertsWithIdenticalTokens(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); // Both currencies are currency0 uninitializedKey.currency1 = currency0; @@ -189,7 +189,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_revertsWithSameTokenCombo(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); uninitializedKey.currency1 = currency0; uninitializedKey.currency0 = currency1; @@ -205,10 +205,10 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { uint16 fee0 = protocolFee.getZeroForOneFee(); uint16 fee1 = protocolFee.getOneForZeroFee(); - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); (uint160 slot0SqrtPriceX96,, uint24 slot0ProtocolFee,) = manager.getSlot0(uninitializedKey.toId()); - assertEq(slot0SqrtPriceX96, SQRT_RATIO_1_1); + assertEq(slot0SqrtPriceX96, SQRT_PRICE_1_1); if ((fee0 > 1000) || (fee1 > 1000)) { assertEq(slot0ProtocolFee, 0); } else { @@ -218,7 +218,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_revertsWhenPoolAlreadyInitialized(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); manager.initialize(uninitializedKey, sqrtPriceX96, ZERO_BYTES); vm.expectRevert(Pool.PoolAlreadyInitialized.selector); @@ -239,12 +239,12 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { // Fails at beforeInitialize hook. vm.expectRevert(Hooks.InvalidHookResponse.selector); - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); // Fail at afterInitialize hook. mockHooks.setReturnValue(mockHooks.beforeInitialize.selector, mockHooks.beforeInitialize.selector); vm.expectRevert(Hooks.InvalidHookResponse.selector); - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); } function test_initialize_succeedsWithCorrectSelectors() public { @@ -269,12 +269,12 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { uninitializedKey.hooks ); - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); } function test_initialize_failsIfTickSpaceTooLarge(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); uninitializedKey.tickSpacing = manager.MAX_TICK_SPACING() + 1; @@ -284,7 +284,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_failsIfTickSpaceZero(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); uninitializedKey.tickSpacing = 0; @@ -294,7 +294,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_failsIfTickSpaceNeg(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); uninitializedKey.tickSpacing = -1; @@ -304,7 +304,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_succeedsWithOutOfBoundsFeeController(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); manager.setProtocolFeeController(outOfBoundsFeeController); // expect initialize to succeed even though the controller reverts @@ -325,7 +325,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_succeedsWithRevertingFeeController(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); manager.setProtocolFeeController(revertingFeeController); // expect initialize to succeed even though the controller reverts @@ -346,7 +346,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_succeedsWithOverflowFeeController(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); manager.setProtocolFeeController(overflowFeeController); // expect initialize to succeed @@ -367,7 +367,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { function test_initialize_succeedsWithWrongReturnSizeFeeController(uint160 sqrtPriceX96) public { // Assumptions tested in Pool.t.sol - sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1)); + sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE - 1)); manager.setProtocolFeeController(invalidReturnSizeFeeController); // expect initialize to succeed @@ -387,7 +387,7 @@ contract PoolManagerInitializeTest is Test, Deployers, GasSnapshot { } function test_initialize_gas() public { - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); snapLastCall("initialize"); } } diff --git a/test/SkipCallsTestHook.t.sol b/test/SkipCallsTestHook.t.sol index 7922fc772..ed30227e0 100644 --- a/test/SkipCallsTestHook.t.sol +++ b/test/SkipCallsTestHook.t.sol @@ -22,7 +22,7 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; IPoolManager.SwapParams swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -35,7 +35,7 @@ contract SkipCallsTest is Test, Deployers, GasSnapshot { assertEq(skipCallsTestHook.counter(), 0); - (key,) = initPool(currency0, currency1, IHooks(address(skipCallsTestHook)), 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPool(currency0, currency1, IHooks(address(skipCallsTestHook)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); } function approveAndAddLiquidity(SkipCallsTestHook skipCallsTestHook) private { diff --git a/test/Sync.t.sol b/test/Sync.t.sol index fe18abf53..dddffef9f 100644 --- a/test/Sync.t.sol +++ b/test/Sync.t.sol @@ -54,7 +54,7 @@ contract SyncTest is Test, Deployers, GasSnapshot { assertGt(currency0.balanceOf(address(manager)), uint256(0)); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -75,7 +75,7 @@ contract SyncTest is Test, Deployers, GasSnapshot { PoolKey memory key2 = PoolKey({currency0: cur0, currency1: cur1, fee: 3000, tickSpacing: 60, hooks: IHooks(address(0))}); - manager.initialize(key2, SQRT_RATIO_1_1, new bytes(0)); + manager.initialize(key2, SQRT_PRICE_1_1, new bytes(0)); // Sync has not been called. vm.expectRevert(Reserves.ReservesMustBeSynced.selector); diff --git a/test/js-scripts/package.json b/test/js-scripts/package.json index 7fe3bc9c0..fd5acbaf8 100644 --- a/test/js-scripts/package.json +++ b/test/js-scripts/package.json @@ -27,7 +27,7 @@ "typescript": "^3.7.3" }, "scripts": { - "forge-test-getSqrtRatioAtTick": "npx ts-node src/getSqrtRatioAtTick.ts", - "forge-test-getTickAtSqrtRatio": "npx ts-node src/getTickAtSqrtRatio.ts" + "forge-test-getSqrtPriceAtTick": "npx ts-node src/getSqrtPriceAtTick.ts", + "forge-test-getTickAtSqrtPrice": "npx ts-node src/getTickAtSqrtPrice.ts" } } diff --git a/test/js-scripts/src/getSqrtRatioAtTick.ts b/test/js-scripts/src/getSqrtPriceAtTick.ts similarity index 100% rename from test/js-scripts/src/getSqrtRatioAtTick.ts rename to test/js-scripts/src/getSqrtPriceAtTick.ts diff --git a/test/js-scripts/src/getTickAtSqrtRatio.ts b/test/js-scripts/src/getTickAtSqrtPrice.ts similarity index 62% rename from test/js-scripts/src/getTickAtSqrtRatio.ts rename to test/js-scripts/src/getTickAtSqrtPrice.ts index 1ae01375c..e829bdd06 100644 --- a/test/js-scripts/src/getTickAtSqrtRatio.ts +++ b/test/js-scripts/src/getTickAtSqrtPrice.ts @@ -1,10 +1,10 @@ import Decimal from 'decimal.js' import { ethers } from 'ethers' -const sqrtRatioArray = process.argv[2].split(',') +const sqrtPriceArray = process.argv[2].split(',') const resultsArray = [] -for (let sqrtRatio of sqrtRatioArray) { - const jsResult = new Decimal(sqrtRatio).div(new Decimal(2).pow(96)).pow(2).log(1.0001).floor().toFixed(0) +for (let sqrtPrice of sqrtPriceArray) { + const jsResult = new Decimal(sqrtPrice).div(new Decimal(2).pow(96)).pow(2).log(1.0001).floor().toFixed(0) resultsArray.push(jsResult) } process.stdout.write(ethers.utils.defaultAbiCoder.encode(['int256[]'], [resultsArray])) diff --git a/test/libraries/Hooks.t.sol b/test/libraries/Hooks.t.sol index a988e601c..7dca256e0 100644 --- a/test/libraries/Hooks.t.sol +++ b/test/libraries/Hooks.t.sol @@ -38,10 +38,10 @@ contract HooksTest is Test, Deployers, GasSnapshot { } function test_initialize_succeedsWithHook() public { - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, new bytes(123)); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, new bytes(123)); (uint160 sqrtPriceX96,,,) = manager.getSlot0(uninitializedKey.toId()); - assertEq(sqrtPriceX96, SQRT_RATIO_1_1); + assertEq(sqrtPriceX96, SQRT_PRICE_1_1); assertEq(mockHooks.beforeInitializeData(), new bytes(123)); assertEq(mockHooks.afterInitializeData(), new bytes(123)); } @@ -49,13 +49,13 @@ contract HooksTest is Test, Deployers, GasSnapshot { function test_beforeInitialize_invalidReturn() public { mockHooks.setReturnValue(mockHooks.beforeInitialize.selector, bytes4(0xdeadbeef)); vm.expectRevert(Hooks.InvalidHookResponse.selector); - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); } function test_afterInitialize_invalidReturn() public { mockHooks.setReturnValue(mockHooks.afterInitialize.selector, bytes4(0xdeadbeef)); vm.expectRevert(Hooks.InvalidHookResponse.selector); - manager.initialize(uninitializedKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(uninitializedKey, SQRT_PRICE_1_1, ZERO_BYTES); } function test_beforeAfterAddLiquidity_beforeAfterRemoveLiquidity_succeedsWithHook() public { @@ -137,7 +137,7 @@ contract HooksTest is Test, Deployers, GasSnapshot { function test_swap_succeedsWithHook() public { IPoolManager.SwapParams memory swapParams = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); @@ -152,7 +152,7 @@ contract HooksTest is Test, Deployers, GasSnapshot { vm.expectRevert(Hooks.InvalidHookResponse.selector); swapRouter.swap( key, - IPoolManager.SwapParams(false, 100, SQRT_RATIO_1_1 + 60), + IPoolManager.SwapParams(false, 100, SQRT_PRICE_1_1 + 60), PoolSwapTest.TestSettings(true, true), ZERO_BYTES ); @@ -163,7 +163,7 @@ contract HooksTest is Test, Deployers, GasSnapshot { vm.expectRevert(Hooks.InvalidHookResponse.selector); swapRouter.swap( key, - IPoolManager.SwapParams(false, 100, SQRT_RATIO_1_1 + 60), + IPoolManager.SwapParams(false, 100, SQRT_PRICE_1_1 + 60), PoolSwapTest.TestSettings(true, true), ZERO_BYTES ); diff --git a/test/libraries/Pool.t.sol b/test/libraries/Pool.t.sol index 4919f68cb..6ce4e4ed9 100644 --- a/test/libraries/Pool.t.sol +++ b/test/libraries/Pool.t.sol @@ -24,14 +24,14 @@ contract PoolTest is Test { uint24 constant MAX_LP_FEE = LPFeeLibrary.MAX_LP_FEE; // 100% function testPoolInitialize(uint160 sqrtPriceX96, uint24 protocolFee, uint24 swapFee) public { - if (sqrtPriceX96 < TickMath.MIN_SQRT_RATIO || sqrtPriceX96 >= TickMath.MAX_SQRT_RATIO) { - vm.expectRevert(TickMath.InvalidSqrtRatio.selector); + if (sqrtPriceX96 < TickMath.MIN_SQRT_PRICE || sqrtPriceX96 >= TickMath.MAX_SQRT_PRICE) { + vm.expectRevert(TickMath.InvalidSqrtPrice.selector); state.initialize(sqrtPriceX96, protocolFee, swapFee); } else { state.initialize(sqrtPriceX96, protocolFee, swapFee); assertEq(state.slot0.sqrtPriceX96, sqrtPriceX96); assertEq(state.slot0.protocolFee, protocolFee); - assertEq(state.slot0.tick, TickMath.getTickAtSqrtRatio(sqrtPriceX96)); + assertEq(state.slot0.tick, TickMath.getTickAtSqrtPrice(sqrtPriceX96)); assertLt(state.slot0.tick, TickMath.MAX_TICK); assertGt(state.slot0.tick, TickMath.MIN_TICK - 1); } @@ -73,8 +73,8 @@ contract PoolTest is Test { uint256 maxInt128InTypeU256 = uint256(uint128(Constants.MAX_UINT128)); (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( sqrtPriceX96, - TickMath.getSqrtRatioAtTick(params.tickLower), - TickMath.getSqrtRatioAtTick(params.tickUpper), + TickMath.getSqrtPriceAtTick(params.tickLower), + TickMath.getSqrtPriceAtTick(params.tickUpper), uint128(params.liquidityDelta) ); @@ -128,7 +128,7 @@ contract PoolTest is Test { ) ); state.swap(params); - } else if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) { + } else if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_PRICE) { vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); state.swap(params); } @@ -140,7 +140,7 @@ contract PoolTest is Test { ) ); state.swap(params); - } else if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) { + } else if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_PRICE) { vm.expectRevert(abi.encodeWithSelector(Pool.PriceLimitOutOfBounds.selector, params.sqrtPriceLimitX96)); state.swap(params); } diff --git a/test/libraries/Position.t.sol b/test/libraries/Position.t.sol index 6a87bff4e..e18cec883 100644 --- a/test/libraries/Position.t.sol +++ b/test/libraries/Position.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; -import {Position} from "src/libraries/Position.sol"; +import {Position} from "../../src/libraries/Position.sol"; contract PositionTest is Test { using Position for mapping(bytes32 => Position.Info); mapping(bytes32 => Position.Info) internal positions; - function test_get_fuzz(address owner, int24 tickLower, int24 tickUpper, bytes32 salt) public view { + function test_fuzz_get(address owner, int24 tickLower, int24 tickUpper, bytes32 salt) public view { bytes32 positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt)); Position.Info storage expectedPosition = positions[positionKey]; Position.Info storage position = positions.get(owner, tickLower, tickUpper, salt); diff --git a/test/libraries/SqrtPriceMath.t.sol b/test/libraries/SqrtPriceMath.t.sol index 46e203415..396132616 100644 --- a/test/libraries/SqrtPriceMath.t.sol +++ b/test/libraries/SqrtPriceMath.t.sol @@ -1,17 +1,16 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; -import "forge-std/console.sol"; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; -import {GasSnapshot} from "lib/forge-gas-snapshot/src/GasSnapshot.sol"; -import {SqrtPriceMath} from "src/libraries/SqrtPriceMath.sol"; -import {Constants} from "test/utils/Constants.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {SqrtPriceMath} from "../../src/libraries/SqrtPriceMath.sol"; +import {Constants} from "../../test/utils/Constants.sol"; -contract SqrtPriceMathTestTest is Test, GasSnapshot { +contract SqrtPriceMathTest is Test, GasSnapshot { function test_getNextSqrtPriceFromInput_revertsIfPriceIsZero() public { vm.expectRevert(SqrtPriceMath.InvalidPriceOrLiquidity.selector); - SqrtPriceMath.getNextSqrtPriceFromInput(0, 0, 0.1 ether, false); + SqrtPriceMath.getNextSqrtPriceFromInput(0, 1, 0.1 ether, false); } function test_getNextSqrtPriceFromInput_revertsIfLiquidityIsZero() public { @@ -33,18 +32,20 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { uint128 liquidity = 1; uint256 amountIn = 2 ** 255; - assertEq(SqrtPriceMath.getNextSqrtPriceFromInput(price, liquidity, amountIn, true), 1); + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(price, liquidity, amountIn, true); + + assertEq(sqrtQ, 1); } function test_getNextSqrtPriceFromInput_returnsInputPriceIfAmountInIsZeroAndZeroForOneEqualsTrue() public pure { - uint160 price = Constants.SQRT_RATIO_1_1; + uint160 price = Constants.SQRT_PRICE_1_1; uint128 liquidity = 1; assertEq(SqrtPriceMath.getNextSqrtPriceFromInput(price, liquidity, 0, true), price); } function test_getNextSqrtPriceFromInput_returnsInputPriceIfAmountInIsZeroAndZeroForOneEqualsFalse() public pure { - uint160 price = Constants.SQRT_RATIO_1_1; + uint160 price = Constants.SQRT_PRICE_1_1; uint128 liquidity = 1; assertEq(SqrtPriceMath.getNextSqrtPriceFromInput(price, liquidity, 0, false), price); @@ -59,15 +60,15 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromInput_inputAmountOf0_1Currency1() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, uint128(1 ether), 0.1 ether, false); - assertEq(sqrtQ, 87150978765690771352898345369); + assertEq(sqrtQ, Constants.SQRT_PRICE_121_100); } function test_getNextSqrtPriceFromInput_inputAmountOf0_1Currency0() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, uint128(1 ether), 0.1 ether, true); @@ -75,7 +76,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromInput_amountInGreaterThanType_uint96_maxAndZeroForOneEqualsTrue() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, uint128(10 ether), 2 ** 100, true); @@ -85,15 +86,16 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromInput_canReturn1WithEnoughAmountInAndZeroForOneEqualsTrue() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, 1, Constants.MAX_UINT256 / 2, true); assertEq(sqrtQ, 1); } + // function test_getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas() public { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; snapStart("getNextSqrtPriceFromInput_zeroForOneEqualsTrueGas"); SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, uint128(1 ether), 0.1 ether, true); @@ -101,7 +103,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas() public { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; snapStart("getNextSqrtPriceFromInput_zeroForOneEqualsFalseGas"); SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, uint128(1 ether), 0.1 ether, false); @@ -110,7 +112,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { function test_getNextSqrtPriceFromOutput_revertsIfPriceIsZero() public { vm.expectRevert(SqrtPriceMath.InvalidPriceOrLiquidity.selector); - SqrtPriceMath.getNextSqrtPriceFromOutput(0, 0, 0.1 ether, false); + SqrtPriceMath.getNextSqrtPriceFromOutput(0, 1, 0.1 ether, false); } function test_getNextSqrtPriceFromOutput_revertsIfLiquidityIsZero() public { @@ -177,7 +179,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromOutput_returnsInputPriceIfAmountInIsZeroAndZeroForOneEqualsTrue() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint256 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, uint128(0.1 ether), 0, true); @@ -185,7 +187,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromOutput_returnsInputPriceIfAmountInIsZeroAndZeroForOneEqualsFalse() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint256 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, uint128(0.1 ether), 0, false); @@ -193,7 +195,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromOutput_outputAmountOf0_1Currency1() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, uint128(1 ether), 0.1 ether, false); @@ -201,7 +203,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromOutput_outputAmountOf0_1Currency0() public pure { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, uint128(1 ether), 0.1 ether, true); @@ -209,21 +211,21 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromOutput_revertsIfAmountOutIsImpossibleInZeroForOneDirection() public { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; vm.expectRevert(); SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, 1, Constants.MAX_UINT256, true); } function test_getNextSqrtPriceFromOutput_revertsIfAmountOutIsImpossibleInOneForZeroDirection() public { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; vm.expectRevert(SqrtPriceMath.PriceOverflow.selector); SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, 1, Constants.MAX_UINT256, false); } function test_getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas() public { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; snapStart("getNextSqrtPriceFromOutput_zeroForOneEqualsTrueGas"); SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, uint128(1 ether), 0.1 ether, true); @@ -231,7 +233,7 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas() public { - uint160 sqrtP = Constants.SQRT_RATIO_1_1; + uint160 sqrtP = Constants.SQRT_PRICE_1_1; snapStart("getNextSqrtPriceFromOutput_zeroForOneEqualsFalseGas"); SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, uint128(1 ether), 0.1 ether, false); @@ -239,26 +241,31 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { } function test_getAmount0Delta_returns0IfLiquidityIs0() public pure { - uint256 amount0 = SqrtPriceMath.getAmount0Delta(Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_2_1, 0, true); + uint256 amount0 = SqrtPriceMath.getAmount0Delta(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_2_1, 0, true); assertEq(amount0, 0); } function test_getAmount0Delta_returns0IfPricesAreEqual() public pure { - uint256 amount0 = SqrtPriceMath.getAmount0Delta(Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_1_1, 0, true); + uint256 amount0 = SqrtPriceMath.getAmount0Delta(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_1_1, 0, true); assertEq(amount0, 0); } + function test_getAmount0Delta_revertsIfPriceIsZero() public { + vm.expectRevert(SqrtPriceMath.InvalidPrice.selector); + SqrtPriceMath.getAmount0Delta(0, 1, 1, true); + } + function test_getAmount0Delta_1Amount1ForPriceOf1To1_21() public pure { uint256 amount0 = SqrtPriceMath.getAmount0Delta( - Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_121_100, uint128(1 ether), true + Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_121_100, uint128(1 ether), true ); assertEq(amount0, 90909090909090910); uint256 amount0RoundedDown = SqrtPriceMath.getAmount0Delta( - Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_121_100, uint128(1 ether), false + Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_121_100, uint128(1 ether), false ); assertEq(amount0RoundedDown, amount0 - 1); @@ -279,37 +286,37 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { function test_getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue() public { snapStart("getAmount0Delta_gasCostForAmount0WhereRoundUpIsTrue"); - SqrtPriceMath.getAmount0Delta(Constants.SQRT_RATIO_121_100, Constants.SQRT_RATIO_1_1, uint128(1 ether), true); + SqrtPriceMath.getAmount0Delta(Constants.SQRT_PRICE_121_100, Constants.SQRT_PRICE_1_1, uint128(1 ether), true); snapEnd(); } function test_getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse() public { snapStart("getAmount0Delta_gasCostForAmount0WhereRoundUpIsFalse"); - SqrtPriceMath.getAmount0Delta(Constants.SQRT_RATIO_121_100, Constants.SQRT_RATIO_1_1, uint128(1 ether), false); + SqrtPriceMath.getAmount0Delta(Constants.SQRT_PRICE_121_100, Constants.SQRT_PRICE_1_1, uint128(1 ether), false); snapEnd(); } function test_getAmount1Delta_returns0IfLiquidityIs0() public pure { - uint256 amount1 = SqrtPriceMath.getAmount1Delta(Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_2_1, 0, true); + uint256 amount1 = SqrtPriceMath.getAmount1Delta(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_2_1, 0, true); assertEq(amount1, 0); } function test_getAmount1Delta_returns0IfPricesAreEqual() public pure { - uint256 amount1 = SqrtPriceMath.getAmount1Delta(Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_1_1, 0, true); + uint256 amount1 = SqrtPriceMath.getAmount1Delta(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_1_1, 0, true); assertEq(amount1, 0); } function test_getAmount1Delta_1Amount1ForPriceOf1To1_21() public pure { uint256 amount1 = SqrtPriceMath.getAmount1Delta( - Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_121_100, uint128(1 ether), true + Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_121_100, uint128(1 ether), true ); assertEq(amount1, 100000000000000000); uint256 amount1RoundedDown = SqrtPriceMath.getAmount1Delta( - Constants.SQRT_RATIO_1_1, Constants.SQRT_RATIO_121_100, uint128(1 ether), false + Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_121_100, uint128(1 ether), false ); assertEq(amount1RoundedDown, amount1 - 1); @@ -317,13 +324,13 @@ contract SqrtPriceMathTestTest is Test, GasSnapshot { function test_getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue() public { snapStart("getAmount1Delta_gasCostForAmount1WhereRoundUpIsTrue"); - SqrtPriceMath.getAmount1Delta(Constants.SQRT_RATIO_121_100, Constants.SQRT_RATIO_1_1, uint128(1 ether), true); + SqrtPriceMath.getAmount1Delta(Constants.SQRT_PRICE_121_100, Constants.SQRT_PRICE_1_1, uint128(1 ether), true); snapEnd(); } function test_getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse() public { snapStart("getAmount1Delta_gasCostForAmount1WhereRoundUpIsFalse"); - SqrtPriceMath.getAmount1Delta(Constants.SQRT_RATIO_121_100, Constants.SQRT_RATIO_1_1, uint128(1 ether), false); + SqrtPriceMath.getAmount1Delta(Constants.SQRT_PRICE_121_100, Constants.SQRT_PRICE_1_1, uint128(1 ether), false); snapEnd(); } diff --git a/test/libraries/SwapMath.t.sol b/test/libraries/SwapMath.t.sol index 48242c314..4ec40c2bb 100644 --- a/test/libraries/SwapMath.t.sol +++ b/test/libraries/SwapMath.t.sol @@ -8,17 +8,17 @@ import {SwapMath} from "src/libraries/SwapMath.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; contract SwapMathTest is Test, GasSnapshot { - uint160 private constant SQRT_RATIO_1_1 = 79228162514264337593543950336; - uint160 private constant SQRT_RATIO_99_100 = 78831026366734652303669917531; - uint160 private constant SQRT_RATIO_99_1000 = 24928559360766947368818086097; - uint160 private constant SQRT_RATIO_101_100 = 79623317895830914510639640423; - uint160 private constant SQRT_RATIO_1000_100 = 250541448375047931186413801569; - uint160 private constant SQRT_RATIO_1010_100 = 251791039410471229173201122529; - uint160 private constant SQRT_RATIO_10000_100 = 792281625142643375935439503360; + uint160 private constant SQRT_PRICE_1_1 = 79228162514264337593543950336; + uint160 private constant SQRT_PRICE_99_100 = 78831026366734652303669917531; + uint160 private constant SQRT_PRICE_99_1000 = 24928559360766947368818086097; + uint160 private constant SQRT_PRICE_101_100 = 79623317895830914510639640423; + uint160 private constant SQRT_PRICE_1000_100 = 250541448375047931186413801569; + uint160 private constant SQRT_PRICE_1010_100 = 251791039410471229173201122529; + uint160 private constant SQRT_PRICE_10000_100 = 792281625142643375935439503360; function test_exactAmountOut_oneForZero_thatGetsCappedAtPriceTargetIn() public pure { - uint160 priceTarget = SQRT_RATIO_101_100; - uint160 price = SQRT_RATIO_1_1; + uint160 priceTarget = SQRT_PRICE_101_100; + uint160 price = SQRT_PRICE_1_1; uint128 liquidity = 2 ether; int256 amount = 1 ether; uint24 lpFee = 600; @@ -40,8 +40,8 @@ contract SwapMathTest is Test, GasSnapshot { } function test_exactAmountIn_oneForZero_thatGetsCappedAtPriceTargetIn() public pure { - uint160 priceTarget = SQRT_RATIO_101_100; - uint160 price = SQRT_RATIO_1_1; + uint160 priceTarget = SQRT_PRICE_101_100; + uint160 price = SQRT_PRICE_1_1; uint128 liquidity = 2 ether; int256 amount = (1 ether) * -1; uint24 lpFee = 600; @@ -63,8 +63,8 @@ contract SwapMathTest is Test, GasSnapshot { } function test_exactAmountIn_oneForZero_thatIsFullySpentIn() public pure { - uint160 priceTarget = SQRT_RATIO_1000_100; - uint160 price = SQRT_RATIO_1_1; + uint160 priceTarget = SQRT_PRICE_1000_100; + uint160 price = SQRT_PRICE_1_1; uint128 liquidity = 2 ether; int256 amount = 1 ether * -1; uint24 lpFee = 600; @@ -86,8 +86,8 @@ contract SwapMathTest is Test, GasSnapshot { } function test_exactAmountOut_oneForZero_thatIsFullyReceivedIn() public pure { - uint160 priceTarget = SQRT_RATIO_10000_100; - uint160 price = SQRT_RATIO_1_1; + uint160 priceTarget = SQRT_PRICE_10000_100; + uint160 price = SQRT_PRICE_1_1; uint128 liquidity = 2 ether; int256 amount = (1 ether); uint24 lpFee = 600; @@ -224,49 +224,49 @@ contract SwapMathTest is Test, GasSnapshot { function test_swapOneForZero_exactInCapped() public { snapStart("SwapMath_oneForZero_exactInCapped"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_101_100, 2 ether, (1 ether) * -1, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_101_100, 2 ether, (1 ether) * -1, 600); snapEnd(); } function test_swapZeroForOne_exactInCapped() public { snapStart("SwapMath_zeroForOne_exactInCapped"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_99_100, 2 ether, (1 ether) * -1, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_99_100, 2 ether, (1 ether) * -1, 600); snapEnd(); } function test_swapOneForZero_exactOutCapped() public { snapStart("SwapMath_oneForZero_exactOutCapped"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_101_100, 2 ether, 1 ether, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_101_100, 2 ether, 1 ether, 600); snapEnd(); } function test_swapZeroForOne_exactOutCapped() public { snapStart("SwapMath_zeroForOne_exactOutCapped"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_99_100, 2 ether, 1 ether, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_99_100, 2 ether, 1 ether, 600); snapEnd(); } function test_swapOneForZero_exactInPartial() public { snapStart("SwapMath_oneForZero_exactInPartial"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_1010_100, 2 ether, 1_000 * -1, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_1010_100, 2 ether, 1_000 * -1, 600); snapEnd(); } function test_swapZeroForOne_exactInPartial() public { snapStart("SwapMath_zeroForOne_exactInPartial"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_99_1000, 2 ether, 1_000 * -1, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_99_1000, 2 ether, 1_000 * -1, 600); snapEnd(); } function test_swapOneForZero_exactOutPartial() public { snapStart("SwapMath_oneForZero_exactOutPartial"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_1010_100, 2 ether, 1_000, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_1010_100, 2 ether, 1_000, 600); snapEnd(); } function test_swapZeroForOne_exactOutPartial() public { snapStart("SwapMath_zeroForOne_exactOutPartial"); - SwapMath.computeSwapStep(SQRT_RATIO_1_1, SQRT_RATIO_99_1000, 2 ether, 1_000, 600); + SwapMath.computeSwapStep(SQRT_PRICE_1_1, SQRT_PRICE_99_1000, 2 ether, 1_000, 600); snapEnd(); } } diff --git a/test/libraries/TickMath.t.sol b/test/libraries/TickMath.t.sol index f70f059ba..05eb505dc 100644 --- a/test/libraries/TickMath.t.sol +++ b/test/libraries/TickMath.t.sol @@ -11,22 +11,22 @@ contract TickMathTestTest is Test, JavascriptFfi { int24 constant MIN_TICK = -887272; int24 constant MAX_TICK = -MIN_TICK; - uint160 constant MIN_SQRT_RATIO = 4295128739; - uint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + uint160 constant MIN_SQRT_PRICE = 4295128739; + uint160 constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342; - uint160 constant SQRT_RATIO_1_1 = 79228162514264337593543950336; + uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336; uint256 constant ONE_PIP = 1e6; - uint160[] getSqrtRatioAtTickFuzzResults; - int24[] getTickAtSqrtRatioFuzzResults; + uint160[] getSqrtPriceAtTickFuzzResults; + int24[] getTickAtSqrtPriceFuzzResults; TickMathTest tickMath; function setUp() public { tickMath = new TickMathTest(); - delete getSqrtRatioAtTickFuzzResults; - delete getTickAtSqrtRatioFuzzResults; + delete getSqrtPriceAtTickFuzzResults; + delete getTickAtSqrtPriceFuzzResults; } function test_MIN_TICK_equalsNegativeMAX_TICK() public view { @@ -44,75 +44,75 @@ contract TickMathTestTest is Test, JavascriptFfi { assertEq(maxTick, MAX_TICK); } - function test_getSqrtRatioAtTick_throwsForTooLow() public { + function test_getSqrtPriceAtTick_throwsForTooLow() public { vm.expectRevert(TickMath.InvalidTick.selector); - tickMath.getSqrtRatioAtTick(MIN_TICK - 1); + tickMath.getSqrtPriceAtTick(MIN_TICK - 1); } - function test_getSqrtRatioAtTick_throwsForTooHigh() public { + function test_getSqrtPriceAtTick_throwsForTooHigh() public { vm.expectRevert(TickMath.InvalidTick.selector); - tickMath.getSqrtRatioAtTick(MAX_TICK + 1); + tickMath.getSqrtPriceAtTick(MAX_TICK + 1); } - function test_getSqrtRatioAtTick_isValidMinTick() public view { - assertEq(tickMath.getSqrtRatioAtTick(MIN_TICK), tickMath.MIN_SQRT_RATIO()); - assertEq(tickMath.getSqrtRatioAtTick(MIN_TICK), 4295128739); + function test_getSqrtPriceAtTick_isValidMinTick() public view { + assertEq(tickMath.getSqrtPriceAtTick(MIN_TICK), tickMath.MIN_SQRT_PRICE()); + assertEq(tickMath.getSqrtPriceAtTick(MIN_TICK), 4295128739); } - function test_getSqrtRatioAtTick_isValidMinTickAddOne() public view { - assertEq(tickMath.getSqrtRatioAtTick(MIN_TICK + 1), 4295343490); + function test_getSqrtPriceAtTick_isValidMinTickAddOne() public view { + assertEq(tickMath.getSqrtPriceAtTick(MIN_TICK + 1), 4295343490); } - function test_getSqrtRatioAtTick_isValidMaxTick() public view { - assertEq(tickMath.getSqrtRatioAtTick(MAX_TICK), tickMath.MAX_SQRT_RATIO()); - assertEq(tickMath.getSqrtRatioAtTick(MAX_TICK), 1461446703485210103287273052203988822378723970342); + function test_getSqrtPriceAtTick_isValidMaxTick() public view { + assertEq(tickMath.getSqrtPriceAtTick(MAX_TICK), tickMath.MAX_SQRT_PRICE()); + assertEq(tickMath.getSqrtPriceAtTick(MAX_TICK), 1461446703485210103287273052203988822378723970342); } - function test_getSqrtRatioAtTick_isValidMaxTickSubOne() public view { - assertEq(tickMath.getSqrtRatioAtTick(MAX_TICK - 1), 1461373636630004318706518188784493106690254656249); + function test_getSqrtPriceAtTick_isValidMaxTickSubOne() public view { + assertEq(tickMath.getSqrtPriceAtTick(MAX_TICK - 1), 1461373636630004318706518188784493106690254656249); } - function test_getSqrtRatioAtTick_isLessThanJSImplMinTick() public view { + function test_getSqrtPriceAtTick_isLessThanJSImplMinTick() public view { // sqrt(1 / 2 ** 127) * 2 ** 96 - uint160 jsMinSqrtRatio = 6085630636; - uint160 solMinSqrtRatio = tickMath.getSqrtRatioAtTick(MIN_TICK); - assertLt(solMinSqrtRatio, jsMinSqrtRatio); + uint160 jsMinSqrtPrice = 6085630636; + uint160 solMinSqrtPrice = tickMath.getSqrtPriceAtTick(MIN_TICK); + assertLt(solMinSqrtPrice, jsMinSqrtPrice); } - function test_getSqrtRatioAtTick_isGreaterThanJSImplMaxTick() public view { + function test_getSqrtPriceAtTick_isGreaterThanJSImplMaxTick() public view { // sqrt(2 ** 127) * 2 ** 96 - uint160 jsMaxSqrtRatio = 1033437718471923706666374484006904511252097097914; - uint160 solMaxSqrtRatio = tickMath.getSqrtRatioAtTick(MAX_TICK); - assertGt(solMaxSqrtRatio, jsMaxSqrtRatio); + uint160 jsMaxSqrtPrice = 1033437718471923706666374484006904511252097097914; + uint160 solMaxSqrtPrice = tickMath.getSqrtPriceAtTick(MAX_TICK); + assertGt(solMaxSqrtPrice, jsMaxSqrtPrice); } - function test_getTickAtSqrtRatio_throwsForTooLow() public { - vm.expectRevert(TickMath.InvalidSqrtRatio.selector); - tickMath.getTickAtSqrtRatio(MIN_SQRT_RATIO - 1); + function test_getTickAtSqrtPrice_throwsForTooLow() public { + vm.expectRevert(TickMath.InvalidSqrtPrice.selector); + tickMath.getTickAtSqrtPrice(MIN_SQRT_PRICE - 1); } - function test_getTickAtSqrtRatio_throwsForTooHigh() public { - vm.expectRevert(TickMath.InvalidSqrtRatio.selector); - tickMath.getTickAtSqrtRatio(MAX_SQRT_RATIO + 1); + function test_getTickAtSqrtPrice_throwsForTooHigh() public { + vm.expectRevert(TickMath.InvalidSqrtPrice.selector); + tickMath.getTickAtSqrtPrice(MAX_SQRT_PRICE + 1); } - function test_getTickAtSqrtRatio_isValidMinSqrtRatio() public view { - assertEq(tickMath.getTickAtSqrtRatio(MIN_SQRT_RATIO), MIN_TICK); + function test_getTickAtSqrtPrice_isValidMinSqrtPrice() public view { + assertEq(tickMath.getTickAtSqrtPrice(MIN_SQRT_PRICE), MIN_TICK); } - function test_getTickAtSqrtRatio_isValidMinSqrtRatioPlusOne() public view { - assertEq(tickMath.getTickAtSqrtRatio(4295343490), MIN_TICK + 1); + function test_getTickAtSqrtPrice_isValidMinSqrtPricePlusOne() public view { + assertEq(tickMath.getTickAtSqrtPrice(4295343490), MIN_TICK + 1); } - function test_getTickAtSqrtRatio_isValidRatioClosestToMaxTick() public view { - assertEq(tickMath.getTickAtSqrtRatio(MAX_SQRT_RATIO - 1), MAX_TICK - 1); + function test_getTickAtSqrtPrice_isValidPriceClosestToMaxTick() public view { + assertEq(tickMath.getTickAtSqrtPrice(MAX_SQRT_PRICE - 1), MAX_TICK - 1); } - function test_getTickAtSqrtRatio_isValidMaxSqrtRatioMinusOne() public view { - assertEq(tickMath.getTickAtSqrtRatio(1461373636630004318706518188784493106690254656249), MAX_TICK - 1); + function test_getTickAtSqrtPrice_isValidMaxSqrtPriceMinusOne() public view { + assertEq(tickMath.getTickAtSqrtPrice(1461373636630004318706518188784493106690254656249), MAX_TICK - 1); } - function test_getSqrtRatioAtTick_matchesJavaScriptImplByOneHundrethOfABip() public { + function test_getSqrtPriceAtTick_matchesJavaScriptImplByOneHundrethOfABip() public { string memory jsParameters = ""; int24 tick = 50; @@ -126,47 +126,47 @@ contract TickMathTestTest is Test, JavascriptFfi { // add tick to javascript parameters to be calculated inside script jsParameters = string(abi.encodePacked(jsParameters, vm.toString(int256(tick)))); // track solidity result for tick - getSqrtRatioAtTickFuzzResults.push(tickMath.getSqrtRatioAtTick(tick)); + getSqrtPriceAtTickFuzzResults.push(tickMath.getSqrtPriceAtTick(tick)); } tick = tick * 2; } - bytes memory jsResult = runScript("forge-test-getSqrtRatioAtTick", jsParameters); - uint160[] memory jsSqrtRatios = abi.decode(jsResult, (uint160[])); + bytes memory jsResult = runScript("forge-test-getSqrtPriceAtTick", jsParameters); + uint160[] memory jsSqrtPrices = abi.decode(jsResult, (uint160[])); - for (uint256 i = 0; i < jsSqrtRatios.length; i++) { - uint160 jsSqrtRatio = jsSqrtRatios[i]; - uint160 solResult = getSqrtRatioAtTickFuzzResults[i]; + for (uint256 i = 0; i < jsSqrtPrices.length; i++) { + uint160 jsSqrtPrice = jsSqrtPrices[i]; + uint160 solResult = getSqrtPriceAtTickFuzzResults[i]; (uint160 gtResult, uint160 ltResult) = - jsSqrtRatio > solResult ? (jsSqrtRatio, solResult) : (solResult, jsSqrtRatio); + jsSqrtPrice > solResult ? (jsSqrtPrice, solResult) : (solResult, jsSqrtPrice); uint160 resultsDiff = gtResult - ltResult; // assert solc/js result is at most off by 1/100th of a bip (aka one pip) - assertEq(resultsDiff * ONE_PIP / jsSqrtRatio, 0); + assertEq(resultsDiff * ONE_PIP / jsSqrtPrice, 0); } } - function test_getTickAtSqrtRatio_matchesJavascriptImplWithin1() public { + function test_getTickAtSqrtPrice_matchesJavascriptImplWithin1() public { string memory jsParameters = ""; - uint160 sqrtRatio = MIN_SQRT_RATIO; + uint160 sqrtPrice = MIN_SQRT_PRICE; unchecked { - while (sqrtRatio < sqrtRatio * 16) { - if (sqrtRatio != MIN_SQRT_RATIO) jsParameters = string(abi.encodePacked(jsParameters, ",")); // do not leave comma in front of first number + while (sqrtPrice < sqrtPrice * 16) { + if (sqrtPrice != MIN_SQRT_PRICE) jsParameters = string(abi.encodePacked(jsParameters, ",")); // do not leave comma in front of first number // add tick to javascript parameters to be calculated inside script - jsParameters = string(abi.encodePacked(jsParameters, vm.toString(sqrtRatio))); - // track solidity result for sqrtRatio - getTickAtSqrtRatioFuzzResults.push(tickMath.getTickAtSqrtRatio(sqrtRatio)); - sqrtRatio = sqrtRatio * 16; + jsParameters = string(abi.encodePacked(jsParameters, vm.toString(sqrtPrice))); + // track solidity result for sqrtPrice + getTickAtSqrtPriceFuzzResults.push(tickMath.getTickAtSqrtPrice(sqrtPrice)); + sqrtPrice = sqrtPrice * 16; } } - bytes memory jsResult = runScript("forge-test-getTickAtSqrtRatio", jsParameters); + bytes memory jsResult = runScript("forge-test-getTickAtSqrtPrice", jsParameters); int24[] memory jsTicks = abi.decode(jsResult, (int24[])); for (uint256 i = 0; i < jsTicks.length; i++) { int24 jsTick = jsTicks[i]; - int24 solTick = getTickAtSqrtRatioFuzzResults[i]; + int24 solTick = getTickAtSqrtPriceFuzzResults[i]; (int24 gtResult, int24 ltResult) = jsTick > solTick ? (jsTick, solTick) : (solTick, jsTick); int24 resultsDiff = gtResult - ltResult; diff --git a/test/utils/AmountHelpers.sol b/test/utils/AmountHelpers.sol index 298fd3f77..6e4f6d761 100644 --- a/test/utils/AmountHelpers.sol +++ b/test/utils/AmountHelpers.sol @@ -19,8 +19,8 @@ library AmountHelpers { uint128 liquidity = manager.getLiquidity(id); (uint160 sqrtPriceX96,,,) = manager.getSlot0(id); - uint160 sqrtPriceX96Lower = TickMath.getSqrtRatioAtTick(params.tickLower); - uint160 sqrtPriceX96Upper = TickMath.getSqrtRatioAtTick(params.tickUpper); + uint160 sqrtPriceX96Lower = TickMath.getSqrtPriceAtTick(params.tickLower); + uint160 sqrtPriceX96Upper = TickMath.getSqrtPriceAtTick(params.tickUpper); amount0 = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96Lower, sqrtPriceX96, liquidity); amount1 = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceX96Upper, sqrtPriceX96, liquidity); diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index 5df565aac..959be5ed8 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.20; library Constants { - uint160 public constant SQRT_RATIO_1_1 = 79228162514264337593543950336; - uint160 public constant SQRT_RATIO_1_2 = 56022770974786139918731938227; - uint160 public constant SQRT_RATIO_1_4 = 39614081257132168796771975168; - uint160 public constant SQRT_RATIO_2_1 = 112045541949572279837463876454; - uint160 public constant SQRT_RATIO_4_1 = 158456325028528675187087900672; - uint160 public constant SQRT_RATIO_121_100 = 87150978765690771352898345369; + uint160 public constant SQRT_PRICE_1_1 = 79228162514264337593543950336; + uint160 public constant SQRT_PRICE_1_2 = 56022770974786139918731938227; + uint160 public constant SQRT_PRICE_1_4 = 39614081257132168796771975168; + uint160 public constant SQRT_PRICE_2_1 = 112045541949572279837463876454; + uint160 public constant SQRT_PRICE_4_1 = 158456325028528675187087900672; + uint160 public constant SQRT_PRICE_121_100 = 87150978765690771352898345369; uint256 constant MAX_UINT256 = type(uint256).max; uint128 constant MAX_UINT128 = type(uint128).max; diff --git a/test/utils/Deployers.sol b/test/utils/Deployers.sol index 45a23c5ad..17b8c7b21 100644 --- a/test/utils/Deployers.sol +++ b/test/utils/Deployers.sol @@ -36,14 +36,14 @@ contract Deployers { // Helpful test constants bytes constant ZERO_BYTES = Constants.ZERO_BYTES; - uint160 constant SQRT_RATIO_1_1 = Constants.SQRT_RATIO_1_1; - uint160 constant SQRT_RATIO_1_2 = Constants.SQRT_RATIO_1_2; - uint160 constant SQRT_RATIO_2_1 = Constants.SQRT_RATIO_2_1; - uint160 constant SQRT_RATIO_1_4 = Constants.SQRT_RATIO_1_4; - uint160 constant SQRT_RATIO_4_1 = Constants.SQRT_RATIO_4_1; + uint160 constant SQRT_PRICE_1_1 = Constants.SQRT_PRICE_1_1; + uint160 constant SQRT_PRICE_1_2 = Constants.SQRT_PRICE_1_2; + uint160 constant SQRT_PRICE_2_1 = Constants.SQRT_PRICE_2_1; + uint160 constant SQRT_PRICE_1_4 = Constants.SQRT_PRICE_1_4; + uint160 constant SQRT_PRICE_4_1 = Constants.SQRT_PRICE_4_1; - uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_RATIO + 1; - uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_RATIO - 1; + uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; + uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1; IPoolManager.ModifyLiquidityParams public LIQUIDITY_PARAMS = IPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); @@ -194,10 +194,10 @@ contract Deployers { deployFreshManagerAndRouters(); // sets the global currencyies and key deployMintAndApprove2Currencies(); - (key,) = initPoolAndAddLiquidity(currency0, currency1, hooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, hooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); nestedActionRouter.executor().setKey(key); (nativeKey,) = initPoolAndAddLiquidityETH( - CurrencyLibrary.NATIVE, currency1, hooks, 3000, SQRT_RATIO_1_1, ZERO_BYTES, 1 ether + CurrencyLibrary.NATIVE, currency1, hooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES, 1 ether ); uninitializedKey = key; uninitializedNativeKey = nativeKey; diff --git a/test/utils/LiquidityAmounts.sol b/test/utils/LiquidityAmounts.sol index 6767e692c..ae83ec7b0 100644 --- a/test/utils/LiquidityAmounts.sol +++ b/test/utils/LiquidityAmounts.sol @@ -16,119 +16,119 @@ library LiquidityAmounts { /// @notice Computes the amount of liquidity received for a given amount of token0 and price range /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower)) - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary /// @param amount0 The amount0 being sent in /// @return liquidity The amount of returned liquidity - function getLiquidityForAmount0(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint256 amount0) + function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0) internal pure returns (uint128 liquidity) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96); - return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96)); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96); + return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96)); } /// @notice Computes the amount of liquidity received for a given amount of token1 and price range /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)). - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary /// @param amount1 The amount1 being sent in /// @return liquidity The amount of returned liquidity - function getLiquidityForAmount1(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint256 amount1) + function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1) internal pure returns (uint128 liquidity) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96)); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96)); } /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current /// pool prices and the prices at the tick boundaries - /// @param sqrtRatioX96 A sqrt price representing the current pool prices - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param sqrtPriceX96 A sqrt price representing the current pool prices + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary /// @param amount0 The amount of token0 being sent in /// @param amount1 The amount of token1 being sent in /// @return liquidity The maximum amount of liquidity received function getLiquidityForAmounts( - uint160 sqrtRatioX96, - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, + uint160 sqrtPriceX96, + uint160 sqrtPriceAX96, + uint160 sqrtPriceBX96, uint256 amount0, uint256 amount1 ) internal pure returns (uint128 liquidity) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); - if (sqrtRatioX96 <= sqrtRatioAX96) { - liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0); - } else if (sqrtRatioX96 < sqrtRatioBX96) { - uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0); - uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1); + if (sqrtPriceX96 <= sqrtPriceAX96) { + liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0); + } else if (sqrtPriceX96 < sqrtPriceBX96) { + uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0); + uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1); liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; } else { - liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1); + liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1); } } /// @notice Computes the amount of token0 for a given amount of liquidity and a price range - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary /// @param liquidity The liquidity being valued /// @return amount0 The amount of token0 - function getAmount0ForLiquidity(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity) + function getAmount0ForLiquidity(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity) internal pure returns (uint256 amount0) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); return FullMath.mulDiv( - uint256(liquidity) << FixedPoint96.RESOLUTION, sqrtRatioBX96 - sqrtRatioAX96, sqrtRatioBX96 - ) / sqrtRatioAX96; + uint256(liquidity) << FixedPoint96.RESOLUTION, sqrtPriceBX96 - sqrtPriceAX96, sqrtPriceBX96 + ) / sqrtPriceAX96; } /// @notice Computes the amount of token1 for a given amount of liquidity and a price range - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary /// @param liquidity The liquidity being valued /// @return amount1 The amount of token1 - function getAmount1ForLiquidity(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity) + function getAmount1ForLiquidity(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity) internal pure returns (uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); - return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + return FullMath.mulDiv(liquidity, sqrtPriceBX96 - sqrtPriceAX96, FixedPoint96.Q96); } /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current /// pool prices and the prices at the tick boundaries - /// @param sqrtRatioX96 A sqrt price representing the current pool prices - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param sqrtPriceX96 A sqrt price representing the current pool prices + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary /// @param liquidity The liquidity being valued /// @return amount0 The amount of token0 /// @return amount1 The amount of token1 function getAmountsForLiquidity( - uint160 sqrtRatioX96, - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, + uint160 sqrtPriceX96, + uint160 sqrtPriceAX96, + uint160 sqrtPriceBX96, uint128 liquidity ) internal pure returns (uint256 amount0, uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); - if (sqrtRatioX96 <= sqrtRatioAX96) { - amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); - } else if (sqrtRatioX96 < sqrtRatioBX96) { - amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity); - amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity); + if (sqrtPriceX96 <= sqrtPriceAX96) { + amount0 = getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); + } else if (sqrtPriceX96 < sqrtPriceBX96) { + amount0 = getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); + amount1 = getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); } else { - amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); + amount1 = getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); } } } From 8dfc5365d77f60db93d65a86dbb581f036d69db2 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Fri, 10 May 2024 21:06:17 -0500 Subject: [PATCH 07/10] Refactor `modifyLiquidity` function in `Pool` (#604) This change optimizes the `modifyLiquidity` function in the Pool.sol file. Local variables were moved, improving code readability and making the code more cohesive. Variables in memory were moved on the stack, improving performance. Corresponding snapshot values were also updated to reflect these changes. --- ...o already existing position with salt.snap | 2 +- .forge-snapshots/addLiquidity CA fee.snap | 2 +- .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ...new liquidity to a position with salt.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity CA fee.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- src/libraries/Pool.sol | 21 +++++++++---------- 12 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index 72bdeb87c..e621284f2 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -151456 \ No newline at end of file +151376 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index b07e289ff..fba9014b1 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -329809 \ No newline at end of file +329729 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 5f8cc2368..9d5441eb8 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -284524 \ No newline at end of file +284443 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index 4c5ad9d9e..adf7fa978 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -141632 \ No newline at end of file +141552 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index a90703535..e8f34d718 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -151432 \ No newline at end of file +151352 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index f21c7a844..2ae8907a6 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -299984 \ No newline at end of file +299904 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 5b1d58da4..3ed46617a 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -23627 \ No newline at end of file +23601 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 4c9438ea5..33c17a883 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -185379 \ No newline at end of file +185299 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 0c5ecb089..572a8d668 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -121413 \ No newline at end of file +121333 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 671b430ad..6bfbd9319 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -118188 \ No newline at end of file +118108 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index efdeffd0f..d09dbdb90 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -121401 \ No newline at end of file +121321 \ No newline at end of file diff --git a/src/libraries/Pool.sol b/src/libraries/Pool.sol index 89e97d34a..0ea9c95c0 100644 --- a/src/libraries/Pool.sol +++ b/src/libraries/Pool.sol @@ -149,8 +149,6 @@ library Pool { uint128 liquidityGrossAfterLower; bool flippedUpper; uint128 liquidityGrossAfterUpper; - uint256 feeGrowthInside0X128; - uint256 feeGrowthInside1X128; } /// @notice Effect changes to a position in a pool @@ -167,8 +165,6 @@ library Pool { int24 tickUpper = params.tickUpper; checkTicks(tickLower, tickUpper); - uint256 feesOwed0; - uint256 feesOwed1; { ModifyLiquidityState memory state; @@ -197,11 +193,17 @@ library Pool { } } - (state.feeGrowthInside0X128, state.feeGrowthInside1X128) = getFeeGrowthInside(self, tickLower, tickUpper); + { + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = + getFeeGrowthInside(self, tickLower, tickUpper); - Position.Info storage position = self.positions.get(params.owner, tickLower, tickUpper, params.salt); - (feesOwed0, feesOwed1) = - position.update(liquidityDelta, state.feeGrowthInside0X128, state.feeGrowthInside1X128); + Position.Info storage position = self.positions.get(params.owner, tickLower, tickUpper, params.salt); + (uint256 feesOwed0, uint256 feesOwed1) = + position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128); + + // Fees earned from LPing are added to the user's currency delta. + feeDelta = toBalanceDelta(feesOwed0.toInt128(), feesOwed1.toInt128()); + } // clear any tick data that is no longer needed if (liquidityDelta < 0) { @@ -246,9 +248,6 @@ library Pool { ); } } - - // Fees earned from LPing are added to the user's currency delta. - feeDelta = toBalanceDelta(feesOwed0.toInt128(), feesOwed1.toInt128()); } struct SwapCache { From 3b698eb044e8c92065ec0b53a580127ddedd3d8d Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Fri, 10 May 2024 21:35:05 -0500 Subject: [PATCH 08/10] Refactor `Position.update` (#592) The `Position.update` method in the Position.sol library has been streamlined. The `liquidityNext` variable was removed and the `self.liquidity` is now updated directly inside the condition that checks if there are any changes in liquidity. Updating `self.liquidity` directly is more efficient, simplifies the logic, and reduces the bytecode size of the library. --- ...add liquidity to already existing position with salt.snap | 2 +- .forge-snapshots/addLiquidity CA fee.snap | 2 +- .forge-snapshots/addLiquidity with empty hook.snap | 2 +- .forge-snapshots/addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- .../create new liquidity to a position with salt.snap | 2 +- .forge-snapshots/poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity CA fee.snap | 2 +- .forge-snapshots/removeLiquidity with empty hook.snap | 2 +- .forge-snapshots/removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- src/libraries/Position.sol | 5 +---- 12 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index e621284f2..52c8c8179 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -151376 \ No newline at end of file +151339 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index fba9014b1..94bcec01a 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -329729 \ No newline at end of file +329692 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index 9d5441eb8..d363ff0b4 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -284443 \ No newline at end of file +284406 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index adf7fa978..d8e053d86 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -141552 \ No newline at end of file +141515 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index e8f34d718..2c229518b 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -151352 \ No newline at end of file +151315 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index 2ae8907a6..1746ddbb6 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -299904 \ No newline at end of file +299867 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 3ed46617a..59f4e5aa7 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -23601 \ No newline at end of file +23585 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 33c17a883..98ed547cc 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -185299 \ No newline at end of file +185262 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 572a8d668..6189c11ea 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -121333 \ No newline at end of file +121296 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 6bfbd9319..395e64073 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -118108 \ No newline at end of file +118071 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index d09dbdb90..1fbde7da0 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -121321 \ No newline at end of file +121284 \ No newline at end of file diff --git a/src/libraries/Position.sol b/src/libraries/Position.sol index 13563b63f..4585cbb62 100644 --- a/src/libraries/Position.sol +++ b/src/libraries/Position.sol @@ -63,12 +63,10 @@ library Position { ) internal returns (uint256 feesOwed0, uint256 feesOwed1) { uint128 liquidity = self.liquidity; - uint128 liquidityNext; if (liquidityDelta == 0) { if (liquidity == 0) revert CannotUpdateEmptyPosition(); // disallow pokes for 0 liquidity positions - liquidityNext = liquidity; } else { - liquidityNext = LiquidityMath.addDelta(liquidity, liquidityDelta); + self.liquidity = LiquidityMath.addDelta(liquidity, liquidityDelta); } // calculate accumulated fees. overflow in the subtraction of fee growth is expected @@ -80,7 +78,6 @@ library Position { } // update the position - if (liquidityDelta != 0) self.liquidity = liquidityNext; self.feeGrowthInside0LastX128 = feeGrowthInside0X128; self.feeGrowthInside1LastX128 = feeGrowthInside1X128; } From 830dfffa01b3e108114c358780bf88d5ab9cef65 Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Fri, 10 May 2024 21:50:11 -0500 Subject: [PATCH 09/10] Make `BalanceDelta` great (#641) * Add inequality test for `BalanceDelta` Added a new function 'test_fuzz_neq' in BalanceDelta.t.sol to test the inequality operator of BalanceDelta objects. This helps in improving the robustness of our test suite. * Refactor `BalanceDelta` assembly and equality comparisons The underlying assembly of the BalanceDelta data type has been modified to enforce a better memory safety. Equality and not-equality comparisons methods are now using .unwrap() for cleaner and more efficient comparison between BalanceDelta instances. * Optimize `add` * Optimize `sub` * Remove unnecessary "memory-safe-assembly" comments from BalanceDelta.sol * Make `amount0` and `amount1` bulletproof Assembly operations have been modified in BalanceDelta.sol for better efficiency and accuracy. Specifically, 'shr' has been replaced with 'sar' in the amount0 function and 'signextend' has been added in the amount1 function. * Address review comments --- ...o already existing position with salt.snap | 2 +- .forge-snapshots/addLiquidity CA fee.snap | 2 +- .../addLiquidity with empty hook.snap | 2 +- .../addLiquidity with native token.snap | 2 +- .forge-snapshots/addLiquidity.snap | 2 +- ...new liquidity to a position with salt.snap | 2 +- .forge-snapshots/donate gas with 1 token.snap | 2 +- .../donate gas with 2 tokens.snap | 2 +- .../poolManager bytecode size.snap | 2 +- .forge-snapshots/removeLiquidity CA fee.snap | 2 +- .../removeLiquidity with empty hook.snap | 2 +- .../removeLiquidity with native token.snap | 2 +- .forge-snapshots/removeLiquidity.snap | 2 +- .forge-snapshots/simple swap with native.snap | 2 +- .forge-snapshots/simple swap.snap | 2 +- .../swap CA custom curve + swap noop.snap | 2 +- .../swap CA fee on unspecified.snap | 2 +- ...p against liquidity with native token.snap | 2 +- .forge-snapshots/swap against liquidity.snap | 2 +- .../swap burn 6909 for input.snap | 2 +- .../swap burn native 6909 for input.snap | 2 +- .../swap mint native output as 6909.snap | 2 +- .../swap mint output as 6909.snap | 2 +- ...wap skips hook call if hook is caller.snap | 2 +- .forge-snapshots/swap with dynamic fee.snap | 2 +- .forge-snapshots/swap with hooks.snap | 2 +- .../swap with lp fee and protocol fee.snap | 2 +- .../update dynamic fee in before swap.snap | 2 +- src/types/BalanceDelta.sol | 42 ++++++++++++++----- test/types/BalanceDelta.t.sol | 6 +++ 30 files changed, 66 insertions(+), 38 deletions(-) diff --git a/.forge-snapshots/add liquidity to already existing position with salt.snap b/.forge-snapshots/add liquidity to already existing position with salt.snap index 52c8c8179..57db8502d 100644 --- a/.forge-snapshots/add liquidity to already existing position with salt.snap +++ b/.forge-snapshots/add liquidity to already existing position with salt.snap @@ -1 +1 @@ -151339 \ No newline at end of file +151132 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity CA fee.snap b/.forge-snapshots/addLiquidity CA fee.snap index 94bcec01a..71d1ed3ad 100644 --- a/.forge-snapshots/addLiquidity CA fee.snap +++ b/.forge-snapshots/addLiquidity CA fee.snap @@ -1 +1 @@ -329692 \ No newline at end of file +329498 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with empty hook.snap b/.forge-snapshots/addLiquidity with empty hook.snap index d363ff0b4..2fbc5708e 100644 --- a/.forge-snapshots/addLiquidity with empty hook.snap +++ b/.forge-snapshots/addLiquidity with empty hook.snap @@ -1 +1 @@ -284406 \ No newline at end of file +284133 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity with native token.snap b/.forge-snapshots/addLiquidity with native token.snap index d8e053d86..b0f1b6580 100644 --- a/.forge-snapshots/addLiquidity with native token.snap +++ b/.forge-snapshots/addLiquidity with native token.snap @@ -1 +1 @@ -141515 \ No newline at end of file +141308 \ No newline at end of file diff --git a/.forge-snapshots/addLiquidity.snap b/.forge-snapshots/addLiquidity.snap index 2c229518b..b12a0aeb1 100644 --- a/.forge-snapshots/addLiquidity.snap +++ b/.forge-snapshots/addLiquidity.snap @@ -1 +1 @@ -151315 \ No newline at end of file +151108 \ No newline at end of file diff --git a/.forge-snapshots/create new liquidity to a position with salt.snap b/.forge-snapshots/create new liquidity to a position with salt.snap index 1746ddbb6..8811db740 100644 --- a/.forge-snapshots/create new liquidity to a position with salt.snap +++ b/.forge-snapshots/create new liquidity to a position with salt.snap @@ -1 +1 @@ -299867 \ No newline at end of file +299660 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 1 token.snap b/.forge-snapshots/donate gas with 1 token.snap index ba015f0c8..835237096 100644 --- a/.forge-snapshots/donate gas with 1 token.snap +++ b/.forge-snapshots/donate gas with 1 token.snap @@ -1 +1 @@ -108879 \ No newline at end of file +108887 \ No newline at end of file diff --git a/.forge-snapshots/donate gas with 2 tokens.snap b/.forge-snapshots/donate gas with 2 tokens.snap index a988246a9..f89390d96 100644 --- a/.forge-snapshots/donate gas with 2 tokens.snap +++ b/.forge-snapshots/donate gas with 2 tokens.snap @@ -1 +1 @@ -149374 \ No newline at end of file +149382 \ No newline at end of file diff --git a/.forge-snapshots/poolManager bytecode size.snap b/.forge-snapshots/poolManager bytecode size.snap index 59f4e5aa7..97c57bf50 100644 --- a/.forge-snapshots/poolManager bytecode size.snap +++ b/.forge-snapshots/poolManager bytecode size.snap @@ -1 +1 @@ -23585 \ No newline at end of file +23529 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity CA fee.snap b/.forge-snapshots/removeLiquidity CA fee.snap index 98ed547cc..5f9b1f75e 100644 --- a/.forge-snapshots/removeLiquidity CA fee.snap +++ b/.forge-snapshots/removeLiquidity CA fee.snap @@ -1 +1 @@ -185262 \ No newline at end of file +185071 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with empty hook.snap b/.forge-snapshots/removeLiquidity with empty hook.snap index 6189c11ea..b7f06d78c 100644 --- a/.forge-snapshots/removeLiquidity with empty hook.snap +++ b/.forge-snapshots/removeLiquidity with empty hook.snap @@ -1 +1 @@ -121296 \ No newline at end of file +121089 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity with native token.snap b/.forge-snapshots/removeLiquidity with native token.snap index 395e64073..3524fddc6 100644 --- a/.forge-snapshots/removeLiquidity with native token.snap +++ b/.forge-snapshots/removeLiquidity with native token.snap @@ -1 +1 @@ -118071 \ No newline at end of file +117864 \ No newline at end of file diff --git a/.forge-snapshots/removeLiquidity.snap b/.forge-snapshots/removeLiquidity.snap index 1fbde7da0..2ffa02bf4 100644 --- a/.forge-snapshots/removeLiquidity.snap +++ b/.forge-snapshots/removeLiquidity.snap @@ -1 +1 @@ -121284 \ No newline at end of file +121077 \ No newline at end of file diff --git a/.forge-snapshots/simple swap with native.snap b/.forge-snapshots/simple swap with native.snap index 096d7a870..eca48267a 100644 --- a/.forge-snapshots/simple swap with native.snap +++ b/.forge-snapshots/simple swap with native.snap @@ -1 +1 @@ -131137 \ No newline at end of file +131040 \ No newline at end of file diff --git a/.forge-snapshots/simple swap.snap b/.forge-snapshots/simple swap.snap index 6aff0f3a3..095efb63e 100644 --- a/.forge-snapshots/simple swap.snap +++ b/.forge-snapshots/simple swap.snap @@ -1 +1 @@ -149496 \ No newline at end of file +149399 \ No newline at end of file diff --git a/.forge-snapshots/swap CA custom curve + swap noop.snap b/.forge-snapshots/swap CA custom curve + swap noop.snap index 7ff2ada5b..186a55e8b 100644 --- a/.forge-snapshots/swap CA custom curve + swap noop.snap +++ b/.forge-snapshots/swap CA custom curve + swap noop.snap @@ -1 +1 @@ -139532 \ No newline at end of file +139399 \ No newline at end of file diff --git a/.forge-snapshots/swap CA fee on unspecified.snap b/.forge-snapshots/swap CA fee on unspecified.snap index e036c50b1..221e1f23e 100644 --- a/.forge-snapshots/swap CA fee on unspecified.snap +++ b/.forge-snapshots/swap CA fee on unspecified.snap @@ -1 +1 @@ -185012 \ No newline at end of file +184879 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity with native token.snap b/.forge-snapshots/swap against liquidity with native token.snap index b61a66610..c5a1d73a5 100644 --- a/.forge-snapshots/swap against liquidity with native token.snap +++ b/.forge-snapshots/swap against liquidity with native token.snap @@ -1 +1 @@ -114032 \ No newline at end of file +113935 \ No newline at end of file diff --git a/.forge-snapshots/swap against liquidity.snap b/.forge-snapshots/swap against liquidity.snap index 6944aef44..7702e4fff 100644 --- a/.forge-snapshots/swap against liquidity.snap +++ b/.forge-snapshots/swap against liquidity.snap @@ -1 +1 @@ -125410 \ No newline at end of file +125313 \ No newline at end of file diff --git a/.forge-snapshots/swap burn 6909 for input.snap b/.forge-snapshots/swap burn 6909 for input.snap index 08df3eac1..19f777efe 100644 --- a/.forge-snapshots/swap burn 6909 for input.snap +++ b/.forge-snapshots/swap burn 6909 for input.snap @@ -1 +1 @@ -137415 \ No newline at end of file +137318 \ No newline at end of file diff --git a/.forge-snapshots/swap burn native 6909 for input.snap b/.forge-snapshots/swap burn native 6909 for input.snap index d00043654..d4bfbdca3 100644 --- a/.forge-snapshots/swap burn native 6909 for input.snap +++ b/.forge-snapshots/swap burn native 6909 for input.snap @@ -1 +1 @@ -126492 \ No newline at end of file +126418 \ No newline at end of file diff --git a/.forge-snapshots/swap mint native output as 6909.snap b/.forge-snapshots/swap mint native output as 6909.snap index 3e4401fe3..f97aa25f1 100644 --- a/.forge-snapshots/swap mint native output as 6909.snap +++ b/.forge-snapshots/swap mint native output as 6909.snap @@ -1 +1 @@ -148527 \ No newline at end of file +148453 \ No newline at end of file diff --git a/.forge-snapshots/swap mint output as 6909.snap b/.forge-snapshots/swap mint output as 6909.snap index 608305d88..d706786b3 100644 --- a/.forge-snapshots/swap mint output as 6909.snap +++ b/.forge-snapshots/swap mint output as 6909.snap @@ -1 +1 @@ -165445 \ No newline at end of file +165348 \ No newline at end of file diff --git a/.forge-snapshots/swap skips hook call if hook is caller.snap b/.forge-snapshots/swap skips hook call if hook is caller.snap index c9ba8fd96..b7c3d28f5 100644 --- a/.forge-snapshots/swap skips hook call if hook is caller.snap +++ b/.forge-snapshots/swap skips hook call if hook is caller.snap @@ -1 +1 @@ -225236 \ No newline at end of file +225042 \ No newline at end of file diff --git a/.forge-snapshots/swap with dynamic fee.snap b/.forge-snapshots/swap with dynamic fee.snap index dff261c55..c115de177 100644 --- a/.forge-snapshots/swap with dynamic fee.snap +++ b/.forge-snapshots/swap with dynamic fee.snap @@ -1 +1 @@ -149712 \ No newline at end of file +149615 \ No newline at end of file diff --git a/.forge-snapshots/swap with hooks.snap b/.forge-snapshots/swap with hooks.snap index 81c4e9eb1..46fbfabfa 100644 --- a/.forge-snapshots/swap with hooks.snap +++ b/.forge-snapshots/swap with hooks.snap @@ -1 +1 @@ -125422 \ No newline at end of file +125325 \ No newline at end of file diff --git a/.forge-snapshots/swap with lp fee and protocol fee.snap b/.forge-snapshots/swap with lp fee and protocol fee.snap index f3266fd4a..0561b4e61 100644 --- a/.forge-snapshots/swap with lp fee and protocol fee.snap +++ b/.forge-snapshots/swap with lp fee and protocol fee.snap @@ -1 +1 @@ -182041 \ No newline at end of file +181967 \ No newline at end of file diff --git a/.forge-snapshots/update dynamic fee in before swap.snap b/.forge-snapshots/update dynamic fee in before swap.snap index 9dbfcd03f..1a498be51 100644 --- a/.forge-snapshots/update dynamic fee in before swap.snap +++ b/.forge-snapshots/update dynamic fee in before swap.snap @@ -1 +1 @@ -160355 \ No newline at end of file +160258 \ No newline at end of file diff --git a/src/types/BalanceDelta.sol b/src/types/BalanceDelta.sol index b807ce410..c7bef7c39 100644 --- a/src/types/BalanceDelta.sol +++ b/src/types/BalanceDelta.sol @@ -1,48 +1,70 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {SafeCast} from "../libraries/SafeCast.sol"; + +/// @dev Two `int128` values packed into a single `int256` where the upper 128 bits represent the amount0 +/// and the lower 128 bits represent the amount1. type BalanceDelta is int256; using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global; using BalanceDeltaLibrary for BalanceDelta global; +using SafeCast for int256; function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) { - /// @solidity memory-safe-assembly assembly { - balanceDelta := or(shl(128, _amount0), and(0xffffffffffffffffffffffffffffffff, _amount1)) + balanceDelta := or(shl(128, _amount0), and(sub(shl(128, 1), 1), _amount1)) } } function add(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) { - return toBalanceDelta(a.amount0() + b.amount0(), a.amount1() + b.amount1()); + int256 res0; + int256 res1; + assembly { + let a0 := sar(128, a) + let a1 := signextend(15, a) + let b0 := sar(128, b) + let b1 := signextend(15, b) + res0 := add(a0, b0) + res1 := add(a1, b1) + } + return toBalanceDelta(res0.toInt128(), res1.toInt128()); } function sub(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) { - return toBalanceDelta(a.amount0() - b.amount0(), a.amount1() - b.amount1()); + int256 res0; + int256 res1; + assembly { + let a0 := sar(128, a) + let a1 := signextend(15, a) + let b0 := sar(128, b) + let b1 := signextend(15, b) + res0 := sub(a0, b0) + res1 := sub(a1, b1) + } + return toBalanceDelta(res0.toInt128(), res1.toInt128()); } function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) { - return a.amount0() == b.amount0() && a.amount1() == b.amount1(); + return BalanceDelta.unwrap(a) == BalanceDelta.unwrap(b); } function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) { - return a.amount0() != b.amount0() || a.amount1() != b.amount1(); + return BalanceDelta.unwrap(a) != BalanceDelta.unwrap(b); } library BalanceDeltaLibrary { BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0); function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) { - /// @solidity memory-safe-assembly assembly { - _amount0 := shr(128, balanceDelta) + _amount0 := sar(128, balanceDelta) } } function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) { - /// @solidity memory-safe-assembly assembly { - _amount1 := balanceDelta + _amount1 := signextend(15, balanceDelta) } } } diff --git a/test/types/BalanceDelta.t.sol b/test/types/BalanceDelta.t.sol index eaeae2de5..d79b8fcbc 100644 --- a/test/types/BalanceDelta.t.sol +++ b/test/types/BalanceDelta.t.sol @@ -130,4 +130,10 @@ contract TestBalanceDelta is Test { if (a == c && b == d) assertTrue(isEqual); else assertFalse(isEqual); } + + function test_fuzz_neq(int128 a, int128 b, int128 c, int128 d) public pure { + bool isNotEqual = (toBalanceDelta(a, b) != toBalanceDelta(c, d)); + if (a != c || b != d) assertTrue(isNotEqual); + else assertFalse(isNotEqual); + } } From 616e98882ccc0dc026553173210e5a4bc7f6744a Mon Sep 17 00:00:00 2001 From: Shuhui Luo <107524008+shuhuiluo@users.noreply.github.com> Date: Fri, 10 May 2024 21:55:58 -0500 Subject: [PATCH 10/10] fix: Replace `shr` with `sar` for integer types (#586) * fix: Replace `shr` with `sar` for integer types This commit updates the `shr` instruction to `sar` in the BalanceDelta and Pool contracts, as `sar` is the correct way to shift right when dealing with signed integers. This change ensures that the sign is preserved during bit shifting operations, preventing any potential bugs related to negative amounts or liquidity values. * Add a fuzz test for `Pool.updateTick` This commit introduces a fuzz test for the `Pool.updateTick` function to ensure proper functionality even when fed with diverse/random data. It also includes the creation of a `LiquidityMathRef` contract which houses the helper methods utilized in this fuzz testing. Any occurrence of an arithmetic error during testing is adequately caught and asserted. * Use `signextend` in `BalanceDeltaLibrary.amount1` --- src/libraries/Pool.sol | 4 +-- test/Tick.t.sol | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/libraries/Pool.sol b/src/libraries/Pool.sol index 0ea9c95c0..d3f55d49f 100644 --- a/src/libraries/Pool.sol +++ b/src/libraries/Pool.sol @@ -536,8 +536,8 @@ library Pool { let liquidity := sload(info.slot) // slice off top 128 bits of liquidity (liquidityNet) to get just liquidityGross liquidityGrossBefore := shr(128, shl(128, liquidity)) - // shift right 128 bits to get just liquidityNet - liquidityNetBefore := shr(128, liquidity) + // signed shift right 128 bits to get just liquidityNet + liquidityNetBefore := sar(128, liquidity) } liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta); diff --git a/test/Tick.t.sol b/test/Tick.t.sol index 22c765bde..205f8cb84 100644 --- a/test/Tick.t.sol +++ b/test/Tick.t.sol @@ -2,12 +2,27 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; +import {stdError} from "forge-std/StdError.sol"; import {GasSnapshot} from "../lib/forge-gas-snapshot/src/GasSnapshot.sol"; import {Constants} from "./utils/Constants.sol"; import {Pool} from "../src/libraries/Pool.sol"; import {TickMath} from "../src/libraries/TickMath.sol"; import {PoolGetters} from "../src/libraries/PoolGetters.sol"; +contract LiquidityMathRef { + function addDelta(uint128 x, int128 y) external pure returns (uint128) { + return y < 0 ? x - uint128(-y) : x + uint128(y); + } + + function addDelta(bool upper, int128 liquidityNetBefore, int128 liquidityDelta) + external + pure + returns (int128 liquidityNet) + { + liquidityNet = upper ? liquidityNetBefore - liquidityDelta : liquidityNetBefore + liquidityDelta; + } +} + contract TickTest is Test, GasSnapshot { using PoolGetters for Pool.State; using Pool for Pool.State; @@ -18,6 +33,12 @@ contract TickTest is Test, GasSnapshot { Pool.State public pool; + LiquidityMathRef internal liquidityMath; + + function setUp() public { + liquidityMath = new LiquidityMathRef(); + } + function ticks(int24 tick) internal view returns (Pool.TickInfo memory) { return pool.ticks[tick]; } @@ -408,6 +429,40 @@ contract TickTest is Test, GasSnapshot { assertEq(info.liquidityNet, int128(Constants.MAX_UINT128 / 2)); } + function testTick_update_fuzz(uint128 liquidityGross, int128 liquidityNet, int128 liquidityDelta, bool upper) + public + { + try liquidityMath.addDelta(liquidityGross, liquidityDelta) returns (uint128 liquidityGrossAfter) { + try liquidityMath.addDelta(upper, liquidityNet, liquidityDelta) returns (int128 liquidityNetAfter) { + Pool.TickInfo memory info = Pool.TickInfo({ + liquidityGross: liquidityGross, + liquidityNet: liquidityNet, + feeGrowthOutside0X128: 0, + feeGrowthOutside1X128: 0 + }); + + setTick(2, info); + update({ + tick: 2, + tickCurrent: 1, + liquidityDelta: liquidityDelta, + feeGrowthGlobal0X128: 0, + feeGrowthGlobal1X128: 0, + upper: upper + }); + + info = ticks(2); + + assertEq(info.liquidityGross, liquidityGrossAfter); + assertEq(info.liquidityNet, liquidityNetAfter); + } catch (bytes memory reason) { + assertEq(reason, stdError.arithmeticError); + } + } catch (bytes memory reason) { + assertEq(reason, stdError.arithmeticError); + } + } + function testTick_clear_deletesAllTheDataInTheTick() public { Pool.TickInfo memory info;