From 128aacf783a4e53993353d89cc54750f1d85a171 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Sun, 14 Jul 2024 23:34:48 -0400 Subject: [PATCH 01/52] create outline --- contracts/middleware/MiddlewareProtect.sol | 56 +++++ .../middleware/MiddlewareProtectFactory.sol | 14 ++ test/MiddlewareProtectFactory.t.sol | 202 ++++++++++++++++++ test/MiddlewareRemoveFactory.t.sol | 25 ++- test/middleware/HooksFrontrun.sol | 89 ++++++++ .../{RemoveOutOfGas.sol => HooksOutOfGas.sol} | 25 ++- test/middleware/HooksReturnDeltas.sol | 54 +++++ .../{RemoveReverts.sol => HooksRevert.sol} | 23 +- 8 files changed, 473 insertions(+), 15 deletions(-) create mode 100644 contracts/middleware/MiddlewareProtect.sol create mode 100644 contracts/middleware/MiddlewareProtectFactory.sol create mode 100644 test/MiddlewareProtectFactory.t.sol create mode 100644 test/middleware/HooksFrontrun.sol rename test/middleware/{RemoveOutOfGas.sol => HooksOutOfGas.sol} (73%) create mode 100644 test/middleware/HooksReturnDeltas.sol rename test/middleware/{RemoveReverts.sol => HooksRevert.sol} (75%) diff --git a/contracts/middleware/MiddlewareProtect.sol b/contracts/middleware/MiddlewareProtect.sol new file mode 100644 index 00000000..0521b454 --- /dev/null +++ b/contracts/middleware/MiddlewareProtect.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; +import {BaseHook} from "../BaseHook.sol"; +import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; +import {NonZeroDeltaCount} from "@uniswap/v4-core/src/libraries/NonZeroDeltaCount.sol"; +import {IExttload} from "@uniswap/v4-core/src/interfaces/IExttload.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {MiddlewareRemove} from "./MiddlewareRemove.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; + +contract MiddlewareProtect is MiddlewareRemove { + using CustomRevert for bytes4; + using Hooks for IHooks; + using StateLibrary for IPoolManager; + using PoolIdLibrary for PoolKey; + + constructor(IPoolManager _manager, address _impl) MiddlewareRemove(_manager, _impl) { + IHooks middleware = IHooks(address(this)); + if ( + middleware.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG) + || middleware.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG) + || middleware.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + || middleware.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) { + HookPermissionForbidden.selector.revertWith(address(this)); + } + } + + // function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + // external + // returns (bytes4, BeforeSwapDelta, uint24) + // { + // return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + // } + + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4) { + address(this).delegatecall{gas: GAS_LIMIT}( + abi.encodeWithSelector(this._callAndEnsurePrice.selector, sender, key, params, hookData) + ); + return BaseHook.beforeAddLiquidity.selector; + } +} diff --git a/contracts/middleware/MiddlewareProtectFactory.sol b/contracts/middleware/MiddlewareProtectFactory.sol new file mode 100644 index 00000000..42a12554 --- /dev/null +++ b/contracts/middleware/MiddlewareProtectFactory.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {BaseMiddlewareFactory} from "./BaseMiddlewareFactory.sol"; +import {MiddlewareProtect} from "./MiddlewareProtect.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +contract MiddlewareProtectFactory is BaseMiddlewareFactory { + constructor(IPoolManager _manager) BaseMiddlewareFactory(_manager) {} + + function _deployMiddleware(address implementation, bytes32 salt) internal override returns (address middleware) { + return address(new MiddlewareProtect{salt: salt}(manager, implementation)); + } +} diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol new file mode 100644 index 00000000..a64d302a --- /dev/null +++ b/test/MiddlewareProtectFactory.t.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {HooksFrontrun} from "./middleware/HooksFrontrun.sol"; +import {MiddlewareRemove} from "../contracts/middleware/MiddlewareRemove.sol"; +import {MiddlewareProtect} from "../contracts/middleware/MiddlewareProtect.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {console} from "../../../lib/forge-std/src/console.sol"; +import {HooksRevert} from "./middleware/HooksRevert.sol"; +import {HooksOutOfGas} from "./middleware/HooksOutOfGas.sol"; +import {MiddlewareProtectFactory} from "./../contracts/middleware/MiddlewareProtectFactory.sol"; +import {HookMiner} from "./utils/HookMiner.sol"; +import {HooksReturnDeltas} from "./middleware/HooksReturnDeltas.sol"; +import {Counter} from "./middleware/Counter.sol"; +import {SafeCallback} from "./../contracts/base/SafeCallback.sol"; + +contract MiddlewareProtectFactoryTest is Test, Deployers { + HookEnabledSwapRouter router; + TestERC20 token0; + TestERC20 token1; + + MiddlewareProtectFactory factory; + Counter counter; + address middleware; + HooksFrontrun hooksFrontrun; + + function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + + router = new HookEnabledSwapRouter(manager); + token0 = TestERC20(Currency.unwrap(currency0)); + token1 = TestERC20(Currency.unwrap(currency1)); + + factory = new MiddlewareProtectFactory(manager); + counter = new Counter(manager); + + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); + + uint160 flags = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG + | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG + ); + + (address hookAddress, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(counter)) + ); + middleware = factory.createMiddleware(address(counter), salt); + assertEq(hookAddress, middleware); + + flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG); + hooksFrontrun = HooksFrontrun(address(uint160(flags))); + HooksFrontrun impl = new HooksFrontrun(manager); + vm.etch(address(hooksFrontrun), address(impl).code); + } + + function testRevertOnDeltas() public { + HooksReturnDeltas hooksReturnDeltas = new HooksReturnDeltas(manager); + uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG); + + (address hookAddress, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(hooksReturnDeltas)) + ); + address implementation = address(hooksReturnDeltas); + vm.expectRevert(abi.encodePacked(bytes16(MiddlewareRemove.HookPermissionForbidden.selector), hookAddress)); + factory.createMiddleware(implementation, salt); + } + + function testFrontrun() public { + return; + (PoolKey memory key,) = + initPoolAndAddLiquidity(currency0, currency1, IHooks(address(0)), 100, SQRT_PRICE_1_1, ZERO_BYTES); + BalanceDelta swapDelta = swap(key, true, 0.001 ether, ZERO_BYTES); + + (key,) = initPoolAndAddLiquidity( + currency0, currency1, IHooks(address(hooksFrontrun)), 100, SQRT_PRICE_1_1, ZERO_BYTES + ); + BalanceDelta swapDelta2 = swap(key, true, 0.001 ether, ZERO_BYTES); + + // while both swaps are in the same pool, the second swap is more expensive + assertEq(swapDelta.amount1(), swapDelta2.amount1()); + assertTrue(abs(swapDelta.amount0()) < abs(swapDelta2.amount0())); + assertTrue(manager.balanceOf(address(hooksFrontrun), CurrencyLibrary.toId(key.currency0)) > 0); + } + + function testRevertOnFrontrun() public { + return; + uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG); + + (address hookAddress, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(hooksFrontrun)) + ); + address implementation = address(hooksFrontrun); + address hookAddressCreated = factory.createMiddleware(implementation, salt); + assertEq(hookAddressCreated, hookAddress); + MiddlewareProtect middlewareProtect = MiddlewareProtect(payable(hookAddress)); + + (key,) = initPoolAndAddLiquidity( + currency0, currency1, IHooks(address(middlewareProtect)), 100, SQRT_PRICE_1_1, ZERO_BYTES + ); + //vm.expectRevert(MiddlewareProtect.ActionBetweenHook.selector); + swap(key, true, 0.001 ether, ZERO_BYTES); + } + + function abs(int256 x) internal pure returns (uint256) { + return x >= 0 ? uint256(x) : uint256(-x); + } + + // from BaseMiddlewareFactory.t.sol + function testRevertOnSameDeployment() public { + uint160 flags = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG + | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG + ); + (, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(counter)) + ); + factory.createMiddleware(address(counter), salt); + // second deployment should revert + vm.expectRevert(bytes("")); + factory.createMiddleware(address(counter), salt); + } + + function testRevertOnIncorrectFlags() public { + Counter counter2 = new Counter(manager); + uint160 flags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); + + (address hookAddress, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(counter2)) + ); + address implementation = address(counter2); + vm.expectRevert(abi.encodePacked(bytes16(Hooks.HookAddressNotValid.selector), hookAddress)); + factory.createMiddleware(implementation, salt); + } + + function testRevertOnIncorrectFlagsMined() public { + Counter counter2 = new Counter(manager); + address implementation = address(counter2); + vm.expectRevert(); // HookAddressNotValid + factory.createMiddleware(implementation, bytes32("who needs to mine a salt?")); + } + + function testRevertOnIncorrectCaller() public { + vm.expectRevert(SafeCallback.NotManager.selector); + counter.afterDonate(address(this), key, 0, 0, ZERO_BYTES); + } + + function testCounters() public { + (PoolKey memory key, PoolId id) = + initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + + Counter counterProxy = Counter(middleware); + assertEq(counterProxy.beforeInitializeCount(id), 1); + assertEq(counterProxy.afterInitializeCount(id), 1); + assertEq(counterProxy.beforeSwapCount(id), 0); + assertEq(counterProxy.afterSwapCount(id), 0); + assertEq(counterProxy.beforeAddLiquidityCount(id), 1); + assertEq(counterProxy.afterAddLiquidityCount(id), 1); + assertEq(counterProxy.beforeRemoveLiquidityCount(id), 0); + assertEq(counterProxy.afterRemoveLiquidityCount(id), 0); + assertEq(counterProxy.beforeDonateCount(id), 0); + assertEq(counterProxy.afterDonateCount(id), 0); + + assertEq(counterProxy.lastHookData(), ZERO_BYTES); + swap(key, true, 1, bytes("hi")); + assertEq(counterProxy.lastHookData(), bytes("hi")); + assertEq(counterProxy.beforeSwapCount(id), 1); + assertEq(counterProxy.afterSwapCount(id), 1); + + // counter does not store data itself + assertEq(counter.lastHookData(), bytes("")); + assertEq(counter.beforeSwapCount(id), 0); + assertEq(counter.afterSwapCount(id), 0); + } +} diff --git a/test/MiddlewareRemoveFactory.t.sol b/test/MiddlewareRemoveFactory.t.sol index 085a34db..311adb3c 100644 --- a/test/MiddlewareRemoveFactory.t.sol +++ b/test/MiddlewareRemoveFactory.t.sol @@ -16,8 +16,8 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {console} from "../../../lib/forge-std/src/console.sol"; -import {RemoveReverts} from "./middleware/RemoveReverts.sol"; -import {RemoveOutOfGas} from "./middleware/RemoveOutOfGas.sol"; +import {HooksRevert} from "./middleware/HooksRevert.sol"; +import {HooksOutOfGas} from "./middleware/HooksOutOfGas.sol"; import {MiddlewareRemoveFactory} from "./../contracts/middleware/MiddlewareRemoveFactory.sol"; import {HookMiner} from "./utils/HookMiner.sol"; import {SafeCallback} from "./../contracts/base/SafeCallback.sol"; @@ -156,24 +156,27 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { ); testOn(address(counter), salt); - RemoveReverts removeReverts = new RemoveReverts(manager); - flags = uint160(Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG); + HooksRevert hooksRevert = new HooksRevert(manager); + flags = uint160( + Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG + ); (hookAddress, salt) = HookMiner.find( address(factory), flags, type(MiddlewareRemove).creationCode, - abi.encode(address(manager), address(removeReverts)) + abi.encode(address(manager), address(hooksRevert)) ); - testOn(address(removeReverts), salt); + testOn(address(hooksRevert), salt); - RemoveOutOfGas removeOutOfGas = new RemoveOutOfGas(manager); + HooksOutOfGas hooksOutOfGas = new HooksOutOfGas(manager); (hookAddress, salt) = HookMiner.find( address(factory), flags, type(MiddlewareRemove).creationCode, - abi.encode(address(manager), address(removeOutOfGas)) + abi.encode(address(manager), address(hooksOutOfGas)) ); - testOn(address(removeOutOfGas), salt); + testOn(address(hooksOutOfGas), salt); } // creates a middleware on an implementation @@ -265,5 +268,9 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { assertEq(counter.lastHookData(), bytes("")); assertEq(counter.beforeSwapCount(id), 0); assertEq(counter.afterSwapCount(id), 0); + + removeLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + assertEq(counterProxy.beforeRemoveLiquidityCount(id), 1); + assertEq(counterProxy.afterRemoveLiquidityCount(id), 1); } } diff --git a/test/middleware/HooksFrontrun.sol b/test/middleware/HooksFrontrun.sol new file mode 100644 index 00000000..edb65f72 --- /dev/null +++ b/test/middleware/HooksFrontrun.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {BaseHook} from "./../../contracts/BaseHook.sol"; + +contract HooksFrontrun is BaseHook { + using SafeCast for uint256; + + bytes internal constant ZERO_BYTES = bytes(""); + uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; + uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1; + + BalanceDelta swapDelta; + IPoolManager.SwapParams swapParams; + + constructor(IPoolManager _manager) BaseHook(_manager) {} + + // middleware implementations do not need to be mined + function validateHookAddress(BaseHook _this) internal pure override {} + + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) + external + override + onlyByManager + returns (bytes4, BeforeSwapDelta, uint24) + { + swapParams = params; + swapDelta = manager.swap(key, params, ZERO_BYTES); + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + onlyByManager + returns (bytes4, int128) + { + BalanceDelta afterDelta = manager.swap( + key, + IPoolManager.SwapParams( + !swapParams.zeroForOne, + -swapParams.amountSpecified, + swapParams.zeroForOne ? MAX_PRICE_LIMIT : MIN_PRICE_LIMIT + ), + ZERO_BYTES + ); + if (swapParams.zeroForOne) { + int256 profit = afterDelta.amount0() + swapDelta.amount0(); + if (profit > 0) { + // else hook reverts + manager.mint(address(this), key.currency0.toId(), uint256(profit)); + } + } else { + int256 profit = afterDelta.amount1() + swapDelta.amount1(); + if (profit > 0) { + // else hook reverts + manager.mint(address(this), key.currency1.toId(), uint256(profit)); + } + } + return (BaseHook.afterSwap.selector, 0); + } +} diff --git a/test/middleware/RemoveOutOfGas.sol b/test/middleware/HooksOutOfGas.sol similarity index 73% rename from test/middleware/RemoveOutOfGas.sol rename to test/middleware/HooksOutOfGas.sol index 79f03ae2..02356593 100644 --- a/test/middleware/RemoveOutOfGas.sol +++ b/test/middleware/HooksOutOfGas.sol @@ -7,9 +7,10 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; import {console} from "./../../lib/forge-gas-snapshot/lib/forge-std/src/console.sol"; -contract RemoveOutOfGas is BaseHook { +contract HooksOutOfGas is BaseHook { uint256 counter; constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} @@ -25,8 +26,8 @@ contract RemoveOutOfGas is BaseHook { afterAddLiquidity: false, beforeRemoveLiquidity: true, afterRemoveLiquidity: true, - beforeSwap: false, - afterSwap: false, + beforeSwap: true, + afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, @@ -36,6 +37,24 @@ contract RemoveOutOfGas is BaseHook { }); } + function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + consumeAllGas(); + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4, int128) + { + consumeAllGas(); + return (BaseHook.afterSwap.selector, 0); + } + function beforeRemoveLiquidity( address, PoolKey calldata, diff --git a/test/middleware/HooksReturnDeltas.sol b/test/middleware/HooksReturnDeltas.sol new file mode 100644 index 00000000..cb32f500 --- /dev/null +++ b/test/middleware/HooksReturnDeltas.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; + +contract HooksReturnDeltas is BaseHook { + constructor(IPoolManager _manager) BaseHook(_manager) {} + + // middleware implementations do not need to be mined + function validateHookAddress(BaseHook _this) internal pure override {} + + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: true, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + external + pure + override + returns (bytes4, BeforeSwapDelta, uint24) + { + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + pure + override + returns (bytes4, int128) + { + return (BaseHook.afterSwap.selector, 0); + } +} diff --git a/test/middleware/RemoveReverts.sol b/test/middleware/HooksRevert.sol similarity index 75% rename from test/middleware/RemoveReverts.sol rename to test/middleware/HooksRevert.sol index 74fbbdc3..1ebf489c 100644 --- a/test/middleware/RemoveReverts.sol +++ b/test/middleware/HooksRevert.sol @@ -7,8 +7,9 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; -contract RemoveReverts is BaseHook { +contract HooksRevert is BaseHook { error AlwaysRevert(); constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} @@ -24,8 +25,8 @@ contract RemoveReverts is BaseHook { afterAddLiquidity: false, beforeRemoveLiquidity: true, afterRemoveLiquidity: true, - beforeSwap: false, - afterSwap: false, + beforeSwap: true, + afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, @@ -35,6 +36,22 @@ contract RemoveReverts is BaseHook { }); } + function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + revert AlwaysRevert(); + } + + function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4, int128) + { + revert AlwaysRevert(); + } + function beforeRemoveLiquidity( address, PoolKey calldata, From 6801684c93f5ca60606ea05d4e8dfae5f7629115 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:32:25 -0400 Subject: [PATCH 02/52] add liquidity protection --- contracts/middleware/MiddlewareProtect.sol | 66 +++++++++++++++++--- contracts/middleware/MiddlewareRemove.sol | 22 +++---- test/MiddlewareProtectFactory.t.sol | 70 +++++++++++++++++++++- test/middleware/FrontrunAdd.sol | 64 ++++++++++++++++++++ test/middleware/FrontrunRemove.sol | 4 +- 5 files changed, 200 insertions(+), 26 deletions(-) create mode 100644 test/middleware/FrontrunAdd.sol diff --git a/contracts/middleware/MiddlewareProtect.sol b/contracts/middleware/MiddlewareProtect.sol index 0521b454..41b88d1e 100644 --- a/contracts/middleware/MiddlewareProtect.sol +++ b/contracts/middleware/MiddlewareProtect.sol @@ -16,12 +16,19 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {MiddlewareRemove} from "./MiddlewareRemove.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {console} from "./../../lib/forge-gas-snapshot/lib/forge-std/src/console.sol"; +import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; contract MiddlewareProtect is MiddlewareRemove { using CustomRevert for bytes4; using Hooks for IHooks; using StateLibrary for IPoolManager; using PoolIdLibrary for PoolKey; + using BeforeSwapDeltaLibrary for BeforeSwapDelta; + using LPFeeLibrary for uint24; + + error ForbiddenDynamicFee(); + error HookModifiedOutput(); constructor(IPoolManager _manager, address _impl) MiddlewareRemove(_manager, _impl) { IHooks middleware = IHooks(address(this)); @@ -29,18 +36,54 @@ contract MiddlewareProtect is MiddlewareRemove { middleware.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG) || middleware.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG) || middleware.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) - || middleware.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) ) { HookPermissionForbidden.selector.revertWith(address(this)); } } - // function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) - // external - // returns (bytes4, BeforeSwapDelta, uint24) - // { - // return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); - // } + function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, bytes calldata hookData) + external + returns (bytes4) + { + if (key.fee.isDynamicFee()) revert ForbiddenDynamicFee(); + (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); + return implementation.beforeInitialize(sender, key, sqrtPriceX96, hookData); + } + + function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + external + returns (bytes4, BeforeSwapDelta, uint24) + { + (bool success, bytes memory returnData) = address(this).delegatecall{gas: GAS_LIMIT}( + abi.encodeWithSelector(this._callAndEnsureOutput.selector, msg.data) + ); + if (!success) { + assembly { + revert(add(32, returnData), mload(returnData)) + } + } + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function _callAndEnsureOutput(bytes calldata data) external { + (, PoolKey memory key, IPoolManager.SwapParams memory params,) = + abi.decode(data[4:], (address, PoolKey, IPoolManager.SwapParams, bytes)); + + // todo: get quote + uint160 outputBefore = 0; + (bool success, bytes memory returnData) = address(implementation).delegatecall(data); + if (!success) { + revert FailedImplementationCall(); + } + (, BeforeSwapDelta delta,) = abi.decode(returnData, (bytes4, BeforeSwapDelta, uint24)); + console.logInt(delta.getUnspecifiedDelta()); + console.logInt(delta.getSpecifiedDelta()); + uint160 outputAfter = 0; + if (outputAfter < outputBefore) { + // purpousely revert to cause the whole hook to reset + revert HookModifiedOutput(); + } + } function beforeAddLiquidity( address sender, @@ -48,9 +91,14 @@ contract MiddlewareProtect is MiddlewareRemove { IPoolManager.ModifyLiquidityParams calldata params, bytes calldata hookData ) external returns (bytes4) { - address(this).delegatecall{gas: GAS_LIMIT}( - abi.encodeWithSelector(this._callAndEnsurePrice.selector, sender, key, params, hookData) + (bool success, bytes memory returnData) = address(this).delegatecall{gas: GAS_LIMIT}( + abi.encodeWithSelector(this._callAndEnsurePrice.selector, msg.data) ); + if (!success) { + assembly { + revert(add(32, returnData), mload(returnData)) + } + } return BaseHook.beforeAddLiquidity.selector; } } diff --git a/contracts/middleware/MiddlewareRemove.sol b/contracts/middleware/MiddlewareRemove.sol index 672aa2d7..4dd1f8ad 100644 --- a/contracts/middleware/MiddlewareRemove.sol +++ b/contracts/middleware/MiddlewareRemove.sol @@ -25,6 +25,7 @@ contract MiddlewareRemove is BaseMiddleware { error HookPermissionForbidden(address hooks); error HookModifiedPrice(); error HookModifiedDeltas(); + error FailedImplementationCall(); bytes internal constant ZERO_BYTES = bytes(""); uint256 public constant GAS_LIMIT = 10_000_000; @@ -41,24 +42,17 @@ contract MiddlewareRemove is BaseMiddleware { IPoolManager.ModifyLiquidityParams calldata params, bytes calldata hookData ) external returns (bytes4) { - (bool success,) = address(this).delegatecall{gas: GAS_LIMIT}( - abi.encodeWithSelector(this._callAndEnsurePrice.selector, sender, key, params, hookData) - ); + address(this).delegatecall{gas: GAS_LIMIT}(abi.encodeWithSelector(this._callAndEnsurePrice.selector, msg.data)); return BaseHook.beforeRemoveLiquidity.selector; } - function _callAndEnsurePrice( - address sender, - PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata params, - bytes calldata hookData - ) external { + function _callAndEnsurePrice(bytes calldata data) external { + (, PoolKey memory key,,) = abi.decode(data[4:], (address, PoolKey, IPoolManager.ModifyLiquidityParams, bytes)); + (uint160 priceBefore,,,) = manager.getSlot0(key.toId()); - (bool success,) = address(implementation).delegatecall( - abi.encodeWithSelector(this.beforeRemoveLiquidity.selector, sender, key, params, hookData) - ); + (bool success,) = address(implementation).delegatecall(data); if (!success) { - revert(); + revert FailedImplementationCall(); } (uint160 priceAfter,,,) = manager.getSlot0(key.toId()); if (priceAfter != priceBefore) { @@ -85,7 +79,7 @@ contract MiddlewareRemove is BaseMiddleware { uint256 countBefore = uint256(IExttload(address(manager)).exttload(slot)); (bool success,) = address(implementation).delegatecall(data); if (!success) { - revert(); + revert FailedImplementationCall(); } uint256 countAfter = uint256(IExttload(address(manager)).exttload(slot)); if (countAfter != countBefore) { diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index a64d302a..80aeb588 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -22,6 +22,7 @@ import {HookMiner} from "./utils/HookMiner.sol"; import {HooksReturnDeltas} from "./middleware/HooksReturnDeltas.sol"; import {Counter} from "./middleware/Counter.sol"; import {SafeCallback} from "./../contracts/base/SafeCallback.sol"; +import {FrontrunAdd} from "./middleware/FrontrunAdd.sol"; contract MiddlewareProtectFactoryTest is Test, Deployers { HookEnabledSwapRouter router; @@ -101,7 +102,6 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { } function testRevertOnFrontrun() public { - return; uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG); (address hookAddress, bytes32 salt) = HookMiner.find( @@ -118,10 +118,76 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { (key,) = initPoolAndAddLiquidity( currency0, currency1, IHooks(address(middlewareProtect)), 100, SQRT_PRICE_1_1, ZERO_BYTES ); - //vm.expectRevert(MiddlewareProtect.ActionBetweenHook.selector); + vm.expectRevert(MiddlewareProtect.HookModifiedOutput.selector); swap(key, true, 0.001 ether, ZERO_BYTES); } + function testRevertOnFailedImplementationCall() public { + HooksRevert hooksRevert = new HooksRevert(manager); + uint160 flags = uint160( + Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG + ); + (address hookAddress, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareRemove).creationCode, + abi.encode(address(manager), address(hooksRevert)) + ); + + HooksOutOfGas hooksOutOfGas = new HooksOutOfGas(manager); + (hookAddress, salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareRemove).creationCode, + abi.encode(address(manager), address(hooksOutOfGas)) + ); + } + + function testFrontrunAdd() public { + uint160 flags = uint160(Hooks.BEFORE_ADD_LIQUIDITY_FLAG); + FrontrunAdd frontrunAdd = FrontrunAdd(address(flags)); + FrontrunAdd impl = new FrontrunAdd(manager); + vm.etch(address(frontrunAdd), address(impl).code); + (, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(frontrunAdd)) + ); + middleware = factory.createMiddleware(address(frontrunAdd), salt); + currency0.transfer(address(frontrunAdd), 1 ether); + currency1.transfer(address(frontrunAdd), 1 ether); + currency0.transfer(address(middleware), 1 ether); + currency1.transfer(address(middleware), 1 ether); + + (PoolKey memory key,) = + initPoolAndAddLiquidity(currency0, currency1, IHooks(frontrunAdd), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + uint256 initialBalance0 = token0.balanceOf(address(this)); + uint256 initialBalance1 = token1.balanceOf(address(this)); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + uint256 inFrontrun0 = initialBalance0 - token0.balanceOf(address(this)); + uint256 inFrontrun1 = initialBalance1 - token1.balanceOf(address(this)); + + IHooks noHooks = IHooks(address(0)); + (key,) = initPoolAndAddLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); + initialBalance0 = token0.balanceOf(address(this)); + initialBalance1 = token1.balanceOf(address(this)); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + uint256 inNormal0 = initialBalance0 - token0.balanceOf(address(this)); + uint256 inNormal1 = initialBalance1 - token1.balanceOf(address(this)); + + // was frontrun + assertTrue(inFrontrun0 > inNormal0); + assertTrue(inFrontrun1 < inNormal1); + + initialBalance0 = token0.balanceOf(address(this)); + initialBalance1 = token1.balanceOf(address(this)); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + vm.expectRevert(MiddlewareRemove.HookModifiedPrice.selector); + modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + } + function abs(int256 x) internal pure returns (uint256) { return x >= 0 ? uint256(x) : uint256(-x); } diff --git a/test/middleware/FrontrunAdd.sol b/test/middleware/FrontrunAdd.sol new file mode 100644 index 00000000..d1649f53 --- /dev/null +++ b/test/middleware/FrontrunAdd.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; + +contract FrontrunAdd is BaseHook { + using PoolIdLibrary for PoolKey; + + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + bytes internal constant ZERO_BYTES = bytes(""); + uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; + int256 public constant SWAP_AMOUNT = 1000; + + mapping(PoolId => bool) hasLiquidity; + + // middleware implementations do not need to be mined + function validateHookAddress(BaseHook _this) internal pure override {} + + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: true, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: false, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + function beforeAddLiquidity( + address, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external override returns (bytes4) { + if (hasLiquidity[key.toId()]) { + BalanceDelta swapDelta = + manager.swap(key, IPoolManager.SwapParams(true, SWAP_AMOUNT, MIN_PRICE_LIMIT), ZERO_BYTES); + key.currency0.transfer(address(manager), uint128(-swapDelta.amount0())); + manager.settle(key.currency0); + manager.take(key.currency1, address(this), uint128(swapDelta.amount1())); + } else { + hasLiquidity[key.toId()] = true; + } + return IHooks.beforeAddLiquidity.selector; + } +} diff --git a/test/middleware/FrontrunRemove.sol b/test/middleware/FrontrunRemove.sol index 4a48368a..244a4eb7 100644 --- a/test/middleware/FrontrunRemove.sol +++ b/test/middleware/FrontrunRemove.sol @@ -15,6 +15,7 @@ contract FrontrunRemove is BaseHook { bytes internal constant ZERO_BYTES = bytes(""); uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; + int256 public constant SWAP_AMOUNT = 1000; // middleware implementations do not need to be mined function validateHookAddress(BaseHook _this) internal pure override {} @@ -44,7 +45,8 @@ contract FrontrunRemove is BaseHook { IPoolManager.ModifyLiquidityParams calldata, bytes calldata ) external override returns (bytes4) { - BalanceDelta swapDelta = manager.swap(key, IPoolManager.SwapParams(true, 1000, MIN_PRICE_LIMIT), ZERO_BYTES); + BalanceDelta swapDelta = + manager.swap(key, IPoolManager.SwapParams(true, SWAP_AMOUNT, MIN_PRICE_LIMIT), ZERO_BYTES); key.currency0.transfer(address(manager), uint128(-swapDelta.amount0())); manager.settle(key.currency0); manager.take(key.currency1, address(this), uint128(swapDelta.amount1())); From 5e6c2aee2fc620776360e96d289817f3e3b44091 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:27:28 -0400 Subject: [PATCH 03/52] add quoter check in beforeswap --- contracts/middleware/MiddlewareProtect.sol | 63 ++++++++++++++-------- test/MiddlewareProtectFactory.t.sol | 24 ++++++++- test/MiddlewareRemoveFactory.t.sol | 32 +++++------ test/utils/HookMiner.sol | 2 +- 4 files changed, 82 insertions(+), 39 deletions(-) diff --git a/contracts/middleware/MiddlewareProtect.sol b/contracts/middleware/MiddlewareProtect.sol index 41b88d1e..beee46a6 100644 --- a/contracts/middleware/MiddlewareProtect.sol +++ b/contracts/middleware/MiddlewareProtect.sol @@ -18,6 +18,7 @@ import {MiddlewareRemove} from "./MiddlewareRemove.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; import {console} from "./../../lib/forge-gas-snapshot/lib/forge-std/src/console.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; contract MiddlewareProtect is MiddlewareRemove { using CustomRevert for bytes4; @@ -26,10 +27,17 @@ contract MiddlewareProtect is MiddlewareRemove { using PoolIdLibrary for PoolKey; using BeforeSwapDeltaLibrary for BeforeSwapDelta; using LPFeeLibrary for uint24; + using BalanceDeltaLibrary for BalanceDelta; error ForbiddenDynamicFee(); error HookModifiedOutput(); + // todo: use tstore + BalanceDelta private quote; + + uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; + uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1; + constructor(IPoolManager _manager, address _impl) MiddlewareRemove(_manager, _impl) { IHooks middleware = IHooks(address(this)); if ( @@ -47,42 +55,55 @@ contract MiddlewareProtect is MiddlewareRemove { { if (key.fee.isDynamicFee()) revert ForbiddenDynamicFee(); (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); - return implementation.beforeInitialize(sender, key, sqrtPriceX96, hookData); + if (!success) { + assembly { + revert(add(32, returnData), mload(returnData)) + } + } + return abi.decode(returnData, (bytes4)); } - function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) external returns (bytes4, BeforeSwapDelta, uint24) { - (bool success, bytes memory returnData) = address(this).delegatecall{gas: GAS_LIMIT}( - abi.encodeWithSelector(this._callAndEnsureOutput.selector, msg.data) - ); + try this._quoteSwapDelta(key, params) {} + catch (bytes memory reason) { + quote = abi.decode(reason, (BalanceDelta)); + } + uint160 outputBefore = 0; + (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { assembly { revert(add(32, returnData), mload(returnData)) } } - return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + return abi.decode(returnData, (bytes4, BeforeSwapDelta, uint24)); } - function _callAndEnsureOutput(bytes calldata data) external { - (, PoolKey memory key, IPoolManager.SwapParams memory params,) = - abi.decode(data[4:], (address, PoolKey, IPoolManager.SwapParams, bytes)); + function _quoteSwapDelta(PoolKey memory key, IPoolManager.SwapParams memory params) + external + returns (bytes memory) + { + BalanceDelta swapDelta = manager.swap(key, params, ZERO_BYTES); + bytes memory result = abi.encode(swapDelta); + assembly { + revert(add(0x20, result), mload(result)) + } + } - // todo: get quote - uint160 outputBefore = 0; - (bool success, bytes memory returnData) = address(implementation).delegatecall(data); + function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta delta, bytes calldata) + external + returns (bytes4, int128) + { + if (delta != quote) revert HookModifiedOutput(); + (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { - revert FailedImplementationCall(); - } - (, BeforeSwapDelta delta,) = abi.decode(returnData, (bytes4, BeforeSwapDelta, uint24)); - console.logInt(delta.getUnspecifiedDelta()); - console.logInt(delta.getSpecifiedDelta()); - uint160 outputAfter = 0; - if (outputAfter < outputBefore) { - // purpousely revert to cause the whole hook to reset - revert HookModifiedOutput(); + assembly { + revert(add(32, returnData), mload(returnData)) + } } + return abi.decode(returnData, (bytes4, int128)); } function beforeAddLiquidity( diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index 80aeb588..253fbe17 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -23,6 +23,9 @@ import {HooksReturnDeltas} from "./middleware/HooksReturnDeltas.sol"; import {Counter} from "./middleware/Counter.sol"; import {SafeCallback} from "./../contracts/base/SafeCallback.sol"; import {FrontrunAdd} from "./middleware/FrontrunAdd.sol"; +import {IQuoter} from "./../contracts/interfaces/IQuoter.sol"; +import {Quoter} from "./../contracts/lens/Quoter.sol"; +import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; contract MiddlewareProtectFactoryTest is Test, Deployers { HookEnabledSwapRouter router; @@ -33,11 +36,14 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { Counter counter; address middleware; HooksFrontrun hooksFrontrun; + IQuoter quoter; function setUp() public { deployFreshManagerAndRouters(); (currency0, currency1) = deployMintAndApprove2Currencies(); + quoter = new Quoter(address(manager)); + router = new HookEnabledSwapRouter(manager); token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); @@ -188,6 +194,22 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); } + function testRevertOnDynamicFee() public { + vm.expectRevert(MiddlewareProtect.ForbiddenDynamicFee.selector); + initPool( + currency0, currency1, IHooks(middleware), LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1, ZERO_BYTES + ); + } + + function testVariousSwaps() public { + (PoolKey memory key, PoolId id) = + initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + swap(key, true, 1 ether, ZERO_BYTES); + swap(key, false, 1 ether, ZERO_BYTES); + swap(key, true, -1 ether, ZERO_BYTES); + swap(key, false, -1 ether, ZERO_BYTES); + } + function abs(int256 x) internal pure returns (uint256) { return x >= 0 ? uint256(x) : uint256(-x); } @@ -207,7 +229,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { ); factory.createMiddleware(address(counter), salt); // second deployment should revert - vm.expectRevert(bytes("")); + vm.expectRevert(ZERO_BYTES); factory.createMiddleware(address(counter), salt); } diff --git a/test/MiddlewareRemoveFactory.t.sol b/test/MiddlewareRemoveFactory.t.sol index 311adb3c..0139da0d 100644 --- a/test/MiddlewareRemoveFactory.t.sol +++ b/test/MiddlewareRemoveFactory.t.sol @@ -77,18 +77,18 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { currency0.transfer(address(middleware), 1 ether); currency1.transfer(address(middleware), 1 ether); - initPoolAndAddLiquidity(currency0, currency1, IHooks(frontrunRemove), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(frontrunRemove), 3000, SQRT_PRICE_1_1, ZERO_BYTES); uint256 initialBalance0 = token0.balanceOf(address(this)); uint256 initialBalance1 = token1.balanceOf(address(this)); - removeLiquidity(currency0, currency1, IHooks(frontrunRemove), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); uint256 outFrontrun0 = token0.balanceOf(address(this)) - initialBalance0; uint256 outFrontrun1 = token1.balanceOf(address(this)) - initialBalance1; IHooks noHooks = IHooks(address(0)); - initPoolAndAddLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); initialBalance0 = token0.balanceOf(address(this)); initialBalance1 = token1.balanceOf(address(this)); - removeLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); uint256 outNormal0 = token0.balanceOf(address(this)) - initialBalance0; uint256 outNormal1 = token1.balanceOf(address(this)) - initialBalance1; @@ -96,10 +96,10 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { assertTrue(outFrontrun0 > outNormal0); assertTrue(outFrontrun1 < outNormal1); - initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); initialBalance0 = token0.balanceOf(address(this)); initialBalance1 = token1.balanceOf(address(this)); - removeLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); uint256 out0 = token0.balanceOf(address(this)) - initialBalance0; uint256 out1 = token1.balanceOf(address(this)) - initialBalance1; @@ -121,22 +121,22 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { ); middleware = factory.createMiddleware(address(feeOnRemove), salt); - initPoolAndAddLiquidity(currency0, currency1, IHooks(feeOnRemove), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(feeOnRemove), 3000, SQRT_PRICE_1_1, ZERO_BYTES); vm.expectRevert(IPoolManager.CurrencyNotSettled.selector); - removeLiquidity(currency0, currency1, IHooks(feeOnRemove), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); IHooks noHooks = IHooks(address(0)); - initPoolAndAddLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); uint256 initialBalance0 = token0.balanceOf(address(this)); uint256 initialBalance1 = token1.balanceOf(address(this)); - removeLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); uint256 outNormal0 = token0.balanceOf(address(this)) - initialBalance0; uint256 outNormal1 = token1.balanceOf(address(this)) - initialBalance1; - initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); initialBalance0 = token0.balanceOf(address(this)); initialBalance1 = token1.balanceOf(address(this)); - removeLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); uint256 out0 = token0.balanceOf(address(this)) - initialBalance0; uint256 out1 = token1.balanceOf(address(this)) - initialBalance1; @@ -183,10 +183,10 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { function testOn(address implementation, bytes32 salt) internal { address hookAddress = factory.createMiddleware(implementation, salt); - initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddress), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddress), 3000, SQRT_PRICE_1_1, ZERO_BYTES); // does not revert - removeLiquidity(currency0, currency1, IHooks(hookAddress), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); assertEq(factory.getImplementation(hookAddress), implementation); } @@ -211,7 +211,7 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { ); factory.createMiddleware(address(counter), salt); // second deployment should revert - vm.expectRevert(bytes("")); + vm.expectRevert(ZERO_BYTES); factory.createMiddleware(address(counter), salt); } @@ -269,7 +269,7 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { assertEq(counter.beforeSwapCount(id), 0); assertEq(counter.afterSwapCount(id), 0); - removeLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); assertEq(counterProxy.beforeRemoveLiquidityCount(id), 1); assertEq(counterProxy.afterRemoveLiquidityCount(id), 1); } diff --git a/test/utils/HookMiner.sol b/test/utils/HookMiner.sol index d6b30c40..f1bf34e3 100644 --- a/test/utils/HookMiner.sol +++ b/test/utils/HookMiner.sol @@ -8,7 +8,7 @@ library HookMiner { uint160 constant FLAG_MASK = 0x3FFF; // Maximum number of iterations to find a salt, avoid infinite loops - uint256 constant MAX_LOOP = 100_000; + uint256 constant MAX_LOOP = 1_000_000; /// @notice Find a salt that produces a hook address with the desired `flags` /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address From 9f6788edcc6e8c0c041bfcd4b650f9d8df3a0c93 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:04:15 -0400 Subject: [PATCH 04/52] increase foundry gas_limit --- .github/workflows/lint.yml | 2 +- foundry.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9d16a66b..b0eb346e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,5 +21,5 @@ jobs: with: version: nightly - - name: Run tests + - name: Check format run: forge fmt --check diff --git a/foundry.toml b/foundry.toml index 4e95a213..f9da8541 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,7 @@ optimizer_runs = 1000000 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] evm_version = "cancun" +gas_limit = "3000000000" [profile.ci] fuzz_runs = 100000 From e3ad3c72fed3d9863a4c2ccbd001ff3a4ae8c5db Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:29:29 -0400 Subject: [PATCH 05/52] forge fmt --- test/MiddlewareProtectFactory.t.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index 253fbe17..24b8d32f 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -196,9 +196,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { function testRevertOnDynamicFee() public { vm.expectRevert(MiddlewareProtect.ForbiddenDynamicFee.selector); - initPool( - currency0, currency1, IHooks(middleware), LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1, ZERO_BYTES - ); + initPool(currency0, currency1, IHooks(middleware), LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1, ZERO_BYTES); } function testVariousSwaps() public { From d3e40ff722ed92f65bc9a5fe0dbade9322789b2e Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:58:12 -0400 Subject: [PATCH 06/52] add function to handle errors --- contracts/middleware/MiddlewareProtect.sol | 22 ++++++++++------------ test/MiddlewareProtectFactory.t.sol | 12 ++++++++++-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/contracts/middleware/MiddlewareProtect.sol b/contracts/middleware/MiddlewareProtect.sol index beee46a6..6afdc8d9 100644 --- a/contracts/middleware/MiddlewareProtect.sol +++ b/contracts/middleware/MiddlewareProtect.sol @@ -56,9 +56,7 @@ contract MiddlewareProtect is MiddlewareRemove { if (key.fee.isDynamicFee()) revert ForbiddenDynamicFee(); (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { - assembly { - revert(add(32, returnData), mload(returnData)) - } + _handleRevert(returnData); } return abi.decode(returnData, (bytes4)); } @@ -74,9 +72,7 @@ contract MiddlewareProtect is MiddlewareRemove { uint160 outputBefore = 0; (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { - assembly { - revert(add(32, returnData), mload(returnData)) - } + _handleRevert(returnData); } return abi.decode(returnData, (bytes4, BeforeSwapDelta, uint24)); } @@ -99,9 +95,7 @@ contract MiddlewareProtect is MiddlewareRemove { if (delta != quote) revert HookModifiedOutput(); (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { - assembly { - revert(add(32, returnData), mload(returnData)) - } + _handleRevert(returnData); } return abi.decode(returnData, (bytes4, int128)); } @@ -116,10 +110,14 @@ contract MiddlewareProtect is MiddlewareRemove { abi.encodeWithSelector(this._callAndEnsurePrice.selector, msg.data) ); if (!success) { - assembly { - revert(add(32, returnData), mload(returnData)) - } + _handleRevert(returnData); } return BaseHook.beforeAddLiquidity.selector; } + + function _handleRevert(bytes memory returnData) internal pure { + assembly { + revert(add(32, returnData), mload(returnData)) + } + } } diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index 24b8d32f..40fa6cf8 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -137,17 +137,25 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { (address hookAddress, bytes32 salt) = HookMiner.find( address(factory), flags, - type(MiddlewareRemove).creationCode, + type(MiddlewareProtect).creationCode, abi.encode(address(manager), address(hooksRevert)) ); + middleware = factory.createMiddleware(address(hooksRevert), salt); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + vm.expectRevert(HooksRevert.AlwaysRevert.selector); + swap(key, true, 1, ZERO_BYTES); HooksOutOfGas hooksOutOfGas = new HooksOutOfGas(manager); (hookAddress, salt) = HookMiner.find( address(factory), flags, - type(MiddlewareRemove).creationCode, + type(MiddlewareProtect).creationCode, abi.encode(address(manager), address(hooksOutOfGas)) ); + middleware = factory.createMiddleware(address(hooksOutOfGas), salt); + (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + vm.expectRevert(Hooks.FailedHookCall.selector); + swap(key, true, 1, ZERO_BYTES); } function testFrontrunAdd() public { From 052f21dadcd3c76729a997f3db3c178b65749ed5 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:15:40 -0400 Subject: [PATCH 07/52] enforce implementation address mined --- .forge-snapshots/NormalSwap.snap | 1 + .forge-snapshots/ProtectedSwap.snap | 1 + .forge-snapshots/UnprotectedSwap.snap | 1 + contracts/interfaces/IBaseHook.sol | 9 --- contracts/middleware/BaseMiddleware.sol | 9 +++ .../middleware/BaseMiddlewareFactory.sol | 6 +- contracts/middleware/MiddlewareProtect.sol | 50 ++++++++++--- .../middleware/MiddlewareProtectFactory.sol | 2 +- contracts/middleware/MiddlewareRemove.sol | 15 ++-- .../middleware/MiddlewareRemoveFactory.sol | 2 +- test/BaseMiddlewareFactory.t.sol | 51 +++++++------ test/MiddlewareProtectFactory.t.sol | 75 ++++++++++++------- test/MiddlewareRemoveFactory.t.sol | 49 +++++++----- test/middleware/Counter.sol | 2 +- test/middleware/FeeOnRemove.sol | 2 +- test/middleware/FrontrunAdd.sol | 2 +- test/middleware/FrontrunRemove.sol | 2 +- test/middleware/HooksFrontrun.sol | 2 +- test/middleware/HooksOutOfGas.sol | 2 +- test/middleware/HooksReturnDeltas.sol | 2 +- test/middleware/HooksRevert.sol | 4 +- 21 files changed, 181 insertions(+), 108 deletions(-) create mode 100644 .forge-snapshots/NormalSwap.snap create mode 100644 .forge-snapshots/ProtectedSwap.snap create mode 100644 .forge-snapshots/UnprotectedSwap.snap delete mode 100644 contracts/interfaces/IBaseHook.sol diff --git a/.forge-snapshots/NormalSwap.snap b/.forge-snapshots/NormalSwap.snap new file mode 100644 index 00000000..29ea32a3 --- /dev/null +++ b/.forge-snapshots/NormalSwap.snap @@ -0,0 +1 @@ +79041 \ No newline at end of file diff --git a/.forge-snapshots/ProtectedSwap.snap b/.forge-snapshots/ProtectedSwap.snap new file mode 100644 index 00000000..959f0098 --- /dev/null +++ b/.forge-snapshots/ProtectedSwap.snap @@ -0,0 +1 @@ +205221 \ No newline at end of file diff --git a/.forge-snapshots/UnprotectedSwap.snap b/.forge-snapshots/UnprotectedSwap.snap new file mode 100644 index 00000000..8c259afd --- /dev/null +++ b/.forge-snapshots/UnprotectedSwap.snap @@ -0,0 +1 @@ +130276 \ No newline at end of file diff --git a/contracts/interfaces/IBaseHook.sol b/contracts/interfaces/IBaseHook.sol deleted file mode 100644 index 7f404cf2..00000000 --- a/contracts/interfaces/IBaseHook.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; - -interface IBaseHook is IHooks { - function getHookPermissions() external pure returns (Hooks.Permissions memory); -} diff --git a/contracts/middleware/BaseMiddleware.sol b/contracts/middleware/BaseMiddleware.sol index e6f38031..6f6132cc 100644 --- a/contracts/middleware/BaseMiddleware.sol +++ b/contracts/middleware/BaseMiddleware.sol @@ -14,7 +14,10 @@ contract BaseMiddleware is Proxy { IPoolManager public immutable manager; address public immutable implementation; + error FlagsMismatch(); + constructor(IPoolManager _manager, address _impl) { + _ensureValidFlags(_impl); manager = _manager; implementation = _impl; } @@ -27,4 +30,10 @@ contract BaseMiddleware is Proxy { receive() external payable { _delegate(_implementation()); } + + function _ensureValidFlags(address _impl) internal view virtual { + if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { + revert FlagsMismatch(); + } + } } diff --git a/contracts/middleware/BaseMiddlewareFactory.sol b/contracts/middleware/BaseMiddlewareFactory.sol index d9385034..7d686ea1 100644 --- a/contracts/middleware/BaseMiddlewareFactory.sol +++ b/contracts/middleware/BaseMiddlewareFactory.sol @@ -4,9 +4,6 @@ pragma solidity ^0.8.19; import {IMiddlewareFactory} from "../interfaces/IMiddlewareFactory.sol"; import {BaseMiddleware} from "./BaseMiddleware.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {IBaseHook} from "../interfaces/IBaseHook.sol"; contract BaseMiddlewareFactory is IMiddlewareFactory { mapping(address => address) private _implementations; @@ -23,12 +20,11 @@ contract BaseMiddlewareFactory is IMiddlewareFactory { function createMiddleware(address implementation, bytes32 salt) external override returns (address middleware) { middleware = _deployMiddleware(implementation, salt); - Hooks.validateHookPermissions(IHooks(middleware), IBaseHook(implementation).getHookPermissions()); _implementations[middleware] = implementation; emit MiddlewareCreated(implementation, middleware); } function _deployMiddleware(address implementation, bytes32 salt) internal virtual returns (address middleware) { - return address(new BaseMiddleware{salt: salt}(manager, implementation)); + middleware = address(new BaseMiddleware{salt: salt}(manager, implementation)); } } diff --git a/contracts/middleware/MiddlewareProtect.sol b/contracts/middleware/MiddlewareProtect.sol index 6afdc8d9..ce9286d3 100644 --- a/contracts/middleware/MiddlewareProtect.sol +++ b/contracts/middleware/MiddlewareProtect.sol @@ -19,6 +19,7 @@ import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/type import {console} from "./../../lib/forge-gas-snapshot/lib/forge-std/src/console.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; contract MiddlewareProtect is MiddlewareRemove { using CustomRevert for bytes4; @@ -29,8 +30,9 @@ contract MiddlewareProtect is MiddlewareRemove { using LPFeeLibrary for uint24; using BalanceDeltaLibrary for BalanceDelta; - error ForbiddenDynamicFee(); error HookModifiedOutput(); + error ForbiddenDynamicFee(); + error MustEnableAfterSwapFlag(); // todo: use tstore BalanceDelta private quote; @@ -38,16 +40,7 @@ contract MiddlewareProtect is MiddlewareRemove { uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1; - constructor(IPoolManager _manager, address _impl) MiddlewareRemove(_manager, _impl) { - IHooks middleware = IHooks(address(this)); - if ( - middleware.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG) - || middleware.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG) - || middleware.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) - ) { - HookPermissionForbidden.selector.revertWith(address(this)); - } - } + constructor(IPoolManager _manager, address _impl) MiddlewareRemove(_manager, _impl) {} function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, bytes calldata hookData) external @@ -92,7 +85,13 @@ contract MiddlewareProtect is MiddlewareRemove { external returns (bytes4, int128) { - if (delta != quote) revert HookModifiedOutput(); + IHooks implementation = IHooks(address(implementation)); + if (implementation.hasPermission(Hooks.BEFORE_SWAP_FLAG)) { + if (delta != quote) revert HookModifiedOutput(); + if (!implementation.hasPermission(Hooks.AFTER_SWAP_FLAG)) { + return (BaseHook.afterSwap.selector, 0); + } + } (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { _handleRevert(returnData); @@ -120,4 +119,31 @@ contract MiddlewareProtect is MiddlewareRemove { revert(add(32, returnData), mload(returnData)) } } + + function _ensureValidFlags(address _impl) internal view virtual override { + IHooks This = IHooks(address(this)); + if ( + This.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG) + || This.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG) + || This.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + || This.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) { + HookPermissionForbidden.selector.revertWith(address(this)); + } + if (This.hasPermission(Hooks.BEFORE_SWAP_FLAG)) { + if ( + uint160(address(this)) & Hooks.ALL_HOOK_MASK + != uint160(_impl) & Hooks.ALL_HOOK_MASK | Hooks.AFTER_SWAP_FLAG + ) { + if (This.hasPermission(Hooks.AFTER_SWAP_FLAG)) { + revert FlagsMismatch(); + } else { + // both flags match, but dev must enable AFTER_SWAP_FLAG + revert MustEnableAfterSwapFlag(); + } + } + } else if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { + revert FlagsMismatch(); + } + } } diff --git a/contracts/middleware/MiddlewareProtectFactory.sol b/contracts/middleware/MiddlewareProtectFactory.sol index 42a12554..511b1260 100644 --- a/contracts/middleware/MiddlewareProtectFactory.sol +++ b/contracts/middleware/MiddlewareProtectFactory.sol @@ -9,6 +9,6 @@ contract MiddlewareProtectFactory is BaseMiddlewareFactory { constructor(IPoolManager _manager) BaseMiddlewareFactory(_manager) {} function _deployMiddleware(address implementation, bytes32 salt) internal override returns (address middleware) { - return address(new MiddlewareProtect{salt: salt}(manager, implementation)); + middleware = address(new MiddlewareProtect{salt: salt}(manager, implementation)); } } diff --git a/contracts/middleware/MiddlewareRemove.sol b/contracts/middleware/MiddlewareRemove.sol index 4dd1f8ad..29ff02ea 100644 --- a/contracts/middleware/MiddlewareRemove.sol +++ b/contracts/middleware/MiddlewareRemove.sol @@ -30,11 +30,7 @@ contract MiddlewareRemove is BaseMiddleware { bytes internal constant ZERO_BYTES = bytes(""); uint256 public constant GAS_LIMIT = 10_000_000; - constructor(IPoolManager _manager, address _impl) BaseMiddleware(_manager, _impl) { - if (IHooks(address(this)).hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)) { - HookPermissionForbidden.selector.revertWith(address(this)); - } - } + constructor(IPoolManager _manager, address _impl) BaseMiddleware(_manager, _impl) {} function beforeRemoveLiquidity( address sender, @@ -87,4 +83,13 @@ contract MiddlewareRemove is BaseMiddleware { revert HookModifiedDeltas(); } } + + function _ensureValidFlags(address _impl) internal view virtual override { + if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { + revert FlagsMismatch(); + } + if (IHooks(address(this)).hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)) { + HookPermissionForbidden.selector.revertWith(address(this)); + } + } } diff --git a/contracts/middleware/MiddlewareRemoveFactory.sol b/contracts/middleware/MiddlewareRemoveFactory.sol index 5dbca5d0..b4f09d08 100644 --- a/contracts/middleware/MiddlewareRemoveFactory.sol +++ b/contracts/middleware/MiddlewareRemoveFactory.sol @@ -9,6 +9,6 @@ contract MiddlewareRemoveFactory is BaseMiddlewareFactory { constructor(IPoolManager _manager) BaseMiddlewareFactory(_manager) {} function _deployMiddleware(address implementation, bytes32 salt) internal override returns (address middleware) { - return address(new MiddlewareRemove{salt: salt}(manager, implementation)); + middleware = address(new MiddlewareRemove{salt: salt}(manager, implementation)); } } diff --git a/test/BaseMiddlewareFactory.t.sol b/test/BaseMiddlewareFactory.t.sol index 98b1184e..7a97da64 100644 --- a/test/BaseMiddlewareFactory.t.sol +++ b/test/BaseMiddlewareFactory.t.sol @@ -27,6 +27,12 @@ contract BaseMiddlewareFactoryTest is Test, Deployers { Counter counter; address middleware; + uint160 COUNTER_FLAGS = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG + | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG + ); + function setUp() public { deployFreshManagerAndRouters(); (currency0, currency1) = deployMintAndApprove2Currencies(); @@ -36,55 +42,56 @@ contract BaseMiddlewareFactoryTest is Test, Deployers { token1 = TestERC20(Currency.unwrap(currency1)); factory = new BaseMiddlewareFactory(manager); - counter = new Counter(manager); + counter = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); token0.approve(address(router), type(uint256).max); token1.approve(address(router), type(uint256).max); - uint160 flags = uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG - | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG - | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG - ); - (address hookAddress, bytes32 salt) = HookMiner.find( - address(factory), flags, type(BaseMiddleware).creationCode, abi.encode(address(manager), address(counter)) + address(factory), + COUNTER_FLAGS, + type(BaseMiddleware).creationCode, + abi.encode(address(manager), address(counter)) ); middleware = factory.createMiddleware(address(counter), salt); assertEq(hookAddress, middleware); } function testRevertOnSameDeployment() public { - uint160 flags = uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG - | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG - | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG - ); (address hookAddress, bytes32 salt) = HookMiner.find( - address(factory), flags, type(BaseMiddleware).creationCode, abi.encode(address(manager), address(counter)) + address(factory), + COUNTER_FLAGS, + type(BaseMiddleware).creationCode, + abi.encode(address(manager), address(counter)) ); factory.createMiddleware(address(counter), salt); // second deployment should revert - vm.expectRevert(bytes("")); + vm.expectRevert(ZERO_BYTES); factory.createMiddleware(address(counter), salt); } function testRevertOnIncorrectFlags() public { - Counter counter2 = new Counter(manager); - uint160 flags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); + Counter counter2 = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); + uint160 incorrectFlags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); (address hookAddress, bytes32 salt) = HookMiner.find( - address(factory), flags, type(BaseMiddleware).creationCode, abi.encode(address(manager), address(counter2)) + address(factory), + incorrectFlags, + type(BaseMiddleware).creationCode, + abi.encode(address(manager), address(counter2)) ); address implementation = address(counter2); - vm.expectRevert(abi.encodePacked(bytes16(Hooks.HookAddressNotValid.selector), hookAddress)); + vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); factory.createMiddleware(implementation, salt); } function testRevertOnIncorrectFlagsMined() public { - Counter counter2 = new Counter(manager); + Counter counter2 = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); address implementation = address(counter2); - vm.expectRevert(); // HookAddressNotValid + vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); factory.createMiddleware(implementation, bytes32("who needs to mine a salt?")); } @@ -116,7 +123,7 @@ contract BaseMiddlewareFactoryTest is Test, Deployers { assertEq(counterProxy.afterSwapCount(id), 1); // counter does not store data itself - assertEq(counter.lastHookData(), bytes("")); + assertEq(counter.lastHookData(), ZERO_BYTES); assertEq(counter.beforeSwapCount(id), 0); assertEq(counter.afterSwapCount(id), 0); } diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index 40fa6cf8..a1bcae17 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -23,11 +23,11 @@ import {HooksReturnDeltas} from "./middleware/HooksReturnDeltas.sol"; import {Counter} from "./middleware/Counter.sol"; import {SafeCallback} from "./../contracts/base/SafeCallback.sol"; import {FrontrunAdd} from "./middleware/FrontrunAdd.sol"; -import {IQuoter} from "./../contracts/interfaces/IQuoter.sol"; -import {Quoter} from "./../contracts/lens/Quoter.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {BaseMiddleware} from "./../contracts/middleware/BaseMiddleware.sol"; -contract MiddlewareProtectFactoryTest is Test, Deployers { +contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { HookEnabledSwapRouter router; TestERC20 token0; TestERC20 token1; @@ -36,43 +36,40 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { Counter counter; address middleware; HooksFrontrun hooksFrontrun; - IQuoter quoter; + + uint160 COUNTER_FLAGS = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG + | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG + ); function setUp() public { deployFreshManagerAndRouters(); (currency0, currency1) = deployMintAndApprove2Currencies(); - quoter = new Quoter(address(manager)); - router = new HookEnabledSwapRouter(manager); token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); factory = new MiddlewareProtectFactory(manager); - counter = new Counter(manager); + counter = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); token0.approve(address(router), type(uint256).max); token1.approve(address(router), type(uint256).max); - uint160 flags = uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG - | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG - | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG - ); - (address hookAddress, bytes32 salt) = HookMiner.find( address(factory), - flags, + COUNTER_FLAGS, type(MiddlewareProtect).creationCode, abi.encode(address(manager), address(counter)) ); middleware = factory.createMiddleware(address(counter), salt); assertEq(hookAddress, middleware); - flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG); + uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG); hooksFrontrun = HooksFrontrun(address(uint160(flags))); - HooksFrontrun impl = new HooksFrontrun(manager); - vm.etch(address(hooksFrontrun), address(impl).code); + vm.etch(address(hooksFrontrun), address(new HooksFrontrun(manager)).code); } function testRevertOnDeltas() public { @@ -129,11 +126,12 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { } function testRevertOnFailedImplementationCall() public { - HooksRevert hooksRevert = new HooksRevert(manager); uint160 flags = uint160( Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG ); + HooksRevert hooksRevert = HooksRevert(address(flags)); + vm.etch(address(hooksRevert), address(new HooksRevert(manager)).code); (address hookAddress, bytes32 salt) = HookMiner.find( address(factory), flags, @@ -145,7 +143,8 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { vm.expectRevert(HooksRevert.AlwaysRevert.selector); swap(key, true, 1, ZERO_BYTES); - HooksOutOfGas hooksOutOfGas = new HooksOutOfGas(manager); + HooksOutOfGas hooksOutOfGas = HooksOutOfGas(address(flags)); + vm.etch(address(hooksOutOfGas), address(new HooksOutOfGas(manager)).code); (hookAddress, salt) = HookMiner.find( address(factory), flags, @@ -161,8 +160,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { function testFrontrunAdd() public { uint160 flags = uint160(Hooks.BEFORE_ADD_LIQUIDITY_FLAG); FrontrunAdd frontrunAdd = FrontrunAdd(address(flags)); - FrontrunAdd impl = new FrontrunAdd(manager); - vm.etch(address(frontrunAdd), address(impl).code); + vm.etch(address(frontrunAdd), address(new FrontrunAdd(manager)).code); (, bytes32 salt) = HookMiner.find( address(factory), flags, @@ -216,6 +214,23 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { swap(key, false, -1 ether, ZERO_BYTES); } + function gasTestSwaps() public { + (PoolKey memory key, PoolId id) = + initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + snapStart("Middleware-ProtectedSwap"); + + swap(key, true, 1, ZERO_BYTES); + snapEnd(); + (key, id) = initPoolAndAddLiquidity(currency0, currency1, IHooks(counter), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + snapStart("Middleware-UnprotectedSwap"); + swap(key, true, 1, ZERO_BYTES); + snapEnd(); + (key, id) = initPoolAndAddLiquidity(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + snapStart("Middleware-NormalSwap"); + swap(key, true, 1, ZERO_BYTES); + snapEnd(); + } + function abs(int256 x) internal pure returns (uint256) { return x >= 0 ? uint256(x) : uint256(-x); } @@ -240,24 +255,26 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { } function testRevertOnIncorrectFlags() public { - Counter counter2 = new Counter(manager); - uint160 flags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); + Counter counter2 = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); + uint160 incorrectFlags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); (address hookAddress, bytes32 salt) = HookMiner.find( address(factory), - flags, + incorrectFlags, type(MiddlewareProtect).creationCode, abi.encode(address(manager), address(counter2)) ); address implementation = address(counter2); - vm.expectRevert(abi.encodePacked(bytes16(Hooks.HookAddressNotValid.selector), hookAddress)); + vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); factory.createMiddleware(implementation, salt); } function testRevertOnIncorrectFlagsMined() public { - Counter counter2 = new Counter(manager); + Counter counter2 = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); address implementation = address(counter2); - vm.expectRevert(); // HookAddressNotValid + vm.expectRevert(); factory.createMiddleware(implementation, bytes32("who needs to mine a salt?")); } @@ -292,5 +309,9 @@ contract MiddlewareProtectFactoryTest is Test, Deployers { assertEq(counter.lastHookData(), bytes("")); assertEq(counter.beforeSwapCount(id), 0); assertEq(counter.afterSwapCount(id), 0); + + modifyLiquidityRouter.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ZERO_BYTES); + assertEq(counterProxy.beforeRemoveLiquidityCount(id), 1); + assertEq(counterProxy.afterRemoveLiquidityCount(id), 1); } } diff --git a/test/MiddlewareRemoveFactory.t.sol b/test/MiddlewareRemoveFactory.t.sol index 0139da0d..bb2f7c3f 100644 --- a/test/MiddlewareRemoveFactory.t.sol +++ b/test/MiddlewareRemoveFactory.t.sol @@ -23,6 +23,7 @@ import {HookMiner} from "./utils/HookMiner.sol"; import {SafeCallback} from "./../contracts/base/SafeCallback.sol"; import {FeeOnRemove} from "./middleware/FeeOnRemove.sol"; import {FrontrunRemove} from "./middleware/FrontrunRemove.sol"; +import {BaseMiddleware} from "./../contracts/middleware/BaseMiddleware.sol"; contract MiddlewareRemoveFactoryTest is Test, Deployers { HookEnabledSwapRouter router; @@ -33,6 +34,12 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { Counter counter; address middleware; + uint160 COUNTER_FLAGS = uint160( + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG + | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG + ); + function setUp() public { deployFreshManagerAndRouters(); (currency0, currency1) = deployMintAndApprove2Currencies(); @@ -42,19 +49,17 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { token1 = TestERC20(Currency.unwrap(currency1)); factory = new MiddlewareRemoveFactory(manager); - counter = new Counter(manager); + counter = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); token0.approve(address(router), type(uint256).max); token1.approve(address(router), type(uint256).max); - uint160 flags = uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG - | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG - | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG - ); - (address hookAddress, bytes32 salt) = HookMiner.find( - address(factory), flags, type(MiddlewareRemove).creationCode, abi.encode(address(manager), address(counter)) + address(factory), + COUNTER_FLAGS, + type(MiddlewareRemove).creationCode, + abi.encode(address(manager), address(counter)) ); middleware = factory.createMiddleware(address(counter), salt); assertEq(hookAddress, middleware); @@ -156,11 +161,12 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { ); testOn(address(counter), salt); - HooksRevert hooksRevert = new HooksRevert(manager); flags = uint160( Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG ); + HooksRevert hooksRevert = HooksRevert(address(flags)); + vm.etch(address(hooksRevert), address(new HooksRevert(manager)).code); (hookAddress, salt) = HookMiner.find( address(factory), flags, @@ -169,7 +175,8 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { ); testOn(address(hooksRevert), salt); - HooksOutOfGas hooksOutOfGas = new HooksOutOfGas(manager); + HooksOutOfGas hooksOutOfGas = HooksOutOfGas(address(flags)); + vm.etch(address(hooksOutOfGas), address(new HooksOutOfGas(manager)).code); (hookAddress, salt) = HookMiner.find( address(factory), flags, @@ -192,11 +199,15 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { function testRevertOnDeltaFlags() public { uint160 flags = uint160(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG); + address removeReturnDeltas = address(1 << 100 | flags); (address hookAddress, bytes32 salt) = HookMiner.find( - address(factory), flags, type(MiddlewareRemove).creationCode, abi.encode(address(manager), address(counter)) + address(factory), + flags, + type(MiddlewareRemove).creationCode, + abi.encode(address(manager), address(removeReturnDeltas)) ); vm.expectRevert(abi.encodePacked(bytes16(MiddlewareRemove.HookPermissionForbidden.selector), hookAddress)); - factory.createMiddleware(address(counter), salt); + factory.createMiddleware(address(removeReturnDeltas), salt); } // from BaseMiddlewareFactory.t.sol @@ -216,24 +227,26 @@ contract MiddlewareRemoveFactoryTest is Test, Deployers { } function testRevertOnIncorrectFlags() public { - Counter counter2 = new Counter(manager); - uint160 flags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); + Counter counter2 = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); + uint160 incorrectFlags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); (address hookAddress, bytes32 salt) = HookMiner.find( address(factory), - flags, + incorrectFlags, type(MiddlewareRemove).creationCode, abi.encode(address(manager), address(counter2)) ); address implementation = address(counter2); - vm.expectRevert(abi.encodePacked(bytes16(Hooks.HookAddressNotValid.selector), hookAddress)); + vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); factory.createMiddleware(implementation, salt); } function testRevertOnIncorrectFlagsMined() public { - Counter counter2 = new Counter(manager); + Counter counter2 = Counter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new Counter(manager)).code); address implementation = address(counter2); - vm.expectRevert(); // HookAddressNotValid + vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); factory.createMiddleware(implementation, bytes32("who needs to mine a salt?")); } diff --git a/test/middleware/Counter.sol b/test/middleware/Counter.sol index 9e4803d1..e5972af4 100644 --- a/test/middleware/Counter.sol +++ b/test/middleware/Counter.sol @@ -30,7 +30,7 @@ contract Counter is BaseHook { constructor(IPoolManager _manager) BaseHook(_manager) {} - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { diff --git a/test/middleware/FeeOnRemove.sol b/test/middleware/FeeOnRemove.sol index f5c9603b..761de93f 100644 --- a/test/middleware/FeeOnRemove.sol +++ b/test/middleware/FeeOnRemove.sol @@ -15,7 +15,7 @@ contract FeeOnRemove is BaseHook { uint128 public constant LIQUIDITY_FEE = 543; // 5.43% uint128 public constant TOTAL_BIPS = 10000; - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { diff --git a/test/middleware/FrontrunAdd.sol b/test/middleware/FrontrunAdd.sol index d1649f53..20fcdf34 100644 --- a/test/middleware/FrontrunAdd.sol +++ b/test/middleware/FrontrunAdd.sol @@ -22,7 +22,7 @@ contract FrontrunAdd is BaseHook { mapping(PoolId => bool) hasLiquidity; - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { diff --git a/test/middleware/FrontrunRemove.sol b/test/middleware/FrontrunRemove.sol index 244a4eb7..877bcf18 100644 --- a/test/middleware/FrontrunRemove.sol +++ b/test/middleware/FrontrunRemove.sol @@ -17,7 +17,7 @@ contract FrontrunRemove is BaseHook { uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; int256 public constant SWAP_AMOUNT = 1000; - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { diff --git a/test/middleware/HooksFrontrun.sol b/test/middleware/HooksFrontrun.sol index edb65f72..238e1aad 100644 --- a/test/middleware/HooksFrontrun.sol +++ b/test/middleware/HooksFrontrun.sol @@ -23,7 +23,7 @@ contract HooksFrontrun is BaseHook { constructor(IPoolManager _manager) BaseHook(_manager) {} - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory) { diff --git a/test/middleware/HooksOutOfGas.sol b/test/middleware/HooksOutOfGas.sol index 02356593..bde1e9ec 100644 --- a/test/middleware/HooksOutOfGas.sol +++ b/test/middleware/HooksOutOfGas.sol @@ -15,7 +15,7 @@ contract HooksOutOfGas is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { diff --git a/test/middleware/HooksReturnDeltas.sol b/test/middleware/HooksReturnDeltas.sol index cb32f500..a4c2567e 100644 --- a/test/middleware/HooksReturnDeltas.sol +++ b/test/middleware/HooksReturnDeltas.sol @@ -12,7 +12,7 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; contract HooksReturnDeltas is BaseHook { constructor(IPoolManager _manager) BaseHook(_manager) {} - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { diff --git a/test/middleware/HooksRevert.sol b/test/middleware/HooksRevert.sol index 1ebf489c..37ef0387 100644 --- a/test/middleware/HooksRevert.sol +++ b/test/middleware/HooksRevert.sol @@ -14,7 +14,7 @@ contract HooksRevert is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - // middleware implementations do not need to be mined + // for testing function validateHookAddress(BaseHook _this) internal pure override {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { @@ -38,6 +38,7 @@ contract HooksRevert is BaseHook { function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) external + pure override returns (bytes4, BeforeSwapDelta, uint24) { @@ -46,6 +47,7 @@ contract HooksRevert is BaseHook { function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) external + pure override returns (bytes4, int128) { From 71962b1077ccc60398473a09a9daeb9470d75327 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:02:57 -0400 Subject: [PATCH 08/52] fix error bug --- contracts/middleware/BaseMiddleware.sol | 5 -- contracts/middleware/MiddlewareProtect.sol | 22 ++++----- test/MiddlewareProtectFactory.t.sol | 56 +++++++++++++++++++++- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/contracts/middleware/BaseMiddleware.sol b/contracts/middleware/BaseMiddleware.sol index 6f6132cc..375239c0 100644 --- a/contracts/middleware/BaseMiddleware.sol +++ b/contracts/middleware/BaseMiddleware.sol @@ -26,11 +26,6 @@ contract BaseMiddleware is Proxy { return implementation; } - // yo i wanna delete this function but how do i remove this warning - receive() external payable { - _delegate(_implementation()); - } - function _ensureValidFlags(address _impl) internal view virtual { if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { revert FlagsMismatch(); diff --git a/contracts/middleware/MiddlewareProtect.sol b/contracts/middleware/MiddlewareProtect.sol index ce9286d3..f486cbed 100644 --- a/contracts/middleware/MiddlewareProtect.sol +++ b/contracts/middleware/MiddlewareProtect.sol @@ -32,7 +32,7 @@ contract MiddlewareProtect is MiddlewareRemove { error HookModifiedOutput(); error ForbiddenDynamicFee(); - error MustEnableAfterSwapFlag(); + error MustHaveAfterSwapFlagOnMiddleware(); // todo: use tstore BalanceDelta private quote; @@ -122,28 +122,28 @@ contract MiddlewareProtect is MiddlewareRemove { function _ensureValidFlags(address _impl) internal view virtual override { IHooks This = IHooks(address(this)); - if ( - This.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG) - || This.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG) - || This.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) - || This.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) - ) { - HookPermissionForbidden.selector.revertWith(address(this)); - } if (This.hasPermission(Hooks.BEFORE_SWAP_FLAG)) { if ( uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK | Hooks.AFTER_SWAP_FLAG ) { - if (This.hasPermission(Hooks.AFTER_SWAP_FLAG)) { + if (IHooks(_impl).hasPermission(Hooks.AFTER_SWAP_FLAG)) { revert FlagsMismatch(); } else { // both flags match, but dev must enable AFTER_SWAP_FLAG - revert MustEnableAfterSwapFlag(); + revert MustHaveAfterSwapFlagOnMiddleware(); } } } else if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { revert FlagsMismatch(); } + if ( + This.hasPermission(Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG) + || This.hasPermission(Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG) + || This.hasPermission(Hooks.AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + || This.hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) { + HookPermissionForbidden.selector.revertWith(address(this)); + } } } diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index a1bcae17..eb418827 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -73,8 +73,9 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { } function testRevertOnDeltas() public { - HooksReturnDeltas hooksReturnDeltas = new HooksReturnDeltas(manager); uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG); + HooksReturnDeltas hooksReturnDeltas = HooksReturnDeltas(address(flags)); + vm.etch(address(hooksReturnDeltas), address(new HooksReturnDeltas(manager)).code); (address hookAddress, bytes32 salt) = HookMiner.find( address(factory), @@ -235,6 +236,59 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { return x >= 0 ? uint256(x) : uint256(-x); } + function testFlagCompatibilities() public { + uint160 BEFORE = Hooks.BEFORE_SWAP_FLAG; + uint160 AFTER = Hooks.AFTER_SWAP_FLAG; + + // No flags + uint160 thisFlags = 0; + uint160 implFlags = 0; + testFlagCompatibilities(thisFlags, implFlags); + + // Both flags + thisFlags = BEFORE | AFTER; + implFlags = BEFORE | AFTER; + testFlagCompatibilities(thisFlags, implFlags); + + // only AFTER_SWAP + thisFlags = AFTER; + implFlags = AFTER; + testFlagCompatibilities(thisFlags, implFlags); + + // thisFlags missing AFTER_SWAP + // REVERTS + thisFlags = BEFORE; + implFlags = BEFORE; + vm.expectRevert(MiddlewareProtect.MustHaveAfterSwapFlagOnMiddleware.selector); + testFlagCompatibilities(thisFlags, implFlags); + + // mismatch + // REVERTS + thisFlags = AFTER; + implFlags = BEFORE; + vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); + testFlagCompatibilities(thisFlags, implFlags); + + // mismatch + // REVERTS + thisFlags = BEFORE; + implFlags = BEFORE | AFTER; + vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); + testFlagCompatibilities(thisFlags, implFlags); + + // mismatch but correct + thisFlags = BEFORE | AFTER; + implFlags = BEFORE; + testFlagCompatibilities(thisFlags, implFlags); + } + + function testFlagCompatibilities(uint160 thisFlags, uint160 implFlags) internal { + (, bytes32 salt) = HookMiner.find( + address(factory), thisFlags, type(MiddlewareProtect).creationCode, abi.encode(address(manager), implFlags) + ); + factory.createMiddleware(address(implFlags), salt); + } + // from BaseMiddlewareFactory.t.sol function testRevertOnSameDeployment() public { uint160 flags = uint160( From 9a60e41fd285c9d895e426815ddbb40d64b6f067 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:41:33 -0400 Subject: [PATCH 09/52] bring back random function --- contracts/middleware/BaseMiddleware.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/middleware/BaseMiddleware.sol b/contracts/middleware/BaseMiddleware.sol index 375239c0..6f6132cc 100644 --- a/contracts/middleware/BaseMiddleware.sol +++ b/contracts/middleware/BaseMiddleware.sol @@ -26,6 +26,11 @@ contract BaseMiddleware is Proxy { return implementation; } + // yo i wanna delete this function but how do i remove this warning + receive() external payable { + _delegate(_implementation()); + } + function _ensureValidFlags(address _impl) internal view virtual { if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { revert FlagsMismatch(); From daf74ceefc595a579901518ed05ba475992a97a1 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:10:33 +0100 Subject: [PATCH 10/52] Recipient mapping! (#246) * map recipient * tests for use cases * gas tests --- ...ayments_swap_settleFromCaller_takeAll.snap | 1 - ...p_settleFromCaller_takeAllToMsgSender.snap | 1 + ...eFromCaller_takeAllToSpecifiedAddress.snap | 1 + ...ayments_swap_settleFromRouter_takeAll.snap | 1 - ..._settleWithBalance_takeAllToMsgSender.snap | 1 + ...WithBalance_takeAllToSpecifiedAddress.snap | 1 + .forge-snapshots/PositionManager_mint.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- .../PositionManager_mint_nativeWithSweep.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- ...Router_ExactOut1Hop_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOut1Hop_nativeOut.snap | 2 +- .../V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .../V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .../V4Router_ExactOut2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOut3Hops_nativeIn.snap | 2 +- .../V4Router_ExactOut3Hops_nativeOut.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- ...r_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/PositionManager.sol | 4 +- src/V4Router.sol | 3 +- src/base/BaseActionsRouter.sol | 14 ++++ src/libraries/Actions.sol | 7 ++ test/BaseActionsRouter.t.sol | 11 +++ test/mocks/MockBaseActionsRouter.sol | 8 +- test/position-managers/Execute.t.sol | 12 +-- .../position-managers/IncreaseLiquidity.t.sol | 48 +++++++++-- test/position-managers/NativeToken.t.sol | 22 ++--- .../PositionManager.gas.t.sol | 71 +++++++++------- .../PositionManager.multicall.t.sol | 2 +- test/position-managers/PositionManager.t.sol | 83 +++++++++++++------ test/router/Payments.gas.t.sol | 39 ++++++++- test/router/V4Router.gas.t.sol | 46 +++++----- test/router/V4Router.t.sol | 66 +++++++++++++-- test/shared/Planner.sol | 4 +- test/shared/RoutingTestHelpers.sol | 23 ++++- 56 files changed, 384 insertions(+), 151 deletions(-) delete mode 100644 .forge-snapshots/Payments_swap_settleFromCaller_takeAll.snap create mode 100644 .forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap create mode 100644 .forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap delete mode 100644 .forge-snapshots/Payments_swap_settleFromRouter_takeAll.snap create mode 100644 .forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap create mode 100644 .forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAll.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAll.snap deleted file mode 100644 index 422a77b8..00000000 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAll.snap +++ /dev/null @@ -1 +0,0 @@ -134171 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap new file mode 100644 index 00000000..a3249887 --- /dev/null +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -0,0 +1 @@ +134122 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap new file mode 100644 index 00000000..bf565cdf --- /dev/null +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -0,0 +1 @@ +134260 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromRouter_takeAll.snap b/.forge-snapshots/Payments_swap_settleFromRouter_takeAll.snap deleted file mode 100644 index dcc72d1c..00000000 --- a/.forge-snapshots/Payments_swap_settleFromRouter_takeAll.snap +++ /dev/null @@ -1 +0,0 @@ -126966 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap new file mode 100644 index 00000000..be94d9c7 --- /dev/null +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -0,0 +1 @@ +126917 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap new file mode 100644 index 00000000..b29fe9a2 --- /dev/null +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -0,0 +1 @@ +127055 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint.snap index 7e0f6688..87615faa 100644 --- a/.forge-snapshots/PositionManager_mint.snap +++ b/.forge-snapshots/PositionManager_mint.snap @@ -1 +1 @@ -372012 \ No newline at end of file +371963 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index e708094b..7d264b1e 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336712 \ No newline at end of file +336663 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap index 15689f38..4ad1137a 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap @@ -1 +1 @@ -345244 \ No newline at end of file +345146 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 90fd8fec..2bce4f8d 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314694 \ No newline at end of file +314645 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 9b6845af..2f87608a 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315336 \ No newline at end of file +315287 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index 597983b5..1097f3c7 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -240918 \ No newline at end of file +240869 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index a6459af0..6bc0e296 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370018 \ No newline at end of file +370009 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index af4a8aa0..59c07930 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320712 \ No newline at end of file +320663 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 28856ebd..df384f33 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416388 \ No newline at end of file +416339 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index c6e2c74d..b6655f3d 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -6942 \ No newline at end of file +7119 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index ec0b0f29..b10f658b 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -120501 \ No newline at end of file +120452 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index 75bad55d..c4498679 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -119696 \ No newline at end of file +119647 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index d83695c7..df5b16d3 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -128568 \ No newline at end of file +128519 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 82eb5f4d..25aad418 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135398 \ No newline at end of file +135349 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index b02cff01..e5d3b159 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -186897 \ No newline at end of file +186848 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index 6f17f2bb..516d7719 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -178832 \ No newline at end of file +178783 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index de05bf83..68b5a2f7 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238421 \ No newline at end of file +238372 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index 5665d2e3..c62a5ed9 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -230380 \ No newline at end of file +230331 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index 422a77b8..a3249887 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134171 \ No newline at end of file +134122 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index 146c27b9..413b7ead 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119274 \ No newline at end of file +119225 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index 52615b42..6ba81107 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118447 \ No newline at end of file +118398 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index 57bde469..ca902a0e 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -126298 \ No newline at end of file +126249 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index 019035c6..be6ff104 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -120531 \ No newline at end of file +120482 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index d40b084a..1a5e4c8d 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -129403 \ No newline at end of file +129354 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index 715f8dca..330497a2 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -134204 \ No newline at end of file +134155 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index abc303ec..4d964996 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186306 \ No newline at end of file +186257 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index 4b39aaa9..0bf4b92f 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -183201 \ No newline at end of file +183152 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index f111bdc0..75ebd28f 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -238448 \ No newline at end of file +238399 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index d6cabe72..cb985312 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -235367 \ No newline at end of file +235318 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index 30c1022c..fb1afd9d 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -229600 \ No newline at end of file +229551 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index c97de2ba..db456dda 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -132975 \ No newline at end of file +132926 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index 5c208ff2..a1f3540c 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -125069 \ No newline at end of file +125020 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index 0465d75c..0ee9b2fc 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -119360 \ No newline at end of file +119311 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 43cafe54..49e13bc4 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -107,7 +107,7 @@ contract PositionManager is address owner, bytes calldata hookData ) = params.decodeMintParams(); - _mint(config, liquidity, amount0Max, amount1Max, owner, hookData); + _mint(config, liquidity, amount0Max, amount1Max, _map(owner), hookData); } else if (action == Actions.CLOSE_CURRENCY) { Currency currency = params.decodeCurrency(); _close(currency); @@ -129,7 +129,7 @@ contract PositionManager is _settleWithBalance(currency); } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); - _sweep(currency, to); + _sweep(currency, _map(to)); } else { revert UnsupportedAction(action); } diff --git a/src/V4Router.sol b/src/V4Router.sol index cc31c69c..03071ec4 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -58,8 +58,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { uint256 amount = _getFullTakeAmount(currency); // TODO should _take have a minAmountOut added slippage check? - // TODO recipient mapping - _take(currency, recipient, amount); + _take(currency, _map(recipient), amount); } else { revert UnsupportedAction(action); } diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index bdc58083..91f3c827 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {SafeCallback} from "./SafeCallback.sol"; import {CalldataDecoder} from "../libraries/CalldataDecoder.sol"; +import {Actions} from "../libraries/Actions.sol"; /// @notice Abstract contract for performing a combination of actions on Uniswap v4. /// @dev Suggested uint256 action values are defined in Actions.sol, however any definition can be used @@ -52,4 +53,17 @@ abstract contract BaseActionsRouter is SafeCallback { /// `msg.sender` shouldnt be used, as this will be the v4 pool manager contract that calls `unlockCallback` /// If using ReentrancyLock.sol, this function can return _getLocker() function _msgSender() internal view virtual returns (address); + + /// @notice Calculates the address for a action + /// @param recipient The address or address-flag for the action + /// @return output The resultant address for the action + function _map(address recipient) internal view returns (address) { + if (recipient == Actions.MSG_SENDER) { + return _msgSender(); + } else if (recipient == Actions.ADDRESS_THIS) { + return address(this); + } else { + return recipient; + } + } } diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 132fde68..61811f0a 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -36,4 +36,11 @@ library Actions { // minting/burning 6909s to close deltas uint256 constant MINT_6909 = 0x20; uint256 constant BURN_6909 = 0x21; + + // helper constants to signal certain addresses + // Used as a flag for identifying that msg.sender should be used + address internal constant MSG_SENDER = address(1); + + // Used as a flag for identifying address(this) should be used + address internal constant ADDRESS_THIS = address(2); } diff --git a/test/BaseActionsRouter.t.sol b/test/BaseActionsRouter.t.sol index fd7b3c14..2780a398 100644 --- a/test/BaseActionsRouter.t.sol +++ b/test/BaseActionsRouter.t.sol @@ -137,4 +137,15 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { router.executeActions(data); assertEq(router.burnCount(), 10); } + + function test_fuzz_map(address recipient) public view { + address mappedRecipient = router.map(recipient); + if (recipient == Actions.MSG_SENDER) { + assertEq(mappedRecipient, address(0xdeadbeef)); + } else if (recipient == Actions.ADDRESS_THIS) { + assertEq(mappedRecipient, address(router)); + } else { + assertEq(mappedRecipient, recipient); + } + } } diff --git a/test/mocks/MockBaseActionsRouter.sol b/test/mocks/MockBaseActionsRouter.sol index 7e8e186d..144c453c 100644 --- a/test/mocks/MockBaseActionsRouter.sol +++ b/test/mocks/MockBaseActionsRouter.sol @@ -41,8 +41,8 @@ contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { } } - function _msgSender() internal view override returns (address) { - return _getLocker(); + function _msgSender() internal pure override returns (address) { + return address(0xdeadbeef); } function _settle(bytes calldata /* params **/ ) internal { @@ -80,4 +80,8 @@ contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { function _clear(bytes calldata /* params **/ ) internal { clearCount++; } + + function map(address recipient) external view returns (address) { + return _map(recipient); + } } diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index d75f3ac7..1d79ceb5 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -67,7 +67,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { initialLiquidity = bound(initialLiquidity, 1e18, 1000e18); liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, address(this), ZERO_BYTES); + mint(config, initialLiquidity, Actions.MSG_SENDER, ZERO_BYTES); increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); @@ -87,7 +87,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); liquidityToAdd2 = bound(liquidityToAdd2, 1e18, 1000e18); uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, address(this), ZERO_BYTES); + mint(config, initialLiquidity, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init(); @@ -122,7 +122,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - config, initialLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES + config, initialLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES ) ); planner.add( @@ -146,7 +146,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { // mint a position on range [-300, 300] uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, address(this), ZERO_BYTES); + mint(config, initialLiquidity, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // we'll burn and mint a new position on [-60, 60]; calculate the liquidity units for the new range @@ -173,7 +173,9 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { ); planner.add( Actions.MINT_POSITION, - abi.encode(newConfig, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + newConfig, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index 49e057c6..ce5c3f42 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -490,7 +490,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revertAmount0() public { // increasing liquidity with strict slippage parameters (amount0) will revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, address(this), ZERO_BYTES); + mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); // revert since amount0Max is too low bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, 1 wei, type(uint128).max, ZERO_BYTES); @@ -501,7 +501,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revertAmount1() public { // increasing liquidity with strict slippage parameters (amount1) will revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, address(this), ZERO_BYTES); + mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); // revert since amount1Max is too low bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, type(uint128).max, 1 wei, ZERO_BYTES); @@ -512,7 +512,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_exactDoesNotRevert() public { // increasing liquidity with perfect slippage parameters does not revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, address(this), ZERO_BYTES); + mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); uint128 newLiquidity = 10e18; (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( @@ -537,7 +537,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revert_swap() public { // increasing liquidity with perfect slippage parameters does not revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, address(this), ZERO_BYTES); + mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); uint128 newLiquidity = 10e18; (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( @@ -557,7 +557,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { lpm.modifyLiquidities(calls, _deadline); } - function test_mint_settleWithBalance() public { + function test_mint_settleWithBalance_andSweepToOtherAddress() public { uint256 liquidityAlice = 3_000e18; Plan memory planner = Planner.init(); @@ -567,6 +567,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { ); planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); + // this test sweeps to the test contract, even though Alice is the caller of the transaction planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); planner.add(Actions.SWEEP, abi.encode(currency1, address(this))); @@ -598,6 +599,42 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { assertEq(currency1.balanceOf(address(this)), balanceBefore1 - amount1); } + function test_mint_settleWithBalance_andSweepToMsgSender() public { + uint256 liquidityAlice = 3_000e18; + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) + ); + planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); + planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); + planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency1, Actions.MSG_SENDER)); + + uint256 balanceBefore0 = currency0.balanceOf(alice); + uint256 balanceBefore1 = currency1.balanceOf(alice); + + uint256 seedAmount = 100e18; + currency0.transfer(address(lpm), seedAmount); + currency1.transfer(address(lpm), seedAmount); + + assertEq(currency0.balanceOf(address(lpm)), seedAmount); + assertEq(currency0.balanceOf(address(lpm)), seedAmount); + + bytes memory calls = planner.encode(); + + vm.prank(alice); + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + uint256 amount0 = uint128(-delta.amount0()); + uint256 amount1 = uint128(-delta.amount1()); + + // alice's balance has increased by the seeded funds that werent used to pay for the mint + assertEq(currency0.balanceOf(alice), balanceBefore0 + (seedAmount - amount0)); + assertEq(currency1.balanceOf(alice), balanceBefore1 + (seedAmount - amount1)); + } + function test_increaseLiquidity_settleWithBalance() public { uint256 liquidityAlice = 3_000e18; @@ -619,6 +656,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { ); planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); + // this test sweeps to the test contract, even though Alice is the caller of the transaction planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); planner.add(Actions.SWEEP, abi.encode(currency1, address(this))); diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index b7a8e901..f99083ad 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -74,7 +74,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - bytes memory calls = getMintEncoded(config, liquidityToAdd, address(this), ZERO_BYTES); + bytes memory calls = getMintEncoded(config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( SQRT_PRICE_1_1, @@ -113,12 +113,14 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + ) ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); // sweep the excess eth - planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); + planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); bytes memory calls = planner.encode(); @@ -157,7 +159,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -210,7 +212,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -257,7 +259,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -299,7 +301,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -320,7 +322,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); // sweep the excess eth - planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); + planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); bytes memory calls = planner.encode(); lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); // overpay on increase liquidity @@ -351,7 +353,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Actions.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -386,7 +388,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Actions.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 8f9aade0..8580f224 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -72,7 +72,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_mint() public { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, - abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode( + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); lpm.modifyLiquidities(calls, _deadline); @@ -88,7 +90,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Mint to a diff config, diff user. Plan memory planner = Planner.init().add( Actions.MINT_POSITION, - abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) + abi.encode( + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); vm.prank(alice); @@ -105,7 +109,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Mint to a diff config, diff user. Plan memory planner = Planner.init().add( Actions.MINT_POSITION, - abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) + abi.encode( + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); vm.prank(alice); @@ -122,7 +128,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Mint to a diff config, diff user. Plan memory planner = Planner.init().add( Actions.MINT_POSITION, - abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) + abi.encode( + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); vm.prank(alice); @@ -132,7 +140,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_increaseLiquidity_erc20() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.INCREASE_LIQUIDITY, @@ -287,7 +295,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_decreaseLiquidity() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -315,7 +323,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES) ); bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); @@ -327,7 +335,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_collect() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -345,11 +353,13 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // same-range gas tests function test_gas_sameRange_mint() public { - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.MINT_POSITION, - abi.encode(config, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) + abi.encode( + config, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); vm.prank(alice); @@ -360,11 +370,11 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_sameRange_decrease() public { // two positions of the same config, one of them decreases the entirety of the liquidity vm.startPrank(alice); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); vm.stopPrank(); uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -379,11 +389,11 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_sameRange_collect() public { // two positions of the same config, one of them collects all their fees vm.startPrank(alice); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); vm.stopPrank(); uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -400,7 +410,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) @@ -413,7 +423,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burnEmpty() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); decreaseLiquidity(tokenId, config, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( @@ -430,7 +440,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Will be more expensive than not encoding a decrease and just encoding a burn. // ie. check this against PositionManager_burn_nonEmpty uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, address(this), ZERO_BYTES); + mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -453,7 +463,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Native Token Gas Tests function test_gas_mint_native() public { uint256 liquidityToAdd = 10_000 ether; - bytes memory calls = getMintEncoded(configNative, liquidityToAdd, address(this), ZERO_BYTES); + bytes memory calls = getMintEncoded(configNative, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( SQRT_PRICE_1_1, @@ -472,12 +482,17 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { planner.add( Actions.MINT_POSITION, abi.encode( - configNative, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES + configNative, + liquidityToAdd, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + Actions.MSG_SENDER, + ZERO_BYTES ) ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); - planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, address(this))); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, Actions.MSG_SENDER)); bytes memory calls = planner.encode(); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( @@ -493,7 +508,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_increase_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); uint256 liquidityToAdd = 10_000 ether; bytes memory calls = getIncreaseEncoded(tokenId, configNative, liquidityToAdd, ZERO_BYTES); @@ -509,7 +524,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_decrease_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); uint256 liquidityToRemove = 10_000 ether; bytes memory calls = getDecreaseEncoded(tokenId, configNative, liquidityToRemove, ZERO_BYTES); @@ -519,7 +534,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_collect_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate{value: 0.2e18}(configNative.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -531,7 +546,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, @@ -545,7 +560,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burnEmpty_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); decreaseLiquidity(tokenId, configNative, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( @@ -563,7 +578,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Will be more expensive than not encoding a decrease and just encoding a burn. // ie. check this against PositionManager_burn_nonEmpty uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, address(this), ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -587,8 +602,8 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { ); planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); - planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); - planner.add(Actions.SWEEP, abi.encode(currency1, address(this))); + planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency1, Actions.MSG_SENDER)); currency0.transfer(address(lpm), 100e18); currency1.transfer(address(lpm), 100e18); diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index da8774fd..05fedfed 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -57,7 +57,7 @@ contract PositionManagerMulticallTest is Test, PosmTestSetup, LiquidityFuzzers { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES) ); bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index d07468e9..db2bbdb4 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -84,7 +84,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // Try to add liquidity at that range, but the token reenters posm PositionConfig memory config = PositionConfig({poolKey: key, tickLower: 0, tickUpper: 60}); - bytes memory calls = getMintEncoded(config, 1e18, address(this), ""); + bytes memory calls = getMintEncoded(config, 1e18, Actions.MSG_SENDER, ""); // Permit2.transferFrom does not bubble the ContractLocked error and instead reverts with its own error vm.expectRevert("TRANSFER_FROM_FAILED"); @@ -106,7 +106,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidityToAdd, address(this), ZERO_BYTES); + mint(config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); assertEq(tokenId, 1); @@ -141,7 +141,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidityToAdd, address(this), ZERO_BYTES); + mint(config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); uint256 balance0After = currency0.balanceOfSelf(); @@ -156,6 +156,41 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(balance1Before - balance1After, uint256(int256(-delta.amount1()))); } + function test_mint_toRecipient() public { + int24 tickLower = -int24(key.tickSpacing); + int24 tickUpper = int24(key.tickSpacing); + uint256 amount0Desired = 100e18; + uint256 amount1Desired = 100e18; + uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + amount0Desired, + amount1Desired + ); + + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper}); + + uint256 balance0Before = currency0.balanceOfSelf(); + uint256 balance1Before = currency1.balanceOfSelf(); + + uint256 tokenId = lpm.nextTokenId(); + // mint to specific recipient, not using the recipient constants + mint(config, liquidityToAdd, alice, ZERO_BYTES); + BalanceDelta delta = getLastDelta(); + + uint256 balance0After = currency0.balanceOfSelf(); + uint256 balance1After = currency1.balanceOfSelf(); + + assertEq(tokenId, 1); + assertEq(lpm.ownerOf(1), alice); + + assertEq(uint256(int256(-delta.amount0())), amount0Desired); + assertEq(uint256(int256(-delta.amount1())), amount1Desired); + assertEq(balance0Before - balance0After, uint256(int256(-delta.amount0()))); + assertEq(balance1Before - balance1After, uint256(int256(-delta.amount1()))); + } + function test_fuzz_mint_recipient(IPoolManager.ModifyLiquidityParams memory seedParams) public { IPoolManager.ModifyLiquidityParams memory params = createFuzzyLiquidityParams(key, seedParams, SQRT_PRICE_1_1); uint256 liquidityToAdd = @@ -207,7 +242,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_mint_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); - bytes memory calls = getMintEncoded(config, 1e18, 1 wei, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES); + bytes memory calls = getMintEncoded(config, 1e18, 1 wei, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES); vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -215,7 +250,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_mint_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); - bytes memory calls = getMintEncoded(config, 1e18, MAX_SLIPPAGE_INCREASE, 1 wei, address(this), ZERO_BYTES); + bytes memory calls = getMintEncoded(config, 1e18, MAX_SLIPPAGE_INCREASE, 1 wei, Actions.MSG_SENDER, ZERO_BYTES); vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -233,7 +268,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(amount0, amount1); // symmetric liquidity uint128 slippage = uint128(amount0) + 1; - bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, address(this), ZERO_BYTES); + bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Actions.MSG_SENDER, ZERO_BYTES); lpm.modifyLiquidities(calls, _deadline); BalanceDelta delta = getLastDelta(); assertEq(uint256(int256(-delta.amount0())), slippage); @@ -254,7 +289,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(amount0, amount1); // symmetric liquidity uint128 slippage = uint128(amount0) + 1; - bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, address(this), ZERO_BYTES); + bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Actions.MSG_SENDER, ZERO_BYTES); // swap to move the price and cause a slippage revert swap(key, true, -1e18, ZERO_BYTES); @@ -269,7 +304,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // create liquidity we can burn uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, address(this), key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); @@ -315,7 +350,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // create liquidity we can burn uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, address(this), key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); @@ -365,7 +400,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = @@ -377,7 +412,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = @@ -389,7 +424,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_exactDoesNotRevert() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // TODO: why does burning a newly minted position return original delta - 1 wei? @@ -407,7 +442,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // swapping will cause a slippage revert PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getBurnEncoded( @@ -426,7 +461,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, address(this), key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); decreaseLiquidityDelta = uint256(bound(int256(decreaseLiquidityDelta), 0, params.liquidityDelta)); PositionConfig memory config = @@ -526,7 +561,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, address(this), key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // require two-sided liquidity decreaseLiquidityDelta = bound(decreaseLiquidityDelta, 1, uint256(params.liquidityDelta)); @@ -563,7 +598,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -576,7 +611,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -589,7 +624,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_exactDoesNotRevert() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // TODO: why does decreasing a newly minted position return original delta - 1 wei? @@ -608,7 +643,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // swapping will cause a slippage revert PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, address(this), ZERO_BYTES); + mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -627,7 +662,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, address(this), key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // require two-sided liquidity vm.assume(0 < decreaseLiquidityDelta); vm.assume(decreaseLiquidityDelta < uint256(type(int256).max)); @@ -660,7 +695,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, address(this), ZERO_BYTES); + mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); BalanceDelta mintDelta = getLastDelta(); // transfer to alice @@ -688,7 +723,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, address(this), ZERO_BYTES); + mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -717,7 +752,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, address(this), ZERO_BYTES); + mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); // transfer to alice lpm.transferFrom(address(this), alice, tokenId); @@ -754,7 +789,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, address(this), ZERO_BYTES); + mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; diff --git a/test/router/Payments.gas.t.sol b/test/router/Payments.gas.t.sol index 241278cd..7872a22f 100644 --- a/test/router/Payments.gas.t.sol +++ b/test/router/Payments.gas.t.sol @@ -18,7 +18,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = Planner.init(); } - function test_gas_swap_settleFromCaller_takeAll() public { + function test_gas_swap_settleFromCaller_takeAllToSpecifiedAddress() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); @@ -29,10 +29,24 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("Payments_swap_settleFromCaller_takeAll"); + snapLastCall("Payments_swap_settleFromCaller_takeAllToSpecifiedAddress"); } - function test_gas_swap_settleFromRouter_takeAll() public { + function test_gas_swap_settleFromCaller_takeAllToMsgSender() public { + uint256 amountIn = 1 ether; + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Actions.MSG_SENDER)); + + bytes memory data = plan.encode(); + router.executeActions(data); + snapLastCall("Payments_swap_settleFromCaller_takeAllToMsgSender"); + } + + function test_gas_swap_settleWithBalance_takeAllToSpecifiedAddress() public { uint256 amountIn = 1 ether; IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); @@ -46,6 +60,23 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("Payments_swap_settleFromRouter_takeAll"); + snapLastCall("Payments_swap_settleWithBalance_takeAllToSpecifiedAddress"); + } + + function test_gas_swap_settleWithBalance_takeAllToMsgSender() public { + uint256 amountIn = 1 ether; + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + // seed the router with tokens + key0.currency0.transfer(address(router), amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_WITH_BALANCE, abi.encode(key0.currency0)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Actions.MSG_SENDER)); + + bytes memory data = plan.encode(); + router.executeActions(data); + snapLastCall("Payments_swap_settleWithBalance_takeAllToMsgSender"); } } diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index 2c48b2c6..d1a59590 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -33,7 +33,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactInputSingle"); @@ -47,7 +47,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency1, address(this)); + bytes memory data = plan.finalizeSwap(currency0, currency1, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_zeroForOne"); @@ -61,7 +61,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency1, currency0, address(this)); + bytes memory data = plan.finalizeSwap(currency1, currency0, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_oneForZero"); @@ -76,7 +76,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency2, address(this)); + bytes memory data = plan.finalizeSwap(currency0, currency2, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn2Hops"); @@ -92,7 +92,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency3, address(this)); + bytes memory data = plan.finalizeSwap(currency0, currency3, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn3Hops"); @@ -109,7 +109,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, address(this)); + bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Actions.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactInputSingle_nativeIn"); @@ -122,7 +122,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, address(this)); + bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactInputSingle_nativeOut"); @@ -136,7 +136,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, address(this)); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Actions.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn1Hop_nativeIn"); @@ -150,7 +150,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, address(this)); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_nativeOut"); @@ -165,7 +165,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, address(this)); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Actions.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn2Hops_nativeIn"); @@ -181,7 +181,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, address(this)); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Actions.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn3Hops_nativeIn"); @@ -198,7 +198,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOutputSingle"); @@ -212,7 +212,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency1, address(this)); + bytes memory data = plan.finalizeSwap(currency0, currency1, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_zeroForOne"); @@ -226,7 +226,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency1, currency0, address(this)); + bytes memory data = plan.finalizeSwap(currency1, currency0, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_oneForZero"); @@ -241,7 +241,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency2, address(this)); + bytes memory data = plan.finalizeSwap(currency0, currency2, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut2Hops"); @@ -257,7 +257,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency3, address(this)); + bytes memory data = plan.finalizeSwap(currency0, currency3, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops"); @@ -274,7 +274,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, address(this)); + bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Actions.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOutputSingle_nativeIn_sweepETH"); @@ -287,7 +287,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, address(this)); + bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Actions.MSG_SENDER); router.executeActionsAndSweepExcessETH(data); snapLastCall("V4Router_ExactOutputSingle_nativeOut"); @@ -301,7 +301,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, address(this)); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Actions.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut1Hop_nativeIn_sweepETH"); @@ -315,7 +315,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, address(this)); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_nativeOut"); @@ -330,7 +330,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, address(this)); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Actions.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut2Hops_nativeIn"); @@ -346,7 +346,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, address(this)); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Actions.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut3Hops_nativeIn"); @@ -363,7 +363,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.NATIVE, address(this)); + bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.NATIVE, Actions.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops_nativeOut"); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index bc34f0a5..172b08a4 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -11,6 +11,8 @@ contract V4RouterTest is RoutingTestHelpers { using CurrencyLibrary for Currency; using Planner for Plan; + address alice = makeAddr("ALICE"); + function setUp() public { setupRouterCurrenciesAndPoolsWithLiquidity(); plan = Planner.init(); @@ -30,13 +32,13 @@ contract V4RouterTest is RoutingTestHelpers { ); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); vm.expectRevert(IV4Router.TooLittleReceived.selector); router.executeActions(data); } - function test_swapExactInputSingle_zeroForOne() public { + function test_swapExactInputSingle_zeroForOne_takeToMsgSender() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -54,6 +56,60 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); } + function test_swapExactInputSingle_zeroForOne_takeToRecipient() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + + uint256 aliceOutputBalanceBefore = key0.currency1.balanceOf(alice); + + // swap with alice as the take recipient + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, alice); + + uint256 aliceOutputBalanceAfter = key0.currency1.balanceOf(alice); + + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + + assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + // this contract's output balance has not changed because funds went to alice + assertEq(outputBalanceAfter, outputBalanceBefore); + assertEq(aliceOutputBalanceAfter - aliceOutputBalanceBefore, expectedAmountOut); + } + + // This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4 + // swap is before another swap on v2/v3 + function test_swapExactInputSingle_zeroForOne_takeToRouter() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + + // the router holds no funds before + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + + // swap with the router as the take recipient + (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) + = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, Actions.ADDRESS_THIS); + + // the output tokens have been left in the router + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), expectedAmountOut); + + assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + // this contract's output balance has not changed because funds went to the router + assertEq(outputBalanceAfter, outputBalanceBefore); + } + function test_swapExactInputSingle_oneForZero() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -82,7 +138,7 @@ contract V4RouterTest is RoutingTestHelpers { params.amountOutMinimum = uint128(expectedAmountOut + 1); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); vm.expectRevert(IV4Router.TooLittleReceived.selector); router.executeActions(data); @@ -328,7 +384,7 @@ contract V4RouterTest is RoutingTestHelpers { ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); vm.expectRevert(IV4Router.TooMuchRequested.selector); router.executeActions(data); @@ -384,7 +440,7 @@ contract V4RouterTest is RoutingTestHelpers { params.amountInMaximum = uint128(expectedAmountIn - 1); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); vm.expectRevert(IV4Router.TooMuchRequested.selector); router.executeActions(data); diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 71020abe..2321eccc 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -47,13 +47,13 @@ library Planner { return abi.encode(plan.actions, plan.params); } - function finalizeSwap(Plan memory plan, Currency inputCurrency, Currency outputCurrency, address recipient) + function finalizeSwap(Plan memory plan, Currency inputCurrency, Currency outputCurrency, address takeRecipient) internal pure returns (bytes memory) { plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, recipient)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, takeRecipient)); return plan.encode(); } } diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index d09b77ec..f47a9526 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -123,7 +123,12 @@ contract RoutingTestHelpers is Test, Deployers { params.amountInMaximum = type(uint128).max; } - function _finalizeAndExecuteSwap(Currency inputCurrency, Currency outputCurrency, uint256 amountIn) + function _finalizeAndExecuteSwap( + Currency inputCurrency, + Currency outputCurrency, + uint256 amountIn, + address takeRecipient + ) internal returns ( uint256 inputBalanceBefore, @@ -135,7 +140,7 @@ contract RoutingTestHelpers is Test, Deployers { inputBalanceBefore = inputCurrency.balanceOfSelf(); outputBalanceBefore = outputCurrency.balanceOfSelf(); - bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, address(this)); + bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, takeRecipient); uint256 value = (inputCurrency.isNative()) ? amountIn : 0; @@ -146,6 +151,18 @@ contract RoutingTestHelpers is Test, Deployers { outputBalanceAfter = outputCurrency.balanceOfSelf(); } + function _finalizeAndExecuteSwap(Currency inputCurrency, Currency outputCurrency, uint256 amountIn) + internal + returns ( + uint256 inputBalanceBefore, + uint256 outputBalanceBefore, + uint256 inputBalanceAfter, + uint256 outputBalanceAfter + ) + { + return _finalizeAndExecuteSwap(inputCurrency, outputCurrency, amountIn, Actions.MSG_SENDER); + } + function _finalizeAndExecuteNativeInputExactOutputSwap( Currency inputCurrency, Currency outputCurrency, @@ -162,7 +179,7 @@ contract RoutingTestHelpers is Test, Deployers { inputBalanceBefore = inputCurrency.balanceOfSelf(); outputBalanceBefore = outputCurrency.balanceOfSelf(); - bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, address(this)); + bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, Actions.MSG_SENDER); // send too much ETH to mimic slippage uint256 value = expectedAmountIn + 0.1 ether; From fdf28e4aebd112a70015a4d36a5b79f62e39247f Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:08:16 -0400 Subject: [PATCH 11/52] allow batching sig based approvals through p2 forwarder (#238) * allow batching sig based approvals through p2 forwarder * forwarder tests * fix imports * add multicall tests * pr comments * use contracts selector --- ...tionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 11 +- src/base/Permit2Forwarder.sol | 32 ++++ test/position-managers/Permit2Forwarder.t.sol | 75 +++++++++ .../PositionManager.multicall.t.sol | 142 +++++++++++++++++- test/shared/Permit2SignatureHelpers.sol | 137 +++++++++++++++++ 6 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 src/base/Permit2Forwarder.sol create mode 100644 test/position-managers/Permit2Forwarder.t.sol create mode 100644 test/shared/Permit2SignatureHelpers.sol diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index df384f33..2a4a2f83 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416339 \ No newline at end of file +416316 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 49e13bc4..42b96d59 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -25,6 +25,7 @@ import {PositionConfig, PositionConfigLibrary} from "./libraries/PositionConfig. import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {Actions} from "./libraries/Actions.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; +import {Permit2Forwarder} from "./base/Permit2Forwarder.sol"; import {SlippageCheckLibrary} from "./libraries/SlippageCheck.sol"; contract PositionManager is @@ -34,7 +35,8 @@ contract PositionManager is Multicall, DeltaResolver, ReentrancyLock, - BaseActionsRouter + BaseActionsRouter, + Permit2Forwarder { using SafeTransferLib for *; using CurrencyLibrary for Currency; @@ -52,14 +54,11 @@ contract PositionManager is /// @inheritdoc IPositionManager mapping(uint256 tokenId => bytes32 configId) public positionConfigs; - IAllowanceTransfer public immutable permit2; - constructor(IPoolManager _poolManager, IAllowanceTransfer _permit2) BaseActionsRouter(_poolManager) + Permit2Forwarder(_permit2) ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM", "1") - { - permit2 = _permit2; - } + {} modifier checkDeadline(uint256 deadline) { if (block.timestamp > deadline) revert DeadlinePassed(); diff --git a/src/base/Permit2Forwarder.sol b/src/base/Permit2Forwarder.sol new file mode 100644 index 00000000..2bd7507b --- /dev/null +++ b/src/base/Permit2Forwarder.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.24; + +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +/// @notice PermitForwarder allows permitting this contract as a spender on permit2 +/// @dev This contract does not enforce the spender to be this contract, but that is the intended use case +contract Permit2Forwarder { + IAllowanceTransfer public immutable permit2; + + constructor(IAllowanceTransfer _permit2) { + permit2 = _permit2; + } + + /// @notice allows forwarding a single permit to permit2 + /// @dev this function is payable to allow multicall with NATIVE based actions + function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature) + external + payable + { + permit2.permit(owner, permitSingle, signature); + } + + /// @notice allows forwarding batch permits to permit2 + /// @dev this function is payable to allow multicall with NATIVE based actions + function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata permitBatch, bytes calldata signature) + external + payable + { + permit2.permit(owner, permitBatch, signature); + } +} diff --git a/test/position-managers/Permit2Forwarder.t.sol b/test/position-managers/Permit2Forwarder.t.sol new file mode 100644 index 00000000..958cba3b --- /dev/null +++ b/test/position-managers/Permit2Forwarder.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; + +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; + +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; +import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; +import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol"; + +contract Permit2ForwarderTest is Test, PosmTestSetup, Permit2SignatureHelpers { + Permit2Forwarder permit2Forwarder; + + uint160 amount0 = 10e18; + // the expiration of the allowance is large + uint48 expiration = uint48(block.timestamp + 10e18); + uint48 nonce = 0; + + bytes32 PERMIT2_DOMAIN_SEPARATOR; + + uint256 alicePrivateKey; + address alice; + + function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployAndMint2Currencies(); + // also deploys permit2 + deployPosm(manager); + permit2Forwarder = new Permit2Forwarder(permit2); + PERMIT2_DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); + + alicePrivateKey = 0x12341234; + alice = vm.addr(alicePrivateKey); + } + + function test_permit_single_succeeds() public { + IAllowanceTransfer.PermitSingle memory permit = + defaultERC20PermitAllowance(Currency.unwrap(currency0), amount0, expiration, nonce); + bytes memory sig = getPermitSignature(permit, alicePrivateKey, PERMIT2_DOMAIN_SEPARATOR); + + permit2Forwarder.permit(alice, permit, sig); + + (uint160 _amount, uint48 _expiration, uint48 _nonce) = + permit2.allowance(alice, Currency.unwrap(currency0), address(this)); + assertEq(_amount, amount0); + assertEq(_expiration, expiration); + assertEq(_nonce, nonce + 1); // the nonce was incremented + } + + function test_permit_batch_succeeds() public { + address[] memory tokens = new address[](2); + tokens[0] = Currency.unwrap(currency0); + tokens[1] = Currency.unwrap(currency1); + + IAllowanceTransfer.PermitBatch memory permit = + defaultERC20PermitBatchAllowance(tokens, amount0, expiration, nonce); + bytes memory sig = getPermitBatchSignature(permit, alicePrivateKey, PERMIT2_DOMAIN_SEPARATOR); + + permit2Forwarder.permitBatch(alice, permit, sig); + + (uint160 _amount, uint48 _expiration, uint48 _nonce) = + permit2.allowance(alice, Currency.unwrap(currency0), address(this)); + assertEq(_amount, amount0); + assertEq(_expiration, expiration); + assertEq(_nonce, nonce + 1); + (uint160 _amount1, uint48 _expiration1, uint48 _nonce1) = + permit2.allowance(alice, Currency.unwrap(currency1), address(this)); + assertEq(_amount1, amount0); + assertEq(_expiration1, expiration); + assertEq(_nonce1, nonce + 1); + } +} diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index 05fedfed..f1d5c548 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -6,11 +6,14 @@ import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; @@ -22,15 +25,34 @@ import {IMulticall} from "../../src/interfaces/IMulticall.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; +import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol"; +import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; -contract PositionManagerMulticallTest is Test, PosmTestSetup, LiquidityFuzzers { +contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTestSetup, LiquidityFuzzers { using FixedPointMathLib for uint256; using CurrencyLibrary for Currency; using Planner for Plan; + using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; PoolId poolId; address alice = makeAddr("ALICE"); + Permit2Forwarder permit2Forwarder; + + uint160 permitAmount = type(uint160).max; + // the expiration of the allowance is large + uint48 permitExpiration = uint48(block.timestamp + 10e18); + uint48 permitNonce = 0; + + bytes32 PERMIT2_DOMAIN_SEPARATOR; + + // bob used for permit2 signature tests + uint256 bobPrivateKey; + address bob; + + PositionConfig config; + function setUp() public { deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); @@ -39,6 +61,15 @@ contract PositionManagerMulticallTest is Test, PosmTestSetup, LiquidityFuzzers { // Requires currency0 and currency1 to be set in base Deployers contract. deployAndApprovePosm(manager); + + permit2Forwarder = new Permit2Forwarder(permit2); + PERMIT2_DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); + + bobPrivateKey = 0x12341234; + bob = vm.addr(bobPrivateKey); + + seedBalance(bob); + approvePosmFor(bob); } function test_multicall_initializePool_mint() public { @@ -71,4 +102,111 @@ contract PositionManagerMulticallTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(result.amount0(), amountSpecified); assertGt(result.amount1(), 0); } + + function test_multicall_permit_mint() public { + PositionConfig memory config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + // 1. revoke the auto permit we give to posm for 1 token + vm.prank(bob); + permit2.approve(Currency.unwrap(currency0), address(lpm), 0, 0); + + (uint160 _amount,, uint48 _expiration) = + permit2.allowance(address(bob), Currency.unwrap(currency0), address(this)); + + assertEq(_amount, 0); + assertEq(_expiration, 0); + + uint256 tokenId = lpm.nextTokenId(); + bytes memory mintCall = getMintEncoded(config, 10e18, bob, ZERO_BYTES); + + // 2 . call a mint that reverts because position manager doesn't have permission on permit2 + vm.expectRevert(abi.encodeWithSelector(IAllowanceTransfer.InsufficientAllowance.selector, 0)); + vm.prank(bob); + lpm.modifyLiquidities(mintCall, _deadline); + + // 3. encode a permit for that revoked token + IAllowanceTransfer.PermitSingle memory permit = + defaultERC20PermitAllowance(Currency.unwrap(currency0), permitAmount, permitExpiration, permitNonce); + permit.spender = address(lpm); + bytes memory sig = getPermitSignature(permit, bobPrivateKey, PERMIT2_DOMAIN_SEPARATOR); + + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector(Permit2Forwarder.permit.selector, bob, permit, sig); + calls[1] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, mintCall, _deadline); + + vm.prank(bob); + lpm.multicall(calls); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + (_amount,,) = permit2.allowance(address(bob), Currency.unwrap(currency0), address(lpm)); + + assertEq(_amount, permitAmount); + assertEq(liquidity, 10e18); + assertEq(lpm.ownerOf(tokenId), bob); + } + + function test_multicall_permit_batch_mint() public { + PositionConfig memory config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + // 1. revoke the auto permit we give to posm for 1 token + vm.prank(bob); + permit2.approve(Currency.unwrap(currency0), address(lpm), 0, 0); + permit2.approve(Currency.unwrap(currency1), address(lpm), 0, 0); + + (uint160 _amount0,, uint48 _expiration0) = + permit2.allowance(address(bob), Currency.unwrap(currency0), address(this)); + + (uint160 _amount1,, uint48 _expiration1) = + permit2.allowance(address(bob), Currency.unwrap(currency1), address(this)); + + assertEq(_amount0, 0); + assertEq(_expiration0, 0); + assertEq(_amount1, 0); + assertEq(_expiration1, 0); + + uint256 tokenId = lpm.nextTokenId(); + bytes memory mintCall = getMintEncoded(config, 10e18, bob, ZERO_BYTES); + + // 2 . call a mint that reverts because position manager doesn't have permission on permit2 + vm.expectRevert(abi.encodeWithSelector(IAllowanceTransfer.InsufficientAllowance.selector, 0)); + vm.prank(bob); + lpm.modifyLiquidities(mintCall, _deadline); + + // 3. encode a permit for that revoked token + address[] memory tokens = new address[](2); + tokens[0] = Currency.unwrap(currency0); + tokens[1] = Currency.unwrap(currency1); + + IAllowanceTransfer.PermitBatch memory permit = + defaultERC20PermitBatchAllowance(tokens, permitAmount, permitExpiration, permitNonce); + permit.spender = address(lpm); + bytes memory sig = getPermitBatchSignature(permit, bobPrivateKey, PERMIT2_DOMAIN_SEPARATOR); + + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector(Permit2Forwarder.permitBatch.selector, bob, permit, sig); + calls[1] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, mintCall, _deadline); + + vm.prank(bob); + lpm.multicall(calls); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + (_amount0,,) = permit2.allowance(address(bob), Currency.unwrap(currency0), address(lpm)); + (_amount1,,) = permit2.allowance(address(bob), Currency.unwrap(currency1), address(lpm)); + assertEq(_amount0, permitAmount); + assertEq(_amount1, permitAmount); + assertEq(liquidity, 10e18); + assertEq(lpm.ownerOf(tokenId), bob); + } } diff --git a/test/shared/Permit2SignatureHelpers.sol b/test/shared/Permit2SignatureHelpers.sol new file mode 100644 index 00000000..1f5d075b --- /dev/null +++ b/test/shared/Permit2SignatureHelpers.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Vm} from "forge-std/Vm.sol"; +import {EIP712} from "openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol"; +import {ECDSA} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; + +/// taken from permit2 utils PermitSignature files +contract Permit2SignatureHelpers { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 public constant _PERMIT_DETAILS_TYPEHASH = + keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"); + + bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + + bytes32 public constant _PERMIT_BATCH_TYPEHASH = keccak256( + "PermitBatch(PermitDetails[] details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + + function getPermitSignatureRaw( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey, + bytes32 domainSeparator + ) internal pure returns (uint8 v, bytes32 r, bytes32 s) { + bytes32 permitHash = keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); + + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permit.spender, permit.sigDeadline)) + ) + ); + + (v, r, s) = vm.sign(privateKey, msgHash); + } + + function getPermitSignature( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey, + bytes32 domainSeparator + ) internal pure returns (bytes memory sig) { + (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); + return bytes.concat(r, s, bytes1(v)); + } + + function getCompactPermitSignature( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey, + bytes32 domainSeparator + ) internal pure returns (bytes memory sig) { + (uint8 v, bytes32 r, bytes32 s) = getPermitSignatureRaw(permit, privateKey, domainSeparator); + bytes32 vs; + (r, vs) = _getCompactSignature(v, r, s); + return bytes.concat(r, vs); + } + + function _getCompactSignature(uint8 vRaw, bytes32 rRaw, bytes32 sRaw) + internal + pure + returns (bytes32 r, bytes32 vs) + { + uint8 v = vRaw - 27; // 27 is 0, 28 is 1 + vs = bytes32(uint256(v) << 255) | sRaw; + return (rRaw, vs); + } + + function getPermitBatchSignature( + IAllowanceTransfer.PermitBatch memory permit, + uint256 privateKey, + bytes32 domainSeparator + ) internal pure returns (bytes memory sig) { + bytes32[] memory permitHashes = new bytes32[](permit.details.length); + for (uint256 i = 0; i < permit.details.length; ++i) { + permitHashes[i] = keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details[i])); + } + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256( + abi.encode( + _PERMIT_BATCH_TYPEHASH, + keccak256(abi.encodePacked(permitHashes)), + permit.spender, + permit.sigDeadline + ) + ) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); + return bytes.concat(r, s, bytes1(v)); + } + + function defaultERC20PermitAllowance(address token0, uint160 amount, uint48 expiration, uint48 nonce) + internal + view + returns (IAllowanceTransfer.PermitSingle memory) + { + IAllowanceTransfer.PermitDetails memory details = + IAllowanceTransfer.PermitDetails({token: token0, amount: amount, expiration: expiration, nonce: nonce}); + return IAllowanceTransfer.PermitSingle({ + details: details, + spender: address(this), + sigDeadline: block.timestamp + 100 + }); + } + + function defaultERC20PermitBatchAllowance(address[] memory tokens, uint160 amount, uint48 expiration, uint48 nonce) + internal + view + returns (IAllowanceTransfer.PermitBatch memory) + { + IAllowanceTransfer.PermitDetails[] memory details = new IAllowanceTransfer.PermitDetails[](tokens.length); + + for (uint256 i = 0; i < tokens.length; ++i) { + details[i] = IAllowanceTransfer.PermitDetails({ + token: tokens[i], + amount: amount, + expiration: expiration, + nonce: nonce + }); + } + + return IAllowanceTransfer.PermitBatch({ + details: details, + spender: address(this), + sigDeadline: block.timestamp + 100 + }); + } +} From ebf7a4de2e4be6fb7af5ed8bc7dea09dbde1892d Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:13:40 +0100 Subject: [PATCH 12/52] Take portion (#250) * Take portion with test * fuzz bips lib * another test * nits --- ...p_settleFromCaller_takeAllToMsgSender.snap | 2 +- ...eFromCaller_takeAllToSpecifiedAddress.snap | 2 +- ..._settleWithBalance_takeAllToMsgSender.snap | 2 +- ...WithBalance_takeAllToSpecifiedAddress.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- ...Router_ExactOut1Hop_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOut1Hop_nativeOut.snap | 2 +- .../V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .../V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .../V4Router_ExactOut2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOut3Hops_nativeIn.snap | 2 +- .../V4Router_ExactOut3Hops_nativeOut.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- ...r_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/V4Router.sol | 5 ++ src/libraries/BipsLibrary.sol | 17 ++++++ src/libraries/CalldataDecoder.sol | 13 +++++ test/libraries/BipsLibrary.t.sol | 20 +++++++ test/router/Payments.t.sol | 58 +++++++++++++++++++ 33 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 src/libraries/BipsLibrary.sol create mode 100644 test/libraries/BipsLibrary.t.sol diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index a3249887..2fdca63f 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -134122 \ No newline at end of file +134126 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index bf565cdf..5ac0d560 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -134260 \ No newline at end of file +134264 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index be94d9c7..9964b11f 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -126917 \ No newline at end of file +126921 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index b29fe9a2..fe73e59d 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -127055 \ No newline at end of file +127059 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index b6655f3d..f482385a 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -7119 \ No newline at end of file +7342 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index b10f658b..43edb963 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -120452 \ No newline at end of file +120464 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index c4498679..9dc5d0f4 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -119647 \ No newline at end of file +119659 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index df5b16d3..88afeda8 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -128519 \ No newline at end of file +128531 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 25aad418..9926192b 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135349 \ No newline at end of file +135361 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index e5d3b159..b33d7cca 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -186848 \ No newline at end of file +186868 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index 516d7719..fa7af13f 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -178783 \ No newline at end of file +178803 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 68b5a2f7..52f5609d 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238372 \ No newline at end of file +238400 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index c62a5ed9..f09aa5a9 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -230331 \ No newline at end of file +230359 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index a3249887..2fdca63f 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134122 \ No newline at end of file +134126 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index 413b7ead..bd2ca7c6 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119225 \ No newline at end of file +119229 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index 6ba81107..35215fe0 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118398 \ No newline at end of file +118402 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index ca902a0e..8ca480a4 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -126249 \ No newline at end of file +126261 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index be6ff104..dc36e641 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -120482 \ No newline at end of file +120494 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index 1a5e4c8d..c9888d60 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -129354 \ No newline at end of file +129366 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index 330497a2..4ea5699e 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -134155 \ No newline at end of file +134167 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index 4d964996..e1f7e630 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186257 \ No newline at end of file +186277 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index 0bf4b92f..63e6800b 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -183152 \ No newline at end of file +183172 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 75ebd28f..9ffbf5f3 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -238399 \ No newline at end of file +238427 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index cb985312..1af00254 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -235318 \ No newline at end of file +235346 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index fb1afd9d..089ed2cd 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -229551 \ No newline at end of file +229579 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index db456dda..b4226dd3 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -132926 \ No newline at end of file +132930 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index a1f3540c..767ad5a9 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -125020 \ No newline at end of file +125024 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index 0ee9b2fc..7f51d037 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -119311 \ No newline at end of file +119315 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 03071ec4..f5a06dc0 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -9,6 +9,7 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; +import {BipsLibrary} from "./libraries/BipsLibrary.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; @@ -23,6 +24,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { using SafeCastTemp for *; using PathKeyLib for PathKey; using CalldataDecoder for bytes; + using BipsLibrary for uint256; constructor(IPoolManager _poolManager) BaseActionsRouter(_poolManager) {} @@ -59,6 +61,9 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { // TODO should _take have a minAmountOut added slippage check? _take(currency, _map(recipient), amount); + } else if (action == Actions.TAKE_PORTION) { + (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _map(recipient), _getFullTakeAmount(currency).calculatePortion(bips)); } else { revert UnsupportedAction(action); } diff --git a/src/libraries/BipsLibrary.sol b/src/libraries/BipsLibrary.sol new file mode 100644 index 00000000..ff2cadd3 --- /dev/null +++ b/src/libraries/BipsLibrary.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.19; + +/// @title For calculating a percentage of an amount, using bips +library BipsLibrary { + uint256 internal constant BIPS_BASE = 10_000; + + /// @notice emitted when an invalid percentage is provided + error InvalidBips(); + + /// @param amount The total amount to calculate a percentage of + /// @param bips The percentage to calculate, in bips + function calculatePortion(uint256 amount, uint256 bips) internal pure returns (uint256) { + if (bips > BIPS_BASE) revert InvalidBips(); + return (amount * bips) / BIPS_BASE; + } +} diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index b2ea3c38..fc3d6834 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -178,6 +178,19 @@ library CalldataDecoder { } } + /// @dev equivalent to: abi.decode(params, (Currency, address, uint256)) in calldata + function decodeCurrencyAddressAndUint256(bytes calldata params) + internal + pure + returns (Currency currency, address _address, uint256 amount) + { + assembly ("memory-safe") { + currency := calldataload(params.offset) + _address := calldataload(add(params.offset, 0x20)) + amount := calldataload(add(params.offset, 0x40)) + } + } + /// @dev equivalent to: abi.decode(params, (Currency, uint256)) in calldata function decodeCurrencyAndUint256(bytes calldata params) internal diff --git a/test/libraries/BipsLibrary.t.sol b/test/libraries/BipsLibrary.t.sol new file mode 100644 index 00000000..c2ff12ff --- /dev/null +++ b/test/libraries/BipsLibrary.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import "forge-std/StdError.sol"; +import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; + +contract PositionConfigTest is Test { + using BipsLibrary for uint256; + + function test_fuzz_calculatePortion(uint256 amount, uint256 bips) public { + amount = bound(amount, 0, uint256(type(uint128).max)); + if (bips > BipsLibrary.BIPS_BASE) { + vm.expectRevert(BipsLibrary.InvalidBips.selector); + amount.calculatePortion(bips); + } else { + assertEq(amount.calculatePortion(bips), amount * bips / BipsLibrary.BIPS_BASE); + } + } +} diff --git a/test/router/Payments.t.sol b/test/router/Payments.t.sol index 51dde092..73090e1c 100644 --- a/test/router/Payments.t.sol +++ b/test/router/Payments.t.sol @@ -8,11 +8,14 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; +import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; contract PaymentsTests is RoutingTestHelpers, GasSnapshot { using CurrencyLibrary for Currency; using Planner for Plan; + address bob = makeAddr("BOB"); + function setUp() public { setupRouterCurrenciesAndPoolsWithLiquidity(); plan = Planner.init(); @@ -81,4 +84,59 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { assertEq(inputBalanceBefore, inputBalanceAfter); assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); } + + function test_settle_takePortion_takeAll() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); + // take 15 bips to Bob + plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, 15)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + + uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); + uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); + uint256 bobBalanceBefore = key0.currency1.balanceOf(bob); + + // router is empty before + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + + bytes memory data = plan.encode(); + router.executeActions(data); + + uint256 inputBalanceAfter = key0.currency0.balanceOfSelf(); + uint256 outputBalanceAfter = key0.currency1.balanceOfSelf(); + uint256 bobBalanceAfter = key0.currency1.balanceOf(bob); + + uint256 expectedFee = expectedAmountOut * 15 / BipsLibrary.BIPS_BASE; + + // router is empty + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + // Bob got expectedFee, and the caller got the rest of the output + assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut - expectedFee); + assertEq(bobBalanceAfter - bobBalanceBefore, expectedFee); + } + + function test_settle_takePortion_reverts() public { + uint256 amountIn = 1 ether; + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); + // bips is larger than maximum bips + plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, BipsLibrary.BIPS_BASE + 1)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + + bytes memory data = plan.encode(); + + vm.expectRevert(BipsLibrary.InvalidBips.selector); + router.executeActions(data); + } } From 30863ccf3638c77ce3331775e82e3d71895792d4 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:48:49 +0100 Subject: [PATCH 13/52] Pay with balance, and use delta as amount in (#249) * map exactIn amount to open delta * Tests * generalised settle * remove console log * other map functions * settle vs swap map amounts * Constants file and compiler warnings * linting * PR comment --- .../BaseActionsRouter_mock10commands.snap | 2 +- ...p_settleFromCaller_takeAllToMsgSender.snap | 2 +- ...eFromCaller_takeAllToSpecifiedAddress.snap | 2 +- ..._settleWithBalance_takeAllToMsgSender.snap | 2 +- ...WithBalance_takeAllToSpecifiedAddress.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- src/PositionManager.sol | 16 ++-- src/V4Router.sol | 26 +++---- src/base/BaseActionsRouter.sol | 14 ++-- src/base/DeltaResolver.sol | 23 ++++++ src/base/Permit2Forwarder.sol | 4 +- src/libraries/Actions.sol | 12 +-- src/libraries/CalldataDecoder.sol | 13 ++++ src/libraries/Constants.sol | 13 ++++ test/BaseActionsRouter.t.sol | 9 ++- test/mocks/MockBaseActionsRouter.sol | 4 +- test/mocks/MockV4Router.sol | 2 + test/position-managers/Execute.t.sol | 11 +-- .../position-managers/IncreaseLiquidity.t.sol | 25 ++++--- test/position-managers/NativeToken.t.sol | 22 +++--- .../PositionManager.gas.t.sol | 61 ++++++++-------- .../PositionManager.multicall.t.sol | 9 ++- test/position-managers/PositionManager.t.sol | 51 +++++++------ test/router/Payments.gas.t.sol | 9 ++- test/router/Payments.t.sol | 3 +- test/router/V4Router.gas.t.sol | 47 ++++++------ test/router/V4Router.t.sol | 73 +++++++++++++++++-- test/shared/RoutingTestHelpers.sol | 5 +- 40 files changed, 301 insertions(+), 187 deletions(-) create mode 100644 src/libraries/Constants.sol diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index 34a072bb..b4ba2efe 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -61756 \ No newline at end of file +61778 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index 2fdca63f..8be49d50 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -134126 \ No newline at end of file +134509 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index 5ac0d560..da685a9d 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -134264 \ No newline at end of file +134647 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index 9964b11f..ee4568b7 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -126921 \ No newline at end of file +127656 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index fe73e59d..76dbb684 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -127059 \ No newline at end of file +127794 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 6bc0e296..2e9e23a4 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370009 \ No newline at end of file +370923 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index f482385a..9a2f1c0d 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -7342 \ No newline at end of file +7995 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index 43edb963..f4c2ccab 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -120464 \ No newline at end of file +120558 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index 9dc5d0f4..f5f1f8b2 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -119659 \ No newline at end of file +119753 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index 88afeda8..e83986bc 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -128531 \ No newline at end of file +128625 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 9926192b..6f6a89bc 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135361 \ No newline at end of file +135455 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index b33d7cca..58e2ffa5 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -186868 \ No newline at end of file +186962 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index fa7af13f..59388aa0 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -178803 \ No newline at end of file +178897 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 52f5609d..66182353 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238400 \ No newline at end of file +238494 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index f09aa5a9..a71256a4 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -230359 \ No newline at end of file +230453 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index 2fdca63f..8be49d50 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134126 \ No newline at end of file +134509 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index bd2ca7c6..a99d4022 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119229 \ No newline at end of file +119612 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index 35215fe0..a7b81119 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118402 \ No newline at end of file +118790 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 42b96d59..8bc6782a 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -106,7 +106,7 @@ contract PositionManager is address owner, bytes calldata hookData ) = params.decodeMintParams(); - _mint(config, liquidity, amount0Max, amount1Max, _map(owner), hookData); + _mint(config, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData); } else if (action == Actions.CLOSE_CURRENCY) { Currency currency = params.decodeCurrency(); _close(currency); @@ -123,12 +123,12 @@ contract PositionManager is bytes calldata hookData ) = params.decodeBurnParams(); _burn(tokenId, config, amount0Min, amount1Min, hookData); - } else if (action == Actions.SETTLE_WITH_BALANCE) { - Currency currency = params.decodeCurrency(); - _settleWithBalance(currency); + } else if (action == Actions.SETTLE) { + (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); + _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); - _sweep(currency, _map(to)); + _sweep(currency, _mapRecipient(to)); } else { revert UnsupportedAction(action); } @@ -214,12 +214,6 @@ contract PositionManager is poolManager.clear(currency, uint256(currencyDelta)); } - /// @dev uses this addresses balance to settle a negative delta - function _settleWithBalance(Currency currency) internal { - // set the payer to this address, performs a transfer. - _settle(currency, address(this), _getFullSettleAmount(currency)); - } - /// @dev this is overloaded with ERC721Permit._burn function _burn( uint256 tokenId, diff --git a/src/V4Router.sol b/src/V4Router.sol index f5a06dc0..91ea9404 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -6,6 +6,7 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; @@ -22,6 +23,7 @@ import {SafeCastTemp} from "./libraries/SafeCast.sol"; /// An inheriting contract should call _executeActions at the point that they wish actions to be executed abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { using SafeCastTemp for *; + using SafeCast for *; using PathKeyLib for PathKey; using CalldataDecoder for bytes; using BipsLibrary for uint256; @@ -31,7 +33,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { // TODO native support !! function _handleAction(uint256 action, bytes calldata params) internal override { // swap actions and payment actions in different blocks for gas efficiency - if (action < Actions.SETTLE) { + if (action < Actions.SETTLE_ALL) { if (action == Actions.SWAP_EXACT_IN) { IV4Router.ExactInputParams calldata swapParams = params.decodeSwapExactInParams(); _swapExactInput(swapParams); @@ -52,18 +54,16 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { Currency currency = params.decodeCurrency(); // TODO should it have a maxAmountOut added slippage protection? _settle(currency, _msgSender(), _getFullSettleAmount(currency)); - } else if (action == Actions.SETTLE_WITH_BALANCE) { - Currency currency = params.decodeCurrency(); - _settle(currency, address(this), _getFullSettleAmount(currency)); + } else if (action == Actions.SETTLE) { + (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); + _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); } else if (action == Actions.TAKE_ALL) { (Currency currency, address recipient) = params.decodeCurrencyAndAddress(); uint256 amount = _getFullTakeAmount(currency); - - // TODO should _take have a minAmountOut added slippage check? - _take(currency, _map(recipient), amount); + _take(currency, _mapRecipient(recipient), amount); } else if (action == Actions.TAKE_PORTION) { (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); - _take(currency, _map(recipient), _getFullTakeAmount(currency).calculatePortion(bips)); + _take(currency, _mapRecipient(recipient), _getFullTakeAmount(currency).calculatePortion(bips)); } else { revert UnsupportedAction(action); } @@ -71,12 +71,10 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { } function _swapExactInputSingle(IV4Router.ExactInputSingleParams calldata params) private { + uint128 amountIn = + _mapInputAmount(params.amountIn, params.zeroForOne ? params.poolKey.currency0 : params.poolKey.currency1); uint128 amountOut = _swap( - params.poolKey, - params.zeroForOne, - int256(-int128(params.amountIn)), - params.sqrtPriceLimitX96, - params.hookData + params.poolKey, params.zeroForOne, int256(-int128(amountIn)), params.sqrtPriceLimitX96, params.hookData ).toUint128(); if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); } @@ -86,8 +84,8 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { // Caching for gas savings uint256 pathLength = params.path.length; uint128 amountOut; - uint128 amountIn = params.amountIn; Currency currencyIn = params.currencyIn; + uint128 amountIn = _mapInputAmount(params.amountIn, currencyIn); PathKey calldata pathKey; for (uint256 i = 0; i < pathLength; i++) { diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index 91f3c827..78193601 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -5,6 +5,7 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {SafeCallback} from "./SafeCallback.sol"; import {CalldataDecoder} from "../libraries/CalldataDecoder.sol"; import {Actions} from "../libraries/Actions.sol"; +import {Constants} from "../libraries/Constants.sol"; /// @notice Abstract contract for performing a combination of actions on Uniswap v4. /// @dev Suggested uint256 action values are defined in Actions.sol, however any definition can be used @@ -55,15 +56,18 @@ abstract contract BaseActionsRouter is SafeCallback { function _msgSender() internal view virtual returns (address); /// @notice Calculates the address for a action - /// @param recipient The address or address-flag for the action - /// @return output The resultant address for the action - function _map(address recipient) internal view returns (address) { - if (recipient == Actions.MSG_SENDER) { + function _mapRecipient(address recipient) internal view returns (address) { + if (recipient == Constants.MSG_SENDER) { return _msgSender(); - } else if (recipient == Actions.ADDRESS_THIS) { + } else if (recipient == Constants.ADDRESS_THIS) { return address(this); } else { return recipient; } } + + /// @notice Calculates the payer for an action + function _mapPayer(bool payerIsUser) internal view returns (address) { + return payerIsUser ? _msgSender() : address(this); + } } diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 9ddd81db..cfed40c4 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -5,11 +5,14 @@ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {ImmutableState} from "./ImmutableState.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {Constants} from "../libraries/Constants.sol"; /// @notice Abstract contract used to sync, send, and settle funds to the pool manager /// @dev Note that sync() is called before any erc-20 transfer in `settle`. abstract contract DeltaResolver is ImmutableState { using TransientStateLibrary for IPoolManager; + using SafeCast for *; /// @notice Emitted trying to settle a positive delta. error IncorrectUseOfSettle(); @@ -59,4 +62,24 @@ abstract contract DeltaResolver is ImmutableState { if (_amount < 0) revert IncorrectUseOfTake(); amount = uint256(_amount); } + + /// @notice Calculates the amount for a settle action + function _mapSettleAmount(uint256 amount, Currency currency) internal view returns (uint256) { + if (amount == Constants.CONTRACT_BALANCE) { + return currency.balanceOfSelf(); + } else if (amount == Constants.OPEN_DELTA) { + return _getFullSettleAmount(currency); + } + return amount; + } + + /// @notice Calculates the amount for a swap action + function _mapInputAmount(uint128 amount, Currency currency) internal view returns (uint128) { + if (amount == Constants.CONTRACT_BALANCE) { + return currency.balanceOfSelf().toUint128(); + } else if (amount == Constants.OPEN_DELTA) { + return _getFullTakeAmount(currency).toUint128(); + } + return amount; + } } diff --git a/src/base/Permit2Forwarder.sol b/src/base/Permit2Forwarder.sol index 2bd7507b..41525c6e 100644 --- a/src/base/Permit2Forwarder.sol +++ b/src/base/Permit2Forwarder.sol @@ -23,10 +23,10 @@ contract Permit2Forwarder { /// @notice allows forwarding batch permits to permit2 /// @dev this function is payable to allow multicall with NATIVE based actions - function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata permitBatch, bytes calldata signature) + function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature) external payable { - permit2.permit(owner, permitBatch, signature); + permit2.permit(owner, _permitBatch, signature); } } diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 61811f0a..ae954424 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -20,9 +20,8 @@ library Actions { // closing deltas on the pool manager // settling - uint256 constant SETTLE = 0x10; - uint256 constant SETTLE_ALL = 0x11; - uint256 constant SETTLE_WITH_BALANCE = 0x12; + uint256 constant SETTLE_ALL = 0x10; + uint256 constant SETTLE = 0x11; // taking uint256 constant TAKE = 0x13; uint256 constant TAKE_ALL = 0x14; @@ -36,11 +35,4 @@ library Actions { // minting/burning 6909s to close deltas uint256 constant MINT_6909 = 0x20; uint256 constant BURN_6909 = 0x21; - - // helper constants to signal certain addresses - // Used as a flag for identifying that msg.sender should be used - address internal constant MSG_SENDER = address(1); - - // Used as a flag for identifying address(this) should be used - address internal constant ADDRESS_THIS = address(2); } diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index fc3d6834..fee5d705 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -203,6 +203,19 @@ library CalldataDecoder { } } + /// @dev equivalent to: abi.decode(params, (Currency, uint256, bool)) in calldata + function decodeCurrencyUint256AndBool(bytes calldata params) + internal + pure + returns (Currency currency, uint256 amount, bool boolean) + { + assembly ("memory-safe") { + currency := calldataload(params.offset) + amount := calldataload(add(params.offset, 0x20)) + boolean := calldataload(add(params.offset, 0x40)) + } + } + /// @notice Decode the `_arg`-th element in `_bytes` as a dynamic array /// @dev The decoding of `length` and `offset` is universal, /// whereas the type declaration of `res` instructs the compiler how to read it. diff --git a/src/libraries/Constants.sol b/src/libraries/Constants.sol new file mode 100644 index 00000000..3a6f1b6f --- /dev/null +++ b/src/libraries/Constants.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.19; + +library Constants { + /// @notice used to signal that an action should use the input value of the open delta on the pool manager + /// or of the balance that the contract holds + uint128 internal constant CONTRACT_BALANCE = 0; + uint128 internal constant OPEN_DELTA = 1; + + /// @notice used to signal that the recipient of an action should be the _msgSender of address(this) + address internal constant MSG_SENDER = address(1); + address internal constant ADDRESS_THIS = address(2); +} diff --git a/test/BaseActionsRouter.t.sol b/test/BaseActionsRouter.t.sol index 2780a398..cb8465c0 100644 --- a/test/BaseActionsRouter.t.sol +++ b/test/BaseActionsRouter.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import {MockBaseActionsRouter} from "./mocks/MockBaseActionsRouter.sol"; import {Planner, Plan} from "./shared/Planner.sol"; import {Actions} from "../src/libraries/Actions.sol"; +import {Constants} from "../src/libraries/Constants.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Test} from "forge-std/Test.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; @@ -138,11 +139,11 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.burnCount(), 10); } - function test_fuzz_map(address recipient) public view { - address mappedRecipient = router.map(recipient); - if (recipient == Actions.MSG_SENDER) { + function test_fuzz_mapRecipient(address recipient) public view { + address mappedRecipient = router.mapRecipient(recipient); + if (recipient == Constants.MSG_SENDER) { assertEq(mappedRecipient, address(0xdeadbeef)); - } else if (recipient == Actions.ADDRESS_THIS) { + } else if (recipient == Constants.ADDRESS_THIS) { assertEq(mappedRecipient, address(router)); } else { assertEq(mappedRecipient, recipient); diff --git a/test/mocks/MockBaseActionsRouter.sol b/test/mocks/MockBaseActionsRouter.sol index 144c453c..39001029 100644 --- a/test/mocks/MockBaseActionsRouter.sol +++ b/test/mocks/MockBaseActionsRouter.sol @@ -81,7 +81,7 @@ contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { clearCount++; } - function map(address recipient) external view returns (address) { - return _map(recipient); + function mapRecipient(address recipient) external view returns (address) { + return _mapRecipient(recipient); } } diff --git a/test/mocks/MockV4Router.sol b/test/mocks/MockV4Router.sol index e821edef..a543024f 100644 --- a/test/mocks/MockV4Router.sol +++ b/test/mocks/MockV4Router.sol @@ -38,4 +38,6 @@ contract MockV4Router is V4Router, ReentrancyLock { function _msgSender() internal view override returns (address) { return _getLocker(); } + + receive() external payable {} } diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index 1d79ceb5..6ff19dce 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -20,6 +20,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; @@ -67,7 +68,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { initialLiquidity = bound(initialLiquidity, 1e18, 1000e18); liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, initialLiquidity, Constants.MSG_SENDER, ZERO_BYTES); increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); @@ -87,7 +88,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); liquidityToAdd2 = bound(liquidityToAdd2, 1e18, 1000e18); uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, initialLiquidity, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init(); @@ -122,7 +123,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - config, initialLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + config, initialLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); planner.add( @@ -146,7 +147,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { // mint a position on range [-300, 300] uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, initialLiquidity, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // we'll burn and mint a new position on [-60, 60]; calculate the liquidity units for the new range @@ -174,7 +175,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - newConfig, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + newConfig, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index ce5c3f42..d96fdb0b 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -27,6 +27,7 @@ import {Actions} from "../../src/libraries/Actions.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {FeeMath} from "../shared/FeeMath.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { using FixedPointMathLib for uint256; @@ -490,7 +491,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revertAmount0() public { // increasing liquidity with strict slippage parameters (amount0) will revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); // revert since amount0Max is too low bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, 1 wei, type(uint128).max, ZERO_BYTES); @@ -501,7 +502,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revertAmount1() public { // increasing liquidity with strict slippage parameters (amount1) will revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); // revert since amount1Max is too low bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, type(uint128).max, 1 wei, ZERO_BYTES); @@ -512,7 +513,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_exactDoesNotRevert() public { // increasing liquidity with perfect slippage parameters does not revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); uint128 newLiquidity = 10e18; (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( @@ -537,7 +538,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revert_swap() public { // increasing liquidity with perfect slippage parameters does not revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); uint128 newLiquidity = 10e18; (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( @@ -565,8 +566,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.MINT_POSITION, abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) ); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); + planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); // this test sweeps to the test contract, even though Alice is the caller of the transaction planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); planner.add(Actions.SWEEP, abi.encode(currency1, address(this))); @@ -607,10 +608,10 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.MINT_POSITION, abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) ); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); - planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); - planner.add(Actions.SWEEP, abi.encode(currency1, Actions.MSG_SENDER)); + planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); + planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency1, Constants.MSG_SENDER)); uint256 balanceBefore0 = currency0.balanceOf(alice); uint256 balanceBefore1 = currency1.balanceOf(alice); @@ -654,8 +655,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); + planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); // this test sweeps to the test contract, even though Alice is the caller of the transaction planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); planner.add(Actions.SWEEP, abi.encode(currency1, address(this))); diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index f99083ad..d98258c4 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -15,7 +15,6 @@ import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; -import {Constants} from "@uniswap/v4-core/test/utils/Constants.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; @@ -27,6 +26,7 @@ import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; @@ -74,7 +74,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - bytes memory calls = getMintEncoded(config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + bytes memory calls = getMintEncoded(config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( SQRT_PRICE_1_1, @@ -114,13 +114,13 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); // sweep the excess eth - planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); bytes memory calls = planner.encode(); @@ -159,7 +159,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -212,7 +212,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -259,7 +259,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -301,7 +301,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -322,7 +322,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); // sweep the excess eth - planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); bytes memory calls = planner.encode(); lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); // overpay on increase liquidity @@ -353,7 +353,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -388,7 +388,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 8580f224..9b63023d 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -23,6 +23,7 @@ import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {IMulticall} from "../../src/interfaces/IMulticall.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { using FixedPointMathLib for uint256; @@ -73,7 +74,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); @@ -91,7 +92,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); @@ -110,7 +111,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); @@ -129,7 +130,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); @@ -140,7 +141,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_increaseLiquidity_erc20() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.INCREASE_LIQUIDITY, @@ -295,7 +296,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_decreaseLiquidity() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -323,7 +324,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES) + abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) ); bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); @@ -335,7 +336,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_collect() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -353,12 +354,12 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // same-range gas tests function test_gas_sameRange_mint() public { - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES + config, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); @@ -370,11 +371,11 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_sameRange_decrease() public { // two positions of the same config, one of them decreases the entirety of the liquidity vm.startPrank(alice); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); vm.stopPrank(); uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -389,11 +390,11 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_sameRange_collect() public { // two positions of the same config, one of them collects all their fees vm.startPrank(alice); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); vm.stopPrank(); uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -410,7 +411,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) @@ -423,7 +424,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burnEmpty() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); decreaseLiquidity(tokenId, config, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( @@ -440,7 +441,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Will be more expensive than not encoding a decrease and just encoding a burn. // ie. check this against PositionManager_burn_nonEmpty uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -463,7 +464,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Native Token Gas Tests function test_gas_mint_native() public { uint256 liquidityToAdd = 10_000 ether; - bytes memory calls = getMintEncoded(configNative, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + bytes memory calls = getMintEncoded(configNative, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( SQRT_PRICE_1_1, @@ -486,13 +487,13 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, - Actions.MSG_SENDER, + Constants.MSG_SENDER, ZERO_BYTES ) ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); - planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, Actions.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, Constants.MSG_SENDER)); bytes memory calls = planner.encode(); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( @@ -508,7 +509,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_increase_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); uint256 liquidityToAdd = 10_000 ether; bytes memory calls = getIncreaseEncoded(tokenId, configNative, liquidityToAdd, ZERO_BYTES); @@ -524,7 +525,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_decrease_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); uint256 liquidityToRemove = 10_000 ether; bytes memory calls = getDecreaseEncoded(tokenId, configNative, liquidityToRemove, ZERO_BYTES); @@ -534,7 +535,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_collect_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate{value: 0.2e18}(configNative.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -546,7 +547,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, @@ -560,7 +561,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burnEmpty_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); decreaseLiquidity(tokenId, configNative, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( @@ -578,7 +579,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Will be more expensive than not encoding a decrease and just encoding a burn. // ie. check this against PositionManager_burn_nonEmpty uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Actions.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -600,10 +601,10 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) ); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency0)); - planner.add(Actions.SETTLE_WITH_BALANCE, abi.encode(currency1)); - planner.add(Actions.SWEEP, abi.encode(currency0, Actions.MSG_SENDER)); - planner.add(Actions.SWEEP, abi.encode(currency1, Actions.MSG_SENDER)); + planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); + planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency1, Constants.MSG_SENDER)); currency0.transfer(address(lpm), 100e18); currency1.transfer(address(lpm), 100e18); diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index f1d5c548..5eaf45b8 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -27,6 +27,7 @@ import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol"; import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTestSetup, LiquidityFuzzers { using FixedPointMathLib for uint256; @@ -79,7 +80,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1, ZERO_BYTES); - PositionConfig memory config = PositionConfig({ + config = PositionConfig({ poolKey: key, tickLower: TickMath.minUsableTick(key.tickSpacing), tickUpper: TickMath.maxUsableTick(key.tickSpacing) @@ -88,7 +89,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES) + abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) ); bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); @@ -104,7 +105,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest } function test_multicall_permit_mint() public { - PositionConfig memory config = PositionConfig({ + config = PositionConfig({ poolKey: key, tickLower: TickMath.minUsableTick(key.tickSpacing), tickUpper: TickMath.maxUsableTick(key.tickSpacing) @@ -152,7 +153,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest } function test_multicall_permit_batch_mint() public { - PositionConfig memory config = PositionConfig({ + config = PositionConfig({ poolKey: key, tickLower: TickMath.minUsableTick(key.tickSpacing), tickUpper: TickMath.maxUsableTick(key.tickSpacing) diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index db2bbdb4..adb81fe0 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -26,6 +26,7 @@ import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {SlippageCheckLibrary} from "../../src/libraries/SlippageCheck.sol"; import {BaseActionsRouter} from "../../src/base/BaseActionsRouter.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {Planner, Plan} from "../shared/Planner.sol"; @@ -84,7 +85,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // Try to add liquidity at that range, but the token reenters posm PositionConfig memory config = PositionConfig({poolKey: key, tickLower: 0, tickUpper: 60}); - bytes memory calls = getMintEncoded(config, 1e18, Actions.MSG_SENDER, ""); + bytes memory calls = getMintEncoded(config, 1e18, Constants.MSG_SENDER, ""); // Permit2.transferFrom does not bubble the ContractLocked error and instead reverts with its own error vm.expectRevert("TRANSFER_FROM_FAILED"); @@ -106,7 +107,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); assertEq(tokenId, 1); @@ -141,7 +142,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidityToAdd, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); uint256 balance0After = currency0.balanceOfSelf(); @@ -242,7 +243,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_mint_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); - bytes memory calls = getMintEncoded(config, 1e18, 1 wei, MAX_SLIPPAGE_INCREASE, Actions.MSG_SENDER, ZERO_BYTES); + bytes memory calls = + getMintEncoded(config, 1e18, 1 wei, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES); vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -250,7 +252,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_mint_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); - bytes memory calls = getMintEncoded(config, 1e18, MAX_SLIPPAGE_INCREASE, 1 wei, Actions.MSG_SENDER, ZERO_BYTES); + bytes memory calls = + getMintEncoded(config, 1e18, MAX_SLIPPAGE_INCREASE, 1 wei, Constants.MSG_SENDER, ZERO_BYTES); vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -268,7 +271,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(amount0, amount1); // symmetric liquidity uint128 slippage = uint128(amount0) + 1; - bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Actions.MSG_SENDER, ZERO_BYTES); + bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Constants.MSG_SENDER, ZERO_BYTES); lpm.modifyLiquidities(calls, _deadline); BalanceDelta delta = getLastDelta(); assertEq(uint256(int256(-delta.amount0())), slippage); @@ -289,7 +292,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(amount0, amount1); // symmetric liquidity uint128 slippage = uint128(amount0) + 1; - bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Actions.MSG_SENDER, ZERO_BYTES); + bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Constants.MSG_SENDER, ZERO_BYTES); // swap to move the price and cause a slippage revert swap(key, true, -1e18, ZERO_BYTES); @@ -304,7 +307,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // create liquidity we can burn uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); @@ -350,7 +353,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // create liquidity we can burn uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); @@ -400,7 +403,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = @@ -412,7 +415,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = @@ -424,7 +427,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_exactDoesNotRevert() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // TODO: why does burning a newly minted position return original delta - 1 wei? @@ -442,7 +445,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // swapping will cause a slippage revert PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getBurnEncoded( @@ -461,7 +464,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); decreaseLiquidityDelta = uint256(bound(int256(decreaseLiquidityDelta), 0, params.liquidityDelta)); PositionConfig memory config = @@ -561,7 +564,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // require two-sided liquidity decreaseLiquidityDelta = bound(decreaseLiquidityDelta, 1, uint256(params.liquidityDelta)); @@ -598,7 +601,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -611,7 +614,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -624,7 +627,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_exactDoesNotRevert() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // TODO: why does decreasing a newly minted position return original delta - 1 wei? @@ -643,7 +646,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // swapping will cause a slippage revert PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -662,7 +665,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Actions.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // require two-sided liquidity vm.assume(0 < decreaseLiquidityDelta); vm.assume(decreaseLiquidityDelta < uint256(type(int256).max)); @@ -695,7 +698,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); BalanceDelta mintDelta = getLastDelta(); // transfer to alice @@ -723,7 +726,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -752,7 +755,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); // transfer to alice lpm.transferFrom(address(this), alice, tokenId); @@ -789,7 +792,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Actions.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; diff --git a/test/router/Payments.gas.t.sol b/test/router/Payments.gas.t.sol index 7872a22f..b6ed4af3 100644 --- a/test/router/Payments.gas.t.sol +++ b/test/router/Payments.gas.t.sol @@ -8,6 +8,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; contract PaymentsTests is RoutingTestHelpers, GasSnapshot { using CurrencyLibrary for Currency; @@ -39,7 +40,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Actions.MSG_SENDER)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Constants.MSG_SENDER)); bytes memory data = plan.encode(); router.executeActions(data); @@ -55,7 +56,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { key0.currency0.transfer(address(router), amountIn); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_WITH_BALANCE, abi.encode(key0.currency0)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, Constants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); bytes memory data = plan.encode(); @@ -72,8 +73,8 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { key0.currency0.transfer(address(router), amountIn); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_WITH_BALANCE, abi.encode(key0.currency0)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Actions.MSG_SENDER)); + plan = plan.add(Actions.SETTLE, abi.encode(currency0, Constants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Constants.MSG_SENDER)); bytes memory data = plan.encode(); router.executeActions(data); diff --git a/test/router/Payments.t.sol b/test/router/Payments.t.sol index 73090e1c..a2798f6d 100644 --- a/test/router/Payments.t.sol +++ b/test/router/Payments.t.sol @@ -8,6 +8,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; contract PaymentsTests is RoutingTestHelpers, GasSnapshot { @@ -68,7 +69,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { assertEq(currency1.balanceOf(address(router)), 0); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_WITH_BALANCE, abi.encode(key0.currency0)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, Constants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); bytes memory data = plan.encode(); diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index d1a59590..d1b962a4 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -8,6 +8,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; contract V4RouterTest is RoutingTestHelpers, GasSnapshot { using CurrencyLibrary for Currency; @@ -33,7 +34,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactInputSingle"); @@ -47,7 +48,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency1, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_zeroForOne"); @@ -61,7 +62,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency1, currency0, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency1, currency0, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_oneForZero"); @@ -76,7 +77,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency2, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency2, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn2Hops"); @@ -92,7 +93,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency3, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency3, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn3Hops"); @@ -109,7 +110,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Constants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactInputSingle_nativeIn"); @@ -122,7 +123,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactInputSingle_nativeOut"); @@ -136,7 +137,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Constants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn1Hop_nativeIn"); @@ -150,7 +151,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_nativeOut"); @@ -165,7 +166,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Constants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn2Hops_nativeIn"); @@ -181,7 +182,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Constants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn3Hops_nativeIn"); @@ -198,7 +199,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOutputSingle"); @@ -212,7 +213,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency1, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_zeroForOne"); @@ -226,7 +227,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency1, currency0, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency1, currency0, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_oneForZero"); @@ -241,7 +242,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency2, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency2, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut2Hops"); @@ -257,7 +258,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency3, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency3, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops"); @@ -274,7 +275,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Constants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOutputSingle_nativeIn_sweepETH"); @@ -287,7 +288,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Constants.MSG_SENDER); router.executeActionsAndSweepExcessETH(data); snapLastCall("V4Router_ExactOutputSingle_nativeOut"); @@ -301,7 +302,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Constants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut1Hop_nativeIn_sweepETH"); @@ -315,7 +316,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_nativeOut"); @@ -330,7 +331,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Constants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut2Hops_nativeIn"); @@ -346,7 +347,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Constants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut3Hops_nativeIn"); @@ -363,7 +364,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.NATIVE, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.NATIVE, Constants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops_nativeOut"); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index 172b08a4..d9627323 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -6,6 +6,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; contract V4RouterTest is RoutingTestHelpers { using CurrencyLibrary for Currency; @@ -32,7 +33,7 @@ contract V4RouterTest is RoutingTestHelpers { ); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); vm.expectRevert(IV4Router.TooLittleReceived.selector); router.executeActions(data); @@ -99,7 +100,7 @@ contract V4RouterTest is RoutingTestHelpers { // swap with the router as the take recipient (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, Actions.ADDRESS_THIS); + = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, Constants.ADDRESS_THIS); // the output tokens have been left in the router assertEq(currency0.balanceOf(address(router)), 0); @@ -138,7 +139,7 @@ contract V4RouterTest is RoutingTestHelpers { params.amountOutMinimum = uint128(expectedAmountOut + 1); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); vm.expectRevert(IV4Router.TooLittleReceived.selector); router.executeActions(data); @@ -234,6 +235,37 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); } + function test_swap_settleRouterBalance_swapOpenDelta() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + key0.currency0.transfer(address(router), amountIn); + + // amount in of 0 to show it should use the open delta + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, Constants.OPEN_DELTA, 0, 0, bytes("")); + + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, Constants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + + bytes memory data = plan.encode(); + + uint256 callerInputBefore = key0.currency0.balanceOfSelf(); + uint256 routerInputBefore = key0.currency0.balanceOf(address(router)); + uint256 callerOutputBefore = key0.currency1.balanceOfSelf(); + router.executeActions(data); + + uint256 callerInputAfter = key0.currency0.balanceOfSelf(); + uint256 routerInputAfter = key0.currency0.balanceOf(address(router)); + uint256 callerOutputAfter = key0.currency1.balanceOfSelf(); + + // caller didnt pay, router paid, caller received the output + assertEq(callerInputBefore, callerInputAfter); + assertEq(routerInputBefore - amountIn, routerInputAfter); + assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter); + } + /*////////////////////////////////////////////////////////////// ETH -> ERC20 and ERC20 -> ETH EXACT INPUT //////////////////////////////////////////////////////////////*/ @@ -371,6 +403,37 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); } + function test_swap_nativeIn_settleRouterBalance_swapOpenDelta() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + nativeKey.currency0.transfer(address(router), amountIn); + + // amount in of 0 to show it should use the open delta + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(nativeKey, true, Constants.OPEN_DELTA, 0, 0, bytes("")); + + plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, Constants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(nativeKey.currency1, address(this))); + + bytes memory data = plan.encode(); + + uint256 callerInputBefore = nativeKey.currency0.balanceOfSelf(); + uint256 routerInputBefore = nativeKey.currency0.balanceOf(address(router)); + uint256 callerOutputBefore = nativeKey.currency1.balanceOfSelf(); + router.executeActions(data); + + uint256 callerInputAfter = nativeKey.currency0.balanceOfSelf(); + uint256 routerInputAfter = nativeKey.currency0.balanceOf(address(router)); + uint256 callerOutputAfter = nativeKey.currency1.balanceOfSelf(); + + // caller didnt pay, router paid, caller received the output + assertEq(callerInputBefore, callerInputAfter); + assertEq(routerInputBefore - amountIn, routerInputAfter); + assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter); + } + /*//////////////////////////////////////////////////////////////å ERC20 -> ERC20 EXACT OUTPUT //////////////////////////////////////////////////////////////*/ @@ -384,7 +447,7 @@ contract V4RouterTest is RoutingTestHelpers { ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); vm.expectRevert(IV4Router.TooMuchRequested.selector); router.executeActions(data); @@ -440,7 +503,7 @@ contract V4RouterTest is RoutingTestHelpers { params.amountInMaximum = uint128(expectedAmountIn - 1); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); vm.expectRevert(IV4Router.TooMuchRequested.selector); router.executeActions(data); diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index f47a9526..4d98f463 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -18,6 +18,7 @@ import {PositionManager} from "../../src/PositionManager.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic helpers for swapping with the router. contract RoutingTestHelpers is Test, Deployers { @@ -160,7 +161,7 @@ contract RoutingTestHelpers is Test, Deployers { uint256 outputBalanceAfter ) { - return _finalizeAndExecuteSwap(inputCurrency, outputCurrency, amountIn, Actions.MSG_SENDER); + return _finalizeAndExecuteSwap(inputCurrency, outputCurrency, amountIn, Constants.MSG_SENDER); } function _finalizeAndExecuteNativeInputExactOutputSwap( @@ -179,7 +180,7 @@ contract RoutingTestHelpers is Test, Deployers { inputBalanceBefore = inputCurrency.balanceOfSelf(); outputBalanceBefore = outputCurrency.balanceOfSelf(); - bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, Actions.MSG_SENDER); + bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, Constants.MSG_SENDER); // send too much ETH to mimic slippage uint256 value = expectedAmountIn + 0.1 ether; From a9e463d6ccabb219a31e50f785bbc317a1502338 Mon Sep 17 00:00:00 2001 From: diana Date: Fri, 2 Aug 2024 18:13:42 -0400 Subject: [PATCH 14/52] Settle pair (#242) * clean * format * with currencydeltas library * remove currencydeltas library --- ...er_increaseLiquidity_erc20_withClose.snap} | 0 ...ncreaseLiquidity_erc20_withSettlePair.snap | 1 + ...anager_mint_nativeWithSweep_withClose.snap | 1 + ...r_mint_nativeWithSweep_withSettlePair.snap | 1 + ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...ap => PositionManager_mint_withClose.snap} | 0 .../PositionManager_mint_withSettlePair.snap | 1 + src/PositionManager.sol | 10 ++ src/libraries/Actions.sol | 22 ++-- src/libraries/CalldataDecoder.sol | 8 ++ src/libraries/CurrencyDeltas.sol | 42 ------- test/libraries/CalldataDecoder.t.sol | 8 ++ test/libraries/CurrencyDeltas.t.sol | 82 -------------- test/mocks/MockCalldataDecoder.sol | 4 + test/mocks/MockCurrencyDeltaReader.sol | 71 ------------ test/position-managers/Execute.t.sol | 40 ++++++- .../position-managers/IncreaseLiquidity.t.sol | 2 +- test/position-managers/NativeToken.t.sol | 105 +++++++++++++++++- .../PositionManager.gas.t.sol | 93 ++++++++++++---- .../PositionManager.multicall.t.sol | 2 +- test/shared/LiquidityOperations.sol | 10 +- test/shared/Planner.sol | 15 ++- test/shared/fuzz/LiquidityFuzzers.sol | 2 +- 23 files changed, 278 insertions(+), 244 deletions(-) rename .forge-snapshots/{PositionManager_increaseLiquidity_erc20.snap => PositionManager_increaseLiquidity_erc20_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap create mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap create mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap rename .forge-snapshots/{PositionManager_mint.snap => PositionManager_mint_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_mint_withSettlePair.snap delete mode 100644 src/libraries/CurrencyDeltas.sol delete mode 100644 test/libraries/CurrencyDeltas.t.sol delete mode 100644 test/mocks/MockCurrencyDeltaReader.sol diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_increaseLiquidity_erc20.snap rename to .forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap new file mode 100644 index 00000000..6ce3534b --- /dev/null +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -0,0 +1 @@ +151341 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap new file mode 100644 index 00000000..6398f05a --- /dev/null +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -0,0 +1 @@ +345169 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap new file mode 100644 index 00000000..141f9455 --- /dev/null +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -0,0 +1 @@ +344710 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 2e9e23a4..977b82bc 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370923 \ No newline at end of file +370969 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_mint.snap rename to .forge-snapshots/PositionManager_mint_withClose.snap diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap new file mode 100644 index 00000000..2a78d147 --- /dev/null +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -0,0 +1 @@ +371342 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 8bc6782a..d1418897 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -126,6 +126,9 @@ contract PositionManager is } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + } else if (action == Actions.SETTLE_PAIR) { + (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); + _settlePair(currency0, currency1); } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); _sweep(currency, _mapRecipient(to)); @@ -214,6 +217,13 @@ contract PositionManager is poolManager.clear(currency, uint256(currencyDelta)); } + function _settlePair(Currency currency0, Currency currency1) internal { + // the locker is the payer when settling + address caller = _msgSender(); + _settle(currency0, caller, _getFullSettleAmount(currency0)); + _settle(currency1, caller, _getFullSettleAmount(currency1)); + } + /// @dev this is overloaded with ERC721Permit._burn function _burn( uint256 tokenId, diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index ae954424..e0005b2e 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -20,19 +20,19 @@ library Actions { // closing deltas on the pool manager // settling - uint256 constant SETTLE_ALL = 0x10; - uint256 constant SETTLE = 0x11; + uint256 constant SETTLE_ALL = 0x09; + uint256 constant SETTLE = 0x10; + uint256 constant SETTLE_PAIR = 0x11; // taking - uint256 constant TAKE = 0x13; - uint256 constant TAKE_ALL = 0x14; - uint256 constant TAKE_PORTION = 0x15; + uint256 constant TAKE = 0x12; + uint256 constant TAKE_ALL = 0x13; + uint256 constant TAKE_PORTION = 0x14; - uint256 constant CLOSE_CURRENCY = 0x16; - uint256 constant CLOSE_PAIR = 0x17; - uint256 constant CLEAR = 0x18; - uint256 constant SWEEP = 0x19; + uint256 constant CLOSE_CURRENCY = 0x15; + uint256 constant CLEAR = 0x16; + uint256 constant SWEEP = 0x17; // minting/burning 6909s to close deltas - uint256 constant MINT_6909 = 0x20; - uint256 constant BURN_6909 = 0x21; + uint256 constant MINT_6909 = 0x18; + uint256 constant BURN_6909 = 0x19; } diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index fee5d705..0a1dd24a 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -166,6 +166,14 @@ library CalldataDecoder { } } + /// @dev equivalent to: abi.decode(params, (Currency, Currency)) in calldata + function decodeCurrencyPair(bytes calldata params) internal pure returns (Currency currency0, Currency currency1) { + assembly ("memory-safe") { + currency0 := calldataload(params.offset) + currency1 := calldataload(add(params.offset, 0x20)) + } + } + /// @dev equivalent to: abi.decode(params, (Currency, address)) in calldata function decodeCurrencyAndAddress(bytes calldata params) internal diff --git a/src/libraries/CurrencyDeltas.sol b/src/libraries/CurrencyDeltas.sol deleted file mode 100644 index 2a7b85f8..00000000 --- a/src/libraries/CurrencyDeltas.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; - -/// @title Currency Deltas -/// @notice Fetch two currency deltas in a single call -library CurrencyDeltas { - using SafeCast for int256; - - /// @notice Get the current delta for a caller in the two given currencies - /// @param _caller The address of the caller - /// @param currency0 The currency to lookup the delta - /// @param currency1 The other currency to lookup the delta - /// @return BalanceDelta The delta of the two currencies packed - /// amount0 corresponding to currency0 and amount1 corresponding to currency1 - function currencyDeltas(IPoolManager manager, address _caller, Currency currency0, Currency currency1) - internal - view - returns (BalanceDelta) - { - bytes32 tloadSlot0; - bytes32 tloadSlot1; - assembly { - mstore(0, _caller) - mstore(32, currency0) - tloadSlot0 := keccak256(0, 64) - - mstore(0, _caller) - mstore(32, currency1) - tloadSlot1 := keccak256(0, 64) - } - bytes32[] memory slots = new bytes32[](2); - slots[0] = tloadSlot0; - slots[1] = tloadSlot1; - bytes32[] memory result = manager.exttload(slots); - return toBalanceDelta(int256(uint256(result[0])).toInt128(), int256(uint256(result[1])).toInt128()); - } -} diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index 25f8593d..b71d2667 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -152,6 +152,14 @@ contract CalldataDecoderTest is Test { assertEq(Currency.unwrap(currency), Currency.unwrap(_currency)); } + function test_fuzz_decodeCurrencyPair(Currency _currency0, Currency _currency1) public view { + bytes memory params = abi.encode(_currency0, _currency1); + (Currency currency0, Currency currency1) = decoder.decodeCurrencyPair(params); + + assertEq(Currency.unwrap(currency0), Currency.unwrap(_currency0)); + assertEq(Currency.unwrap(currency1), Currency.unwrap(_currency1)); + } + function test_fuzz_decodeCurrencyAndUint256(Currency _currency, uint256 _amount) public view { bytes memory params = abi.encode(_currency, _amount); (Currency currency, uint256 amount) = decoder.decodeCurrencyAndUint256(params); diff --git a/test/libraries/CurrencyDeltas.t.sol b/test/libraries/CurrencyDeltas.t.sol deleted file mode 100644 index 53dad9e4..00000000 --- a/test/libraries/CurrencyDeltas.t.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "forge-std/Test.sol"; -import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; - -import {MockCurrencyDeltaReader} from "../mocks/MockCurrencyDeltaReader.sol"; - -contract CurrencyDeltasTest is Test, Deployers { - using CurrencyLibrary for Currency; - - MockCurrencyDeltaReader reader; - - function setUp() public { - deployFreshManagerAndRouters(); - deployMintAndApprove2Currencies(); - - reader = new MockCurrencyDeltaReader(manager); - - IERC20 token0 = IERC20(Currency.unwrap(currency0)); - IERC20 token1 = IERC20(Currency.unwrap(currency1)); - - token0.approve(address(reader), type(uint256).max); - token1.approve(address(reader), type(uint256).max); - - // send tokens to PoolManager so tests can .take() - token0.transfer(address(manager), 1_000_000e18); - token1.transfer(address(manager), 1_000_000e18); - - // convert some ERC20s into ERC6909 - claimsRouter.deposit(currency0, address(this), 1_000_000e18); - claimsRouter.deposit(currency1, address(this), 1_000_000e18); - manager.approve(address(reader), currency0.toId(), type(uint256).max); - manager.approve(address(reader), currency1.toId(), type(uint256).max); - } - - function test_fuzz_currencyDeltas(uint8 depth, uint256 seed, uint128 amount0, uint128 amount1) public { - int128 delta0Expected = 0; - int128 delta1Expected = 0; - - bytes[] memory calls = new bytes[](depth); - for (uint256 i = 0; i < depth; i++) { - amount0 = uint128(bound(amount0, 1, 100e18)); - amount1 = uint128(bound(amount1, 1, 100e18)); - uint256 _seed = seed % (i + 1); - if (_seed % 8 == 0) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.settle.selector, currency0, amount0); - delta0Expected += int128(amount0); - } else if (_seed % 8 == 1) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.settle.selector, currency1, amount1); - delta1Expected += int128(amount1); - } else if (_seed % 8 == 2) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.burn.selector, currency0, amount0); - delta0Expected += int128(amount0); - } else if (_seed % 8 == 3) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.burn.selector, currency1, amount1); - delta1Expected += int128(amount1); - } else if (_seed % 8 == 4) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.take.selector, currency0, amount0); - delta0Expected -= int128(amount0); - } else if (_seed % 8 == 5) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.take.selector, currency1, amount1); - delta1Expected -= int128(amount1); - } else if (_seed % 8 == 6) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.mint.selector, currency0, amount0); - delta0Expected -= int128(amount0); - } else if (_seed % 8 == 7) { - calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.mint.selector, currency1, amount1); - delta1Expected -= int128(amount1); - } - } - - BalanceDelta delta = reader.execute(calls, currency0, currency1); - assertEq(delta.amount0(), delta0Expected); - assertEq(delta.amount1(), delta1Expected); - } -} diff --git a/test/mocks/MockCalldataDecoder.sol b/test/mocks/MockCalldataDecoder.sol index 2839e6a5..35825228 100644 --- a/test/mocks/MockCalldataDecoder.sol +++ b/test/mocks/MockCalldataDecoder.sol @@ -98,6 +98,10 @@ contract MockCalldataDecoder { return params.decodeCurrency(); } + function decodeCurrencyPair(bytes calldata params) external pure returns (Currency currency0, Currency currency1) { + return params.decodeCurrencyPair(); + } + function decodeCurrencyAndUint256(bytes calldata params) external pure returns (Currency currency, uint256 _uint) { return params.decodeCurrencyAndUint256(); } diff --git a/test/mocks/MockCurrencyDeltaReader.sol b/test/mocks/MockCurrencyDeltaReader.sol deleted file mode 100644 index 94aa3db7..00000000 --- a/test/mocks/MockCurrencyDeltaReader.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; -import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; - -import {CurrencyDeltas} from "../../src/libraries/CurrencyDeltas.sol"; - -/// @dev A minimal helper strictly for testing -contract MockCurrencyDeltaReader { - using TransientStateLibrary for IPoolManager; - using CurrencyDeltas for IPoolManager; - using CurrencySettler for Currency; - - IPoolManager public poolManager; - - address sender; - - constructor(IPoolManager _poolManager) { - poolManager = _poolManager; - } - - /// @param calls an array of abi.encodeWithSelector - function execute(bytes[] calldata calls, Currency currency0, Currency currency1) external returns (BalanceDelta) { - sender = msg.sender; - return abi.decode(poolManager.unlock(abi.encode(calls, currency0, currency1)), (BalanceDelta)); - } - - function unlockCallback(bytes calldata data) external returns (bytes memory) { - (bytes[] memory calls, Currency currency0, Currency currency1) = abi.decode(data, (bytes[], Currency, Currency)); - for (uint256 i; i < calls.length; i++) { - (bool success,) = address(this).call(calls[i]); - if (!success) revert("CurrencyDeltaReader"); - } - - BalanceDelta delta = poolManager.currencyDeltas(address(this), currency0, currency1); - int256 delta0 = poolManager.currencyDelta(address(this), currency0); - int256 delta1 = poolManager.currencyDelta(address(this), currency1); - - // confirm agreement between currencyDeltas and single-read currencyDelta - require(delta.amount0() == int128(delta0), "CurrencyDeltaReader: delta0"); - require(delta.amount1() == int128(delta1), "CurrencyDeltaReader: delta1"); - - // close deltas - if (delta.amount0() < 0) currency0.settle(poolManager, sender, uint256(-int256(delta.amount0())), false); - if (delta.amount1() < 0) currency1.settle(poolManager, sender, uint256(-int256(delta.amount1())), false); - if (delta.amount0() > 0) currency0.take(poolManager, sender, uint256(int256(delta.amount0())), false); - if (delta.amount1() > 0) currency1.take(poolManager, sender, uint256(int256(delta.amount1())), false); - return abi.encode(delta); - } - - function settle(Currency currency, uint256 amount) external { - currency.settle(poolManager, sender, amount, false); - } - - function burn(Currency currency, uint256 amount) external { - currency.settle(poolManager, sender, amount, true); - } - - function take(Currency currency, uint256 amount) external { - currency.take(poolManager, sender, amount, false); - } - - function mint(Currency currency, uint256 amount) external { - currency.take(poolManager, sender, amount, true); - } -} diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index 6ff19dce..4556c55d 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -79,7 +79,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(liquidity, initialLiquidity + liquidityToAdd); } - function test_fuzz_execute_increaseLiquidity_twice( + function test_fuzz_execute_increaseLiquidity_twice_withClose( uint256 initialLiquidity, uint256 liquidityToAdd, uint256 liquidityToAdd2 @@ -101,7 +101,39 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { abi.encode(tokenId, config, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); + lpm.modifyLiquidities(calls, _deadline); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2); + } + + function test_fuzz_execute_increaseLiquidity_twice_withSettlePair( + uint256 initialLiquidity, + uint256 liquidityToAdd, + uint256 liquidityToAdd2 + ) public { + initialLiquidity = bound(initialLiquidity, 1e18, 1000e18); + liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); + liquidityToAdd2 = bound(liquidityToAdd2, 1e18, 1000e18); + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + Plan memory planner = Planner.init(); + + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + + bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); lpm.modifyLiquidities(calls, _deadline); bytes32 positionId = @@ -131,7 +163,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); bytes32 positionId = @@ -178,7 +210,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { newConfig, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); { diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index d96fdb0b..c6d076f4 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -120,7 +120,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { planner.add( Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, 0 wei, 0 wei, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.startPrank(alice); lpm.modifyLiquidities(calls, _deadline); vm.stopPrank(); diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index d98258c4..7d03721c 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -96,7 +96,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { } // minting with excess native tokens are returned to caller - function test_fuzz_mint_native_excess(IPoolManager.ModifyLiquidityParams memory params) public { + function test_fuzz_mint_native_excess_withClose(IPoolManager.ModifyLiquidityParams memory params) public { params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity @@ -146,6 +146,53 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); } + function test_fuzz_mint_native_excess_withSettlePair(IPoolManager.ModifyLiquidityParams memory params) public { + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + uint256 liquidityToAdd = + params.liquidityDelta < 0 ? uint256(-params.liquidityDelta) : uint256(params.liquidityDelta); + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + uint256 balance0Before = currency0.balanceOfSelf(); + uint256 balance1Before = currency1.balanceOfSelf(); + + uint256 tokenId = lpm.nextTokenId(); + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode(config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + ); + planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); + // sweep the excess eth + planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); + + bytes memory calls = planner.encode(); + + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(params.tickLower), + TickMath.getSqrtPriceAtTick(params.tickUpper), + liquidityToAdd.toUint128() + ); + + // Mint with excess native tokens + lpm.modifyLiquidities{value: amount0 * 2 + 1}(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, uint256(params.liquidityDelta)); + + // only paid the delta amount, with excess tokens returned to caller + assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0()))); + assertEq(balance0Before - currency0.balanceOfSelf(), amount0 + 1); // TODO: off by one?? + assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); + } + function test_fuzz_burn_native_emptyPosition(IPoolManager.ModifyLiquidityParams memory params) public { uint256 balance0Start = address(this).balance; uint256 balance1Start = currency1.balanceOfSelf(); @@ -289,7 +336,9 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { } // overpaying native tokens on increase liquidity is returned to caller - function test_fuzz_increaseLiquidity_native_excess(IPoolManager.ModifyLiquidityParams memory params) public { + function test_fuzz_increaseLiquidity_native_excess_withClose(IPoolManager.ModifyLiquidityParams memory params) + public + { // fuzz for the range params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity @@ -340,6 +389,58 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); } + function test_fuzz_increaseLiquidity_native_excess_withSettlePair(IPoolManager.ModifyLiquidityParams memory params) + public + { + // fuzz for the range + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + // TODO: figure out if we can fuzz the increase liquidity delta. we're annoyingly getting TickLiquidityOverflow + uint256 liquidityToAdd = 1e18; + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + // mint the position with native token liquidity + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, address(this), ZERO_BYTES); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = currency1.balanceOfSelf(); + + // calculate how much native token is required for the liquidity increase (doubling the liquidity) + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(params.tickLower), + TickMath.getSqrtPriceAtTick(params.tickUpper), + uint128(liquidityToAdd) + ); + + Plan memory planner = Planner.init(); + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); + // sweep the excess eth + planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); + bytes memory calls = planner.encode(); + + lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); // overpay on increase liquidity + BalanceDelta delta = getLastDelta(); + + // verify position liquidity increased + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled + + // verify native token balances changed as expected, with overpaid tokens returned + assertEq(balance0Before - currency0.balanceOfSelf(), amount0 + 1 wei); + assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0()))); + assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); + } + function test_fuzz_decreaseLiquidity_native( IPoolManager.ModifyLiquidityParams memory params, uint256 decreaseLiquidityDelta diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 9b63023d..84da682b 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -70,16 +70,26 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { configNative = PositionConfig({poolKey: nativeKey, tickLower: -300, tickUpper: 300}); } - function test_gas_mint() public { + function test_gas_mint_withClose() public { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_mint"); + snapLastCall("PositionManager_mint_withClose"); + } + + function test_gas_mint_withSettlePair() public { + Plan memory planner = Planner.init().add( + Actions.MINT_POSITION, + abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_mint_withSettlePair"); } function test_gas_mint_differentRanges() public { @@ -95,7 +105,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_warmedPool_differentRange"); @@ -114,7 +124,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_onSameTickLower"); @@ -133,13 +143,13 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_onSameTickUpper"); } - function test_gas_increaseLiquidity_erc20() public { + function test_gas_increaseLiquidity_erc20_withClose() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); @@ -148,9 +158,23 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_increaseLiquidity_erc20_withClose"); + } + + function test_gas_increaseLiquidity_erc20_withSettlePair() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 10_000 ether, address(this), ZERO_BYTES); + + Plan memory planner = Planner.init().add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + + bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_increaseLiquidity_erc20"); + snapLastCall("PositionManager_increaseLiquidity_erc20_withSettlePair"); } function test_gas_autocompound_exactUnclaimedFees() public { @@ -287,7 +311,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); @@ -303,7 +327,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decreaseLiquidity"); } @@ -326,7 +350,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) ); - bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); @@ -347,7 +371,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_collect"); } @@ -362,7 +386,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { config, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES ) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_sameRange"); @@ -382,7 +406,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_sameRange_allLiquidity"); } @@ -404,7 +428,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_collect_sameRange"); } @@ -416,7 +440,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_burn_nonEmpty"); @@ -452,7 +476,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { ); // We must include CLOSE commands. - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_burnEmpty"); } @@ -476,7 +500,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { snapLastCall("PositionManager_mint_native"); } - function test_gas_mint_native_excess() public { + function test_gas_mint_native_excess_withClose() public { uint256 liquidityToAdd = 10_000 ether; Plan memory planner = Planner.init(); @@ -504,7 +528,32 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { ); // overpay on the native token lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); - snapLastCall("PositionManager_mint_nativeWithSweep"); + snapLastCall("PositionManager_mint_nativeWithSweep_withClose"); + } + + function test_gas_mint_native_excess_withSettlePair() public { + uint256 liquidityToAdd = 10_000 ether; + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode( + configNative, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES + ) + ); + planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, address(this))); + bytes memory calls = planner.encode(); + + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(configNative.tickLower), + TickMath.getSqrtPriceAtTick(configNative.tickUpper), + uint128(liquidityToAdd) + ); + // overpay on the native token + lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); + snapLastCall("PositionManager_mint_nativeWithSweep_withSettlePair"); } function test_gas_increase_native() public { @@ -553,7 +602,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.BURN_POSITION, abi.encode(tokenId, configNative, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(configNative.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_burn_nonEmpty_native"); @@ -588,7 +637,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { planner.add(Actions.BURN_POSITION, abi.encode(tokenId, configNative, 0 wei, 0 wei, ZERO_BYTES)); // We must include CLOSE commands. - bytes memory calls = planner.finalizeModifyLiquidity(configNative.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_burnEmpty_native"); } diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index 5eaf45b8..e5efff11 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -91,7 +91,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest Actions.MINT_POSITION, abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) ); - bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); diff --git a/test/shared/LiquidityOperations.sol b/test/shared/LiquidityOperations.sol index a4623092..026abe8c 100644 --- a/test/shared/LiquidityOperations.sol +++ b/test/shared/LiquidityOperations.sol @@ -101,7 +101,7 @@ abstract contract LiquidityOperations is CommonBase { Plan memory planner = Planner.init(); planner.add(Actions.MINT_POSITION, abi.encode(config, liquidity, amount0Max, amount1Max, recipient, hookData)); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getIncreaseEncoded( @@ -127,7 +127,7 @@ abstract contract LiquidityOperations is CommonBase { planner.add( Actions.INCREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityToAdd, amount0Max, amount1Max, hookData) ); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getDecreaseEncoded( @@ -153,7 +153,7 @@ abstract contract LiquidityOperations is CommonBase { planner.add( Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityToRemove, amount0Min, amount1Min, hookData) ); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getCollectEncoded(uint256 tokenId, PositionConfig memory config, bytes memory hookData) @@ -173,7 +173,7 @@ abstract contract LiquidityOperations is CommonBase { ) internal pure returns (bytes memory) { Plan memory planner = Planner.init(); planner.add(Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, 0, amount0Min, amount1Min, hookData)); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getBurnEncoded(uint256 tokenId, PositionConfig memory config, bytes memory hookData) @@ -194,6 +194,6 @@ abstract contract LiquidityOperations is CommonBase { Plan memory planner = Planner.init(); planner.add(Actions.BURN_POSITION, abi.encode(tokenId, config, amount0Min, amount1Min, hookData)); // Close needed on burn in case there is liquidity left in the position. - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } } diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 2321eccc..ffcd10fd 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -37,12 +37,25 @@ library Planner { return plan; } - function finalizeModifyLiquidity(Plan memory plan, PoolKey memory poolKey) internal pure returns (bytes memory) { + function finalizeModifyLiquidityWithClose(Plan memory plan, PoolKey memory poolKey) + internal + pure + returns (bytes memory) + { plan.add(Actions.CLOSE_CURRENCY, abi.encode(poolKey.currency0)); plan.add(Actions.CLOSE_CURRENCY, abi.encode(poolKey.currency1)); return plan.encode(); } + function finalizeModifyLiquidityWithSettlePair(Plan memory plan, PoolKey memory poolKey) + internal + pure + returns (bytes memory) + { + plan.add(Actions.SETTLE_PAIR, abi.encode(poolKey.currency0, poolKey.currency1)); + return plan.encode(); + } + function encode(Plan memory plan) internal pure returns (bytes memory) { return abi.encode(plan.actions, plan.params); } diff --git a/test/shared/fuzz/LiquidityFuzzers.sol b/test/shared/fuzz/LiquidityFuzzers.sol index 5bbfbc73..02d6583f 100644 --- a/test/shared/fuzz/LiquidityFuzzers.sol +++ b/test/shared/fuzz/LiquidityFuzzers.sol @@ -41,7 +41,7 @@ contract LiquidityFuzzers is Fuzzers { ); uint256 tokenId = lpm.nextTokenId(); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, block.timestamp + 1); return (tokenId, params); From e764aecd375ec8e5774d78e94fadb05038a83a5b Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Fri, 2 Aug 2024 18:23:35 -0400 Subject: [PATCH 15/52] posm: add staking through subscribers (#229) * compiling * compiling, gas snaps added * add unit tests for PositionConfigLibrary * subscriber tests * pr comments * use gas limit calcualtor * test return data * use solidity * comments * natspec & more tests * payable * pr comments * pr comments * inheritdoc --- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- .../PositionManager_burn_nonEmpty.snap | 2 +- .../PositionManager_burn_nonEmpty_native.snap | 2 +- .forge-snapshots/PositionManager_collect.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_decreaseLiquidity.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- .../PositionManager_mint_nativeWithSweep.snap | 1 - ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- foundry.toml | 3 + src/PositionManager.sol | 85 ++++- src/base/Notifier.sol | 57 ++++ src/interfaces/IERC721Permit.sol | 1 + src/interfaces/INotifier.sol | 26 ++ src/interfaces/IPositionManager.sol | 15 +- src/interfaces/ISubscriber.sol | 11 + src/libraries/GasLimitCalculator.sol | 12 + src/libraries/PositionConfig.sol | 49 ++- test/libraries/GasLimitCalculator.t.sol | 22 ++ test/libraries/PositionConfig.t.sol | 102 +++++- test/mocks/MockBadSubscribers.sol | 61 ++++ test/mocks/MockSubscriber.sol | 48 +++ test/position-managers/NativeToken.t.sol | 36 +++ .../PositionManager.notifier.t.sol | 298 ++++++++++++++++++ 45 files changed, 828 insertions(+), 57 deletions(-) delete mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep.snap create mode 100644 src/base/Notifier.sol create mode 100644 src/interfaces/INotifier.sol create mode 100644 src/interfaces/ISubscriber.sol create mode 100644 src/libraries/GasLimitCalculator.sol create mode 100644 test/libraries/GasLimitCalculator.t.sol create mode 100644 test/mocks/MockBadSubscribers.sol create mode 100644 test/mocks/MockSubscriber.sol create mode 100644 test/position-managers/PositionManager.notifier.t.sol diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 4c184642..a3f146ac 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47059 \ No newline at end of file +47186 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 9230f513..a04aaf5c 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46876 \ No newline at end of file +47004 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty.snap b/.forge-snapshots/PositionManager_burn_nonEmpty.snap index c15f4f2d..49379c42 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty.snap @@ -1 +1 @@ -129852 \ No newline at end of file +130136 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap index d2ae1c7d..5238cf5f 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap @@ -1 +1 @@ -122773 \ No newline at end of file +123058 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect.snap b/.forge-snapshots/PositionManager_collect.snap index 0cf00f2a..1d6bec04 100644 --- a/.forge-snapshots/PositionManager_collect.snap +++ b/.forge-snapshots/PositionManager_collect.snap @@ -1 +1 @@ -149984 \ No newline at end of file +150257 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 1b627db8..089f3305 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141136 \ No newline at end of file +141409 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 0cf00f2a..1d6bec04 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -149984 \ No newline at end of file +150257 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity.snap b/.forge-snapshots/PositionManager_decreaseLiquidity.snap index c30fc9de..5bc59fda 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity.snap @@ -1 +1 @@ -115527 \ No newline at end of file +115800 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 3c0e3daf..f183904d 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108384 \ No newline at end of file +108602 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 58fe1844..f2edca7c 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -133885 \ No newline at end of file +134196 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 23aac82f..19fbfe9c 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126624 \ No newline at end of file +126935 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 48ed6687..47317e7b 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128243 \ No newline at end of file +128516 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 26723396..f7f7fed6 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152100 \ No newline at end of file +152363 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 6ce3534b..473fcfde 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151341 \ No newline at end of file +151604 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 35295eaf..7d3f9d6c 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -133900 \ No newline at end of file +134163 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 04c138b3..2a9f67c8 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130065 \ No newline at end of file +130328 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index e55d6257..d1db687b 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -170759 \ No newline at end of file +171022 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 375f518b..6f428511 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140581 \ No newline at end of file +140866 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 7d264b1e..99535ce2 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336663 \ No newline at end of file +336841 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap deleted file mode 100644 index 4ad1137a..00000000 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap +++ /dev/null @@ -1 +0,0 @@ -345146 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 6398f05a..3604c314 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345169 \ No newline at end of file +345347 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 141f9455..50673d18 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344710 \ No newline at end of file +344888 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 2bce4f8d..00e13762 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314645 \ No newline at end of file +314823 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 2f87608a..bf2a5e88 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315287 \ No newline at end of file +315465 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index 1097f3c7..de4b5cb3 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -240869 \ No newline at end of file +241047 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 977b82bc..34f57131 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370969 \ No newline at end of file +371147 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 59c07930..0ed51014 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320663 \ No newline at end of file +320841 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 87615faa..f9a92b51 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -371963 \ No newline at end of file +372141 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 2a78d147..7bbeb8cb 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371342 \ No newline at end of file +371520 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 2a4a2f83..15fd1f72 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416316 \ No newline at end of file +416538 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 54e2ac0c..002bc8be 100644 --- a/foundry.toml +++ b/foundry.toml @@ -11,4 +11,7 @@ fuzz_runs = 10_000 [profile.ci] fuzz_runs = 100_000 +[profile.gas] +gas_limit=30_000_000 + # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/src/PositionManager.sol b/src/PositionManager.sol index d1418897..ef0041c9 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -24,7 +24,9 @@ import {DeltaResolver} from "./base/DeltaResolver.sol"; import {PositionConfig, PositionConfigLibrary} from "./libraries/PositionConfig.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {Actions} from "./libraries/Actions.sol"; +import {Notifier} from "./base/Notifier.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; +import {INotifier} from "./interfaces/INotifier.sol"; import {Permit2Forwarder} from "./base/Permit2Forwarder.sol"; import {SlippageCheckLibrary} from "./libraries/SlippageCheck.sol"; @@ -36,12 +38,13 @@ contract PositionManager is DeltaResolver, ReentrancyLock, BaseActionsRouter, + Notifier, Permit2Forwarder { using SafeTransferLib for *; using CurrencyLibrary for Currency; using PoolIdLibrary for PoolKey; - using PositionConfigLibrary for PositionConfig; + using PositionConfigLibrary for *; using StateLibrary for IPoolManager; using TransientStateLibrary for IPoolManager; using SafeCast for uint256; @@ -51,8 +54,7 @@ contract PositionManager is /// @dev The ID of the next token that will be minted. Skips 0 uint256 public nextTokenId = 1; - /// @inheritdoc IPositionManager - mapping(uint256 tokenId => bytes32 configId) public positionConfigs; + mapping(uint256 tokenId => bytes32 config) private positionConfigs; constructor(IPoolManager _poolManager, IAllowanceTransfer _permit2) BaseActionsRouter(_poolManager) @@ -60,11 +62,31 @@ contract PositionManager is ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM", "1") {} + /// @notice Reverts if the deadline has passed + /// @param deadline The timestamp at which the call is no longer valid, passed in by the caller modifier checkDeadline(uint256 deadline) { if (block.timestamp > deadline) revert DeadlinePassed(); _; } + /// @notice Reverts if the caller is not the owner or approved for the ERC721 token + /// @param caller The address of the caller + /// @param tokenId the unique identifier of the ERC721 token + /// @dev either msg.sender or _msgSender() is passed in as the caller + /// _msgSender() should ONLY be used if this is being called from within the unlockCallback + modifier onlyIfApproved(address caller, uint256 tokenId) { + if (!_isApprovedOrOwner(caller, tokenId)) revert NotApproved(caller); + _; + } + + /// @notice Reverts if the hash of the config does not equal the saved hash + /// @param tokenId the unique identifier of the ERC721 token + /// @param config the PositionConfig to check against + modifier onlyValidConfig(uint256 tokenId, PositionConfig calldata config) { + if (positionConfigs.getConfigId(tokenId) != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); + _; + } + /// @param unlockData is an encoding of actions, params, and currencies /// @param deadline is the timestamp at which the unlockData will no longer be valid function modifyLiquidities(bytes calldata unlockData, uint256 deadline) @@ -76,6 +98,29 @@ contract PositionManager is _executeActions(unlockData); } + /// @inheritdoc INotifier + function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber) + external + payable + onlyIfApproved(msg.sender, tokenId) + onlyValidConfig(tokenId, config) + { + // call to _subscribe will revert if the user already has a sub + positionConfigs.setSubscribe(tokenId); + _subscribe(tokenId, config, subscriber); + } + + /// @inheritdoc INotifier + function unsubscribe(uint256 tokenId, PositionConfig calldata config) + external + payable + onlyIfApproved(msg.sender, tokenId) + onlyValidConfig(tokenId, config) + { + positionConfigs.setUnsubscribe(tokenId); + _unsubscribe(tokenId, config); + } + function _handleAction(uint256 action, bytes calldata params) internal virtual override { if (action == Actions.INCREASE_LIQUIDITY) { ( @@ -149,8 +194,7 @@ contract PositionManager is uint128 amount0Max, uint128 amount1Max, bytes calldata hookData - ) internal { - if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); + ) internal onlyValidConfig(tokenId, config) { // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); liquidityDelta.validateMaxInNegative(amount0Max, amount1Max); @@ -164,10 +208,7 @@ contract PositionManager is uint128 amount0Min, uint128 amount1Min, bytes calldata hookData - ) internal { - if (!_isApprovedOrOwner(_msgSender(), tokenId)) revert NotApproved(_msgSender()); - if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); - + ) internal onlyIfApproved(_msgSender(), tokenId) onlyValidConfig(tokenId, config) { // Note: the tokenId is used as the salt. BalanceDelta liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); liquidityDelta.validateMinOut(amount0Min, amount1Min); @@ -192,7 +233,7 @@ contract PositionManager is // _beforeModify is not called here because the tokenId is newly minted BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); liquidityDelta.validateMaxIn(amount0Max, amount1Max); - positionConfigs[tokenId] = config.toId(); + positionConfigs.setConfigId(tokenId, config); } function _close(Currency currency) internal { @@ -231,9 +272,7 @@ contract PositionManager is uint128 amount0Min, uint128 amount1Min, bytes calldata hookData - ) internal { - if (!_isApprovedOrOwner(_msgSender(), tokenId)) revert NotApproved(_msgSender()); - if (positionConfigs[tokenId] != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); + ) internal onlyIfApproved(_msgSender(), tokenId) onlyValidConfig(tokenId, config) { uint256 liquidity = uint256(_getPositionLiquidity(config, tokenId)); BalanceDelta liquidityDelta; @@ -264,6 +303,10 @@ contract PositionManager is }), hookData ); + + if (positionConfigs.hasSubscriber(uint256(salt))) { + _notifyModifyLiquidity(uint256(salt), config, liquidityChange); + } } function _getPositionLiquidity(PositionConfig calldata config, uint256 tokenId) @@ -291,4 +334,20 @@ contract PositionManager is permit2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(currency)); } } + + /// @dev overrides solmate transferFrom in case a notification to subscribers is needed + function transferFrom(address from, address to, uint256 id) public override { + super.transferFrom(from, to, id); + if (positionConfigs.hasSubscriber(id)) _notifyTransfer(id, from, to); + } + + /// @inheritdoc IPositionManager + function getPositionConfigId(uint256 tokenId) external view returns (bytes32) { + return positionConfigs.getConfigId(tokenId); + } + + /// @inheritdoc INotifier + function hasSubscriber(uint256 tokenId) external view returns (bool) { + return positionConfigs.hasSubscriber(tokenId); + } } diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol new file mode 100644 index 00000000..97510888 --- /dev/null +++ b/src/base/Notifier.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.24; + +import {ISubscriber} from "../interfaces/ISubscriber.sol"; +import {PositionConfig} from "../libraries/PositionConfig.sol"; +import {GasLimitCalculator} from "../libraries/GasLimitCalculator.sol"; + +import "../interfaces/INotifier.sol"; + +/// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers +abstract contract Notifier is INotifier { + using GasLimitCalculator for uint256; + + error AlreadySubscribed(address subscriber); + + event Subscribed(uint256 tokenId, address subscriber); + event Unsubscribed(uint256 tokenId, address subscriber); + + ISubscriber private constant NO_SUBSCRIBER = ISubscriber(address(0)); + + // a percentage of the block.gaslimit denoted in BPS, used as the gas limit for subscriber calls + // 100 bps is 1% + // at 30M gas, the limit is 300K + uint256 private constant BLOCK_LIMIT_BPS = 100; + + mapping(uint256 tokenId => ISubscriber subscriber) public subscriber; + + function _subscribe(uint256 tokenId, PositionConfig memory config, address newSubscriber) internal { + ISubscriber _subscriber = subscriber[tokenId]; + + if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(address(_subscriber)); + subscriber[tokenId] = ISubscriber(newSubscriber); + + ISubscriber(newSubscriber).notifySubscribe(tokenId, config); + emit Subscribed(tokenId, address(newSubscriber)); + } + + /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. + function _unsubscribe(uint256 tokenId, PositionConfig memory config) internal { + ISubscriber _subscriber = subscriber[tokenId]; + + uint256 subscriberGasLimit = BLOCK_LIMIT_BPS.toGasLimit(); + + try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config) {} catch {} + + delete subscriber[tokenId]; + emit Unsubscribed(tokenId, address(_subscriber)); + } + + function _notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) internal { + subscriber[tokenId].notifyModifyLiquidity(tokenId, config, liquidityChange); + } + + function _notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) internal { + subscriber[tokenId].notifyTransfer(tokenId, previousOwner, newOwner); + } +} diff --git a/src/interfaces/IERC721Permit.sol b/src/interfaces/IERC721Permit.sol index 213bca2a..875a5568 100644 --- a/src/interfaces/IERC721Permit.sol +++ b/src/interfaces/IERC721Permit.sol @@ -21,6 +21,7 @@ interface IERC721Permit { /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + /// @dev payable so it can be multicalled with NATIVE related actions function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, uint8 v, bytes32 r, bytes32 s) external payable; diff --git a/src/interfaces/INotifier.sol b/src/interfaces/INotifier.sol new file mode 100644 index 00000000..6fced60e --- /dev/null +++ b/src/interfaces/INotifier.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {PositionConfig} from "../libraries/PositionConfig.sol"; + +/// @notice This interface is used to opt in to sending updates to external contracts about position modifications or transfers +interface INotifier { + /// @notice Enables the subscriber to receive notifications for a respective position + /// @param tokenId the ERC721 tokenId + /// @param config the corresponding PositionConfig for the tokenId + /// @param subscriber the address to notify + /// @dev Calling subscribe when a position is already subscribed will revert + /// @dev payable so it can be multicalled with NATIVE related actions + function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber) external payable; + + /// @notice Removes the subscriber from receiving notifications for a respective position + /// @param tokenId the ERC721 tokenId + /// @param config the corresponding PositionConfig for the tokenId + /// @dev payable so it can be multicalled with NATIVE related actions + function unsubscribe(uint256 tokenId, PositionConfig calldata config) external payable; + + /// @notice Returns whether a a position should call out to notify a subscribing contract on modification or transfer + /// @param tokenId the ERC721 tokenId + /// @return bool whether or not the position has a subscriber + function hasSubscriber(uint256 tokenId) external view returns (bool); +} diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 371aed93..c2ae7201 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -4,22 +4,23 @@ pragma solidity ^0.8.24; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -interface IPositionManager { +import {INotifier} from "./INotifier.sol"; + +interface IPositionManager is INotifier { error NotApproved(address caller); error DeadlinePassed(); error IncorrectPositionConfigForTokenId(uint256 tokenId); error ClearExceedsMaxAmount(Currency currency, int256 amount, uint256 maxAmount); - /// @notice Maps the ERC721 tokenId to a configId, which is a keccak256 hash of the position's pool key, and range (tickLower, tickUpper) - /// Enforces that a minted ERC721 token is tied to one range on one pool. - /// @param tokenId the ERC721 tokenId, assigned at mint - /// @return configId the hash of the position's poolkey, tickLower, and tickUpper - function positionConfigs(uint256 tokenId) external view returns (bytes32 configId); - /// @notice Batches many liquidity modification calls to pool manager /// @param payload is an encoding of actions, and parameters for those actions /// @param deadline is the deadline for the batched actions to be executed function modifyLiquidities(bytes calldata payload, uint256 deadline) external payable; function nextTokenId() external view returns (uint256); + + /// @param tokenId the ERC721 tokenId + /// @return configId a truncated hash of the position's poolkey, tickLower, and tickUpper + /// @dev truncates the least significant bit of the hash + function getPositionConfigId(uint256 tokenId) external view returns (bytes32 configId); } diff --git a/src/interfaces/ISubscriber.sol b/src/interfaces/ISubscriber.sol new file mode 100644 index 00000000..a74efeeb --- /dev/null +++ b/src/interfaces/ISubscriber.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {PositionConfig} from "../libraries/PositionConfig.sol"; + +interface ISubscriber { + function notifySubscribe(uint256 tokenId, PositionConfig memory config) external; + function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config) external; + function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) external; + function notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) external; +} diff --git a/src/libraries/GasLimitCalculator.sol b/src/libraries/GasLimitCalculator.sol new file mode 100644 index 00000000..d5a8a440 --- /dev/null +++ b/src/libraries/GasLimitCalculator.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.24; + +// TODO: Post-audit move to core, as v4-core will use something similar. +library GasLimitCalculator { + uint256 constant BPS_DENOMINATOR = 10_000; + + /// calculates a gas limit as a percentage of the currenct block's gas limit + function toGasLimit(uint256 bps) internal view returns (uint256 gasLimit) { + return block.gaslimit * bps / BPS_DENOMINATOR; + } +} diff --git a/src/libraries/PositionConfig.sol b/src/libraries/PositionConfig.sol index d3bd8e24..7c5d632a 100644 --- a/src/libraries/PositionConfig.sol +++ b/src/libraries/PositionConfig.sol @@ -3,17 +3,58 @@ pragma solidity ^0.8.24; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -// A PositionConfig is the input for creating and modifying a Position in core, set per tokenId +// A PositionConfig is the input for creating and modifying a Position in core, whose truncated hash is set per tokenId struct PositionConfig { PoolKey poolKey; int24 tickLower; int24 tickUpper; } -/// @notice Library for computing the configId given a PositionConfig +/// @notice Library to get and set the PositionConfigId and subscriber status for a given tokenId library PositionConfigLibrary { + using PositionConfigLibrary for PositionConfig; + + bytes32 constant MASK_UPPER_BIT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + bytes32 constant DIRTY_UPPER_BIT = 0x8000000000000000000000000000000000000000000000000000000000000000; + + /// @notice returns the truncated hash of the PositionConfig for a given tokenId + function getConfigId(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) + internal + view + returns (bytes32 configId) + { + configId = positionConfigs[tokenId] & MASK_UPPER_BIT; + } + + function setConfigId( + mapping(uint256 => bytes32) storage positionConfigs, + uint256 tokenId, + PositionConfig calldata config + ) internal { + positionConfigs[tokenId] = config.toId(); + } + + function setSubscribe(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) internal { + positionConfigs[tokenId] |= DIRTY_UPPER_BIT; + } + + function setUnsubscribe(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) internal { + positionConfigs[tokenId] &= MASK_UPPER_BIT; + } + + function hasSubscriber(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) + internal + view + returns (bool subscribed) + { + bytes32 _config = positionConfigs[tokenId]; + assembly ("memory-safe") { + subscribed := shr(255, _config) + } + } + function toId(PositionConfig calldata config) internal pure returns (bytes32 id) { - // id = keccak256(abi.encodePacked(currency0, currency1, fee, tickSpacing, hooks, tickLower, tickUpper))) + // id = keccak256(abi.encodePacked(currency0, currency1, fee, tickSpacing, hooks, tickLower, tickUpper))) >> 1 assembly ("memory-safe") { let fmp := mload(0x40) mstore(add(fmp, 0x34), calldataload(add(config, 0xc0))) // tickUpper: [0x51, 0x54) @@ -24,7 +65,7 @@ library PositionConfigLibrary { mstore(add(fmp, 0x14), calldataload(add(config, 0x20))) // currency1: [0x20, 0x34) mstore(fmp, calldataload(config)) // currency0: [0x0c, 0x20) - id := keccak256(add(fmp, 0x0c), 0x48) // len is 72 bytes + id := shr(1, keccak256(add(fmp, 0x0c), 0x48)) // len is 72 bytes, truncate lower bit of the hash // now clean the memory we used mstore(add(fmp, 0x40), 0) // fmp+0x40 held hooks (14 bytes), tickLower, tickUpper diff --git a/test/libraries/GasLimitCalculator.t.sol b/test/libraries/GasLimitCalculator.t.sol new file mode 100644 index 00000000..47129537 --- /dev/null +++ b/test/libraries/GasLimitCalculator.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {GasLimitCalculator} from "../../src/libraries/GasLimitCalculator.sol"; + +contract GasLimitCalculatorTest is Test { + function test_gasLimit_100_percent() public { + assertEq(block.gaslimit, GasLimitCalculator.toGasLimit(10_000)); + } + + function test_gasLimit_1_percent() public { + /// 100 bps = 1% + // 1% of 3_000_000_000 is 30_000_000 + assertEq(30_000_000, GasLimitCalculator.toGasLimit(100)); + } + + function test_gasLimit_1BP() public { + /// 1bp is 0.01% + assertEq(300_000, GasLimitCalculator.toGasLimit(1)); + } +} diff --git a/test/libraries/PositionConfig.t.sol b/test/libraries/PositionConfig.t.sol index 1eeedc18..cfec1a54 100644 --- a/test/libraries/PositionConfig.t.sol +++ b/test/libraries/PositionConfig.t.sol @@ -2,13 +2,108 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; + import {PositionConfig, PositionConfigLibrary} from "../../src/libraries/PositionConfig.sol"; contract PositionConfigTest is Test { - using PositionConfigLibrary for PositionConfig; + using PositionConfigLibrary for *; + + mapping(uint256 => bytes32) internal testConfigs; + + bytes32 public constant UPPER_BIT_SET = 0x8000000000000000000000000000000000000000000000000000000000000000; function test_fuzz_toId(PositionConfig calldata config) public pure { - bytes32 expectedId = keccak256( + bytes32 expectedId = _calculateExpectedId(config); + assertEq(expectedId, config.toId()); + } + + function test_fuzz_setConfigId(uint256 tokenId, PositionConfig calldata config) public { + testConfigs.setConfigId(tokenId, config); + + bytes32 expectedConfigId = _calculateExpectedId(config); + + bytes32 actualConfigId = testConfigs[tokenId]; + assertEq(expectedConfigId, actualConfigId); + } + + function test_fuzz_getConfigId(uint256 tokenId, PositionConfig calldata config) public { + bytes32 expectedId = _calculateExpectedId(config); + // set + testConfigs[tokenId] = expectedId; + + assertEq(expectedId, testConfigs.getConfigId(tokenId)); + } + + function test_fuzz_setConfigId_getConfigId(uint256 tokenId, PositionConfig calldata config) public { + testConfigs.setConfigId(tokenId, config); + + bytes32 expectedId = _calculateExpectedId(config); + + assertEq(testConfigs.getConfigId(tokenId), testConfigs[tokenId]); + assertEq(testConfigs.getConfigId(tokenId), expectedId); + } + + function test_fuzz_getConfigId_equal_afterSubscribe(uint256 tokenId, PositionConfig calldata config) public { + testConfigs.setConfigId(tokenId, config); + testConfigs.setSubscribe(tokenId); + + assertEq(testConfigs.getConfigId(tokenId), config.toId()); + } + + function test_fuzz_setSubscribe(uint256 tokenId) public { + testConfigs.setSubscribe(tokenId); + bytes32 upperBitSet = testConfigs[tokenId]; + + assertEq(upperBitSet, UPPER_BIT_SET); + } + + function test_fuzz_setConfigId_setSubscribe(uint256 tokenId, PositionConfig calldata config) public { + testConfigs.setConfigId(tokenId, config); + testConfigs.setSubscribe(tokenId); + + bytes32 expectedConfig = _calculateExpectedId(config) | UPPER_BIT_SET; + + bytes32 _config = testConfigs[tokenId]; + + assertEq(_config, expectedConfig); + } + + function test_fuzz_setUnsubscribe(uint256 tokenId) public { + testConfigs.setSubscribe(tokenId); + bytes32 _config = testConfigs[tokenId]; + assertEq(_config, UPPER_BIT_SET); + testConfigs.setUnsubscribe(tokenId); + _config = testConfigs[tokenId]; + assertEq(_config, 0); + } + + function test_hasSubscriber(uint256 tokenId) public { + testConfigs.setSubscribe(tokenId); + assert(testConfigs.hasSubscriber(tokenId)); + testConfigs.setUnsubscribe(tokenId); + assert(!testConfigs.hasSubscriber(tokenId)); + } + + function test_fuzz_setConfigId_setSubscribe_setUnsubscribe_getConfigId( + uint256 tokenId, + PositionConfig calldata config + ) public { + assertEq(testConfigs.getConfigId(tokenId), 0); + + testConfigs.setConfigId(tokenId, config); + assertEq(testConfigs.getConfigId(tokenId), config.toId()); + + testConfigs.setSubscribe(tokenId); + assertEq(testConfigs.getConfigId(tokenId), config.toId()); + assertEq(testConfigs.hasSubscriber(tokenId), true); + + testConfigs.setUnsubscribe(tokenId); + assertEq(testConfigs.getConfigId(tokenId), config.toId()); + assertEq(testConfigs.hasSubscriber(tokenId), false); + } + + function _calculateExpectedId(PositionConfig calldata config) internal pure returns (bytes32 expectedId) { + expectedId = keccak256( abi.encodePacked( config.poolKey.currency0, config.poolKey.currency1, @@ -19,6 +114,7 @@ contract PositionConfigTest is Test { config.tickUpper ) ); - assertEq(expectedId, config.toId()); + // truncate the upper bit + expectedId = expectedId >> 1; } } diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol new file mode 100644 index 00000000..2fa517a1 --- /dev/null +++ b/test/mocks/MockBadSubscribers.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; +import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +/// @notice A subscriber contract that returns values from the subscriber entrypoints +contract MockReturnDataSubscriber is ISubscriber { + PositionManager posm; + + uint256 public notifySubscribeCount; + uint256 public notifyUnsubscribeCount; + uint256 public notifyModifyLiquidityCount; + uint256 public notifyTransferCount; + + error NotAuthorizedNotifer(address sender); + + error NotImplemented(); + + uint256 memPtr; + + constructor(PositionManager _posm) { + posm = _posm; + } + + modifier onlyByPosm() { + if (msg.sender != address(posm)) revert NotAuthorizedNotifer(msg.sender); + _; + } + + function notifySubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + notifySubscribeCount++; + } + + function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + notifyUnsubscribeCount++; + uint256 _memPtr = memPtr; + assembly { + let fmp := mload(0x40) + mstore(fmp, 0xBEEF) + mstore(add(fmp, 0x20), 0xCAFE) + return(fmp, _memPtr) + } + } + + function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) + external + onlyByPosm + { + notifyModifyLiquidityCount++; + } + + function notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) external onlyByPosm { + notifyTransferCount++; + } + + function setReturnDataSize(uint256 _value) external { + memPtr = _value; + } +} diff --git a/test/mocks/MockSubscriber.sol b/test/mocks/MockSubscriber.sol new file mode 100644 index 00000000..b6c92cad --- /dev/null +++ b/test/mocks/MockSubscriber.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; +import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; + +/// @notice A subscriber contract that ingests updates from the v4 position manager +contract MockSubscriber is ISubscriber { + PositionManager posm; + + uint256 public notifySubscribeCount; + uint256 public notifyUnsubscribeCount; + uint256 public notifyModifyLiquidityCount; + uint256 public notifyTransferCount; + + error NotAuthorizedNotifer(address sender); + + error NotImplemented(); + + constructor(PositionManager _posm) { + posm = _posm; + } + + modifier onlyByPosm() { + if (msg.sender != address(posm)) revert NotAuthorizedNotifer(msg.sender); + _; + } + + function notifySubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + notifySubscribeCount++; + } + + function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + notifyUnsubscribeCount++; + } + + function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) + external + onlyByPosm + { + notifyModifyLiquidityCount++; + } + + function notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) external onlyByPosm { + notifyTransferCount++; + } +} diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index 7d03721c..74e89af9 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -28,6 +28,7 @@ import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {Constants} from "../../src/libraries/Constants.sol"; +import {MockSubscriber} from "../mocks/MockSubscriber.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Planner, Plan} from "../shared/Planner.sol"; @@ -44,6 +45,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PoolId poolId; + MockSubscriber sub; + function setUp() public { deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); @@ -58,6 +61,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // currency0 is the native token so only execute approvals for currency1. approvePosmCurrency(currency1); + sub = new MockSubscriber(lpm); + vm.deal(address(this), type(uint256).max); } @@ -505,4 +510,35 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(currency0.balanceOfSelf() - balance0Before, uint128(delta.amount0())); assertEq(currency1.balanceOfSelf() - balance1Before, uint128(delta.amount1())); } + + // this test fails unless subscribe is payable + function test_multicall_mint_subscribe_native() public { + uint256 tokenId = lpm.nextTokenId(); + + PositionConfig memory config = PositionConfig({poolKey: nativeKey, tickLower: -60, tickUpper: 60}); + + Plan memory plan = Planner.init(); + plan.add( + Actions.MINT_POSITION, + abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + ); + plan.add(Actions.CLOSE_CURRENCY, abi.encode(config.poolKey.currency0)); + plan.add(Actions.CLOSE_CURRENCY, abi.encode(config.poolKey.currency1)); + plan.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, address(this))); + bytes memory actions = plan.encode(); + + bytes[] memory calls = new bytes[](2); + + calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub); + + lpm.multicall{value: 10e18}(calls); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + assertEq(liquidity, 100e18); + assertEq(sub.notifySubscribeCount(), 1); + } } diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol new file mode 100644 index 00000000..a1544f13 --- /dev/null +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; +import {MockSubscriber} from "../mocks/MockSubscriber.sol"; +import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; +import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {Plan, Planner} from "../shared/Planner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {MockReturnDataSubscriber} from "../mocks/MockBadSubscribers.sol"; + +contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { + using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; + using Planner for Plan; + + MockSubscriber sub; + MockReturnDataSubscriber badSubscriber; + PositionConfig config; + + address alice = makeAddr("ALICE"); + address bob = makeAddr("BOB"); + + function setUp() public { + deployFreshManagerAndRouters(); + deployMintAndApprove2Currencies(); + + (key,) = initPool(currency0, currency1, IHooks(hook), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + + // Requires currency0 and currency1 to be set in base Deployers contract. + deployAndApprovePosm(manager); + + sub = new MockSubscriber(lpm); + badSubscriber = new MockReturnDataSubscriber(lpm); + config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); + + // TODO: Test NATIVE poolKey + } + + function test_subscribe_revertsWithEmptyPositionConfig() public { + uint256 tokenId = lpm.nextTokenId(); + vm.expectRevert("NOT_MINTED"); + lpm.subscribe(tokenId, config, address(sub)); + } + + function test_subscribe_revertsWhenNotApproved() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // this contract is not approved to operate on alice's liq + + vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(this))); + lpm.subscribe(tokenId, config, address(sub)); + } + + function test_subscribe_reverts_withIncorrectConfig() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + PositionConfig memory incorrectConfig = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 301}); + + vm.expectRevert(abi.encodeWithSelector(IPositionManager.IncorrectPositionConfigForTokenId.selector, tokenId)); + lpm.subscribe(tokenId, incorrectConfig, address(sub)); + } + + function test_subscribe_succeeds() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub)); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + assertEq(sub.notifySubscribeCount(), 1); + } + + function test_notifyModifyLiquidity_succeeds() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub)); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + Plan memory plan = Planner.init(); + for (uint256 i = 0; i < 10; i++) { + plan.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + } + + bytes memory calls = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); + lpm.modifyLiquidities(calls, _deadline); + + assertEq(sub.notifySubscribeCount(), 1); + assertEq(sub.notifyModifyLiquidityCount(), 10); + } + + function test_notifyTransfer_withTransferFrom_succeeds() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub)); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + lpm.transferFrom(alice, bob, tokenId); + + assertEq(sub.notifyTransferCount(), 1); + } + + function test_notifyTransfer_withSafeTransferFrom_succeeds() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub)); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + lpm.safeTransferFrom(alice, bob, tokenId); + + assertEq(sub.notifyTransferCount(), 1); + } + + function test_notifyTransfer_withSafeTransferFromData_succeeds() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub)); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + lpm.safeTransferFrom(alice, bob, tokenId, ""); + + assertEq(sub.notifyTransferCount(), 1); + } + + function test_unsubscribe_succeeds() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub)); + + lpm.unsubscribe(tokenId, config); + + assertEq(sub.notifyUnsubscribeCount(), 1); + assertEq(lpm.hasSubscriber(tokenId), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); + } + + function test_unsubscribe_isSuccessfulWithBadSubscriber() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(badSubscriber)); + + MockReturnDataSubscriber(badSubscriber).setReturnDataSize(0x600000); + lpm.unsubscribe(tokenId, config); + + // the subscriber contract call failed bc it used too much gas + assertEq(MockReturnDataSubscriber(badSubscriber).notifyUnsubscribeCount(), 0); + assertEq(lpm.hasSubscriber(tokenId), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); + } + + function test_multicall_mint_subscribe() public { + uint256 tokenId = lpm.nextTokenId(); + + Plan memory plan = Planner.init(); + plan.add( + Actions.MINT_POSITION, + abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + ); + bytes memory actions = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); + + bytes[] memory calls = new bytes[](2); + + calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub); + + lpm.multicall(calls); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + assertEq(liquidity, 100e18); + assertEq(sub.notifySubscribeCount(), 1); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + } + + function test_multicall_mint_subscribe_increase() public { + uint256 tokenId = lpm.nextTokenId(); + + // Encode mint. + Plan memory plan = Planner.init(); + plan.add( + Actions.MINT_POSITION, + abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + ); + bytes memory actions = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); + + // Encode increase separately. + plan = Planner.init(); + plan.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + bytes memory actions2 = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); + + bytes[] memory calls = new bytes[](3); + + calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub); + calls[2] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions2, _deadline); + + lpm.multicall(calls); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + assertEq(liquidity, 110e18); + assertEq(sub.notifySubscribeCount(), 1); + assertEq(sub.notifyModifyLiquidityCount(), 1); + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + } + + function test_unsubscribe_revertsWhenNotSubscribed() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + vm.expectRevert(); + lpm.unsubscribe(tokenId, config); + } +} From 912536c6ea359f11532aee366a411b9f3e309add Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:37:15 +0100 Subject: [PATCH 16/52] make msgSender public (#253) Co-authored-by: Sara Reynolds --- .forge-snapshots/BaseActionsRouter_mock10commands.snap | 2 +- ...ments_swap_settleFromCaller_takeAllToMsgSender.snap | 2 +- ...wap_settleFromCaller_takeAllToSpecifiedAddress.snap | 2 +- ...ents_swap_settleWithBalance_takeAllToMsgSender.snap | 2 +- ...ap_settleWithBalance_takeAllToSpecifiedAddress.snap | 2 +- .forge-snapshots/PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- .forge-snapshots/PositionManager_burn_nonEmpty.snap | 2 +- .../PositionManager_burn_nonEmpty_native.snap | 2 +- .forge-snapshots/PositionManager_collect.snap | 2 +- .forge-snapshots/PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_decreaseLiquidity.snap | 2 +- .../PositionManager_decreaseLiquidity_native.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- .../PositionManager_decrease_burnEmpty_native.snap | 2 +- ...ositionManager_decrease_sameRange_allLiquidity.snap | 2 +- ...itionManager_increaseLiquidity_erc20_withClose.snap | 2 +- ...Manager_increaseLiquidity_erc20_withSettlePair.snap | 2 +- .../PositionManager_increaseLiquidity_native.snap | 2 +- ...anager_increase_autocompoundExactUnclaimedFees.snap | 2 +- ...nManager_increase_autocompoundExcessFeesCredit.snap | 2 +- ...itionManager_increase_autocompound_clearExcess.snap | 2 +- .forge-snapshots/PositionManager_mint_native.snap | 2 +- ...PositionManager_mint_nativeWithSweep_withClose.snap | 2 +- ...ionManager_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .forge-snapshots/PositionManager_mint_sameRange.snap | 2 +- .../PositionManager_mint_settleWithBalance_sweep.snap | 2 +- ...PositionManager_mint_warmedPool_differentRange.snap | 2 +- .forge-snapshots/PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- .../PositionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- .../V4Router_ExactOut1Hop_nativeIn_sweepETH.snap | 2 +- .forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap | 2 +- .forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap | 2 +- .forge-snapshots/V4Router_ExactOutputSingle.snap | 2 +- .../V4Router_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/PositionManager.sol | 10 +++++----- src/V4Router.sol | 2 +- src/base/BaseActionsRouter.sol | 6 +++--- src/libraries/Constants.sol | 2 +- test/mocks/MockBaseActionsRouter.sol | 2 +- test/mocks/MockV4Router.sol | 2 +- 64 files changed, 70 insertions(+), 70 deletions(-) diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index b4ba2efe..34a072bb 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -61778 \ No newline at end of file +61756 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index 8be49d50..57e24db8 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -134509 \ No newline at end of file +134555 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index da685a9d..85681dc4 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -134647 \ No newline at end of file +134693 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index ee4568b7..f0186102 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -127656 \ No newline at end of file +127702 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index 76dbb684..f85df8a0 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -127794 \ No newline at end of file +127840 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index a3f146ac..b4f9b770 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47186 \ No newline at end of file +47168 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index a04aaf5c..58f0bac9 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -47004 \ No newline at end of file +46986 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty.snap b/.forge-snapshots/PositionManager_burn_nonEmpty.snap index 49379c42..274fdc43 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty.snap @@ -1 +1 @@ -130136 \ No newline at end of file +130119 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap index 5238cf5f..0621747c 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap @@ -1 +1 @@ -123058 \ No newline at end of file +123040 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect.snap b/.forge-snapshots/PositionManager_collect.snap index 1d6bec04..d68e06f1 100644 --- a/.forge-snapshots/PositionManager_collect.snap +++ b/.forge-snapshots/PositionManager_collect.snap @@ -1 +1 @@ -150257 \ No newline at end of file +150235 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 089f3305..edac16c2 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141409 \ No newline at end of file +141387 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 1d6bec04..d68e06f1 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150257 \ No newline at end of file +150235 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity.snap b/.forge-snapshots/PositionManager_decreaseLiquidity.snap index 5bc59fda..ff261d83 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity.snap @@ -1 +1 @@ -115800 \ No newline at end of file +115778 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index f183904d..c3def0ba 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108602 \ No newline at end of file +108584 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index f2edca7c..bcbd95de 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134196 \ No newline at end of file +134178 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 19fbfe9c..be94d9c7 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126935 \ No newline at end of file +126917 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 47317e7b..d53ad25c 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128516 \ No newline at end of file +128494 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index f7f7fed6..7be08370 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152363 \ No newline at end of file +152341 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 473fcfde..10bef205 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151604 \ No newline at end of file +151582 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 7d3f9d6c..3bdd6761 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134163 \ No newline at end of file +134141 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 2a9f67c8..e09bd90f 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130328 \ No newline at end of file +130306 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index d1db687b..4aed0cee 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171022 \ No newline at end of file +171000 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 6f428511..32491bda 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140866 \ No newline at end of file +140844 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 99535ce2..3abc1720 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336841 \ No newline at end of file +336819 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 3604c314..3b5a65d2 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345347 \ No newline at end of file +345325 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 50673d18..1813daec 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344888 \ No newline at end of file +344866 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 00e13762..d98d938b 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314823 \ No newline at end of file +314801 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index bf2a5e88..cc26d78b 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315465 \ No newline at end of file +315443 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index de4b5cb3..b90f5486 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241047 \ No newline at end of file +241025 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 34f57131..486ee25a 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -371147 \ No newline at end of file +371125 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 0ed51014..d8288af1 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320841 \ No newline at end of file +320819 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index f9a92b51..376974e4 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372141 \ No newline at end of file +372119 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 7bbeb8cb..fe27d17c 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371520 \ No newline at end of file +371498 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 15fd1f72..3a2b15c8 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416538 \ No newline at end of file +416560 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 9a2f1c0d..e5e6f155 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -7995 \ No newline at end of file +8068 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index f4c2ccab..8ef5d869 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -120558 \ No newline at end of file +120604 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index f5f1f8b2..df894b65 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -119753 \ No newline at end of file +119799 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index e83986bc..93c415cb 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -128625 \ No newline at end of file +128671 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 6f6a89bc..2b629d08 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135455 \ No newline at end of file +135501 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 58e2ffa5..87bac22b 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -186962 \ No newline at end of file +187008 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index 59388aa0..a043c47c 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -178897 \ No newline at end of file +178943 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 66182353..911cd648 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238494 \ No newline at end of file +238540 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index a71256a4..644d8cdd 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -230453 \ No newline at end of file +230499 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index 8be49d50..57e24db8 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134509 \ No newline at end of file +134555 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index a99d4022..5f290a78 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119612 \ No newline at end of file +119658 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index a7b81119..cb7d107b 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118790 \ No newline at end of file +118836 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index 8ca480a4..d215d2c2 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -126261 \ No newline at end of file +126262 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index dc36e641..27a2601a 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -120494 \ No newline at end of file +120540 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index c9888d60..6bc4c749 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -129366 \ No newline at end of file +129412 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index 4ea5699e..a9fb35ce 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -134167 \ No newline at end of file +134213 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index e1f7e630..f310ec6e 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186277 \ No newline at end of file +186323 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index 63e6800b..246b3b0b 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -183172 \ No newline at end of file +183173 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 9ffbf5f3..88228ad4 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -238427 \ No newline at end of file +238473 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index 1af00254..d5d40ded 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -235346 \ No newline at end of file +235347 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index 089ed2cd..70351463 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -229579 \ No newline at end of file +229625 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index b4226dd3..feaddcf8 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -132930 \ No newline at end of file +132976 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index 767ad5a9..81a221be 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -125024 \ No newline at end of file +125025 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index 7f51d037..523122f7 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -119315 \ No newline at end of file +119316 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index ef0041c9..f94f1525 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -182,7 +182,7 @@ contract PositionManager is } } - function _msgSender() internal view override returns (address) { + function msgSender() public view override returns (address) { return _getLocker(); } @@ -208,7 +208,7 @@ contract PositionManager is uint128 amount0Min, uint128 amount1Min, bytes calldata hookData - ) internal onlyIfApproved(_msgSender(), tokenId) onlyValidConfig(tokenId, config) { + ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { // Note: the tokenId is used as the salt. BalanceDelta liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); liquidityDelta.validateMinOut(amount0Min, amount1Min); @@ -242,7 +242,7 @@ contract PositionManager is int256 currencyDelta = poolManager.currencyDelta(address(this), currency); // the locker is the payer or receiver - address caller = _msgSender(); + address caller = msgSender(); if (currencyDelta < 0) { _settle(currency, caller, uint256(-currencyDelta)); } else if (currencyDelta > 0) { @@ -260,7 +260,7 @@ contract PositionManager is function _settlePair(Currency currency0, Currency currency1) internal { // the locker is the payer when settling - address caller = _msgSender(); + address caller = msgSender(); _settle(currency0, caller, _getFullSettleAmount(currency0)); _settle(currency1, caller, _getFullSettleAmount(currency1)); } @@ -272,7 +272,7 @@ contract PositionManager is uint128 amount0Min, uint128 amount1Min, bytes calldata hookData - ) internal onlyIfApproved(_msgSender(), tokenId) onlyValidConfig(tokenId, config) { + ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { uint256 liquidity = uint256(_getPositionLiquidity(config, tokenId)); BalanceDelta liquidityDelta; diff --git a/src/V4Router.sol b/src/V4Router.sol index 91ea9404..5adbc03c 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -53,7 +53,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { if (action == Actions.SETTLE_ALL) { Currency currency = params.decodeCurrency(); // TODO should it have a maxAmountOut added slippage protection? - _settle(currency, _msgSender(), _getFullSettleAmount(currency)); + _settle(currency, msgSender(), _getFullSettleAmount(currency)); } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index 78193601..fb8d020e 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -53,12 +53,12 @@ abstract contract BaseActionsRouter is SafeCallback { /// In many contracts this will be the address that calls the initial entry point that calls `_executeActions` /// `msg.sender` shouldnt be used, as this will be the v4 pool manager contract that calls `unlockCallback` /// If using ReentrancyLock.sol, this function can return _getLocker() - function _msgSender() internal view virtual returns (address); + function msgSender() public view virtual returns (address); /// @notice Calculates the address for a action function _mapRecipient(address recipient) internal view returns (address) { if (recipient == Constants.MSG_SENDER) { - return _msgSender(); + return msgSender(); } else if (recipient == Constants.ADDRESS_THIS) { return address(this); } else { @@ -68,6 +68,6 @@ abstract contract BaseActionsRouter is SafeCallback { /// @notice Calculates the payer for an action function _mapPayer(bool payerIsUser) internal view returns (address) { - return payerIsUser ? _msgSender() : address(this); + return payerIsUser ? msgSender() : address(this); } } diff --git a/src/libraries/Constants.sol b/src/libraries/Constants.sol index 3a6f1b6f..edeb0ea3 100644 --- a/src/libraries/Constants.sol +++ b/src/libraries/Constants.sol @@ -7,7 +7,7 @@ library Constants { uint128 internal constant CONTRACT_BALANCE = 0; uint128 internal constant OPEN_DELTA = 1; - /// @notice used to signal that the recipient of an action should be the _msgSender of address(this) + /// @notice used to signal that the recipient of an action should be the msgSender of address(this) address internal constant MSG_SENDER = address(1); address internal constant ADDRESS_THIS = address(2); } diff --git a/test/mocks/MockBaseActionsRouter.sol b/test/mocks/MockBaseActionsRouter.sol index 39001029..a6a033ce 100644 --- a/test/mocks/MockBaseActionsRouter.sol +++ b/test/mocks/MockBaseActionsRouter.sol @@ -41,7 +41,7 @@ contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { } } - function _msgSender() internal pure override returns (address) { + function msgSender() public pure override returns (address) { return address(0xdeadbeef); } diff --git a/test/mocks/MockV4Router.sol b/test/mocks/MockV4Router.sol index a543024f..fc935bb3 100644 --- a/test/mocks/MockV4Router.sol +++ b/test/mocks/MockV4Router.sol @@ -35,7 +35,7 @@ contract MockV4Router is V4Router, ReentrancyLock { } } - function _msgSender() internal view override returns (address) { + function msgSender() public view override returns (address) { return _getLocker(); } From eee5a0eed02fb219f478b7ebfb97a59ed1f2cc56 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:46:45 -0400 Subject: [PATCH 17/52] posm: CLEAR_OR_TAKE (#252) * clear or take * misc code comment * update naming; use delta resolver helper for clearOrTake * nits * reorder * nits --- ...ger_increase_autocompound_clearExcess.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- src/PositionManager.sol | 24 +++++---- src/V4Router.sol | 6 +-- src/base/DeltaResolver.sol | 26 ++++++---- src/interfaces/IPositionManager.sol | 1 - src/libraries/Actions.sol | 2 +- test/BaseActionsRouter.t.sol | 2 +- test/mocks/MockBaseActionsRouter.sol | 2 +- .../position-managers/IncreaseLiquidity.t.sol | 50 +++++++++++-------- .../PositionManager.gas.t.sol | 4 +- test/position-managers/PositionManager.t.sol | 43 +++++++++++----- 12 files changed, 100 insertions(+), 64 deletions(-) diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 32491bda..16f17b7f 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140844 \ No newline at end of file +140956 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index e5e6f155..79693d11 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8068 \ No newline at end of file +8116 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index f94f1525..fabc4d12 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -48,6 +48,7 @@ contract PositionManager is using StateLibrary for IPoolManager; using TransientStateLibrary for IPoolManager; using SafeCast for uint256; + using SafeCast for int256; using CalldataDecoder for bytes; using SlippageCheckLibrary for BalanceDelta; @@ -155,9 +156,9 @@ contract PositionManager is } else if (action == Actions.CLOSE_CURRENCY) { Currency currency = params.decodeCurrency(); _close(currency); - } else if (action == Actions.CLEAR) { + } else if (action == Actions.CLEAR_OR_TAKE) { (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); - _clear(currency, amountMax); + _clearOrTake(currency, amountMax); } else if (action == Actions.BURN_POSITION) { // Will automatically decrease liquidity to 0 if the position is not already empty. ( @@ -251,18 +252,23 @@ contract PositionManager is } /// @dev integrators may elect to forfeit positive deltas with clear - /// provides a safety check that amount-to-clear is less than a user-provided maximum - function _clear(Currency currency, uint256 amountMax) internal { - int256 currencyDelta = poolManager.currencyDelta(address(this), currency); - if (uint256(currencyDelta) > amountMax) revert ClearExceedsMaxAmount(currency, currencyDelta, amountMax); - poolManager.clear(currency, uint256(currencyDelta)); + /// if the forfeit amount exceeds the user-specified max, the amount is taken instead + function _clearOrTake(Currency currency, uint256 amountMax) internal { + uint256 delta = _getFullCredit(currency); + + // forfeit the delta if its less than or equal to the user-specified limit + if (delta <= amountMax) { + poolManager.clear(currency, delta); + } else { + _take(currency, msgSender(), delta); + } } function _settlePair(Currency currency0, Currency currency1) internal { // the locker is the payer when settling address caller = msgSender(); - _settle(currency0, caller, _getFullSettleAmount(currency0)); - _settle(currency1, caller, _getFullSettleAmount(currency1)); + _settle(currency0, caller, _getFullDebt(currency0)); + _settle(currency1, caller, _getFullDebt(currency1)); } /// @dev this is overloaded with ERC721Permit._burn diff --git a/src/V4Router.sol b/src/V4Router.sol index 5adbc03c..a1e4908f 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -53,17 +53,17 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { if (action == Actions.SETTLE_ALL) { Currency currency = params.decodeCurrency(); // TODO should it have a maxAmountOut added slippage protection? - _settle(currency, msgSender(), _getFullSettleAmount(currency)); + _settle(currency, msgSender(), _getFullDebt(currency)); } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); } else if (action == Actions.TAKE_ALL) { (Currency currency, address recipient) = params.decodeCurrencyAndAddress(); - uint256 amount = _getFullTakeAmount(currency); + uint256 amount = _getFullCredit(currency); _take(currency, _mapRecipient(recipient), amount); } else if (action == Actions.TAKE_PORTION) { (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); - _take(currency, _mapRecipient(recipient), _getFullTakeAmount(currency).calculatePortion(bips)); + _take(currency, _mapRecipient(recipient), _getFullCredit(currency).calculatePortion(bips)); } else { revert UnsupportedAction(action); } diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index cfed40c4..75bc5649 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -15,9 +15,9 @@ abstract contract DeltaResolver is ImmutableState { using SafeCast for *; /// @notice Emitted trying to settle a positive delta. - error IncorrectUseOfSettle(); + error DeltaNotPositive(Currency currency); /// @notice Emitted trying to take a negative delta. - error IncorrectUseOfTake(); + error DeltaNotNegative(Currency currency); /// @notice Take an amount of currency out of the PoolManager /// @param currency Currency to take @@ -49,17 +49,23 @@ abstract contract DeltaResolver is ImmutableState { /// @param amount The number of tokens to send function _pay(Currency token, address payer, uint256 amount) internal virtual; - function _getFullSettleAmount(Currency currency) internal view returns (uint256 amount) { + /// @notice Obtain the full amount owed by this contract (negative delta) + /// @param currency Currency to get the delta for + /// @return amount The amount owed by this contract as a uint256 + function _getFullDebt(Currency currency) internal view returns (uint256 amount) { int256 _amount = poolManager.currencyDelta(address(this), currency); - // If the amount is positive, it should be taken not settled for. - if (_amount > 0) revert IncorrectUseOfSettle(); + // If the amount is negative, it should be settled not taken. + if (_amount > 0) revert DeltaNotNegative(currency); amount = uint256(-_amount); } - function _getFullTakeAmount(Currency currency) internal view returns (uint256 amount) { + /// @notice Obtain the full credit owed to this contract (positive delta) + /// @param currency Currency to get the delta for + /// @return amount The amount owed to this contract as a uint256 + function _getFullCredit(Currency currency) internal view returns (uint256 amount) { int256 _amount = poolManager.currencyDelta(address(this), currency); - // If the amount is negative, it should be settled not taken. - if (_amount < 0) revert IncorrectUseOfTake(); + // If the amount is negative, it should be taken not settled for. + if (_amount < 0) revert DeltaNotPositive(currency); amount = uint256(_amount); } @@ -68,7 +74,7 @@ abstract contract DeltaResolver is ImmutableState { if (amount == Constants.CONTRACT_BALANCE) { return currency.balanceOfSelf(); } else if (amount == Constants.OPEN_DELTA) { - return _getFullSettleAmount(currency); + return _getFullDebt(currency); } return amount; } @@ -78,7 +84,7 @@ abstract contract DeltaResolver is ImmutableState { if (amount == Constants.CONTRACT_BALANCE) { return currency.balanceOfSelf().toUint128(); } else if (amount == Constants.OPEN_DELTA) { - return _getFullTakeAmount(currency).toUint128(); + return _getFullCredit(currency).toUint128(); } return amount; } diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index c2ae7201..0fde8d62 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -10,7 +10,6 @@ interface IPositionManager is INotifier { error NotApproved(address caller); error DeadlinePassed(); error IncorrectPositionConfigForTokenId(uint256 tokenId); - error ClearExceedsMaxAmount(Currency currency, int256 amount, uint256 maxAmount); /// @notice Batches many liquidity modification calls to pool manager /// @param payload is an encoding of actions, and parameters for those actions diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index e0005b2e..da5d015f 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -29,7 +29,7 @@ library Actions { uint256 constant TAKE_PORTION = 0x14; uint256 constant CLOSE_CURRENCY = 0x15; - uint256 constant CLEAR = 0x16; + uint256 constant CLEAR_OR_TAKE = 0x16; uint256 constant SWEEP = 0x17; // minting/burning 6909s to close deltas diff --git a/test/BaseActionsRouter.t.sol b/test/BaseActionsRouter.t.sol index cb8465c0..61020b2c 100644 --- a/test/BaseActionsRouter.t.sol +++ b/test/BaseActionsRouter.t.sol @@ -77,7 +77,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { function test_clear_suceeds() public { Plan memory plan = Planner.init(); for (uint256 i = 0; i < 10; i++) { - plan.add(Actions.CLEAR, ""); + plan.add(Actions.CLEAR_OR_TAKE, ""); } assertEq(router.clearCount(), 0); diff --git a/test/mocks/MockBaseActionsRouter.sol b/test/mocks/MockBaseActionsRouter.sol index a6a033ce..b7630297 100644 --- a/test/mocks/MockBaseActionsRouter.sol +++ b/test/mocks/MockBaseActionsRouter.sol @@ -34,7 +34,7 @@ contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { } else { if (action == Actions.SETTLE) _settle(params); else if (action == Actions.TAKE) _take(params); - else if (action == Actions.CLEAR) _clear(params); + else if (action == Actions.CLEAR_OR_TAKE) _clear(params); else if (action == Actions.MINT_6909) _mint6909(params); else if (action == Actions.BURN_6909) _burn6909(params); else revert UnsupportedAction(action); diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index c6d076f4..dc413612 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -20,6 +20,7 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {PositionManager} from "../../src/PositionManager.sol"; +import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {SlippageCheckLibrary} from "../../src/libraries/SlippageCheck.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; @@ -177,8 +178,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency0, 18 wei)); // alice is willing to forfeit 18 wei - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency1, 18 wei)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, 18 wei)); // alice is willing to forfeit 18 wei + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, 18 wei)); bytes memory calls = planner.encode(); vm.prank(alice); @@ -280,8 +281,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency0, 1 wei)); // alice is willing to forfeit 1 wei - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency1, 1 wei)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, 1 wei)); // alice is willing to forfeit 1 wei + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, 1 wei)); bytes memory calls = planner.encode(); vm.prank(alice); @@ -692,7 +693,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { assertEq(currency1.balanceOf(address(this)), balanceBefore1 - amount1); } - function test_increaseLiquidity_clearExceeds_revert() public { + /// @dev if clearing exceeds the max amount, the amount is taken instead + function test_increaseLiquidity_clearExceedsThenTake() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 1000e18, address(this), ZERO_BYTES); @@ -702,6 +704,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { // calculate the amount of liquidity to add, using half of the proceeds uint256 amountToReinvest = amountToDonate / 2; + uint256 amountToReclaim = amountToDonate / 2; // expect to reclaim the other half of the fee revenue uint256 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts( SQRT_PRICE_1_1, TickMath.getSqrtPriceAtTick(config.tickLower), @@ -710,28 +713,33 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { amountToReinvest ); + // set the max-forfeit to less than the amount we expect to claim + uint256 maxClear = amountToReclaim - 2 wei; + Plan memory planner = Planner.init(); planner.add( Actions.INCREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency0, amountToReinvest - 2 wei)); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency1, amountToReinvest - 2 wei)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, maxClear)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, maxClear)); bytes memory calls = planner.encode(); - // revert since we're forfeiting beyond the max tolerance - vm.expectRevert( - abi.encodeWithSelector( - IPositionManager.ClearExceedsMaxAmount.selector, - config.poolKey.currency0, - int256(amountToReinvest - 1 wei), // imprecision, PM expects us to collect half of the fees (minus 1 wei) - uint256(amountToReinvest - 2 wei) // the maximum amount we were willing to forfeit - ) - ); + uint256 balance0Before = currency0.balanceOf(address(this)); + uint256 balance1Before = currency1.balanceOf(address(this)); + + // expect to take the excess, as it exceeds the amount to clear lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + assertEq(uint128(delta.amount0()), amountToReclaim - 1 wei); // imprecision + assertEq(uint128(delta.amount1()), amountToReclaim - 1 wei); + + assertEq(currency0.balanceOf(address(this)), balance0Before + amountToReclaim - 1 wei); + assertEq(currency1.balanceOf(address(this)), balance1Before + amountToReclaim - 1 wei); } - /// @dev clearing a negative delta reverts in core with SafeCastOverflow + /// @dev clearing a negative delta reverts function test_increaseLiquidity_clearNegative_revert() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 1000e18, address(this), ZERO_BYTES); @@ -742,12 +750,12 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.INCREASE_LIQUIDITY, abi.encode(tokenId, config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency0, type(uint256).max)); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency1, type(uint256).max)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, type(uint256).max)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, type(uint256).max)); bytes memory calls = planner.encode(); - // revert since we're forfeiting beyond the max tolerance - vm.expectRevert(SafeCast.SafeCastOverflow.selector); + // revert since we're trying to clear a negative delta + vm.expectRevert(abi.encodeWithSelector(DeltaResolver.DeltaNotPositive.selector, currency0)); lpm.modifyLiquidities(calls, _deadline); } } diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 84da682b..d0c9517d 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -263,8 +263,8 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency0, halfTokensOwedAlice + 1 wei)); - planner.add(Actions.CLEAR, abi.encode(config.poolKey.currency1, halfTokensOwedAlice + 1 wei)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency0, halfTokensOwedAlice + 1 wei)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(config.poolKey.currency1, halfTokensOwedAlice + 1 wei)); bytes memory calls = planner.encode(); vm.prank(alice); diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index adb81fe0..7afae777 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -23,6 +23,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; +import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {SlippageCheckLibrary} from "../../src/libraries/SlippageCheck.sol"; import {BaseActionsRouter} from "../../src/base/BaseActionsRouter.sol"; @@ -218,7 +219,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(currency1.balanceOf(alice), balance1BeforeAlice); } - /// @dev test that clear does not work on minting + /// @dev clear cannot be used on mint (negative delta) function test_fuzz_mint_clear_revert(IPoolManager.ModifyLiquidityParams memory seedParams) public { IPoolManager.ModifyLiquidityParams memory params = createFuzzyLiquidityParams(key, seedParams, SQRT_PRICE_1_1); uint256 liquidityToAdd = @@ -232,11 +233,17 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Actions.MINT_POSITION, abi.encode(config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) ); - planner.add(Actions.CLEAR, abi.encode(key.currency0, type(uint256).max)); - planner.add(Actions.CLEAR, abi.encode(key.currency1, type(uint256).max)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency0, type(uint256).max)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency1, type(uint256).max)); bytes memory calls = planner.encode(); - vm.expectRevert(SafeCast.SafeCastOverflow.selector); + Currency negativeDeltaCurrency = currency0; + // because we're fuzzing the range, single-sided mint with currency1 means currency0Delta = 0 and currency1Delta < 0 + if (config.tickUpper <= 0) { + negativeDeltaCurrency = currency1; + } + + vm.expectRevert(abi.encodeWithSelector(DeltaResolver.DeltaNotPositive.selector, negativeDeltaCurrency)); lpm.modifyLiquidities(calls, _deadline); } @@ -507,8 +514,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { tokenId, config, decreaseLiquidityDelta, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES ) ); - planner.add(Actions.CLEAR, abi.encode(key.currency0, type(uint256).max)); - planner.add(Actions.CLEAR, abi.encode(key.currency1, type(uint256).max)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency0, type(uint256).max)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency1, type(uint256).max)); bytes memory calls = planner.encode(); lpm.modifyLiquidities(calls, _deadline); @@ -523,8 +530,10 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(currency1.balanceOfSelf(), balance1Before); } - /// @dev Clearing on decrease reverts if it exceeds user threshold - function test_fuzz_decreaseLiquidity_clearRevert(IPoolManager.ModifyLiquidityParams memory params) public { + /// @dev Clearing on decrease will take tokens if the amount exceeds the clear limit + function test_fuzz_decreaseLiquidity_clearExceedsThenTake(IPoolManager.ModifyLiquidityParams memory params) + public + { // use fuzzer for tick range params = createFuzzyLiquidityParams(key, params, SQRT_PRICE_1_1); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // require two-sided liquidity @@ -549,14 +558,22 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityToRemove, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - planner.add(Actions.CLEAR, abi.encode(key.currency0, amount0 - 1 wei)); - planner.add(Actions.CLEAR, abi.encode(key.currency1, amount1 - 1 wei)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency0, amount0 - 1 wei)); + planner.add(Actions.CLEAR_OR_TAKE, abi.encode(key.currency1, amount1 - 1 wei)); bytes memory calls = planner.encode(); - vm.expectRevert( - abi.encodeWithSelector(IPositionManager.ClearExceedsMaxAmount.selector, currency0, amount0, amount0 - 1 wei) - ); + uint256 balance0Before = currency0.balanceOfSelf(); + uint256 balance1Before = currency1.balanceOfSelf(); + + // expect to take the tokens lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + // amount exceeded clear limit, so we should have the tokens + assertEq(uint128(delta.amount0()), amount0); + assertEq(uint128(delta.amount1()), amount1); + assertEq(currency0.balanceOfSelf(), balance0Before + amount0); + assertEq(currency1.balanceOfSelf(), balance1Before + amount1); } function test_decreaseLiquidity_collectFees( From 6fe5428ce3ba5cc873991d9ad3734175ce0da3cd Mon Sep 17 00:00:00 2001 From: diana Date: Fri, 2 Aug 2024 20:59:09 -0400 Subject: [PATCH 18/52] TAKE_PAIR (#254) * TAKE_PAIR * getfullcredit * take pair recipient * add recipient test --- ...nager_burn_nonEmpty_native_withClose.snap} | 0 ...ger_burn_nonEmpty_native_withTakePair.snap | 1 + ...itionManager_burn_nonEmpty_withClose.snap} | 0 ...ionManager_burn_nonEmpty_withTakePair.snap | 1 + ...=> PositionManager_collect_withClose.snap} | 0 .../PositionManager_collect_withTakePair.snap | 1 + ...nManager_decreaseLiquidity_withClose.snap} | 0 ...anager_decreaseLiquidity_withTakePair.snap | 1 + ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- src/PositionManager.sol | 9 + src/libraries/Actions.sol | 11 +- src/libraries/CalldataDecoder.sol | 13 + test/libraries/CalldataDecoder.t.sol | 12 + test/mocks/MockCalldataDecoder.sol | 8 + test/position-managers/NativeToken.t.sol | 284 +++++++++++++++++- .../PositionManager.gas.t.sol | 75 ++++- test/shared/Planner.sol | 9 + 19 files changed, 411 insertions(+), 20 deletions(-) rename .forge-snapshots/{PositionManager_burn_nonEmpty_native.snap => PositionManager_burn_nonEmpty_native_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap rename .forge-snapshots/{PositionManager_burn_nonEmpty.snap => PositionManager_burn_nonEmpty_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap rename .forge-snapshots/{PositionManager_collect.snap => PositionManager_collect_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_collect_withTakePair.snap rename .forge-snapshots/{PositionManager_decreaseLiquidity.snap => PositionManager_decreaseLiquidity_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_burn_nonEmpty_native.snap rename to .forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap new file mode 100644 index 00000000..08759fbb --- /dev/null +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -0,0 +1 @@ +122739 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_burn_nonEmpty.snap rename to .forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap new file mode 100644 index 00000000..928b3a87 --- /dev/null +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -0,0 +1 @@ +129817 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect.snap b/.forge-snapshots/PositionManager_collect_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_collect.snap rename to .forge-snapshots/PositionManager_collect_withClose.snap diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap new file mode 100644 index 00000000..e55db207 --- /dev/null +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -0,0 +1 @@ +149846 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_decreaseLiquidity.snap rename to .forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap new file mode 100644 index 00000000..aa89754c --- /dev/null +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -0,0 +1 @@ +115389 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 3b5a65d2..db8a171a 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345325 \ No newline at end of file +345348 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 1813daec..7a330a93 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344866 \ No newline at end of file +344889 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 486ee25a..9634d8a1 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -371125 \ No newline at end of file +371171 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index fabc4d12..ebf326e6 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -175,6 +175,9 @@ contract PositionManager is } else if (action == Actions.SETTLE_PAIR) { (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); _settlePair(currency0, currency1); + } else if (action == Actions.TAKE_PAIR) { + (Currency currency0, Currency currency1, address to) = params.decodeCurrencyPairAndAddress(); + _takePair(currency0, currency1, to); } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); _sweep(currency, _mapRecipient(to)); @@ -271,6 +274,12 @@ contract PositionManager is _settle(currency1, caller, _getFullDebt(currency1)); } + function _takePair(Currency currency0, Currency currency1, address to) internal { + address recipient = _mapRecipient(to); + _take(currency0, recipient, _getFullCredit(currency0)); + _take(currency1, recipient, _getFullCredit(currency1)); + } + /// @dev this is overloaded with ERC721Permit._burn function _burn( uint256 tokenId, diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index da5d015f..542c935a 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -27,12 +27,13 @@ library Actions { uint256 constant TAKE = 0x12; uint256 constant TAKE_ALL = 0x13; uint256 constant TAKE_PORTION = 0x14; + uint256 constant TAKE_PAIR = 0x15; - uint256 constant CLOSE_CURRENCY = 0x15; - uint256 constant CLEAR_OR_TAKE = 0x16; - uint256 constant SWEEP = 0x17; + uint256 constant CLOSE_CURRENCY = 0x16; + uint256 constant CLEAR_OR_TAKE = 0x17; + uint256 constant SWEEP = 0x18; // minting/burning 6909s to close deltas - uint256 constant MINT_6909 = 0x18; - uint256 constant BURN_6909 = 0x19; + uint256 constant MINT_6909 = 0x19; + uint256 constant BURN_6909 = 0x20; } diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index 0a1dd24a..197987d3 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -174,6 +174,19 @@ library CalldataDecoder { } } + /// @dev equivalent to: abi.decode(params, (Currency, Currency, address)) in calldata + function decodeCurrencyPairAndAddress(bytes calldata params) + internal + pure + returns (Currency currency0, Currency currency1, address _address) + { + assembly ("memory-safe") { + currency0 := calldataload(params.offset) + currency1 := calldataload(add(params.offset, 0x20)) + _address := calldataload(add(params.offset, 0x40)) + } + } + /// @dev equivalent to: abi.decode(params, (Currency, address)) in calldata function decodeCurrencyAndAddress(bytes calldata params) internal diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index b71d2667..9ce41fb0 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -160,6 +160,18 @@ contract CalldataDecoderTest is Test { assertEq(Currency.unwrap(currency1), Currency.unwrap(_currency1)); } + function test_fuzz_decodeCurrencyPairAndAddress(Currency _currency0, Currency _currency1, address __address) + public + view + { + bytes memory params = abi.encode(_currency0, _currency1, __address); + (Currency currency0, Currency currency1, address _address) = decoder.decodeCurrencyPairAndAddress(params); + + assertEq(Currency.unwrap(currency0), Currency.unwrap(_currency0)); + assertEq(Currency.unwrap(currency1), Currency.unwrap(_currency1)); + assertEq(_address, __address); + } + function test_fuzz_decodeCurrencyAndUint256(Currency _currency, uint256 _amount) public view { bytes memory params = abi.encode(_currency, _amount); (Currency currency, uint256 amount) = decoder.decodeCurrencyAndUint256(params); diff --git a/test/mocks/MockCalldataDecoder.sol b/test/mocks/MockCalldataDecoder.sol index 35825228..b63654e9 100644 --- a/test/mocks/MockCalldataDecoder.sol +++ b/test/mocks/MockCalldataDecoder.sol @@ -102,6 +102,14 @@ contract MockCalldataDecoder { return params.decodeCurrencyPair(); } + function decodeCurrencyPairAndAddress(bytes calldata params) + external + pure + returns (Currency currency0, Currency currency1, address _address) + { + return params.decodeCurrencyPairAndAddress(); + } + function decodeCurrencyAndUint256(bytes calldata params) external pure returns (Currency currency, uint256 _uint) { return params.decodeCurrencyAndUint256(); } diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index 74e89af9..2bad5254 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -198,7 +198,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); } - function test_fuzz_burn_native_emptyPosition(IPoolManager.ModifyLiquidityParams memory params) public { + function test_fuzz_burn_native_emptyPosition_withClose(IPoolManager.ModifyLiquidityParams memory params) public { uint256 balance0Start = address(this).balance; uint256 balance1Start = currency1.balanceOfSelf(); @@ -251,7 +251,69 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertApproxEqAbs(currency1.balanceOfSelf(), balance1Start, 1 wei); } - function test_fuzz_burn_native_nonEmptyPosition(IPoolManager.ModifyLiquidityParams memory params) public { + function test_fuzz_burn_native_emptyPosition_withTakePair(IPoolManager.ModifyLiquidityParams memory params) + public + { + uint256 balance0Start = address(this).balance; + uint256 balance1Start = currency1.balanceOfSelf(); + + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + uint256 liquidityToAdd = + params.liquidityDelta < 0 ? uint256(-params.liquidityDelta) : uint256(params.liquidityDelta); + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, uint256(params.liquidityDelta)); + + // burn liquidity + uint256 balance0BeforeBurn = currency0.balanceOfSelf(); + uint256 balance1BeforeBurn = currency1.balanceOfSelf(); + + decreaseLiquidity(tokenId, config, liquidity, ZERO_BYTES); + BalanceDelta deltaDecrease = getLastDelta(); + + uint256 numDeltas = hook.numberDeltasReturned(); + Plan memory planner = Planner.init(); + planner.add( + Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); + lpm.modifyLiquidities(calls, _deadline); + // No decrease/modifyLiq call will actually happen on the call to burn so the deltas array will be the same length. + assertEq(numDeltas, hook.numberDeltasReturned()); + + (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, 0); + + // TODO: slightly off by 1 bip (0.0001%) + assertApproxEqRel( + currency0.balanceOfSelf(), balance0BeforeBurn + uint256(uint128(deltaDecrease.amount0())), 0.0001e18 + ); + assertApproxEqRel( + currency1.balanceOfSelf(), balance1BeforeBurn + uint256(uint128(deltaDecrease.amount1())), 0.0001e18 + ); + + // OZ 721 will revert if the token does not exist + vm.expectRevert(); + lpm.ownerOf(1); + + // no tokens were lost, TODO: fuzzer showing off by 1 sometimes + assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei); + assertApproxEqAbs(address(this).balance, balance0Start, 1 wei); + assertApproxEqAbs(currency1.balanceOfSelf(), balance1Start, 1 wei); + } + + function test_fuzz_burn_native_nonEmptyPosition_withClose(IPoolManager.ModifyLiquidityParams memory params) + public + { uint256 balance0Start = address(this).balance; uint256 balance1Start = currency1.balanceOfSelf(); @@ -299,6 +361,61 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertApproxEqAbs(currency1.balanceOfSelf(), balance1Start, 1 wei); } + function test_fuzz_burn_native_nonEmptyPosition_withTakePair(IPoolManager.ModifyLiquidityParams memory params) + public + { + uint256 balance0Start = address(this).balance; + uint256 balance1Start = currency1.balanceOfSelf(); + + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + uint256 liquidityToAdd = + params.liquidityDelta < 0 ? uint256(-params.liquidityDelta) : uint256(params.liquidityDelta); + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, uint256(params.liquidityDelta)); + + // burn liquidity + uint256 balance0BeforeBurn = currency0.balanceOfSelf(); + uint256 balance1BeforeBurn = currency1.balanceOfSelf(); + + Plan memory planner = Planner.init(); + planner.add( + Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta deltaBurn = getLastDelta(); + + (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, 0); + + // TODO: slightly off by 1 bip (0.0001%) + assertApproxEqRel( + currency0.balanceOfSelf(), balance0BeforeBurn + uint256(uint128(deltaBurn.amount0())), 0.0001e18 + ); + assertApproxEqRel( + currency1.balanceOfSelf(), balance1BeforeBurn + uint256(uint128(deltaBurn.amount1())), 0.0001e18 + ); + + // OZ 721 will revert if the token does not exist + vm.expectRevert(); + lpm.ownerOf(1); + + // no tokens were lost, TODO: fuzzer showing off by 1 sometimes + assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei); + assertApproxEqAbs(address(this).balance, balance0Start, 1 wei); + assertApproxEqAbs(currency1.balanceOfSelf(), balance1Start, 1 wei); + } + function test_fuzz_increaseLiquidity_native(IPoolManager.ModifyLiquidityParams memory params) public { // fuzz for the range params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); @@ -446,7 +563,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); } - function test_fuzz_decreaseLiquidity_native( + function test_fuzz_decreaseLiquidity_native_withClose( IPoolManager.ModifyLiquidityParams memory params, uint256 decreaseLiquidityDelta ) public { @@ -485,7 +602,54 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(currency1.balanceOfSelf() - balance1Before, uint128(delta.amount1())); } - function test_fuzz_collect_native(IPoolManager.ModifyLiquidityParams memory params) public { + function test_fuzz_decreaseLiquidity_native_withTakePair( + IPoolManager.ModifyLiquidityParams memory params, + uint256 decreaseLiquidityDelta + ) public { + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + decreaseLiquidityDelta = bound(decreaseLiquidityDelta, 1, uint256(params.liquidityDelta)); + + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + // mint the position with native token liquidity + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = currency1.balanceOfSelf(); + + // decrease liquidity and receive native tokens + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(params.tickLower), + TickMath.getSqrtPriceAtTick(params.tickUpper), + uint128(decreaseLiquidityDelta) + ); + Plan memory planner = Planner.init(); + planner.add( + Actions.DECREASE_LIQUIDITY, + abi.encode( + tokenId, config, decreaseLiquidityDelta, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES + ) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); + + // verify native token balances changed as expected + assertApproxEqAbs(currency0.balanceOfSelf() - balance0Before, amount0, 1 wei); + assertEq(currency0.balanceOfSelf() - balance0Before, uint128(delta.amount0())); + assertEq(currency1.balanceOfSelf() - balance1Before, uint128(delta.amount1())); + } + + function test_fuzz_collect_native_withClose(IPoolManager.ModifyLiquidityParams memory params) public { params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity @@ -511,6 +675,118 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(currency1.balanceOfSelf() - balance1Before, uint128(delta.amount1())); } + function test_fuzz_collect_native_withTakePair(IPoolManager.ModifyLiquidityParams memory params) public { + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + // mint the position with native token liquidity + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + + // donate to generate fee revenue + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate{value: 1e18}(nativeKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = currency1.balanceOfSelf(); + Plan memory planner = Planner.init(); + planner.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + assertApproxEqAbs(currency0.balanceOfSelf() - balance0Before, feeRevenue0, 1 wei); // TODO: fuzzer off by 1 wei + assertEq(currency0.balanceOfSelf() - balance0Before, uint128(delta.amount0())); + assertEq(currency1.balanceOfSelf() - balance1Before, uint128(delta.amount1())); + } + + function test_fuzz_collect_native_withTakePair_addressRecipient(IPoolManager.ModifyLiquidityParams memory params) + public + { + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + // mint the position with native token liquidity + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + + // donate to generate fee revenue + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate{value: 1e18}(nativeKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = currency1.balanceOfSelf(); + + Plan memory planner = Planner.init(); + planner.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + + address alice = address(0xABCD); + + uint256 aliceBalance0Before = currency0.balanceOf(alice); + uint256 aliceBalance1Before = currency1.balanceOf(alice); + + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, alice); + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + assertEq(currency0.balanceOfSelf() - balance0Before, 0); + assertEq(currency1.balanceOfSelf() - balance1Before, 0); + + assertApproxEqAbs(currency0.balanceOf(alice) - aliceBalance0Before, feeRevenue0, 1 wei); // TODO: fuzzer off by 1 wei + assertEq(currency0.balanceOf(alice) - aliceBalance0Before, uint128(delta.amount0())); + assertEq(currency1.balanceOf(alice) - aliceBalance1Before, uint128(delta.amount1())); + } + + function test_fuzz_collect_native_withTakePair_msgSenderRecipient(IPoolManager.ModifyLiquidityParams memory params) + public + { + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + // mint the position with native token liquidity + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + + // donate to generate fee revenue + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate{value: 1e18}(nativeKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = currency1.balanceOfSelf(); + + Plan memory planner = Planner.init(); + planner.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, Constants.MSG_SENDER); + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + assertApproxEqAbs(currency0.balanceOfSelf() - balance0Before, feeRevenue0, 1 wei); // TODO: fuzzer off by 1 wei + assertEq(currency0.balanceOfSelf() - balance0Before, uint128(delta.amount0())); + assertEq(currency1.balanceOfSelf() - balance1Before, uint128(delta.amount1())); + } + // this test fails unless subscribe is payable function test_multicall_mint_subscribe_native() public { uint256 tokenId = lpm.nextTokenId(); diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index d0c9517d..5c90192f 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -318,7 +318,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { snapLastCall("PositionManager_increase_autocompoundExcessFeesCredit"); } - function test_gas_decreaseLiquidity() public { + function test_gas_decreaseLiquidity_withClose() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); @@ -329,7 +329,21 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_decreaseLiquidity"); + snapLastCall("PositionManager_decreaseLiquidity_withClose"); + } + + function test_gas_decreaseLiquidity_withTakePair() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + + Plan memory planner = Planner.init().add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_decreaseLiquidity_withTakePair"); } function test_gas_multicall_initialize_mint() public { @@ -358,7 +372,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { snapLastCall("PositionManager_multicall_initialize_mint"); } - function test_gas_collect() public { + function test_gas_collect_withClose() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); @@ -373,7 +387,25 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_collect"); + snapLastCall("PositionManager_collect_withClose"); + } + + function test_gas_collect_withTakePair() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + + // donate to create fee revenue + donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); + + // Collect by calling decrease with 0. + Plan memory planner = Planner.init().add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_collect_withTakePair"); } // same-range gas tests @@ -433,7 +465,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { snapLastCall("PositionManager_collect_sameRange"); } - function test_gas_burn_nonEmptyPosition() public { + function test_gas_burn_nonEmptyPosition_withClose() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); @@ -443,7 +475,20 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_burn_nonEmpty"); + snapLastCall("PositionManager_burn_nonEmpty_withClose"); + } + + function test_gas_burn_nonEmptyPosition_withTakePair() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + + Plan memory planner = Planner.init().add( + Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(this)); + + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_burn_nonEmpty_withTakePair"); } function test_gas_burnEmpty() public { @@ -594,7 +639,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { snapLastCall("PositionManager_collect_native"); } - function test_gas_burn_nonEmptyPosition_native() public { + function test_gas_burn_nonEmptyPosition_native_withClose() public { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); @@ -605,7 +650,21 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_burn_nonEmpty_native"); + snapLastCall("PositionManager_burn_nonEmpty_native_withClose"); + } + + function test_gas_burn_nonEmptyPosition_native_withTakePair() public { + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + + Plan memory planner = Planner.init().add( + Actions.BURN_POSITION, + abi.encode(tokenId, configNative, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(configNative.poolKey, address(this)); + + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_burn_nonEmpty_native_withTakePair"); } function test_gas_burnEmpty_native() public { diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index ffcd10fd..8a0ffd8a 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -56,6 +56,15 @@ library Planner { return plan.encode(); } + function finalizeModifyLiquidityWithTakePair(Plan memory plan, PoolKey memory poolKey, address takeRecipient) + internal + pure + returns (bytes memory) + { + plan.add(Actions.TAKE_PAIR, abi.encode(poolKey.currency0, poolKey.currency1, takeRecipient)); + return plan.encode(); + } + function encode(Plan memory plan) internal pure returns (bytes memory) { return abi.encode(plan.actions, plan.params); } From 1f28ac2018fe92096d324317d0587d9332e33165 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Fri, 2 Aug 2024 21:06:49 -0400 Subject: [PATCH 19/52] ERC721Permit (#210) * allow for nonsigners to call permit * forge fmt * test permit with multicall * make DOMAIN_SEPARATOR immutable * avoid chain fork replays * misc test cleanup * custom errors * move magic hex to a constant * unpayable permit * use OZ EIP712 * separate out UnorderedNonce into a reusable contract * move token URI to posm * add back in payable permit * fix cherry picked commits * remove public digest getter * replace range with config naming * deprecate old test: requiring permission to increase liq * pr feedback * borrow pertmi2 nonce tests for UnorderedNonce * dedicated permit and approve testing for ERC721Permit * pr feedback: operator should be broadcaster of permit calls * reorganize permit hashing and verification * refactor ERC721Permit signature verification with generic signature calldata handler * remove deprecated library * fix imports * formatting * pr feedback * optimize nonce bit flipping * discard public PERMIT_TYPEHASH * renaming * library-ify bit flipping * yall crazy for sending through the ringer * nits --- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- ...sitionManager_increaseLiquidity_erc20.snap | 1 + ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .forge-snapshots/PositionManager_mint.snap | 1 + .../PositionManager_mint_native.snap | 2 +- .../PositionManager_mint_nativeWithSweep.snap | 1 + ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/PositionManager_permit.snap | 1 + ...PositionManager_permit_secondPosition.snap | 1 + .../PositionManager_permit_twice.snap | 1 + src/PositionManager.sol | 5 + src/base/ERC721Permit.sol | 68 +++- src/base/UnorderedNonce.sol | 24 ++ src/interfaces/IERC721Permit.sol | 14 +- src/libraries/ChainId.sol | 13 - src/libraries/ERC721PermitHash.sol | 11 + test/ERC721Permit.t.sol | 293 ++++++++++++++++++ test/UnorderedNonce.t.sol | 110 +++++++ test/mocks/MockERC721Permit.sol | 19 ++ test/mocks/MockUnorderedNonce.sol | 15 + test/position-managers/Permit.t.sol | 249 +++++++++++++++ .../PositionManager.gas.t.sol | 84 +++++ .../PositionManager.multicall.t.sol | 56 +++- test/shared/PosmTestSetup.sol | 25 ++ 53 files changed, 985 insertions(+), 73 deletions(-) create mode 100644 .forge-snapshots/PositionManager_increaseLiquidity_erc20.snap create mode 100644 .forge-snapshots/PositionManager_mint.snap create mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep.snap create mode 100644 .forge-snapshots/PositionManager_permit.snap create mode 100644 .forge-snapshots/PositionManager_permit_secondPosition.snap create mode 100644 .forge-snapshots/PositionManager_permit_twice.snap create mode 100644 src/base/UnorderedNonce.sol delete mode 100644 src/libraries/ChainId.sol create mode 100644 src/libraries/ERC721PermitHash.sol create mode 100644 test/ERC721Permit.t.sol create mode 100644 test/UnorderedNonce.t.sol create mode 100644 test/mocks/MockERC721Permit.sol create mode 100644 test/mocks/MockUnorderedNonce.sol create mode 100644 test/position-managers/Permit.t.sol diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index b4f9b770..a3f146ac 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47168 \ No newline at end of file +47186 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 58f0bac9..a04aaf5c 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46986 \ No newline at end of file +47004 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 0621747c..5238cf5f 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123040 \ No newline at end of file +123058 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 08759fbb..d5955ea5 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122739 \ No newline at end of file +122756 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 274fdc43..49379c42 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130119 \ No newline at end of file +130136 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 928b3a87..3e3dc429 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129817 \ No newline at end of file +129835 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index edac16c2..089f3305 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141387 \ No newline at end of file +141409 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index d68e06f1..1d6bec04 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150235 \ No newline at end of file +150257 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index d68e06f1..1d6bec04 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150235 \ No newline at end of file +150257 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index e55db207..d4d3c6bf 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149846 \ No newline at end of file +149868 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index c3def0ba..f183904d 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108584 \ No newline at end of file +108602 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index ff261d83..5bc59fda 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115778 \ No newline at end of file +115800 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index aa89754c..5927a619 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115389 \ No newline at end of file +115411 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index bcbd95de..f2edca7c 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134178 \ No newline at end of file +134196 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index be94d9c7..19fbfe9c 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126917 \ No newline at end of file +126935 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index d53ad25c..47317e7b 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128494 \ No newline at end of file +128516 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap new file mode 100644 index 00000000..076b23da --- /dev/null +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap @@ -0,0 +1 @@ +152144 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 7be08370..f7f7fed6 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152341 \ No newline at end of file +152363 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 10bef205..473fcfde 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151582 \ No newline at end of file +151604 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 3bdd6761..7d3f9d6c 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134141 \ No newline at end of file +134163 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index e09bd90f..2a9f67c8 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130306 \ No newline at end of file +130328 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 4aed0cee..d1db687b 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171000 \ No newline at end of file +171022 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 16f17b7f..9f7f5ce9 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140956 \ No newline at end of file +141002 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint.snap new file mode 100644 index 00000000..964f844e --- /dev/null +++ b/.forge-snapshots/PositionManager_mint.snap @@ -0,0 +1 @@ +372007 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 3abc1720..99535ce2 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336819 \ No newline at end of file +336841 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap new file mode 100644 index 00000000..d691d43c --- /dev/null +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap @@ -0,0 +1 @@ +345190 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index db8a171a..2a17e5f2 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345348 \ No newline at end of file +345370 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 7a330a93..ad8a0dd7 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344889 \ No newline at end of file +344911 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index d98d938b..00e13762 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314801 \ No newline at end of file +314823 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index cc26d78b..bf2a5e88 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315443 \ No newline at end of file +315465 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index b90f5486..de4b5cb3 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241025 \ No newline at end of file +241047 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 9634d8a1..43e466e6 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -371171 \ No newline at end of file +371193 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index d8288af1..0ed51014 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320819 \ No newline at end of file +320841 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 376974e4..f9a92b51 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372119 \ No newline at end of file +372141 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index fe27d17c..7bbeb8cb 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371498 \ No newline at end of file +371520 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 3a2b15c8..2f927ade 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416560 \ No newline at end of file +416516 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap new file mode 100644 index 00000000..b2faa7cc --- /dev/null +++ b/.forge-snapshots/PositionManager_permit.snap @@ -0,0 +1 @@ +79585 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap new file mode 100644 index 00000000..60f10b03 --- /dev/null +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -0,0 +1 @@ +62497 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap new file mode 100644 index 00000000..3df69310 --- /dev/null +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -0,0 +1 @@ +45397 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index ebf326e6..88873384 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -70,6 +70,11 @@ contract PositionManager is _; } + // TODO: to be implemented after audits + function tokenURI(uint256) public pure override returns (string memory) { + return "https://example.com"; + } + /// @notice Reverts if the caller is not the owner or approved for the ERC721 token /// @param caller The address of the caller /// @param tokenId the unique identifier of the ERC721 token diff --git a/src/base/ERC721Permit.sol b/src/base/ERC721Permit.sol index 34597ee6..7a4acc4d 100644 --- a/src/base/ERC721Permit.sol +++ b/src/base/ERC721Permit.sol @@ -1,20 +1,70 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.24; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {ERC721} from "solmate/src/tokens/ERC721.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ERC721PermitHashLibrary} from "../libraries/ERC721PermitHash.sol"; +import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; -/// @notice An ERC721 contract that supports permit. -/// TODO: Support permit. -contract ERC721Permit is ERC721 { - constructor(string memory name_, string memory symbol_, string memory version_) ERC721(name_, symbol_) {} +import {IERC721Permit} from "../interfaces/IERC721Permit.sol"; +import {UnorderedNonce} from "./UnorderedNonce.sol"; + +/// @title ERC721 with permit +/// @notice Nonfungible tokens that support an approve via signature, i.e. permit +abstract contract ERC721Permit is ERC721, IERC721Permit, EIP712, UnorderedNonce { + using SignatureVerification for bytes; + + /// @notice Computes the nameHash and versionHash + constructor(string memory name_, string memory symbol_, string memory version_) + ERC721(name_, symbol_) + EIP712(name_, version_) + {} + + /// @inheritdoc IERC721Permit + function DOMAIN_SEPARATOR() external view returns (bytes32) { + return _domainSeparatorV4(); + } + + /// @inheritdoc IERC721Permit + function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) + external + payable + { + if (block.timestamp > deadline) revert DeadlineExpired(); + + address owner = ownerOf(tokenId); + if (spender == owner) revert NoSelfPermit(); + + bytes32 hash = ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline); + signature.verify(_hashTypedDataV4(hash), owner); + + _useUnorderedNonce(owner, nonce); + _approve(owner, spender, tokenId); + } + + /// @notice Change or reaffirm the approved address for an NFT + /// @dev override Solmate's ERC721 approve so approve() and permit() share the _approve method + /// The zero address indicates there is no approved address + /// Throws error unless `msg.sender` is the current NFT owner, + /// or an authorized operator of the current owner. + /// @param spender The new approved NFT controller + /// @param id The tokenId of the NFT to approve + function approve(address spender, uint256 id) public override { + address owner = _ownerOf[id]; + + if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) revert Unauthorized(); + + _approve(owner, spender, id); + } + + function _approve(address owner, address spender, uint256 id) internal { + getApproved[id] = spender; + emit Approval(owner, spender, id); + } function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { return spender == ownerOf(tokenId) || getApproved[tokenId] == spender || isApprovedForAll[ownerOf(tokenId)][spender]; } - - // TODO: Use PositionDescriptor. - function tokenURI(uint256 id) public pure override returns (string memory) { - return string(abi.encode(id)); - } } diff --git a/src/base/UnorderedNonce.sol b/src/base/UnorderedNonce.sol new file mode 100644 index 00000000..a1efe769 --- /dev/null +++ b/src/base/UnorderedNonce.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.24; + +/// @title Unordered Nonce +/// @notice Contract state and methods for using unordered nonces in signatures +contract UnorderedNonce { + error NonceAlreadyUsed(); + + /// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap + /// @dev word is at most type(uint248).max + mapping(address owner => mapping(uint256 word => uint256 bitmap)) public nonces; + + /// @notice Consume a nonce, reverting if its already been used + /// @param owner address, the owner/signer of the nonce + /// @param nonce uint256, the nonce to consume. the top 248 bits are the word, the bottom 8 bits indicate the bit position + function _useUnorderedNonce(address owner, uint256 nonce) internal { + uint256 wordPos = nonce >> 8; + uint256 bitPos = uint8(nonce); + + uint256 bit = 1 << bitPos; + uint256 flipped = nonces[owner][wordPos] ^= bit; + if (flipped & bit == 0) revert NonceAlreadyUsed(); + } +} diff --git a/src/interfaces/IERC721Permit.sol b/src/interfaces/IERC721Permit.sol index 875a5568..646c1af0 100644 --- a/src/interfaces/IERC721Permit.sol +++ b/src/interfaces/IERC721Permit.sol @@ -4,11 +4,9 @@ pragma solidity >=0.7.5; /// @title ERC721 with permit /// @notice Extension to ERC721 that includes a permit function for signature based approvals interface IERC721Permit { - error NonceAlreadyUsed(); - - /// @notice The permit typehash used in the permit signature - /// @return The typehash for the permit - function PERMIT_TYPEHASH() external pure returns (bytes32); + error DeadlineExpired(); + error NoSelfPermit(); + error Unauthorized(); /// @notice The domain separator used in the permit signature /// @return The domain seperator used in encoding of permit signature @@ -18,11 +16,9 @@ interface IERC721Permit { /// @param spender The account that is being approved /// @param tokenId The ID of the token that is being approved for spending /// @param deadline The deadline timestamp by which the call must be mined for the approve to work - /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` - /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` - /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v) /// @dev payable so it can be multicalled with NATIVE related actions - function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, uint8 v, bytes32 r, bytes32 s) + function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) external payable; } diff --git a/src/libraries/ChainId.sol b/src/libraries/ChainId.sol deleted file mode 100644 index 7e67989c..00000000 --- a/src/libraries/ChainId.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.7.0; - -/// @title Function for getting the current chain ID -library ChainId { - /// @dev Gets the current chain ID - /// @return chainId The current chain ID - function get() internal view returns (uint256 chainId) { - assembly { - chainId := chainid() - } - } -} diff --git a/src/libraries/ERC721PermitHash.sol b/src/libraries/ERC721PermitHash.sol new file mode 100644 index 00000000..cf4c74f0 --- /dev/null +++ b/src/libraries/ERC721PermitHash.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.24; + +library ERC721PermitHashLibrary { + /// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); + bytes32 constant PERMIT_TYPEHASH = 0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad; + + function hash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) internal pure returns (bytes32) { + return keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + } +} diff --git a/test/ERC721Permit.t.sol b/test/ERC721Permit.t.sol new file mode 100644 index 00000000..d7126223 --- /dev/null +++ b/test/ERC721Permit.t.sol @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; + +import {ERC721PermitHashLibrary} from "../src/libraries/ERC721PermitHash.sol"; +import {MockERC721Permit} from "./mocks/MockERC721Permit.sol"; +import {IERC721Permit} from "../src/interfaces/IERC721Permit.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; +import {UnorderedNonce} from "../src/base/UnorderedNonce.sol"; + +contract ERC721PermitTest is Test { + MockERC721Permit erc721Permit; + address alice; + uint256 alicePK; + address bob; + uint256 bobPK; + + string constant name = "Mock ERC721Permit"; + string constant symbol = "MOCK721"; + string constant version = "1"; + + function setUp() public { + (alice, alicePK) = makeAddrAndKey("ALICE"); + (bob, bobPK) = makeAddrAndKey("BOB"); + + erc721Permit = new MockERC721Permit(name, symbol, version); + } + + // --- Test the overriden approval --- + function test_fuzz_approve(address spender) public { + uint256 tokenId = erc721Permit.mint(); + assertEq(erc721Permit.getApproved(tokenId), address(0)); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.Approval(address(this), spender, tokenId); + erc721Permit.approve(spender, tokenId); + assertEq(erc721Permit.getApproved(tokenId), spender); + } + + function test_fuzz_approvedOperator_reapproves(address operator, address spender) public { + uint256 tokenId = erc721Permit.mint(); + erc721Permit.setApprovalForAll(operator, true); + assertEq(erc721Permit.isApprovedForAll(address(this), operator), true); + + assertEq(erc721Permit.getApproved(tokenId), address(0)); + vm.startPrank(operator); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.Approval(address(this), spender, tokenId); + erc721Permit.approve(spender, tokenId); + vm.stopPrank(); + assertEq(erc721Permit.getApproved(tokenId), spender); + } + + function test_fuzz_approve_unauthorizedRevert(address caller) public { + uint256 tokenId = erc721Permit.mint(); + vm.prank(caller); + if (caller != address(this)) vm.expectRevert(IERC721Permit.Unauthorized.selector); + erc721Permit.approve(address(this), tokenId); + } + + // --- Test the signature-based approvals (permit) --- + function test_permitTypeHash() public view { + assertEq( + ERC721PermitHashLibrary.PERMIT_TYPEHASH, + keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)") + ); + } + + function test_domainSeparator() public view { + assertEq( + IERC721Permit(address(erc721Permit)).DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(name)), + keccak256(bytes(version)), + block.chainid, + address(erc721Permit) + ) + ) + ); + } + + /// @dev spender uses alice's signature to approve itself + function test_fuzz_erc721permit_spender(address spender) public { + vm.assume(spender != alice); + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + bytes32 digest = _getDigest(spender, tokenId, nonce, block.timestamp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, spender), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // -- Permit -- // + vm.startPrank(spender); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.Approval(alice, spender, tokenId); + erc721Permit.permit(spender, tokenId, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals set + assertEq(erc721Permit.getApproved(tokenId), spender); + assertEq(erc721Permit.isApprovedForAll(alice, spender), false); + + // nonce was spent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 2); // 2 = 0010 + } + + /// @dev a third party caller uses alice's signature to give `spender` the approval + function test_fuzz_erc721permit_caller(address caller, address spender) public { + vm.assume(spender != alice); + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + bytes32 digest = _getDigest(spender, tokenId, nonce, block.timestamp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, spender), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // -- Permit by third-party caller -- // + vm.startPrank(caller); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.Approval(alice, spender, tokenId); + erc721Permit.permit(spender, tokenId, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals set + assertEq(erc721Permit.getApproved(tokenId), spender); + assertEq(erc721Permit.isApprovedForAll(alice, spender), false); + + // nonce was spent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 2); // 2 = 0010 + } + + function test_fuzz_erc721permit_nonceAlreadyUsed() public { + vm.prank(alice); + uint256 tokenIdAlice = erc721Permit.mint(); + + // alice gives bob operator permissions + uint256 nonce = 1; + _permit(alicePK, tokenIdAlice, bob, nonce); + + // alice cannot reuse the nonce + bytes32 digest = _getDigest(bob, tokenIdAlice, nonce, block.timestamp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.startPrank(alice); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permit(bob, tokenIdAlice, block.timestamp, nonce, signature); + vm.stopPrank(); + } + + function test_fuzz_erc721permit_nonceAlreadyUsed_twoPositions() public { + vm.prank(alice); + uint256 tokenIdAlice = erc721Permit.mint(); + + vm.prank(alice); + uint256 tokenIdAlice2 = erc721Permit.mint(); + + // alice gives bob operator permissions for first token + uint256 nonce = 1; + _permit(alicePK, tokenIdAlice, bob, nonce); + + // alice cannot reuse the nonce for the second token + bytes32 digest = _getDigest(bob, tokenIdAlice2, nonce, block.timestamp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.startPrank(alice); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permit(bob, tokenIdAlice2, block.timestamp, nonce, signature); + vm.stopPrank(); + } + + function test_fuzz_erc721permit_unauthorized() public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + bytes32 digest = _getDigest(bob, tokenId, nonce, block.timestamp); + + // bob attempts signing an approval for himself + (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // approvals unset + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + erc721Permit.permit(bob, tokenId, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + function test_fuzz_erc721Permit_deadlineExpired(address spender) public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + uint256 deadline = block.timestamp; + bytes32 digest = _getDigest(spender, tokenId, nonce, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, spender), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // fast forward to exceed deadline + skip(1); + + // -- Permit but deadline expired -- // + vm.startPrank(spender); + vm.expectRevert(IERC721Permit.DeadlineExpired.selector); + erc721Permit.permit(spender, tokenId, deadline, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, spender), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + // Helpers related to permit + function _permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { + bytes32 digest = _getDigest(operator, tokenId, 1, block.timestamp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(operator); + erc721Permit.permit(operator, tokenId, block.timestamp, nonce, signature); + } + + function _getDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + internal + view + returns (bytes32 digest) + { + digest = keccak256( + abi.encodePacked( + "\x19\x01", + erc721Permit.DOMAIN_SEPARATOR(), + keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) + ) + ); + } + + // copied the private function from UnorderedNonce.sol + function _getBitmapFromNonce(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) { + wordPos = uint248(nonce >> 8); + bitPos = uint8(nonce); + } +} diff --git a/test/UnorderedNonce.t.sol b/test/UnorderedNonce.t.sol new file mode 100644 index 00000000..9683c763 --- /dev/null +++ b/test/UnorderedNonce.t.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import {UnorderedNonce} from "../src/base/UnorderedNonce.sol"; +import {MockUnorderedNonce} from "./mocks/MockUnorderedNonce.sol"; + +contract UnorderedNonceTest is Test { + MockUnorderedNonce unorderedNonce; + + function setUp() public { + unorderedNonce = new MockUnorderedNonce(); + } + + function testLowNonces() public { + unorderedNonce.spendNonce(address(this), 5); + unorderedNonce.spendNonce(address(this), 0); + unorderedNonce.spendNonce(address(this), 1); + + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 1); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 5); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 0); + unorderedNonce.spendNonce(address(this), 4); + } + + function testNonceWordBoundary() public { + unorderedNonce.spendNonce(address(this), 255); + unorderedNonce.spendNonce(address(this), 256); + + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 255); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 256); + } + + function testHighNonces() public { + unorderedNonce.spendNonce(address(this), 2 ** 240); + unorderedNonce.spendNonce(address(this), 2 ** 240 + 1); + + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 2 ** 240); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 2 ** 240 + 1); + + unorderedNonce.spendNonce(address(this), 2 ** 240 + 2); + } + + function testInvalidateFullWord() public { + unorderedNonce.batchSpendNonces(0, 2 ** 256 - 1); + + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 0); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 1); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 254); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 255); + unorderedNonce.spendNonce(address(this), 256); + } + + function testInvalidateNonzeroWord() public { + unorderedNonce.batchSpendNonces(1, 2 ** 256 - 1); + + unorderedNonce.spendNonce(address(this), 0); + unorderedNonce.spendNonce(address(this), 254); + unorderedNonce.spendNonce(address(this), 255); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 256); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), 511); + unorderedNonce.spendNonce(address(this), 512); + } + + function test_fuzz_InvalidateNonzeroWord(uint256 word, uint256 nonce) public { + word = bound(word, 0, 1000e18); + // spend the entirety of a word + // word = 0, bits [0, 256) + // word = 1, bits [256, 512) + // word = 2, bits [512, 768), etc + unorderedNonce.batchSpendNonces(word, 2 ** 256 - 1); + + // bound the nonce to be from 0 to 256 bits after the word + nonce = bound(nonce, 0, (word + 2) * 256); + + if ((word * 256) <= nonce && nonce < ((word + 1) * 256)) { + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + } + unorderedNonce.spendNonce(address(this), nonce); + } + + function test_fuzz_UsingNonceTwiceFails(uint256 nonce) public { + unorderedNonce.spendNonce(address(this), nonce); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), nonce); + } + + function test_fuzz_UseTwoRandomNonces(uint256 first, uint256 second) public { + unorderedNonce.spendNonce(address(this), first); + if (first == second) { + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + unorderedNonce.spendNonce(address(this), second); + } else { + unorderedNonce.spendNonce(address(this), second); + } + } +} diff --git a/test/mocks/MockERC721Permit.sol b/test/mocks/MockERC721Permit.sol new file mode 100644 index 00000000..d6e82a3e --- /dev/null +++ b/test/mocks/MockERC721Permit.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import {ERC721Permit} from "../../src/base/ERC721Permit.sol"; + +contract MockERC721Permit is ERC721Permit { + uint256 public lastTokenId; + + constructor(string memory name, string memory symbol, string memory version) ERC721Permit(name, symbol, version) {} + + function tokenURI(uint256) public pure override returns (string memory) { + return ""; + } + + function mint() external returns (uint256 tokenId) { + tokenId = ++lastTokenId; + _mint(msg.sender, tokenId); + } +} diff --git a/test/mocks/MockUnorderedNonce.sol b/test/mocks/MockUnorderedNonce.sol new file mode 100644 index 00000000..8f3cfc57 --- /dev/null +++ b/test/mocks/MockUnorderedNonce.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; + +contract MockUnorderedNonce is UnorderedNonce { + function spendNonce(address owner, uint256 nonce) external { + _useUnorderedNonce(owner, nonce); + } + + /// @dev Bulk-spend nonces on a single word. FOR TESTING ONLY + function batchSpendNonces(uint256 wordPos, uint256 mask) external { + nonces[msg.sender][wordPos] |= mask; + } +} diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol new file mode 100644 index 00000000..7da608a2 --- /dev/null +++ b/test/position-managers/Permit.t.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; + +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {IERC721Permit} from "../../src/interfaces/IERC721Permit.sol"; +import {ERC721Permit} from "../../src/base/ERC721Permit.sol"; +import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; + +import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; + +import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; + +contract PermitTest is Test, PosmTestSetup { + using FixedPointMathLib for uint256; + using CurrencyLibrary for Currency; + using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; + + PoolId poolId; + address alice; + uint256 alicePK; + address bob; + uint256 bobPK; + + PositionConfig config; + + function setUp() public { + (alice, alicePK) = makeAddrAndKey("ALICE"); + (bob, bobPK) = makeAddrAndKey("BOB"); + + deployFreshManagerAndRouters(); + deployMintAndApprove2Currencies(); + + (key, poolId) = initPool(currency0, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + + // Requires currency0 and currency1 to be set in base Deployers contract. + deployAndApprovePosm(manager); + + seedBalance(alice); + seedBalance(bob); + + approvePosmFor(alice); + approvePosmFor(bob); + + // define a reusable range + config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); + } + + function test_domainSeparator() public view { + assertEq( + IERC721Permit(address(lpm)).DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("Uniswap V4 Positions NFT"), // storage is private on EIP712.sol so we need to hardcode these + keccak256("1"), + block.chainid, + address(lpm) + ) + ) + ); + } + + function test_permit_decreaseLiquidity() public { + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // alice gives bob operator permissions + permit(alicePK, tokenIdAlice, bob, 1); + + // bob can decrease liquidity on alice's token + uint256 liquidityToRemove = 0.4444e18; + vm.startPrank(bob); + decreaseLiquidity(tokenIdAlice, config, liquidityToRemove, ZERO_BYTES); + vm.stopPrank(); + + // alice's position decreased liquidity + bytes32 positionId = + keccak256(abi.encodePacked(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenIdAlice))); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + assertEq(liquidity, liquidityAlice - liquidityToRemove); + } + + function test_permit_collect() public { + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // donate to create fee revenue + uint256 currency0Revenue = 0.4444e18; + uint256 currency1Revenue = 0.2222e18; + donateRouter.donate(key, currency0Revenue, currency1Revenue, ZERO_BYTES); + + // alice gives bob operator permissions + permit(alicePK, tokenIdAlice, bob, 1); + + // TODO: test collection to recipient with a permissioned operator + + // bob collects fees to himself + address recipient = bob; + uint256 balance0BobBefore = currency0.balanceOf(bob); + uint256 balance1BobBefore = currency1.balanceOf(bob); + vm.startPrank(bob); + collect(tokenIdAlice, config, ZERO_BYTES); + vm.stopPrank(); + + assertApproxEqAbs(currency0.balanceOf(recipient), balance0BobBefore + currency0Revenue, 1 wei); + assertApproxEqAbs(currency1.balanceOf(recipient), balance1BobBefore + currency1Revenue, 1 wei); + } + + // --- Fail Scenarios --- // + function test_permit_notOwnerRevert() public { + // calling permit on a token that is not owned will fail + + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // bob cannot permit himself on alice's token + uint256 nonce = 1; + bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); + vm.stopPrank(); + } + + // unapproved callers can increase others' positions + // see `test_increaseLiquidity_withUnapprovedCaller()` + // function test_noPermit_increaseLiquidityRevert() public {} + + function test_noPermit_decreaseLiquidityRevert() public { + // decreaseLiquidity fails if the owner did not permit + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // bob cannot decrease liquidity on alice's token + uint256 liquidityToRemove = 0.4444e18; + bytes memory decrease = getDecreaseEncoded(tokenIdAlice, config, liquidityToRemove, ZERO_BYTES); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(bob))); + lpm.modifyLiquidities(decrease, _deadline); + vm.stopPrank(); + } + + function test_noPermit_collectRevert() public { + // collect fails if the owner did not permit + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // donate to create fee revenue + uint256 currency0Revenue = 0.4444e18; + uint256 currency1Revenue = 0.2222e18; + donateRouter.donate(key, currency0Revenue, currency1Revenue, ZERO_BYTES); + + // bob cannot collect fees + bytes memory collect = getCollectEncoded(tokenIdAlice, config, ZERO_BYTES); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(bob))); + lpm.modifyLiquidities(collect, block.timestamp + 1); + vm.stopPrank(); + } + + // Bob can use alice's signature to permit & decrease liquidity + function test_permit_operatorSelfPermit() public { + uint256 liquidityAlice = 1e18; + vm.startPrank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + uint256 tokenId = lpm.nextTokenId() - 1; + + // Alice gives Bob permission to operate on her liquidity + uint256 nonce = 1; + bytes32 digest = getDigest(bob, tokenId, nonce, block.timestamp + 1); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // bob gives himself permission + vm.prank(bob); + lpm.permit(bob, tokenId, block.timestamp + 1, nonce, signature); + + // bob can decrease liquidity on alice's token + uint256 liquidityToRemove = 0.4444e18; + vm.startPrank(bob); + decreaseLiquidity(tokenId, config, liquidityToRemove, ZERO_BYTES); + vm.stopPrank(); + + bytes32 positionId = + keccak256(abi.encodePacked(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId))); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, liquidityAlice - liquidityToRemove); + } + + // Charlie uses Alice's signature to give permission to Bob + function test_permit_thirdParty() public { + uint256 liquidityAlice = 1e18; + vm.startPrank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + uint256 tokenId = lpm.nextTokenId() - 1; + + // Alice gives Bob permission to operate on her liquidity + uint256 nonce = 1; + bytes32 digest = getDigest(bob, tokenId, nonce, block.timestamp + 1); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // charlie gives Bob permission to operate on alice's token + address charlie = makeAddr("CHARLIE"); + vm.prank(charlie); + lpm.permit(bob, tokenId, block.timestamp + 1, nonce, signature); + + // bob can decrease liquidity on alice's token + uint256 liquidityToRemove = 0.4444e18; + vm.startPrank(bob); + decreaseLiquidity(tokenId, config, liquidityToRemove, ZERO_BYTES); + vm.stopPrank(); + + bytes32 positionId = + keccak256(abi.encodePacked(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId))); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, liquidityAlice - liquidityToRemove); + } +} diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 5c90192f..c092f2fe 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -701,6 +701,90 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { snapLastCall("PositionManager_decrease_burnEmpty_native"); } + function test_gas_permit() public { + // alice permits for the first time + uint256 liquidityAlice = 1e18; + vm.startPrank(alice); + uint256 tokenIdAlice = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // alice gives operator permission to bob + uint256 nonce = 1; + bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(bob); + lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); + snapLastCall("PositionManager_permit"); + } + + function test_gas_permit_secondPosition() public { + // alice permits for her two tokens, benchmark the 2nd permit + uint256 liquidityAlice = 1e18; + vm.startPrank(alice); + uint256 tokenIdAlice = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // alice gives operator permission to bob + uint256 nonce = 1; + bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(bob); + lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); + + // alice creates another position + vm.startPrank(alice); + tokenIdAlice = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // alice gives operator permission to bob + nonce = 2; + digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1); + (v, r, s) = vm.sign(alicePK, digest); + signature = abi.encodePacked(r, s, v); + + vm.prank(bob); + lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); + snapLastCall("PositionManager_permit_secondPosition"); + } + + function test_gas_permit_twice() public { + // alice permits the same token, twice + address charlie = makeAddr("CHARLIE"); + + uint256 liquidityAlice = 1e18; + vm.startPrank(alice); + uint256 tokenIdAlice = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // alice gives operator permission to bob + uint256 nonce = 1; + bytes32 digest = getDigest(bob, tokenIdAlice, nonce, block.timestamp + 1); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(bob); + lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); + + // alice gives operator permission to charlie + nonce = 2; + digest = getDigest(charlie, tokenIdAlice, nonce, block.timestamp + 1); + (v, r, s) = vm.sign(alicePK, digest); + signature = abi.encodePacked(r, s, v); + + vm.prank(bob); + lpm.permit(charlie, tokenIdAlice, block.timestamp + 1, nonce, signature); + snapLastCall("PositionManager_permit_twice"); + } + function test_gas_mint_settleWithBalance_sweep() public { uint256 liquidityAlice = 3_000e18; diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index e5efff11..8bbd600a 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -28,16 +28,23 @@ import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol"; import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; import {Constants} from "../../src/libraries/Constants.sol"; +import {IERC721Permit} from "../../src/interfaces/IERC721Permit.sol"; contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTestSetup, LiquidityFuzzers { using FixedPointMathLib for uint256; using CurrencyLibrary for Currency; + using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; using Planner for Plan; using PoolIdLibrary for PoolKey; using StateLibrary for IPoolManager; PoolId poolId; - address alice = makeAddr("ALICE"); + address alice; + uint256 alicePK; + address bob; + // bob used for permit2 signature tests + uint256 bobPK; Permit2Forwarder permit2Forwarder; @@ -48,13 +55,12 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest bytes32 PERMIT2_DOMAIN_SEPARATOR; - // bob used for permit2 signature tests - uint256 bobPrivateKey; - address bob; - PositionConfig config; function setUp() public { + (alice, alicePK) = makeAddrAndKey("ALICE"); + (bob, bobPK) = makeAddrAndKey("BOB"); + deployFreshManagerAndRouters(); deployMintAndApprove2Currencies(); @@ -66,8 +72,8 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest permit2Forwarder = new Permit2Forwarder(permit2); PERMIT2_DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); - bobPrivateKey = 0x12341234; - bob = vm.addr(bobPrivateKey); + seedBalance(alice); + approvePosmFor(alice); seedBalance(bob); approvePosmFor(bob); @@ -104,6 +110,38 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest assertGt(result.amount1(), 0); } + function test_multicall_permitAndDecrease() public { + config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); + uint256 liquidityAlice = 1e18; + vm.startPrank(alice); + uint256 tokenId = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // Alice gives Bob permission to operate on her liquidity + uint256 nonce = 1; + bytes32 digest = getDigest(bob, tokenId, nonce, block.timestamp + 1); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // bob gives himself permission and decreases liquidity + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector( + IERC721Permit(lpm).permit.selector, bob, tokenId, block.timestamp + 1, nonce, signature + ); + uint256 liquidityToRemove = 0.4444e18; + bytes memory actions = getDecreaseEncoded(tokenId, config, liquidityToRemove, ZERO_BYTES); + calls[1] = abi.encodeWithSelector(PositionManager(lpm).modifyLiquidities.selector, actions, _deadline); + + vm.prank(bob); + lpm.multicall(calls); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, liquidityAlice - liquidityToRemove); + } + function test_multicall_permit_mint() public { config = PositionConfig({ poolKey: key, @@ -132,7 +170,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest IAllowanceTransfer.PermitSingle memory permit = defaultERC20PermitAllowance(Currency.unwrap(currency0), permitAmount, permitExpiration, permitNonce); permit.spender = address(lpm); - bytes memory sig = getPermitSignature(permit, bobPrivateKey, PERMIT2_DOMAIN_SEPARATOR); + bytes memory sig = getPermitSignature(permit, bobPK, PERMIT2_DOMAIN_SEPARATOR); bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(Permit2Forwarder.permit.selector, bob, permit, sig); @@ -190,7 +228,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest IAllowanceTransfer.PermitBatch memory permit = defaultERC20PermitBatchAllowance(tokens, permitAmount, permitExpiration, permitNonce); permit.spender = address(lpm); - bytes memory sig = getPermitBatchSignature(permit, bobPrivateKey, PERMIT2_DOMAIN_SEPARATOR); + bytes memory sig = getPermitBatchSignature(permit, bobPK, PERMIT2_DOMAIN_SEPARATOR); bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(Permit2Forwarder.permitBatch.selector, bob, permit, sig); diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol index 4cfe740f..3ae10590 100644 --- a/test/shared/PosmTestSetup.sol +++ b/test/shared/PosmTestSetup.sol @@ -14,6 +14,7 @@ import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; import {HookSavesDelta} from "./HookSavesDelta.sol"; +import {ERC721PermitHashLibrary} from "../../src/libraries/ERC721PermitHash.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { @@ -65,6 +66,30 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { vm.stopPrank(); } + function permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { + bytes32 digest = getDigest(operator, tokenId, 1, block.timestamp + 1); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(operator); + lpm.permit(operator, tokenId, block.timestamp + 1, nonce, signature); + } + + function getDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + internal + view + returns (bytes32 digest) + { + digest = keccak256( + abi.encodePacked( + "\x19\x01", + lpm.DOMAIN_SEPARATOR(), + keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) + ) + ); + } + function getLastDelta() internal view returns (BalanceDelta delta) { delta = hook.deltas(hook.numberDeltasReturned() - 1); // just want the most recently written delta } From 2f15bb2c34cd2d80d8aa76bed46c338c7e0b9575 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Sat, 3 Aug 2024 00:35:44 -0400 Subject: [PATCH 20/52] Take (#257) * add take * merge conf * noooo console * pr comments --- .../PositionManager_decrease_take_take.snap | 1 + ...sitionManager_increaseLiquidity_erc20.snap | 1 - .forge-snapshots/PositionManager_mint.snap | 1 - .../PositionManager_mint_nativeWithSweep.snap | 1 - src/PositionManager.sol | 3 + src/base/DeltaResolver.sol | 8 +++ test/libraries/CalldataDecoder.t.sol | 12 ++++ test/mocks/MockCalldataDecoder.sol | 8 +++ .../PositionManager.gas.t.sol | 16 +++++ test/position-managers/PositionManager.t.sol | 71 +++++++++++++++++++ test/shared/Planner.sol | 11 +++ 11 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 .forge-snapshots/PositionManager_decrease_take_take.snap delete mode 100644 .forge-snapshots/PositionManager_increaseLiquidity_erc20.snap delete mode 100644 .forge-snapshots/PositionManager_mint.snap delete mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep.snap diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap new file mode 100644 index 00000000..1e5f58d8 --- /dev/null +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -0,0 +1 @@ +116877 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap deleted file mode 100644 index 076b23da..00000000 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap +++ /dev/null @@ -1 +0,0 @@ -152144 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint.snap deleted file mode 100644 index 964f844e..00000000 --- a/.forge-snapshots/PositionManager_mint.snap +++ /dev/null @@ -1 +0,0 @@ -372007 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap deleted file mode 100644 index d691d43c..00000000 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap +++ /dev/null @@ -1 +0,0 @@ -345190 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 88873384..131d4526 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -186,6 +186,9 @@ contract PositionManager is } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); _sweep(currency, _mapRecipient(to)); + } else if (action == Actions.TAKE) { + (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); } else { revert UnsupportedAction(action); } diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 75bc5649..9521cd82 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -79,6 +79,14 @@ abstract contract DeltaResolver is ImmutableState { return amount; } + /// @notice Calculates the amount for a take action + function _mapTakeAmount(uint256 amount, Currency currency) internal view returns (uint256) { + if (amount == Constants.OPEN_DELTA) { + return _getFullCredit(currency).toUint128(); + } + return amount; + } + /// @notice Calculates the amount for a swap action function _mapInputAmount(uint128 amount, Currency currency) internal view returns (uint128) { if (amount == Constants.CONTRACT_BALANCE) { diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index 9ce41fb0..29398697 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -172,6 +172,18 @@ contract CalldataDecoderTest is Test { assertEq(_address, __address); } + function test_fuzz_decodeCurrencyAddressAndUint256(Currency _currency, address _addr, uint256 _amount) + public + view + { + bytes memory params = abi.encode(_currency, _addr, _amount); + (Currency currency, address addr, uint256 amount) = decoder.decodeCurrencyAddressAndUint256(params); + + assertEq(Currency.unwrap(currency), Currency.unwrap(_currency)); + assertEq(addr, _addr); + assertEq(amount, _amount); + } + function test_fuzz_decodeCurrencyAndUint256(Currency _currency, uint256 _amount) public view { bytes memory params = abi.encode(_currency, _amount); (Currency currency, uint256 amount) = decoder.decodeCurrencyAndUint256(params); diff --git a/test/mocks/MockCalldataDecoder.sol b/test/mocks/MockCalldataDecoder.sol index b63654e9..695a526b 100644 --- a/test/mocks/MockCalldataDecoder.sol +++ b/test/mocks/MockCalldataDecoder.sol @@ -113,4 +113,12 @@ contract MockCalldataDecoder { function decodeCurrencyAndUint256(bytes calldata params) external pure returns (Currency currency, uint256 _uint) { return params.decodeCurrencyAndUint256(); } + + function decodeCurrencyAddressAndUint256(bytes calldata params) + external + pure + returns (Currency currency, address addr, uint256 amount) + { + return params.decodeCurrencyAddressAndUint256(); + } } diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index c092f2fe..299d4697 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -807,4 +807,20 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_settleWithBalance_sweep"); } + + // Does not encode a take pair + function test_gas_decrease_take_take() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + + Plan memory plan = Planner.init(); + plan.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, Constants.MSG_SENDER); + + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_decrease_take_take"); + } } diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index 7afae777..4a47e7e6 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -878,5 +878,76 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(lpFee, fee); } + // tests a decrease and take in both currencies + // does not use take pair, so its less optimal + function test_decrease_take() public { + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); + uint256 tokenId = lpm.nextTokenId(); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + + hook.clearDeltas(); + + uint256 balanceBefore0 = currency0.balanceOfSelf(); + uint256 balanceBefore1 = currency1.balanceOfSelf(); + + Plan memory plan = Planner.init(); + plan.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, Constants.MSG_SENDER); + + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + assertEq(currency0.balanceOfSelf(), balanceBefore0 + uint256(int256(delta.amount0()))); + assertEq(currency1.balanceOfSelf(), balanceBefore1 + uint256(int256(delta.amount1()))); + } + + // decrease full range position + // mint new one sided position in currency1 + // expect to TAKE currency0 and SETTLE currency1 + function test_decrease_increaseCurrency1_take_settle() public { + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); + uint256 tokenId = lpm.nextTokenId(); + mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + + hook.clearDeltas(); + + uint256 balanceBefore0 = currency0.balanceOfSelf(); + uint256 balanceBefore1 = currency1.balanceOfSelf(); + + uint256 tokenIdMint = lpm.nextTokenId(); + + // one-sided liq in currency1 + PositionConfig memory configMint = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 0}); + + Plan memory plan = Planner.init(); + plan.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + plan.add( + Actions.MINT_POSITION, + abi.encode(configMint, 1e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) + ); + plan.add(Actions.TAKE, abi.encode(key.currency0, Constants.MSG_SENDER, Constants.OPEN_DELTA)); + plan.add(Actions.SETTLE, abi.encode(key.currency1, Constants.OPEN_DELTA, true)); + bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, Constants.MSG_SENDER); + + lpm.modifyLiquidities(calls, _deadline); + BalanceDelta deltaDecrease = hook.deltas(0); + BalanceDelta deltaMint = hook.deltas(1); + + assertEq(deltaMint.amount0(), 0); // there is no currency0 in the new position + assertEq(currency0.balanceOfSelf(), balanceBefore0 + uint256(int256(deltaDecrease.amount0()))); + assertEq( + currency1.balanceOfSelf(), balanceBefore1 - uint256(-int256(deltaDecrease.amount1() + deltaMint.amount1())) + ); + assertEq(lpm.ownerOf(tokenIdMint), address(this)); + assertLt(currency1.balanceOfSelf(), balanceBefore1); // currency1 was owed + assertLt(uint256(int256(deltaDecrease.amount1())), uint256(int256(-deltaMint.amount1()))); // amount1 in the second position was greater than amount1 in the first position + } + function test_mint_slippageRevert() public {} } diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 8a0ffd8a..ac5a81aa 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -6,6 +6,7 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Constants} from "../../src/libraries/Constants.sol"; struct Plan { bytes actions; @@ -37,6 +38,16 @@ library Planner { return plan; } + function finalizeModifyLiquidityWithTake(Plan memory plan, PoolKey memory poolKey, address takeRecipient) + internal + pure + returns (bytes memory) + { + plan.add(Actions.TAKE, abi.encode(poolKey.currency0, takeRecipient, Constants.OPEN_DELTA)); + plan.add(Actions.TAKE, abi.encode(poolKey.currency1, takeRecipient, Constants.OPEN_DELTA)); + return plan.encode(); + } + function finalizeModifyLiquidityWithClose(Plan memory plan, PoolKey memory poolKey) internal pure From 86b5ea3b51ea8b6727690deb280f5a16bc887063 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:29:03 -0400 Subject: [PATCH 21/52] multicall: bubble up revert reason (#236) * wip bubble up revert * fix formatting * simple bubble * test different error types on multicall * additional testing for external contract reverts * example core revert bubbling * testing for different lengths * cleanup * cleanup unused imports * delete stale gas * minor nits --- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- src/base/Multicall.sol | 12 +- test/Multicall.t.sol | 117 +++++++++++++++++- test/mocks/MockMulticall.sol | 55 +++++++- .../PositionManager.multicall.t.sol | 77 ++++++++++++ 37 files changed, 279 insertions(+), 48 deletions(-) diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index a3f146ac..a60858d1 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47186 \ No newline at end of file +47184 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index a04aaf5c..3a7e7f99 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -47004 \ No newline at end of file +47001 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 5238cf5f..0828f010 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123058 \ No newline at end of file +123056 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index d5955ea5..1b6c526f 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122756 \ No newline at end of file +122754 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 49379c42..85bf5088 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130136 \ No newline at end of file +130134 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 3e3dc429..95bc49cb 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129835 \ No newline at end of file +129832 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 089f3305..7940b8e0 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141409 \ No newline at end of file +141406 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 1d6bec04..214a92f6 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150257 \ No newline at end of file +150254 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index 1d6bec04..214a92f6 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150257 \ No newline at end of file +150254 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index d4d3c6bf..0d682131 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149868 \ No newline at end of file +149865 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index f183904d..8c1915ba 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108602 \ No newline at end of file +108600 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index 5bc59fda..a877133d 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115800 \ No newline at end of file +115797 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index 5927a619..241b44a3 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115411 \ No newline at end of file +115408 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index f2edca7c..eb10283f 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134196 \ No newline at end of file +134193 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 19fbfe9c..cdfc3d41 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126935 \ No newline at end of file +126932 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 47317e7b..934de5bc 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128516 \ No newline at end of file +128513 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index f7f7fed6..9da786cd 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152363 \ No newline at end of file +152360 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 473fcfde..867577e2 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151604 \ No newline at end of file +151601 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 7d3f9d6c..617e1740 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134163 \ No newline at end of file +134160 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 2a9f67c8..d8adae8f 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130328 \ No newline at end of file +130325 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index d1db687b..69f35938 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171022 \ No newline at end of file +171019 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 9f7f5ce9..17997e03 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -141002 \ No newline at end of file +140999 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 99535ce2..7f1af379 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336841 \ No newline at end of file +336838 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 2a17e5f2..a02553e4 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345370 \ No newline at end of file +345367 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index ad8a0dd7..1e3a2821 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344911 \ No newline at end of file +344908 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 00e13762..e4f0f7de 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314823 \ No newline at end of file +314820 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index bf2a5e88..fc1ae160 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315465 \ No newline at end of file +315462 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index de4b5cb3..e85f6f57 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241047 \ No newline at end of file +241044 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 43e466e6..4a9bb521 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -371193 \ No newline at end of file +371190 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 0ed51014..f0fe4b37 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320841 \ No newline at end of file +320838 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index f9a92b51..1b05c224 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372141 \ No newline at end of file +372138 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 7bbeb8cb..961588e5 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371520 \ No newline at end of file +371517 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 2f927ade..05da9fe8 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416516 \ No newline at end of file +416513 \ No newline at end of file diff --git a/src/base/Multicall.sol b/src/base/Multicall.sol index bd926766..7f97d045 100644 --- a/src/base/Multicall.sol +++ b/src/base/Multicall.sol @@ -13,18 +13,10 @@ abstract contract Multicall is IMulticall { (bool success, bytes memory result) = address(this).delegatecall(data[i]); if (!success) { - // handle custom errors - if (result.length == 4) { - assembly { - revert(add(result, 0x20), mload(result)) - } - } - // Next 5 lines from https://ethereum.stackexchange.com/a/83577 - if (result.length < 68) revert(); + // bubble up the revert reason assembly { - result := add(result, 0x04) + revert(add(result, 0x20), mload(result)) } - revert(abi.decode(result, (string))); } results[i] = result; diff --git a/test/Multicall.t.sol b/test/Multicall.t.sol index 18cdd2a3..8591c271 100644 --- a/test/Multicall.t.sol +++ b/test/Multicall.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; -import {MockMulticall} from "./mocks/MockMulticall.sol"; +import {MockMulticall, RevertContract} from "./mocks/MockMulticall.sol"; contract MulticallTest is Test { MockMulticall multicall; @@ -30,7 +30,7 @@ contract MulticallTest is Test { function test_multicall_firstRevert() public { bytes[] memory calls = new bytes[](2); calls[0] = - abi.encodeWithSelector(MockMulticall(multicall).functionThatRevertsWithError.selector, "First call failed"); + abi.encodeWithSelector(MockMulticall(multicall).functionThatRevertsWithString.selector, "First call failed"); calls[1] = abi.encodeWithSelector(MockMulticall(multicall).functionThatReturnsTuple.selector, 1, 2); vm.expectRevert("First call failed"); @@ -40,8 +40,9 @@ contract MulticallTest is Test { function test_multicall_secondRevert() public { bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(MockMulticall(multicall).functionThatReturnsTuple.selector, 1, 2); - calls[1] = - abi.encodeWithSelector(MockMulticall(multicall).functionThatRevertsWithError.selector, "Second call failed"); + calls[1] = abi.encodeWithSelector( + MockMulticall(multicall).functionThatRevertsWithString.selector, "Second call failed" + ); vm.expectRevert("Second call failed"); multicall.multicall(calls); @@ -106,4 +107,112 @@ contract MulticallTest is Test { assertEq(multicall.msgValue(), 100); assertEq(multicall.msgValueDouble(), 200); } + + // revert bubbling + function test_multicall_bubbleRevert_string() public { + bytes[] memory calls = new bytes[](1); + calls[0] = + abi.encodeWithSelector(MockMulticall(multicall).functionThatRevertsWithString.selector, "errorString"); + + vm.expectRevert("errorString"); + multicall.multicall(calls); + } + + function test_multicall_bubbleRevert_4bytes() public { + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWith4Bytes.selector); + + // revert is caught + vm.expectRevert(MockMulticall.Error4Bytes.selector); + multicall.multicall(calls); + + // confirm expected length of the revert + try multicall.revertWith4Bytes() {} + catch (bytes memory reason) { + assertEq(reason.length, 4); + } + } + + function test_fuzz_multicall_bubbleRevert_36bytes(uint8 num) public { + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWith36Bytes.selector, num); + + // revert is caught + vm.expectRevert(abi.encodeWithSelector(MockMulticall.Error36Bytes.selector, num)); + multicall.multicall(calls); + + // confirm expected length of the revert + try multicall.revertWith36Bytes(num) {} + catch (bytes memory reason) { + assertEq(reason.length, 36); + } + } + + function test_fuzz_multicall_bubbleRevert_68bytes(uint256 a, uint256 b) public { + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWith68Bytes.selector, a, b); + + // revert is caught + vm.expectRevert(abi.encodeWithSelector(MockMulticall.Error68Bytes.selector, a, b)); + multicall.multicall(calls); + + // confirm expected length of the revert + try multicall.revertWith68Bytes(a, b) {} + catch (bytes memory reason) { + assertEq(reason.length, 68); + } + } + + function test_fuzz_multicall_bubbleRevert_arbitraryBytes(uint16 length) public { + length = uint16(bound(length, 0, 4096)); + bytes memory data = new bytes(length); + for (uint256 i = 0; i < data.length; i++) { + data[i] = bytes1(uint8(i)); + } + + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWithBytes.selector, data); + + // revert is caught + vm.expectRevert(abi.encodeWithSelector(MockMulticall.ErrorBytes.selector, data)); + multicall.multicall(calls); + + // confirm expected length of the revert + try multicall.revertWithBytes(data) {} + catch (bytes memory reason) { + // errors with 0 bytes are by default 64 bytes of data (length & pointer?) + 4 bytes of selector + if (length == 0) { + assertEq(reason.length, 68); + } else { + uint256 expectedLength = 64 + 4; // default length + selector + // 32 bytes added to the reason for each 32 bytes of data + expectedLength += (((data.length - 1) / 32) + 1) * 32; + assertEq(reason.length, expectedLength); + } + } + } + + function test_multicall_bubbleRevert_externalRevertString() public { + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector(MockMulticall(multicall).externalRevertString.selector, "errorString"); + + vm.expectRevert("errorString"); + multicall.multicall(calls); + } + + function test_multicall_bubbleRevert_externalRevertSimple() public { + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(MockMulticall(multicall).externalRevertError1.selector); + + vm.expectRevert(RevertContract.Error1.selector); + multicall.multicall(calls); + } + + function test_multicall_bubbleRevert_externalRevertWithParams(uint256 a, uint256 b) public { + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(MockMulticall(multicall).externalRevertError2.selector, a, b); + + vm.expectRevert(abi.encodeWithSelector(RevertContract.Error2.selector, a, b)); + multicall.multicall(calls); + } } diff --git a/test/mocks/MockMulticall.sol b/test/mocks/MockMulticall.sol index 4b96d915..1a936deb 100644 --- a/test/mocks/MockMulticall.sol +++ b/test/mocks/MockMulticall.sol @@ -3,7 +3,30 @@ pragma solidity ^0.8.20; import "../../src/base/Multicall.sol"; +/// @dev If MockMulticall is to PositionManager, then RevertContract is to PoolManager +contract RevertContract { + error Error1(); + error Error2(uint256 a, uint256 b); + + function revertWithString(string memory error) external pure { + revert(error); + } + + function revertWithError1() external pure { + revert Error1(); + } + + function revertWithError2(uint256 a, uint256 b) external pure { + revert Error2(a, b); + } +} + contract MockMulticall is Multicall { + error Error4Bytes(); // 4 bytes of selector + error Error36Bytes(uint8 a); // 32 bytes + 4 bytes of selector + error Error68Bytes(uint256 a, uint256 b); // 64 bytes + 4 bytes of selector + error ErrorBytes(bytes data); // arbitrary byte length + struct Tuple { uint256 a; uint256 b; @@ -12,7 +35,9 @@ contract MockMulticall is Multicall { uint256 public msgValue; uint256 public msgValueDouble; - function functionThatRevertsWithError(string memory error) external pure { + RevertContract public revertContract = new RevertContract(); + + function functionThatRevertsWithString(string memory error) external pure { revert(error); } @@ -31,4 +56,32 @@ contract MockMulticall is Multicall { function returnSender() external view returns (address) { return msg.sender; } + + function externalRevertString(string memory error) external view { + revertContract.revertWithString(error); + } + + function externalRevertError1() external view { + revertContract.revertWithError1(); + } + + function externalRevertError2(uint256 a, uint256 b) external view { + revertContract.revertWithError2(a, b); + } + + function revertWith4Bytes() external pure { + revert Error4Bytes(); + } + + function revertWith36Bytes(uint8 a) external pure { + revert Error36Bytes(a); + } + + function revertWith68Bytes(uint256 a, uint256 b) external pure { + revert Error68Bytes(a, b); + } + + function revertWithBytes(bytes memory data) external pure { + revert ErrorBytes(data); + } } diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index 8bbd600a..8c030164 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -18,6 +18,7 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {PoolInitializer} from "../../src/base/PoolInitializer.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; @@ -110,6 +111,82 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest assertGt(result.amount1(), 0); } + // charlie will attempt to decrease liquidity without approval + // posm's NotApproved(charlie) should bubble up through Multicall + function test_multicall_bubbleRevert() public { + config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, address(this), ZERO_BYTES); + + Plan memory planner = Planner.init(); + planner.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 100e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); + + // Use multicall to decrease liquidity + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); + + address charlie = makeAddr("CHARLIE"); + vm.startPrank(charlie); + vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, charlie)); + lpm.multicall(calls); + vm.stopPrank(); + } + + // decrease liquidity but forget to close + // core's CurrencyNotSettled should bubble up through Multicall + function test_multicall_bubbleRevert_core() public { + config = PositionConfig({ + poolKey: key, + tickLower: TickMath.minUsableTick(key.tickSpacing), + tickUpper: TickMath.maxUsableTick(key.tickSpacing) + }); + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, address(this), ZERO_BYTES); + + // do not close deltas to throw CurrencyNotSettled in core + Plan memory planner = Planner.init(); + planner.add( + Actions.DECREASE_LIQUIDITY, + abi.encode(tokenId, config, 100e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + bytes memory actions = planner.encode(); + + // Use multicall to decrease liquidity + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); + + vm.expectRevert(IPoolManager.CurrencyNotSettled.selector); + lpm.multicall(calls); + } + + // create a pool where tickSpacing is negative + // core's TickSpacingTooSmall(int24) should bubble up through Multicall + function test_multicall_bubbleRevert_core_args() public { + int24 tickSpacing = -10; + key = PoolKey({ + currency0: currency0, + currency1: currency1, + fee: 0, + tickSpacing: tickSpacing, + hooks: IHooks(address(0)) + }); + + // Use multicall to initialize a pool + bytes[] memory calls = new bytes[](1); + calls[0] = abi.encodeWithSelector(PoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1, ZERO_BYTES); + + vm.expectRevert(abi.encodeWithSelector(IPoolManager.TickSpacingTooSmall.selector, tickSpacing)); + lpm.multicall(calls); + } + function test_multicall_permitAndDecrease() public { config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); uint256 liquidityAlice = 1e18; From d65f1581f0224d1f6caf69de271153766a23ca32 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:38:15 +0100 Subject: [PATCH 22/52] Optimise permit hashing (#260) * permit hash in assembly * fuzz test --- .forge-snapshots/PositionManager_permit.snap | 2 +- .../PositionManager_permit_secondPosition.snap | 2 +- .../PositionManager_permit_twice.snap | 2 +- src/libraries/ERC721PermitHash.sol | 17 +++++++++++++++-- test/ERC721Permit.t.sol | 6 ++++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap index b2faa7cc..dfab40b6 100644 --- a/.forge-snapshots/PositionManager_permit.snap +++ b/.forge-snapshots/PositionManager_permit.snap @@ -1 +1 @@ -79585 \ No newline at end of file +79486 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap index 60f10b03..19350c74 100644 --- a/.forge-snapshots/PositionManager_permit_secondPosition.snap +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -1 +1 @@ -62497 \ No newline at end of file +62398 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index 3df69310..ecadddb0 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -45397 \ No newline at end of file +45298 \ No newline at end of file diff --git a/src/libraries/ERC721PermitHash.sol b/src/libraries/ERC721PermitHash.sol index cf4c74f0..9a36d6de 100644 --- a/src/libraries/ERC721PermitHash.sol +++ b/src/libraries/ERC721PermitHash.sol @@ -5,7 +5,20 @@ library ERC721PermitHashLibrary { /// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); bytes32 constant PERMIT_TYPEHASH = 0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad; - function hash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) internal pure returns (bytes32) { - return keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + function hash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + internal + pure + returns (bytes32 digest) + { + // equivalent to: keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, PERMIT_TYPEHASH) + mstore(add(fmp, 0x20), spender) + mstore(add(fmp, 0x40), tokenId) + mstore(add(fmp, 0x60), nonce) + mstore(add(fmp, 0x80), deadline) + digest := keccak256(fmp, 0xa0) + } } } diff --git a/test/ERC721Permit.t.sol b/test/ERC721Permit.t.sol index d7126223..16f4f39f 100644 --- a/test/ERC721Permit.t.sol +++ b/test/ERC721Permit.t.sol @@ -67,6 +67,12 @@ contract ERC721PermitTest is Test { ); } + function test_fuzz_permitHash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) public view { + bytes32 expectedHash = + keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + assertEq(expectedHash, ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline)); + } + function test_domainSeparator() public view { assertEq( IERC721Permit(address(erc721Permit)).DOMAIN_SEPARATOR(), From 0c956bfbbcda316145b87bc5c5a03bb6589d6965 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Sun, 4 Aug 2024 11:51:10 -0400 Subject: [PATCH 23/52] Replace OZ EIP712 (#256) * WOOF WOOF WOOF BARK BARK WOOF BARK * remove address(this) check * forge fmt * discard version from EIP712 * Update src/base/EIP712.sol Co-authored-by: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> * natspec for delegatecall error * assembly optimization, pr nits * test to validate assembly --------- Co-authored-by: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> --- .../PositionManager_decrease_take_take.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/PositionManager_permit.snap | 2 +- ...PositionManager_permit_secondPosition.snap | 2 +- .../PositionManager_permit_twice.snap | 2 +- remappings.txt | 1 - src/PositionManager.sol | 2 +- src/base/EIP712.sol | 51 +++++++++++++++++++ src/base/ERC721Permit.sol | 14 ++--- src/interfaces/IEIP712.sol | 6 +++ src/interfaces/IERC721Permit.sol | 4 -- test/EIP712.t.sol | 47 +++++++++++++++++ test/ERC721Permit.t.sol | 8 ++- test/mocks/MockERC721Permit.sol | 2 +- test/position-managers/Permit.t.sol | 5 +- 15 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 src/base/EIP712.sol create mode 100644 src/interfaces/IEIP712.sol create mode 100644 test/EIP712.t.sol diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index 1e5f58d8..59ec6411 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116877 \ No newline at end of file +116874 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 05da9fe8..806bba59 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416513 \ No newline at end of file +416535 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap index dfab40b6..296e82ca 100644 --- a/.forge-snapshots/PositionManager_permit.snap +++ b/.forge-snapshots/PositionManager_permit.snap @@ -1 +1 @@ -79486 \ No newline at end of file +79467 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap index 19350c74..3f96aa23 100644 --- a/.forge-snapshots/PositionManager_permit_secondPosition.snap +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -1 +1 @@ -62398 \ No newline at end of file +62355 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index ecadddb0..913eee8f 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -45298 \ No newline at end of file +45243 \ No newline at end of file diff --git a/remappings.txt b/remappings.txt index c7951f83..e7868fe9 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,4 @@ @uniswap/v4-core/=lib/v4-core/ -@openzeppelin/=lib/v4-core/lib/openzeppelin-contracts/ forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/ ds-test/=lib/v4-core/lib/forge-std/lib/ds-test/src/ forge-std/=lib/v4-core/lib/forge-std/src/ diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 131d4526..39bfe195 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -60,7 +60,7 @@ contract PositionManager is constructor(IPoolManager _poolManager, IAllowanceTransfer _permit2) BaseActionsRouter(_poolManager) Permit2Forwarder(_permit2) - ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM", "1") + ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM") {} /// @notice Reverts if the deadline has passed diff --git a/src/base/EIP712.sol b/src/base/EIP712.sol new file mode 100644 index 00000000..5b4ea0f5 --- /dev/null +++ b/src/base/EIP712.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IEIP712} from "../interfaces/IEIP712.sol"; + +/// @notice Generic EIP712 implementation +/// @dev Maintains cross-chain replay protection in the event of a fork +/// @dev Should not be delegatecall'd because DOMAIN_SEPARATOR returns the cached hash and does not recompute with the delegatecallers address +/// @dev Reference: https://github.com/Uniswap/permit2/blob/main/src/EIP712.sol +/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol +contract EIP712 is IEIP712 { + // Cache the domain separator as an immutable value, but also store the chain id that it + // corresponds to, in order to invalidate the cached domain separator if the chain id changes. + bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; + uint256 private immutable _CACHED_CHAIN_ID; + bytes32 private immutable _HASHED_NAME; + + /// @dev equal to keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)") + bytes32 private constant _TYPE_HASH = 0x8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866; + + constructor(string memory name) { + _HASHED_NAME = keccak256(bytes(name)); + + _CACHED_CHAIN_ID = block.chainid; + _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(); + } + + /// @notice Returns the domain separator for the current chain. + /// @dev Uses cached version if chainid is unchanged from construction. + function DOMAIN_SEPARATOR() public view override returns (bytes32) { + return block.chainid == _CACHED_CHAIN_ID ? _CACHED_DOMAIN_SEPARATOR : _buildDomainSeparator(); + } + + /// @notice Builds a domain separator using the current chainId and contract address. + function _buildDomainSeparator() private view returns (bytes32) { + return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, block.chainid, address(this))); + } + + /// @notice Creates an EIP-712 typed data hash + function _hashTypedData(bytes32 dataHash) internal view returns (bytes32 digest) { + // equal to keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash)); + bytes32 domainSeparator = DOMAIN_SEPARATOR(); + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, hex"1901") + mstore(add(fmp, 0x02), domainSeparator) + mstore(add(fmp, 0x22), dataHash) + digest := keccak256(fmp, 0x42) + } + } +} diff --git a/src/base/ERC721Permit.sol b/src/base/ERC721Permit.sol index 7a4acc4d..1605b79c 100644 --- a/src/base/ERC721Permit.sol +++ b/src/base/ERC721Permit.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {ERC721} from "solmate/src/tokens/ERC721.sol"; -import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {EIP712} from "./EIP712.sol"; import {ERC721PermitHashLibrary} from "../libraries/ERC721PermitHash.sol"; import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; @@ -16,15 +16,7 @@ abstract contract ERC721Permit is ERC721, IERC721Permit, EIP712, UnorderedNonce using SignatureVerification for bytes; /// @notice Computes the nameHash and versionHash - constructor(string memory name_, string memory symbol_, string memory version_) - ERC721(name_, symbol_) - EIP712(name_, version_) - {} - - /// @inheritdoc IERC721Permit - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) EIP712(name_) {} /// @inheritdoc IERC721Permit function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) @@ -37,7 +29,7 @@ abstract contract ERC721Permit is ERC721, IERC721Permit, EIP712, UnorderedNonce if (spender == owner) revert NoSelfPermit(); bytes32 hash = ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline); - signature.verify(_hashTypedDataV4(hash), owner); + signature.verify(_hashTypedData(hash), owner); _useUnorderedNonce(owner, nonce); _approve(owner, spender, tokenId); diff --git a/src/interfaces/IEIP712.sol b/src/interfaces/IEIP712.sol new file mode 100644 index 00000000..355c443f --- /dev/null +++ b/src/interfaces/IEIP712.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IEIP712 { + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/src/interfaces/IERC721Permit.sol b/src/interfaces/IERC721Permit.sol index 646c1af0..29a6fe2a 100644 --- a/src/interfaces/IERC721Permit.sol +++ b/src/interfaces/IERC721Permit.sol @@ -8,10 +8,6 @@ interface IERC721Permit { error NoSelfPermit(); error Unauthorized(); - /// @notice The domain separator used in the permit signature - /// @return The domain seperator used in encoding of permit signature - function DOMAIN_SEPARATOR() external view returns (bytes32); - /// @notice Approve of a specific token ID for spending by spender via signature /// @param spender The account that is being approved /// @param tokenId The ID of the token that is being approved for spending diff --git a/test/EIP712.t.sol b/test/EIP712.t.sol new file mode 100644 index 00000000..badf6c9c --- /dev/null +++ b/test/EIP712.t.sol @@ -0,0 +1,47 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; + +import {EIP712} from "../src/base/EIP712.sol"; + +contract EIP712Test is EIP712, Test { + constructor() EIP712("EIP712Test") {} + + function setUp() public {} + + function test_domainSeparator() public view { + assertEq( + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), + keccak256(bytes("EIP712Test")), + block.chainid, + address(this) + ) + ) + ); + } + + function test_hashTypedData() public view { + bytes32 dataHash = keccak256(abi.encodePacked("data")); + assertEq(_hashTypedData(dataHash), keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash))); + } + + function test_rebuildDomainSeparator() public { + uint256 chainId = 4444; + vm.chainId(chainId); + assertEq( + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), + keccak256(bytes("EIP712Test")), + chainId, + address(this) + ) + ) + ); + } +} diff --git a/test/ERC721Permit.t.sol b/test/ERC721Permit.t.sol index 16f4f39f..53e3ecf8 100644 --- a/test/ERC721Permit.t.sol +++ b/test/ERC721Permit.t.sol @@ -19,13 +19,12 @@ contract ERC721PermitTest is Test { string constant name = "Mock ERC721Permit"; string constant symbol = "MOCK721"; - string constant version = "1"; function setUp() public { (alice, alicePK) = makeAddrAndKey("ALICE"); (bob, bobPK) = makeAddrAndKey("BOB"); - erc721Permit = new MockERC721Permit(name, symbol, version); + erc721Permit = new MockERC721Permit(name, symbol); } // --- Test the overriden approval --- @@ -75,12 +74,11 @@ contract ERC721PermitTest is Test { function test_domainSeparator() public view { assertEq( - IERC721Permit(address(erc721Permit)).DOMAIN_SEPARATOR(), + erc721Permit.DOMAIN_SEPARATOR(), keccak256( abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), - keccak256(bytes(version)), block.chainid, address(erc721Permit) ) diff --git a/test/mocks/MockERC721Permit.sol b/test/mocks/MockERC721Permit.sol index d6e82a3e..ba1c732d 100644 --- a/test/mocks/MockERC721Permit.sol +++ b/test/mocks/MockERC721Permit.sol @@ -6,7 +6,7 @@ import {ERC721Permit} from "../../src/base/ERC721Permit.sol"; contract MockERC721Permit is ERC721Permit { uint256 public lastTokenId; - constructor(string memory name, string memory symbol, string memory version) ERC721Permit(name, symbol, version) {} + constructor(string memory name, string memory symbol) ERC721Permit(name, symbol) {} function tokenURI(uint256) public pure override returns (string memory) { return ""; diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol index 7da608a2..91a22a25 100644 --- a/test/position-managers/Permit.t.sol +++ b/test/position-managers/Permit.t.sol @@ -60,12 +60,11 @@ contract PermitTest is Test, PosmTestSetup { function test_domainSeparator() public view { assertEq( - IERC721Permit(address(lpm)).DOMAIN_SEPARATOR(), + ERC721Permit(address(lpm)).DOMAIN_SEPARATOR(), keccak256( abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), keccak256("Uniswap V4 Positions NFT"), // storage is private on EIP712.sol so we need to hardcode these - keccak256("1"), block.chainid, address(lpm) ) From 60a983ef740208fe096ab95bbdb2dd1dd1991a98 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:29:52 +0100 Subject: [PATCH 24/52] Take command in router (#261) * take in router, plus remove safecast * nested if optimisation in posm * test of take command * lint * bring back contract balance and atspec --- ...p_settleFromCaller_takeAllToMsgSender.snap | 2 +- ...eFromCaller_takeAllToSpecifiedAddress.snap | 2 +- ..._settleWithBalance_takeAllToMsgSender.snap | 2 +- ...WithBalance_takeAllToSpecifiedAddress.snap | 2 +- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- ...Router_ExactOut1Hop_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOut1Hop_nativeOut.snap | 2 +- .../V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .../V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .../V4Router_ExactOut2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOut3Hops_nativeIn.snap | 2 +- .../V4Router_ExactOut3Hops_nativeOut.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- ...r_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/PositionManager.sol | 130 +++++++++--------- src/V4Router.sol | 10 +- src/base/DeltaResolver.sol | 6 +- test/router/V4Router.t.sol | 36 ++++- 66 files changed, 176 insertions(+), 130 deletions(-) diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index 57e24db8..762bc2b1 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -134555 \ No newline at end of file +134520 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index 85681dc4..460e1677 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -134693 \ No newline at end of file +134658 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index f0186102..16684144 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -127702 \ No newline at end of file +127690 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index f85df8a0..5451a329 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -127840 \ No newline at end of file +127828 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index a60858d1..492cae30 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47184 \ No newline at end of file +47167 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 3a7e7f99..6ad16fc4 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -47001 \ No newline at end of file +46984 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 0828f010..130b6ee7 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123056 \ No newline at end of file +122980 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 1b6c526f..1cddf5e5 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122754 \ No newline at end of file +122689 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 85bf5088..211bfaf9 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130134 \ No newline at end of file +130058 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 95bc49cb..58bede9f 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129832 \ No newline at end of file +129768 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 7940b8e0..3b272ddf 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141406 \ No newline at end of file +141357 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 214a92f6..3352dfe7 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150254 \ No newline at end of file +150205 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index 214a92f6..3352dfe7 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150254 \ No newline at end of file +150205 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 0d682131..d188e1fa 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149865 \ No newline at end of file +149830 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 8c1915ba..17b24e44 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108600 \ No newline at end of file +108560 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index a877133d..cbd40ace 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115797 \ No newline at end of file +115748 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index 241b44a3..1c42c2a2 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115408 \ No newline at end of file +115373 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index eb10283f..de137414 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134193 \ No newline at end of file +134137 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index cdfc3d41..ab7b40f5 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126932 \ No newline at end of file +126876 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 934de5bc..07d19a82 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128513 \ No newline at end of file +128464 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index 59ec6411..60fee948 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116874 \ No newline at end of file +116639 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 9da786cd..66fe2ca8 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152360 \ No newline at end of file +152311 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 867577e2..d3de4546 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151601 \ No newline at end of file +151566 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 617e1740..a93023c8 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134160 \ No newline at end of file +134111 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index d8adae8f..87776e95 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130325 \ No newline at end of file +130350 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 69f35938..a4c4b715 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171019 \ No newline at end of file +170970 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 17997e03..bafd15d6 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140999 \ No newline at end of file +140950 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 7f1af379..d2181e08 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336838 \ No newline at end of file +336789 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index a02553e4..db016355 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345367 \ No newline at end of file +345258 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 1e3a2821..acddf123 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344908 \ No newline at end of file +344813 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index e4f0f7de..dfd3f6c8 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314820 \ No newline at end of file +314771 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index fc1ae160..04f38148 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315462 \ No newline at end of file +315413 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index e85f6f57..b0645399 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241044 \ No newline at end of file +240995 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 4a9bb521..661a3f0e 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -371190 \ No newline at end of file +370975 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index f0fe4b37..bdad41bc 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320838 \ No newline at end of file +320789 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 1b05c224..6146a741 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372138 \ No newline at end of file +372089 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 961588e5..6fcfb86a 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371517 \ No newline at end of file +371482 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 806bba59..93b17f36 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416535 \ No newline at end of file +416486 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 79693d11..ac992df5 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8116 \ No newline at end of file +8199 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index 8ef5d869..923bb90b 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -120604 \ No newline at end of file +120569 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index df894b65..d4cf2340 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -119799 \ No newline at end of file +119764 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index 93c415cb..dfc10e53 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -128671 \ No newline at end of file +128636 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 2b629d08..e3bda2b6 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135501 \ No newline at end of file +135466 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 87bac22b..92b96ddc 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -187008 \ No newline at end of file +186973 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index a043c47c..c29999bd 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -178943 \ No newline at end of file +178908 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 911cd648..70ec54f0 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238540 \ No newline at end of file +238505 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index 644d8cdd..469d47f6 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -230499 \ No newline at end of file +230464 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index 57e24db8..762bc2b1 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134555 \ No newline at end of file +134520 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index 5f290a78..2e31b245 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119658 \ No newline at end of file +119623 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index cb7d107b..4db689f3 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118836 \ No newline at end of file +118801 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index d215d2c2..b8ac3ed6 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -126262 \ No newline at end of file +126227 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index 27a2601a..3e4bc907 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -120540 \ No newline at end of file +120505 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index 6bc4c749..1dc41920 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -129412 \ No newline at end of file +129377 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index a9fb35ce..bcbd95de 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -134213 \ No newline at end of file +134178 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index f310ec6e..b807f27b 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186323 \ No newline at end of file +186288 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index 246b3b0b..a5edf43b 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -183173 \ No newline at end of file +183138 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 88228ad4..ce17195c 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -238473 \ No newline at end of file +238438 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index d5d40ded..f82bf262 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -235347 \ No newline at end of file +235312 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index 70351463..8a909d9b 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -229625 \ No newline at end of file +229590 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index feaddcf8..04fea4fd 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -132976 \ No newline at end of file +132941 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index 81a221be..95370c23 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -125025 \ No newline at end of file +124990 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index 523122f7..23c6d01a 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -119316 \ No newline at end of file +119281 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 39bfe195..10315a28 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -128,69 +128,75 @@ contract PositionManager is } function _handleAction(uint256 action, bytes calldata params) internal virtual override { - if (action == Actions.INCREASE_LIQUIDITY) { - ( - uint256 tokenId, - PositionConfig calldata config, - uint256 liquidity, - uint128 amount0Max, - uint128 amount1Max, - bytes calldata hookData - ) = params.decodeModifyLiquidityParams(); - _increase(tokenId, config, liquidity, amount0Max, amount1Max, hookData); - } else if (action == Actions.DECREASE_LIQUIDITY) { - ( - uint256 tokenId, - PositionConfig calldata config, - uint256 liquidity, - uint128 amount0Min, - uint128 amount1Min, - bytes calldata hookData - ) = params.decodeModifyLiquidityParams(); - _decrease(tokenId, config, liquidity, amount0Min, amount1Min, hookData); - } else if (action == Actions.MINT_POSITION) { - ( - PositionConfig calldata config, - uint256 liquidity, - uint128 amount0Max, - uint128 amount1Max, - address owner, - bytes calldata hookData - ) = params.decodeMintParams(); - _mint(config, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData); - } else if (action == Actions.CLOSE_CURRENCY) { - Currency currency = params.decodeCurrency(); - _close(currency); - } else if (action == Actions.CLEAR_OR_TAKE) { - (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); - _clearOrTake(currency, amountMax); - } else if (action == Actions.BURN_POSITION) { - // Will automatically decrease liquidity to 0 if the position is not already empty. - ( - uint256 tokenId, - PositionConfig calldata config, - uint128 amount0Min, - uint128 amount1Min, - bytes calldata hookData - ) = params.decodeBurnParams(); - _burn(tokenId, config, amount0Min, amount1Min, hookData); - } else if (action == Actions.SETTLE) { - (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); - _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); - } else if (action == Actions.SETTLE_PAIR) { - (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); - _settlePair(currency0, currency1); - } else if (action == Actions.TAKE_PAIR) { - (Currency currency0, Currency currency1, address to) = params.decodeCurrencyPairAndAddress(); - _takePair(currency0, currency1, to); - } else if (action == Actions.SWEEP) { - (Currency currency, address to) = params.decodeCurrencyAndAddress(); - _sweep(currency, _mapRecipient(to)); - } else if (action == Actions.TAKE) { - (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); - _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + if (action < Actions.SETTLE) { + if (action == Actions.INCREASE_LIQUIDITY) { + ( + uint256 tokenId, + PositionConfig calldata config, + uint256 liquidity, + uint128 amount0Max, + uint128 amount1Max, + bytes calldata hookData + ) = params.decodeModifyLiquidityParams(); + _increase(tokenId, config, liquidity, amount0Max, amount1Max, hookData); + } else if (action == Actions.DECREASE_LIQUIDITY) { + ( + uint256 tokenId, + PositionConfig calldata config, + uint256 liquidity, + uint128 amount0Min, + uint128 amount1Min, + bytes calldata hookData + ) = params.decodeModifyLiquidityParams(); + _decrease(tokenId, config, liquidity, amount0Min, amount1Min, hookData); + } else if (action == Actions.MINT_POSITION) { + ( + PositionConfig calldata config, + uint256 liquidity, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) = params.decodeMintParams(); + _mint(config, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData); + } else if (action == Actions.BURN_POSITION) { + // Will automatically decrease liquidity to 0 if the position is not already empty. + ( + uint256 tokenId, + PositionConfig calldata config, + uint128 amount0Min, + uint128 amount1Min, + bytes calldata hookData + ) = params.decodeBurnParams(); + _burn(tokenId, config, amount0Min, amount1Min, hookData); + } else { + revert UnsupportedAction(action); + } } else { - revert UnsupportedAction(action); + if (action == Actions.CLOSE_CURRENCY) { + Currency currency = params.decodeCurrency(); + _close(currency); + } else if (action == Actions.CLEAR_OR_TAKE) { + (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); + _clearOrTake(currency, amountMax); + } else if (action == Actions.SETTLE) { + (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); + _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + } else if (action == Actions.SETTLE_PAIR) { + (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); + _settlePair(currency0, currency1); + } else if (action == Actions.TAKE_PAIR) { + (Currency currency0, Currency currency1, address to) = params.decodeCurrencyPairAndAddress(); + _takePair(currency0, currency1, to); + } else if (action == Actions.SWEEP) { + (Currency currency, address to) = params.decodeCurrencyAndAddress(); + _sweep(currency, _mapRecipient(to)); + } else if (action == Actions.TAKE) { + (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + } else { + revert UnsupportedAction(action); + } } } diff --git a/src/V4Router.sol b/src/V4Router.sol index a1e4908f..168846f6 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -54,13 +54,15 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { Currency currency = params.decodeCurrency(); // TODO should it have a maxAmountOut added slippage protection? _settle(currency, msgSender(), _getFullDebt(currency)); + } else if (action == Actions.TAKE_ALL) { + (Currency currency, address recipient) = params.decodeCurrencyAndAddress(); + _take(currency, _mapRecipient(recipient), _getFullCredit(currency)); } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); - } else if (action == Actions.TAKE_ALL) { - (Currency currency, address recipient) = params.decodeCurrencyAndAddress(); - uint256 amount = _getFullCredit(currency); - _take(currency, _mapRecipient(recipient), amount); + } else if (action == Actions.TAKE) { + (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); } else if (action == Actions.TAKE_PORTION) { (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _getFullCredit(currency).calculatePortion(bips)); diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 9521cd82..e202753d 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -82,12 +82,16 @@ abstract contract DeltaResolver is ImmutableState { /// @notice Calculates the amount for a take action function _mapTakeAmount(uint256 amount, Currency currency) internal view returns (uint256) { if (amount == Constants.OPEN_DELTA) { - return _getFullCredit(currency).toUint128(); + return _getFullCredit(currency); } return amount; } /// @notice Calculates the amount for a swap action + /// @dev This is to be used for swaps where the input amount isn't known before the transaction, and + /// isn't possible using a v4 multi-hop command. + /// For example USDC-v2->DAI-v4->USDT. This intermediate DAI amount could be swapped using CONTRACT_BALANCE + /// or settled using CONTRACT_BALANCE then swapped using OPEN_DELTA. function _mapInputAmount(uint128 amount, Currency currency) internal view returns (uint128) { if (amount == Constants.CONTRACT_BALANCE) { return currency.balanceOfSelf().toUint128(); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index d9627323..9a8705d3 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -85,7 +85,7 @@ contract V4RouterTest is RoutingTestHelpers { // This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4 // swap is before another swap on v2/v3 - function test_swapExactInputSingle_zeroForOne_takeToRouter() public { + function test_swapExactInputSingle_zeroForOne_takeAllToRouter() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -111,6 +111,40 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter, outputBalanceBefore); } + // This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4 + // swap is before another swap on v2/v3 + function test_swapExactInputSingle_zeroForOne_takeToRouter() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); + // take the entire open delta to the router's address + plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, Constants.ADDRESS_THIS, Constants.OPEN_DELTA)); + bytes memory data = plan.encode(); + + // the router holds no funds before + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); + uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); + + router.executeActions(data); + + // the output tokens have been left in the router + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), expectedAmountOut); + uint256 inputBalanceAfter = key0.currency0.balanceOfSelf(); + uint256 outputBalanceAfter = key0.currency1.balanceOfSelf(); + + assertEq(inputBalanceBefore - inputBalanceAfter, amountIn); + // this contract's output balance has not changed because funds went to the router + assertEq(outputBalanceAfter, outputBalanceBefore); + } + function test_swapExactInputSingle_oneForZero() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; From dfa18650a6a4a968a04c1a5c6b8951e63c29a881 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:49:35 -0400 Subject: [PATCH 25/52] posm: Rename File Collisions (#263) * rename files to avoid hardhat collisions * rename colliding files with _v4 suffix * remove unused import --- src/PositionManager.sol | 12 ++++++------ src/base/{EIP712.sol => EIP712_v4.sol} | 4 ++-- src/base/{ERC721Permit.sol => ERC721Permit_v4.sol} | 11 +++++------ src/base/{Multicall.sol => Multicall_v4.sol} | 8 ++++---- src/interfaces/{IEIP712.sol => IEIP712_v4.sol} | 2 +- .../{IERC721Permit.sol => IERC721Permit_v4.sol} | 2 +- src/interfaces/{IMulticall.sol => IMulticall_v4.sol} | 4 ++-- test/EIP712.t.sol | 6 +++--- test/ERC721Permit.t.sol | 8 ++++---- test/mocks/MockERC721Permit.sol | 6 +++--- test/mocks/MockMulticall.sol | 4 ++-- test/position-managers/Permit.t.sol | 6 +++--- test/position-managers/PositionManager.gas.t.sol | 4 ++-- .../PositionManager.multicall.t.sol | 8 ++++---- 14 files changed, 42 insertions(+), 43 deletions(-) rename src/base/{EIP712.sol => EIP712_v4.sol} (96%) rename src/base/{ERC721Permit.sol => ERC721Permit_v4.sol} (88%) rename src/base/{Multicall.sol => Multicall_v4.sol} (80%) rename src/interfaces/{IEIP712.sol => IEIP712_v4.sol} (84%) rename src/interfaces/{IERC721Permit.sol => IERC721Permit_v4.sol} (97%) rename src/interfaces/{IMulticall.sol => IMulticall_v4.sol} (91%) diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 10315a28..720275d4 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -15,10 +15,10 @@ import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; -import {ERC721Permit} from "./base/ERC721Permit.sol"; +import {ERC721Permit_v4} from "./base/ERC721Permit_v4.sol"; import {ReentrancyLock} from "./base/ReentrancyLock.sol"; import {IPositionManager} from "./interfaces/IPositionManager.sol"; -import {Multicall} from "./base/Multicall.sol"; +import {Multicall_v4} from "./base/Multicall_v4.sol"; import {PoolInitializer} from "./base/PoolInitializer.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; import {PositionConfig, PositionConfigLibrary} from "./libraries/PositionConfig.sol"; @@ -32,9 +32,9 @@ import {SlippageCheckLibrary} from "./libraries/SlippageCheck.sol"; contract PositionManager is IPositionManager, - ERC721Permit, + ERC721Permit_v4, PoolInitializer, - Multicall, + Multicall_v4, DeltaResolver, ReentrancyLock, BaseActionsRouter, @@ -60,7 +60,7 @@ contract PositionManager is constructor(IPoolManager _poolManager, IAllowanceTransfer _permit2) BaseActionsRouter(_poolManager) Permit2Forwarder(_permit2) - ERC721Permit("Uniswap V4 Positions NFT", "UNI-V4-POSM") + ERC721Permit_v4("Uniswap V4 Positions NFT", "UNI-V4-POSM") {} /// @notice Reverts if the deadline has passed @@ -294,7 +294,7 @@ contract PositionManager is _take(currency1, recipient, _getFullCredit(currency1)); } - /// @dev this is overloaded with ERC721Permit._burn + /// @dev this is overloaded with ERC721Permit_v4._burn function _burn( uint256 tokenId, PositionConfig calldata config, diff --git a/src/base/EIP712.sol b/src/base/EIP712_v4.sol similarity index 96% rename from src/base/EIP712.sol rename to src/base/EIP712_v4.sol index 5b4ea0f5..48e607bd 100644 --- a/src/base/EIP712.sol +++ b/src/base/EIP712_v4.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IEIP712} from "../interfaces/IEIP712.sol"; +import {IEIP712_v4} from "../interfaces/IEIP712_v4.sol"; /// @notice Generic EIP712 implementation /// @dev Maintains cross-chain replay protection in the event of a fork /// @dev Should not be delegatecall'd because DOMAIN_SEPARATOR returns the cached hash and does not recompute with the delegatecallers address /// @dev Reference: https://github.com/Uniswap/permit2/blob/main/src/EIP712.sol /// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol -contract EIP712 is IEIP712 { +contract EIP712_v4 is IEIP712_v4 { // Cache the domain separator as an immutable value, but also store the chain id that it // corresponds to, in order to invalidate the cached domain separator if the chain id changes. bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; diff --git a/src/base/ERC721Permit.sol b/src/base/ERC721Permit_v4.sol similarity index 88% rename from src/base/ERC721Permit.sol rename to src/base/ERC721Permit_v4.sol index 1605b79c..7c35a570 100644 --- a/src/base/ERC721Permit.sol +++ b/src/base/ERC721Permit_v4.sol @@ -1,24 +1,23 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.24; -import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {ERC721} from "solmate/src/tokens/ERC721.sol"; -import {EIP712} from "./EIP712.sol"; +import {EIP712_v4} from "./EIP712_v4.sol"; import {ERC721PermitHashLibrary} from "../libraries/ERC721PermitHash.sol"; import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; -import {IERC721Permit} from "../interfaces/IERC721Permit.sol"; +import {IERC721Permit_v4} from "../interfaces/IERC721Permit_v4.sol"; import {UnorderedNonce} from "./UnorderedNonce.sol"; /// @title ERC721 with permit /// @notice Nonfungible tokens that support an approve via signature, i.e. permit -abstract contract ERC721Permit is ERC721, IERC721Permit, EIP712, UnorderedNonce { +abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, UnorderedNonce { using SignatureVerification for bytes; /// @notice Computes the nameHash and versionHash - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) EIP712(name_) {} + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) EIP712_v4(name_) {} - /// @inheritdoc IERC721Permit + /// @inheritdoc IERC721Permit_v4 function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) external payable diff --git a/src/base/Multicall.sol b/src/base/Multicall_v4.sol similarity index 80% rename from src/base/Multicall.sol rename to src/base/Multicall_v4.sol index 7f97d045..b6190a33 100644 --- a/src/base/Multicall.sol +++ b/src/base/Multicall_v4.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.19; -import {IMulticall} from "../interfaces/IMulticall.sol"; +import {IMulticall_v4} from "../interfaces/IMulticall_v4.sol"; -/// @title Multicall +/// @title Multicall_v4 /// @notice Enables calling multiple methods in a single call to the contract -abstract contract Multicall is IMulticall { - /// @inheritdoc IMulticall +abstract contract Multicall_v4 is IMulticall_v4 { + /// @inheritdoc IMulticall_v4 function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { diff --git a/src/interfaces/IEIP712.sol b/src/interfaces/IEIP712_v4.sol similarity index 84% rename from src/interfaces/IEIP712.sol rename to src/interfaces/IEIP712_v4.sol index 355c443f..622f7c06 100644 --- a/src/interfaces/IEIP712.sol +++ b/src/interfaces/IEIP712_v4.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -interface IEIP712 { +interface IEIP712_v4 { function DOMAIN_SEPARATOR() external view returns (bytes32); } diff --git a/src/interfaces/IERC721Permit.sol b/src/interfaces/IERC721Permit_v4.sol similarity index 97% rename from src/interfaces/IERC721Permit.sol rename to src/interfaces/IERC721Permit_v4.sol index 29a6fe2a..3fa2894f 100644 --- a/src/interfaces/IERC721Permit.sol +++ b/src/interfaces/IERC721Permit_v4.sol @@ -3,7 +3,7 @@ pragma solidity >=0.7.5; /// @title ERC721 with permit /// @notice Extension to ERC721 that includes a permit function for signature based approvals -interface IERC721Permit { +interface IERC721Permit_v4 { error DeadlineExpired(); error NoSelfPermit(); error Unauthorized(); diff --git a/src/interfaces/IMulticall.sol b/src/interfaces/IMulticall_v4.sol similarity index 91% rename from src/interfaces/IMulticall.sol rename to src/interfaces/IMulticall_v4.sol index dfa9db24..f1629f12 100644 --- a/src/interfaces/IMulticall.sol +++ b/src/interfaces/IMulticall_v4.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -/// @title Multicall interface +/// @title Multicall_v4 interface /// @notice Enables calling multiple methods in a single call to the contract -interface IMulticall { +interface IMulticall_v4 { /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed /// @dev The `msg.value` should not be trusted for any method callable from multicall. /// @param data The encoded function data for each of the calls to make to this contract diff --git a/test/EIP712.t.sol b/test/EIP712.t.sol index badf6c9c..390a7c9b 100644 --- a/test/EIP712.t.sol +++ b/test/EIP712.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; -import {EIP712} from "../src/base/EIP712.sol"; +import {EIP712_v4} from "../src/base/EIP712_v4.sol"; -contract EIP712Test is EIP712, Test { - constructor() EIP712("EIP712Test") {} +contract EIP712Test is EIP712_v4, Test { + constructor() EIP712_v4("EIP712Test") {} function setUp() public {} diff --git a/test/ERC721Permit.t.sol b/test/ERC721Permit.t.sol index 53e3ecf8..726709b1 100644 --- a/test/ERC721Permit.t.sol +++ b/test/ERC721Permit.t.sol @@ -6,7 +6,7 @@ import {SignatureVerification} from "permit2/src/libraries/SignatureVerification import {ERC721PermitHashLibrary} from "../src/libraries/ERC721PermitHash.sol"; import {MockERC721Permit} from "./mocks/MockERC721Permit.sol"; -import {IERC721Permit} from "../src/interfaces/IERC721Permit.sol"; +import {IERC721Permit_v4} from "../src/interfaces/IERC721Permit_v4.sol"; import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {UnorderedNonce} from "../src/base/UnorderedNonce.sol"; @@ -17,7 +17,7 @@ contract ERC721PermitTest is Test { address bob; uint256 bobPK; - string constant name = "Mock ERC721Permit"; + string constant name = "Mock ERC721Permit_v4"; string constant symbol = "MOCK721"; function setUp() public { @@ -54,7 +54,7 @@ contract ERC721PermitTest is Test { function test_fuzz_approve_unauthorizedRevert(address caller) public { uint256 tokenId = erc721Permit.mint(); vm.prank(caller); - if (caller != address(this)) vm.expectRevert(IERC721Permit.Unauthorized.selector); + if (caller != address(this)) vm.expectRevert(IERC721Permit_v4.Unauthorized.selector); erc721Permit.approve(address(this), tokenId); } @@ -252,7 +252,7 @@ contract ERC721PermitTest is Test { // -- Permit but deadline expired -- // vm.startPrank(spender); - vm.expectRevert(IERC721Permit.DeadlineExpired.selector); + vm.expectRevert(IERC721Permit_v4.DeadlineExpired.selector); erc721Permit.permit(spender, tokenId, deadline, nonce, signature); vm.stopPrank(); diff --git a/test/mocks/MockERC721Permit.sol b/test/mocks/MockERC721Permit.sol index ba1c732d..b76bc547 100644 --- a/test/mocks/MockERC721Permit.sol +++ b/test/mocks/MockERC721Permit.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import {ERC721Permit} from "../../src/base/ERC721Permit.sol"; +import {ERC721Permit_v4} from "../../src/base/ERC721Permit_v4.sol"; -contract MockERC721Permit is ERC721Permit { +contract MockERC721Permit is ERC721Permit_v4 { uint256 public lastTokenId; - constructor(string memory name, string memory symbol) ERC721Permit(name, symbol) {} + constructor(string memory name, string memory symbol) ERC721Permit_v4(name, symbol) {} function tokenURI(uint256) public pure override returns (string memory) { return ""; diff --git a/test/mocks/MockMulticall.sol b/test/mocks/MockMulticall.sol index 1a936deb..38ddaa09 100644 --- a/test/mocks/MockMulticall.sol +++ b/test/mocks/MockMulticall.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "../../src/base/Multicall.sol"; +import "../../src/base/Multicall_v4.sol"; /// @dev If MockMulticall is to PositionManager, then RevertContract is to PoolManager contract RevertContract { @@ -21,7 +21,7 @@ contract RevertContract { } } -contract MockMulticall is Multicall { +contract MockMulticall is Multicall_v4 { error Error4Bytes(); // 4 bytes of selector error Error36Bytes(uint8 a); // 32 bytes + 4 bytes of selector error Error68Bytes(uint256 a, uint256 b); // 64 bytes + 4 bytes of selector diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol index 91a22a25..8de6b6c5 100644 --- a/test/position-managers/Permit.t.sol +++ b/test/position-managers/Permit.t.sol @@ -13,8 +13,8 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {IERC721Permit} from "../../src/interfaces/IERC721Permit.sol"; -import {ERC721Permit} from "../../src/base/ERC721Permit.sol"; +import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; +import {ERC721Permit_v4} from "../../src/base/ERC721Permit_v4.sol"; import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; @@ -60,7 +60,7 @@ contract PermitTest is Test, PosmTestSetup { function test_domainSeparator() public view { assertEq( - ERC721Permit(address(lpm)).DOMAIN_SEPARATOR(), + ERC721Permit_v4(address(lpm)).DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 299d4697..cc009664 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -20,7 +20,7 @@ import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; -import {IMulticall} from "../../src/interfaces/IMulticall.sol"; +import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Constants} from "../../src/libraries/Constants.sol"; @@ -368,7 +368,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); - IMulticall(lpm).multicall(calls); + IMulticall_v4(lpm).multicall(calls); snapLastCall("PositionManager_multicall_initialize_mint"); } diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index 8c030164..4e86c280 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -22,14 +22,14 @@ import {PoolInitializer} from "../../src/base/PoolInitializer.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; -import {IMulticall} from "../../src/interfaces/IMulticall.sol"; +import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol"; import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; import {Constants} from "../../src/libraries/Constants.sol"; -import {IERC721Permit} from "../../src/interfaces/IERC721Permit.sol"; +import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTestSetup, LiquidityFuzzers { using FixedPointMathLib for uint256; @@ -102,7 +102,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); - IMulticall(address(lpm)).multicall(calls); + IMulticall_v4(address(lpm)).multicall(calls); // test swap, doesn't revert, showing the pool was initialized int256 amountSpecified = -1e18; @@ -204,7 +204,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest // bob gives himself permission and decreases liquidity bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector( - IERC721Permit(lpm).permit.selector, bob, tokenId, block.timestamp + 1, nonce, signature + IERC721Permit_v4(lpm).permit.selector, bob, tokenId, block.timestamp + 1, nonce, signature ); uint256 liquidityToRemove = 0.4444e18; bytes memory actions = getDecreaseEncoded(tokenId, config, liquidityToRemove, ZERO_BYTES); From 2d07bd4eea612de8147010a7fc215decd632c826 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Sun, 4 Aug 2024 20:59:20 +0100 Subject: [PATCH 26/52] Align constants with UR (#267) * Align constants with UR * rename * another rename --- ..._settleWithBalance_takeAllToMsgSender.snap | 2 +- ...WithBalance_takeAllToSpecifiedAddress.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- src/base/BaseActionsRouter.sol | 6 +- src/base/DeltaResolver.sol | 12 +-- .../{Constants.sol => ActionConstants.sol} | 7 +- test/BaseActionsRouter.t.sol | 6 +- test/position-managers/Execute.t.sol | 22 ++-- .../position-managers/IncreaseLiquidity.t.sol | 26 ++--- test/position-managers/NativeToken.t.sol | 41 +++---- .../PositionManager.gas.t.sol | 101 +++++++++++------- .../PositionManager.multicall.t.sol | 6 +- test/position-managers/PositionManager.t.sol | 68 ++++++------ test/router/Payments.gas.t.sol | 10 +- test/router/Payments.t.sol | 4 +- test/router/V4Router.gas.t.sol | 48 ++++----- test/router/V4Router.t.sol | 23 ++-- test/shared/Planner.sol | 6 +- test/shared/RoutingTestHelpers.sol | 6 +- 21 files changed, 226 insertions(+), 176 deletions(-) rename src/libraries/{Constants.sol => ActionConstants.sol} (61%) diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index 16684144..aec2b78e 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -127690 \ No newline at end of file +127708 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index 5451a329..424c0794 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -127828 \ No newline at end of file +127846 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index 60fee948..120eefd1 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116639 \ No newline at end of file +116603 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 661a3f0e..e6d8ab99 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370975 \ No newline at end of file +370951 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index ac992df5..2ba531ff 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8199 \ No newline at end of file +8165 \ No newline at end of file diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index fb8d020e..821fa0e0 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -5,7 +5,7 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {SafeCallback} from "./SafeCallback.sol"; import {CalldataDecoder} from "../libraries/CalldataDecoder.sol"; import {Actions} from "../libraries/Actions.sol"; -import {Constants} from "../libraries/Constants.sol"; +import {ActionConstants} from "../libraries/ActionConstants.sol"; /// @notice Abstract contract for performing a combination of actions on Uniswap v4. /// @dev Suggested uint256 action values are defined in Actions.sol, however any definition can be used @@ -57,9 +57,9 @@ abstract contract BaseActionsRouter is SafeCallback { /// @notice Calculates the address for a action function _mapRecipient(address recipient) internal view returns (address) { - if (recipient == Constants.MSG_SENDER) { + if (recipient == ActionConstants.MSG_SENDER) { return msgSender(); - } else if (recipient == Constants.ADDRESS_THIS) { + } else if (recipient == ActionConstants.ADDRESS_THIS) { return address(this); } else { return recipient; diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index e202753d..4269092e 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -6,7 +6,7 @@ import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientSta import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {ImmutableState} from "./ImmutableState.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {Constants} from "../libraries/Constants.sol"; +import {ActionConstants} from "../libraries/ActionConstants.sol"; /// @notice Abstract contract used to sync, send, and settle funds to the pool manager /// @dev Note that sync() is called before any erc-20 transfer in `settle`. @@ -71,9 +71,9 @@ abstract contract DeltaResolver is ImmutableState { /// @notice Calculates the amount for a settle action function _mapSettleAmount(uint256 amount, Currency currency) internal view returns (uint256) { - if (amount == Constants.CONTRACT_BALANCE) { + if (amount == ActionConstants.CONTRACT_BALANCE) { return currency.balanceOfSelf(); - } else if (amount == Constants.OPEN_DELTA) { + } else if (amount == ActionConstants.OPEN_DELTA) { return _getFullDebt(currency); } return amount; @@ -81,7 +81,7 @@ abstract contract DeltaResolver is ImmutableState { /// @notice Calculates the amount for a take action function _mapTakeAmount(uint256 amount, Currency currency) internal view returns (uint256) { - if (amount == Constants.OPEN_DELTA) { + if (amount == ActionConstants.OPEN_DELTA) { return _getFullCredit(currency); } return amount; @@ -93,9 +93,9 @@ abstract contract DeltaResolver is ImmutableState { /// For example USDC-v2->DAI-v4->USDT. This intermediate DAI amount could be swapped using CONTRACT_BALANCE /// or settled using CONTRACT_BALANCE then swapped using OPEN_DELTA. function _mapInputAmount(uint128 amount, Currency currency) internal view returns (uint128) { - if (amount == Constants.CONTRACT_BALANCE) { + if (amount == ActionConstants.CONTRACT_BALANCE) { return currency.balanceOfSelf().toUint128(); - } else if (amount == Constants.OPEN_DELTA) { + } else if (amount == ActionConstants.OPEN_DELTA) { return _getFullCredit(currency).toUint128(); } return amount; diff --git a/src/libraries/Constants.sol b/src/libraries/ActionConstants.sol similarity index 61% rename from src/libraries/Constants.sol rename to src/libraries/ActionConstants.sol index edeb0ea3..504d9052 100644 --- a/src/libraries/Constants.sol +++ b/src/libraries/ActionConstants.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.19; -library Constants { +library ActionConstants { /// @notice used to signal that an action should use the input value of the open delta on the pool manager /// or of the balance that the contract holds - uint128 internal constant CONTRACT_BALANCE = 0; - uint128 internal constant OPEN_DELTA = 1; + uint128 internal constant OPEN_DELTA = 0; + /// This value is equivalent to 1<<255, i.e. a singular 1 in the most significant bit. + uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000; /// @notice used to signal that the recipient of an action should be the msgSender of address(this) address internal constant MSG_SENDER = address(1); diff --git a/test/BaseActionsRouter.t.sol b/test/BaseActionsRouter.t.sol index 61020b2c..57ca984f 100644 --- a/test/BaseActionsRouter.t.sol +++ b/test/BaseActionsRouter.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import {MockBaseActionsRouter} from "./mocks/MockBaseActionsRouter.sol"; import {Planner, Plan} from "./shared/Planner.sol"; import {Actions} from "../src/libraries/Actions.sol"; -import {Constants} from "../src/libraries/Constants.sol"; +import {ActionConstants} from "../src/libraries/ActionConstants.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Test} from "forge-std/Test.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; @@ -141,9 +141,9 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { function test_fuzz_mapRecipient(address recipient) public view { address mappedRecipient = router.mapRecipient(recipient); - if (recipient == Constants.MSG_SENDER) { + if (recipient == ActionConstants.MSG_SENDER) { assertEq(mappedRecipient, address(0xdeadbeef)); - } else if (recipient == Constants.ADDRESS_THIS) { + } else if (recipient == ActionConstants.ADDRESS_THIS) { assertEq(mappedRecipient, address(router)); } else { assertEq(mappedRecipient, recipient); diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index 4556c55d..c443d173 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -20,7 +20,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; @@ -68,7 +68,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { initialLiquidity = bound(initialLiquidity, 1e18, 1000e18); liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, initialLiquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); @@ -88,7 +88,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); liquidityToAdd2 = bound(liquidityToAdd2, 1e18, 1000e18); uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, initialLiquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init(); @@ -155,7 +155,12 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - config, initialLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + config, + initialLiquidity, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); planner.add( @@ -179,7 +184,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { // mint a position on range [-300, 300] uint256 tokenId = lpm.nextTokenId(); - mint(config, initialLiquidity, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, initialLiquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // we'll burn and mint a new position on [-60, 60]; calculate the liquidity units for the new range @@ -207,7 +212,12 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - newConfig, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + newConfig, + newLiquidity, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index dc413612..018765e5 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -28,7 +28,7 @@ import {Actions} from "../../src/libraries/Actions.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {FeeMath} from "../shared/FeeMath.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { using FixedPointMathLib for uint256; @@ -492,7 +492,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revertAmount0() public { // increasing liquidity with strict slippage parameters (amount0) will revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, ActionConstants.MSG_SENDER, ZERO_BYTES); // revert since amount0Max is too low bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, 1 wei, type(uint128).max, ZERO_BYTES); @@ -503,7 +503,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revertAmount1() public { // increasing liquidity with strict slippage parameters (amount1) will revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, ActionConstants.MSG_SENDER, ZERO_BYTES); // revert since amount1Max is too low bytes memory calls = getIncreaseEncoded(tokenId, config, 100e18, type(uint128).max, 1 wei, ZERO_BYTES); @@ -514,7 +514,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_exactDoesNotRevert() public { // increasing liquidity with perfect slippage parameters does not revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, ActionConstants.MSG_SENDER, ZERO_BYTES); uint128 newLiquidity = 10e18; (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( @@ -539,7 +539,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { function test_increaseLiquidity_slippage_revert_swap() public { // increasing liquidity with perfect slippage parameters does not revert uint256 tokenId = lpm.nextTokenId(); - mint(config, 100e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 100e18, ActionConstants.MSG_SENDER, ZERO_BYTES); uint128 newLiquidity = 10e18; (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( @@ -567,8 +567,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.MINT_POSITION, abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) ); - planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); - planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); // this test sweeps to the test contract, even though Alice is the caller of the transaction planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); planner.add(Actions.SWEEP, abi.encode(currency1, address(this))); @@ -609,10 +609,10 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.MINT_POSITION, abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) ); - planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); - planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); - planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); - planner.add(Actions.SWEEP, abi.encode(currency1, Constants.MSG_SENDER)); + planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); + planner.add(Actions.SWEEP, abi.encode(currency0, ActionConstants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency1, ActionConstants.MSG_SENDER)); uint256 balanceBefore0 = currency0.balanceOf(alice); uint256 balanceBefore1 = currency1.balanceOf(alice); @@ -656,8 +656,8 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); - planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); // this test sweeps to the test contract, even though Alice is the caller of the transaction planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); planner.add(Actions.SWEEP, abi.encode(currency1, address(this))); diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index 2bad5254..ee3779da 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -26,7 +26,7 @@ import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {MockSubscriber} from "../mocks/MockSubscriber.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; @@ -79,7 +79,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - bytes memory calls = getMintEncoded(config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + bytes memory calls = getMintEncoded(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( SQRT_PRICE_1_1, @@ -119,13 +119,18 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add( Actions.MINT_POSITION, abi.encode( - config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + config, + liquidityToAdd, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); // sweep the excess eth - planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency0, ActionConstants.MSG_SENDER)); bytes memory calls = planner.encode(); @@ -211,7 +216,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -266,7 +271,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -326,7 +331,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -376,7 +381,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); bytes32 positionId = Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); @@ -428,7 +433,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -472,7 +477,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -493,7 +498,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); // sweep the excess eth - planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency0, ActionConstants.MSG_SENDER)); bytes memory calls = planner.encode(); lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); // overpay on increase liquidity @@ -576,7 +581,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), ActionConstants.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -615,7 +620,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), ActionConstants.MSG_SENDER, ZERO_BYTES); uint256 balance0Before = address(this).balance; uint256 balance1Before = currency1.balanceOfSelf(); @@ -658,7 +663,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -684,7 +689,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -718,7 +723,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -762,7 +767,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // mint the position with native token liquidity uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, config, uint256(params.liquidityDelta), ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -778,7 +783,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, Constants.MSG_SENDER); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, ActionConstants.MSG_SENDER); lpm.modifyLiquidities(calls, _deadline); BalanceDelta delta = getLastDelta(); diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index cc009664..161321ea 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -23,7 +23,7 @@ import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { using FixedPointMathLib for uint256; @@ -74,7 +74,12 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + config, + 10_000 ether, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -102,7 +107,12 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + config, + 10_000 ether, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -121,7 +131,12 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + config, + 10_000 ether, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -140,7 +155,12 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + config, + 10_000 ether, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -151,7 +171,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_increaseLiquidity_erc20_withClose() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.INCREASE_LIQUIDITY, @@ -320,7 +340,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_decreaseLiquidity_withClose() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -334,7 +354,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_decreaseLiquidity_withTakePair() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -362,7 +382,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) + abi.encode( + config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES + ) ); bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -374,7 +396,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_collect_withClose() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -392,7 +414,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_collect_withTakePair() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -410,12 +432,17 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // same-range gas tests function test_gas_sameRange_mint() public { - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode( - config, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES + config, + 10_001 ether, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); @@ -427,11 +454,11 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_sameRange_decrease() public { // two positions of the same config, one of them decreases the entirety of the liquidity vm.startPrank(alice); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); vm.stopPrank(); uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -446,11 +473,11 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_sameRange_collect() public { // two positions of the same config, one of them collects all their fees vm.startPrank(alice); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); vm.stopPrank(); uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate(config.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -467,7 +494,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition_withClose() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) @@ -480,7 +507,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition_withTakePair() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) @@ -493,7 +520,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burnEmpty() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); decreaseLiquidity(tokenId, config, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( @@ -510,7 +537,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Will be more expensive than not encoding a decrease and just encoding a burn. // ie. check this against PositionManager_burn_nonEmpty uint256 tokenId = lpm.nextTokenId(); - mint(config, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -533,7 +560,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Native Token Gas Tests function test_gas_mint_native() public { uint256 liquidityToAdd = 10_000 ether; - bytes memory calls = getMintEncoded(configNative, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + bytes memory calls = getMintEncoded(configNative, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( SQRT_PRICE_1_1, @@ -556,13 +583,13 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, - Constants.MSG_SENDER, + ActionConstants.MSG_SENDER, ZERO_BYTES ) ); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency0)); planner.add(Actions.CLOSE_CURRENCY, abi.encode(nativeKey.currency1)); - planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, Constants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER)); bytes memory calls = planner.encode(); (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( @@ -603,7 +630,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_increase_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); uint256 liquidityToAdd = 10_000 ether; bytes memory calls = getIncreaseEncoded(tokenId, configNative, liquidityToAdd, ZERO_BYTES); @@ -619,7 +646,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_decrease_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); uint256 liquidityToRemove = 10_000 ether; bytes memory calls = getDecreaseEncoded(tokenId, configNative, liquidityToRemove, ZERO_BYTES); @@ -629,7 +656,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_collect_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to create fee revenue donateRouter.donate{value: 0.2e18}(configNative.poolKey, 0.2e18, 0.2e18, ZERO_BYTES); @@ -641,7 +668,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition_native_withClose() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, @@ -655,7 +682,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burn_nonEmptyPosition_native_withTakePair() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.BURN_POSITION, @@ -669,7 +696,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { function test_gas_burnEmpty_native() public { uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); decreaseLiquidity(tokenId, configNative, 10_000 ether, ZERO_BYTES); Plan memory planner = Planner.init().add( @@ -687,7 +714,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Will be more expensive than not encoding a decrease and just encoding a burn. // ie. check this against PositionManager_burn_nonEmpty uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, Constants.MSG_SENDER, ZERO_BYTES); + mintWithNative(SQRT_PRICE_1_1, configNative, 10_000 ether, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory planner = Planner.init().add( Actions.DECREASE_LIQUIDITY, @@ -793,10 +820,10 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, liquidityAlice, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, alice, ZERO_BYTES) ); - planner.add(Actions.SETTLE, abi.encode(currency0, Constants.OPEN_DELTA, false)); - planner.add(Actions.SETTLE, abi.encode(currency1, Constants.OPEN_DELTA, false)); - planner.add(Actions.SWEEP, abi.encode(currency0, Constants.MSG_SENDER)); - planner.add(Actions.SWEEP, abi.encode(currency1, Constants.MSG_SENDER)); + planner.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.OPEN_DELTA, false)); + planner.add(Actions.SETTLE, abi.encode(currency1, ActionConstants.OPEN_DELTA, false)); + planner.add(Actions.SWEEP, abi.encode(currency0, ActionConstants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(currency1, ActionConstants.MSG_SENDER)); currency0.transfer(address(lpm), 100e18); currency1.transfer(address(lpm), 100e18); @@ -811,14 +838,14 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { // Does not encode a take pair function test_gas_decrease_take_take() public { uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); Plan memory plan = Planner.init(); plan.add( Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, Constants.MSG_SENDER); + bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, ActionConstants.MSG_SENDER); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_take_take"); diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index 4e86c280..1106f733 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -28,7 +28,7 @@ import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol"; import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTestSetup, LiquidityFuzzers { @@ -96,7 +96,9 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest Plan memory planner = Planner.init(); planner.add( Actions.MINT_POSITION, - abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) + abi.encode( + config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES + ) ); bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index 4a47e7e6..64e4045a 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -27,7 +27,7 @@ import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {SlippageCheckLibrary} from "../../src/libraries/SlippageCheck.sol"; import {BaseActionsRouter} from "../../src/base/BaseActionsRouter.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {Planner, Plan} from "../shared/Planner.sol"; @@ -86,7 +86,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // Try to add liquidity at that range, but the token reenters posm PositionConfig memory config = PositionConfig({poolKey: key, tickLower: 0, tickUpper: 60}); - bytes memory calls = getMintEncoded(config, 1e18, Constants.MSG_SENDER, ""); + bytes memory calls = getMintEncoded(config, 1e18, ActionConstants.MSG_SENDER, ""); // Permit2.transferFrom does not bubble the ContractLocked error and instead reverts with its own error vm.expectRevert("TRANSFER_FROM_FAILED"); @@ -108,7 +108,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); assertEq(tokenId, 1); @@ -143,7 +143,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidityToAdd, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); uint256 balance0After = currency0.balanceOfSelf(); @@ -251,7 +251,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); bytes memory calls = - getMintEncoded(config, 1e18, 1 wei, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES); + getMintEncoded(config, 1e18, 1 wei, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES); vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -260,7 +260,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); bytes memory calls = - getMintEncoded(config, 1e18, MAX_SLIPPAGE_INCREASE, 1 wei, Constants.MSG_SENDER, ZERO_BYTES); + getMintEncoded(config, 1e18, MAX_SLIPPAGE_INCREASE, 1 wei, ActionConstants.MSG_SENDER, ZERO_BYTES); vm.expectRevert(SlippageCheckLibrary.MaximumAmountExceeded.selector); lpm.modifyLiquidities(calls, _deadline); } @@ -278,7 +278,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(amount0, amount1); // symmetric liquidity uint128 slippage = uint128(amount0) + 1; - bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Constants.MSG_SENDER, ZERO_BYTES); + bytes memory calls = + getMintEncoded(config, liquidity, slippage, slippage, ActionConstants.MSG_SENDER, ZERO_BYTES); lpm.modifyLiquidities(calls, _deadline); BalanceDelta delta = getLastDelta(); assertEq(uint256(int256(-delta.amount0())), slippage); @@ -299,7 +300,8 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(amount0, amount1); // symmetric liquidity uint128 slippage = uint128(amount0) + 1; - bytes memory calls = getMintEncoded(config, liquidity, slippage, slippage, Constants.MSG_SENDER, ZERO_BYTES); + bytes memory calls = + getMintEncoded(config, liquidity, slippage, slippage, ActionConstants.MSG_SENDER, ZERO_BYTES); // swap to move the price and cause a slippage revert swap(key, true, -1e18, ZERO_BYTES); @@ -314,7 +316,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // create liquidity we can burn uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, ActionConstants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); @@ -360,7 +362,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // create liquidity we can burn uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, ActionConstants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); @@ -410,7 +412,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = @@ -422,7 +424,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = @@ -434,7 +436,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_burn_slippage_exactDoesNotRevert() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // TODO: why does burning a newly minted position return original delta - 1 wei? @@ -452,7 +454,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // swapping will cause a slippage revert PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getBurnEncoded( @@ -471,7 +473,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, ActionConstants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); decreaseLiquidityDelta = uint256(bound(int256(decreaseLiquidityDelta), 0, params.liquidityDelta)); PositionConfig memory config = @@ -581,7 +583,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, ActionConstants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // require two-sided liquidity decreaseLiquidityDelta = bound(decreaseLiquidityDelta, 1, uint256(params.liquidityDelta)); @@ -618,7 +620,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_revertAmount0() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -631,7 +633,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_revertAmount1() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -644,7 +646,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decreaseLiquidity_slippage_exactDoesNotRevert() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); // TODO: why does decreasing a newly minted position return original delta - 1 wei? @@ -663,7 +665,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // swapping will cause a slippage revert PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta delta = getLastDelta(); bytes memory calls = getDecreaseEncoded( @@ -682,7 +684,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 decreaseLiquidityDelta ) public { uint256 tokenId; - (tokenId, params) = addFuzzyLiquidity(lpm, Constants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); + (tokenId, params) = addFuzzyLiquidity(lpm, ActionConstants.MSG_SENDER, key, params, SQRT_PRICE_1_1, ZERO_BYTES); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // require two-sided liquidity vm.assume(0 < decreaseLiquidityDelta); vm.assume(decreaseLiquidityDelta < uint256(type(int256).max)); @@ -715,7 +717,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); BalanceDelta mintDelta = getLastDelta(); // transfer to alice @@ -743,7 +745,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -772,7 +774,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); // transfer to alice lpm.transferFrom(address(this), alice, tokenId); @@ -809,7 +811,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -600, tickUpper: 600}); uint256 liquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidity, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, liquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); // donate to generate fee revenue uint256 feeRevenue0 = 1e18; @@ -883,7 +885,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decrease_take() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); hook.clearDeltas(); @@ -895,7 +897,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, 1e18, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, Constants.MSG_SENDER); + bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, ActionConstants.MSG_SENDER); lpm.modifyLiquidities(calls, _deadline); BalanceDelta delta = getLastDelta(); @@ -910,7 +912,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { function test_decrease_increaseCurrency1_take_settle() public { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -120, tickUpper: 120}); uint256 tokenId = lpm.nextTokenId(); - mint(config, 1e18, Constants.MSG_SENDER, ZERO_BYTES); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); hook.clearDeltas(); @@ -929,11 +931,13 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { ); plan.add( Actions.MINT_POSITION, - abi.encode(configMint, 1e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, Constants.MSG_SENDER, ZERO_BYTES) + abi.encode( + configMint, 1e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ActionConstants.MSG_SENDER, ZERO_BYTES + ) ); - plan.add(Actions.TAKE, abi.encode(key.currency0, Constants.MSG_SENDER, Constants.OPEN_DELTA)); - plan.add(Actions.SETTLE, abi.encode(key.currency1, Constants.OPEN_DELTA, true)); - bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, Constants.MSG_SENDER); + plan.add(Actions.TAKE, abi.encode(key.currency0, ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA)); + plan.add(Actions.SETTLE, abi.encode(key.currency1, ActionConstants.OPEN_DELTA, true)); + bytes memory calls = plan.finalizeModifyLiquidityWithTake(config.poolKey, ActionConstants.MSG_SENDER); lpm.modifyLiquidities(calls, _deadline); BalanceDelta deltaDecrease = hook.deltas(0); diff --git a/test/router/Payments.gas.t.sol b/test/router/Payments.gas.t.sol index b6ed4af3..ef96ed31 100644 --- a/test/router/Payments.gas.t.sol +++ b/test/router/Payments.gas.t.sol @@ -8,7 +8,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; contract PaymentsTests is RoutingTestHelpers, GasSnapshot { using CurrencyLibrary for Currency; @@ -40,7 +40,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Constants.MSG_SENDER)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, ActionConstants.MSG_SENDER)); bytes memory data = plan.encode(); router.executeActions(data); @@ -56,7 +56,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { key0.currency0.transfer(address(router), amountIn); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, Constants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); bytes memory data = plan.encode(); @@ -73,8 +73,8 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { key0.currency0.transfer(address(router), amountIn); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE, abi.encode(currency0, Constants.CONTRACT_BALANCE, false)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, Constants.MSG_SENDER)); + plan = plan.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, ActionConstants.MSG_SENDER)); bytes memory data = plan.encode(); router.executeActions(data); diff --git a/test/router/Payments.t.sol b/test/router/Payments.t.sol index a2798f6d..0aaa4823 100644 --- a/test/router/Payments.t.sol +++ b/test/router/Payments.t.sol @@ -8,7 +8,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; contract PaymentsTests is RoutingTestHelpers, GasSnapshot { @@ -69,7 +69,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { assertEq(currency1.balanceOf(address(router)), 0); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, Constants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); bytes memory data = plan.encode(); diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index d1b962a4..1a63d9fa 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -8,7 +8,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; contract V4RouterTest is RoutingTestHelpers, GasSnapshot { using CurrencyLibrary for Currency; @@ -34,7 +34,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactInputSingle"); @@ -48,7 +48,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency1, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_zeroForOne"); @@ -62,7 +62,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency1, currency0, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency1, currency0, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_oneForZero"); @@ -77,7 +77,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency2, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency2, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn2Hops"); @@ -93,7 +93,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency3, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency3, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn3Hops"); @@ -110,7 +110,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, ActionConstants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactInputSingle_nativeIn"); @@ -123,7 +123,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactInputSingle_nativeOut"); @@ -137,7 +137,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, ActionConstants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn1Hop_nativeIn"); @@ -151,7 +151,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_nativeOut"); @@ -166,7 +166,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, ActionConstants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn2Hops_nativeIn"); @@ -182,7 +182,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, ActionConstants.MSG_SENDER); router.executeActions{value: amountIn}(data); snapLastCall("V4Router_ExactIn3Hops_nativeIn"); @@ -199,7 +199,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOutputSingle"); @@ -213,7 +213,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency1, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_zeroForOne"); @@ -227,7 +227,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency1, currency0, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency1, currency0, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_oneForZero"); @@ -242,7 +242,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency2, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency2, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut2Hops"); @@ -258,7 +258,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, currency3, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, currency3, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops"); @@ -275,7 +275,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOutputSingle_nativeIn_sweepETH"); @@ -288,7 +288,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH(data); snapLastCall("V4Router_ExactOutputSingle_nativeOut"); @@ -302,7 +302,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency0, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut1Hop_nativeIn_sweepETH"); @@ -316,7 +316,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency0, CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_nativeOut"); @@ -331,7 +331,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency1, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut2Hops_nativeIn"); @@ -347,7 +347,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(CurrencyLibrary.NATIVE, currency2, ActionConstants.MSG_SENDER); router.executeActionsAndSweepExcessETH{value: 2 ether}(data); snapLastCall("V4Router_ExactOut3Hops_nativeIn"); @@ -364,7 +364,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.NATIVE, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(currency2, CurrencyLibrary.NATIVE, ActionConstants.MSG_SENDER); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops_nativeOut"); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index 9a8705d3..9c926431 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -6,7 +6,7 @@ import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; contract V4RouterTest is RoutingTestHelpers { using CurrencyLibrary for Currency; @@ -33,7 +33,7 @@ contract V4RouterTest is RoutingTestHelpers { ); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); vm.expectRevert(IV4Router.TooLittleReceived.selector); router.executeActions(data); @@ -100,7 +100,7 @@ contract V4RouterTest is RoutingTestHelpers { // swap with the router as the take recipient (uint256 inputBalanceBefore, uint256 outputBalanceBefore, uint256 inputBalanceAfter, uint256 outputBalanceAfter) - = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, Constants.ADDRESS_THIS); + = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, ActionConstants.ADDRESS_THIS); // the output tokens have been left in the router assertEq(currency0.balanceOf(address(router)), 0); @@ -123,7 +123,8 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); // take the entire open delta to the router's address - plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, Constants.ADDRESS_THIS, Constants.OPEN_DELTA)); + plan = + plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); bytes memory data = plan.encode(); // the router holds no funds before @@ -173,7 +174,7 @@ contract V4RouterTest is RoutingTestHelpers { params.amountOutMinimum = uint128(expectedAmountOut + 1); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); vm.expectRevert(IV4Router.TooLittleReceived.selector); router.executeActions(data); @@ -277,9 +278,9 @@ contract V4RouterTest is RoutingTestHelpers { // amount in of 0 to show it should use the open delta IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, Constants.OPEN_DELTA, 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); - plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, Constants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); @@ -445,9 +446,9 @@ contract V4RouterTest is RoutingTestHelpers { // amount in of 0 to show it should use the open delta IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(nativeKey, true, Constants.OPEN_DELTA, 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(nativeKey, true, ActionConstants.OPEN_DELTA, 0, 0, bytes("")); - plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, Constants.CONTRACT_BALANCE, false)); + plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.TAKE_ALL, abi.encode(nativeKey.currency1, address(this))); @@ -481,7 +482,7 @@ contract V4RouterTest is RoutingTestHelpers { ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); vm.expectRevert(IV4Router.TooMuchRequested.selector); router.executeActions(data); @@ -537,7 +538,7 @@ contract V4RouterTest is RoutingTestHelpers { params.amountInMaximum = uint128(expectedAmountIn - 1); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); vm.expectRevert(IV4Router.TooMuchRequested.selector); router.executeActions(data); diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index ac5a81aa..18876490 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -6,7 +6,7 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; struct Plan { bytes actions; @@ -43,8 +43,8 @@ library Planner { pure returns (bytes memory) { - plan.add(Actions.TAKE, abi.encode(poolKey.currency0, takeRecipient, Constants.OPEN_DELTA)); - plan.add(Actions.TAKE, abi.encode(poolKey.currency1, takeRecipient, Constants.OPEN_DELTA)); + plan.add(Actions.TAKE, abi.encode(poolKey.currency0, takeRecipient, ActionConstants.OPEN_DELTA)); + plan.add(Actions.TAKE, abi.encode(poolKey.currency1, takeRecipient, ActionConstants.OPEN_DELTA)); return plan.encode(); } diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index 4d98f463..c0535b7c 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -18,7 +18,7 @@ import {PositionManager} from "../../src/PositionManager.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; -import {Constants} from "../../src/libraries/Constants.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic helpers for swapping with the router. contract RoutingTestHelpers is Test, Deployers { @@ -161,7 +161,7 @@ contract RoutingTestHelpers is Test, Deployers { uint256 outputBalanceAfter ) { - return _finalizeAndExecuteSwap(inputCurrency, outputCurrency, amountIn, Constants.MSG_SENDER); + return _finalizeAndExecuteSwap(inputCurrency, outputCurrency, amountIn, ActionConstants.MSG_SENDER); } function _finalizeAndExecuteNativeInputExactOutputSwap( @@ -180,7 +180,7 @@ contract RoutingTestHelpers is Test, Deployers { inputBalanceBefore = inputCurrency.balanceOfSelf(); outputBalanceBefore = outputCurrency.balanceOfSelf(); - bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, Constants.MSG_SENDER); + bytes memory data = plan.finalizeSwap(inputCurrency, outputCurrency, ActionConstants.MSG_SENDER); // send too much ETH to mimic slippage uint256 value = expectedAmountIn + 0.1 ether; From e2d250892f61449b4a2c5221da546f0dfd71be7f Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:30:29 +0100 Subject: [PATCH 27/52] One BPS library (#268) --- src/base/Notifier.sol | 6 +++--- src/libraries/BipsLibrary.sol | 7 ++++--- src/libraries/GasLimitCalculator.sol | 12 ----------- test/libraries/BipsLibrary.t.sol | 28 +++++++++++++++++++++++-- test/libraries/GasLimitCalculator.t.sol | 22 ------------------- test/router/Payments.t.sol | 4 ++-- 6 files changed, 35 insertions(+), 44 deletions(-) delete mode 100644 src/libraries/GasLimitCalculator.sol delete mode 100644 test/libraries/GasLimitCalculator.t.sol diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 97510888..174d8ea1 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.24; import {ISubscriber} from "../interfaces/ISubscriber.sol"; import {PositionConfig} from "../libraries/PositionConfig.sol"; -import {GasLimitCalculator} from "../libraries/GasLimitCalculator.sol"; +import {BipsLibrary} from "../libraries/BipsLibrary.sol"; import "../interfaces/INotifier.sol"; /// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers abstract contract Notifier is INotifier { - using GasLimitCalculator for uint256; + using BipsLibrary for uint256; error AlreadySubscribed(address subscriber); @@ -39,7 +39,7 @@ abstract contract Notifier is INotifier { function _unsubscribe(uint256 tokenId, PositionConfig memory config) internal { ISubscriber _subscriber = subscriber[tokenId]; - uint256 subscriberGasLimit = BLOCK_LIMIT_BPS.toGasLimit(); + uint256 subscriberGasLimit = block.gaslimit.calculatePortion(BLOCK_LIMIT_BPS); try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config) {} catch {} diff --git a/src/libraries/BipsLibrary.sol b/src/libraries/BipsLibrary.sol index ff2cadd3..32bd97fc 100644 --- a/src/libraries/BipsLibrary.sol +++ b/src/libraries/BipsLibrary.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.19; /// @title For calculating a percentage of an amount, using bips +// TODO: Post-audit move to core, as v4-core will use something similar. library BipsLibrary { - uint256 internal constant BIPS_BASE = 10_000; + uint256 internal constant BPS_DENOMINATOR = 10_000; /// @notice emitted when an invalid percentage is provided error InvalidBips(); @@ -11,7 +12,7 @@ library BipsLibrary { /// @param amount The total amount to calculate a percentage of /// @param bips The percentage to calculate, in bips function calculatePortion(uint256 amount, uint256 bips) internal pure returns (uint256) { - if (bips > BIPS_BASE) revert InvalidBips(); - return (amount * bips) / BIPS_BASE; + if (bips > BPS_DENOMINATOR) revert InvalidBips(); + return (amount * bips) / BPS_DENOMINATOR; } } diff --git a/src/libraries/GasLimitCalculator.sol b/src/libraries/GasLimitCalculator.sol deleted file mode 100644 index d5a8a440..00000000 --- a/src/libraries/GasLimitCalculator.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.24; - -// TODO: Post-audit move to core, as v4-core will use something similar. -library GasLimitCalculator { - uint256 constant BPS_DENOMINATOR = 10_000; - - /// calculates a gas limit as a percentage of the currenct block's gas limit - function toGasLimit(uint256 bps) internal view returns (uint256 gasLimit) { - return block.gaslimit * bps / BPS_DENOMINATOR; - } -} diff --git a/test/libraries/BipsLibrary.t.sol b/test/libraries/BipsLibrary.t.sol index c2ff12ff..df9f815a 100644 --- a/test/libraries/BipsLibrary.t.sol +++ b/test/libraries/BipsLibrary.t.sol @@ -10,11 +10,35 @@ contract PositionConfigTest is Test { function test_fuzz_calculatePortion(uint256 amount, uint256 bips) public { amount = bound(amount, 0, uint256(type(uint128).max)); - if (bips > BipsLibrary.BIPS_BASE) { + if (bips > BipsLibrary.BPS_DENOMINATOR) { vm.expectRevert(BipsLibrary.InvalidBips.selector); amount.calculatePortion(bips); } else { - assertEq(amount.calculatePortion(bips), amount * bips / BipsLibrary.BIPS_BASE); + assertEq(amount.calculatePortion(bips), amount * bips / BipsLibrary.BPS_DENOMINATOR); } } + + function test_fuzz_gasLimitt(uint256 bips) public { + if (bips > BipsLibrary.BPS_DENOMINATOR) { + vm.expectRevert(BipsLibrary.InvalidBips.selector); + block.gaslimit.calculatePortion(bips); + } else { + assertEq(block.gaslimit.calculatePortion(bips), block.gaslimit * bips / BipsLibrary.BPS_DENOMINATOR); + } + } + + function test_gasLimit_100_percent() public { + assertEq(block.gaslimit, block.gaslimit.calculatePortion(10_000)); + } + + function test_gasLimit_1_percent() public { + /// 100 bps = 1% + // 1% of 3_000_000_000 is 30_000_000 + assertEq(30_000_000, block.gaslimit.calculatePortion(100)); + } + + function test_gasLimit_1BP() public { + /// 1bp is 0.01% + assertEq(300_000, block.gaslimit.calculatePortion(1)); + } } diff --git a/test/libraries/GasLimitCalculator.t.sol b/test/libraries/GasLimitCalculator.t.sol deleted file mode 100644 index 47129537..00000000 --- a/test/libraries/GasLimitCalculator.t.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "forge-std/Test.sol"; -import {GasLimitCalculator} from "../../src/libraries/GasLimitCalculator.sol"; - -contract GasLimitCalculatorTest is Test { - function test_gasLimit_100_percent() public { - assertEq(block.gaslimit, GasLimitCalculator.toGasLimit(10_000)); - } - - function test_gasLimit_1_percent() public { - /// 100 bps = 1% - // 1% of 3_000_000_000 is 30_000_000 - assertEq(30_000_000, GasLimitCalculator.toGasLimit(100)); - } - - function test_gasLimit_1BP() public { - /// 1bp is 0.01% - assertEq(300_000, GasLimitCalculator.toGasLimit(1)); - } -} diff --git a/test/router/Payments.t.sol b/test/router/Payments.t.sol index 0aaa4823..acf2dfa4 100644 --- a/test/router/Payments.t.sol +++ b/test/router/Payments.t.sol @@ -113,7 +113,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { uint256 outputBalanceAfter = key0.currency1.balanceOfSelf(); uint256 bobBalanceAfter = key0.currency1.balanceOf(bob); - uint256 expectedFee = expectedAmountOut * 15 / BipsLibrary.BIPS_BASE; + uint256 expectedFee = expectedAmountOut * 15 / BipsLibrary.BPS_DENOMINATOR; // router is empty assertEq(currency0.balanceOf(address(router)), 0); @@ -132,7 +132,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); // bips is larger than maximum bips - plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, BipsLibrary.BIPS_BASE + 1)); + plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, BipsLibrary.BPS_DENOMINATOR + 1)); plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); bytes memory data = plan.encode(); From eb0cf58b240e235b9d3b660366634ab74cb903de Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:53:12 +0100 Subject: [PATCH 28/52] slippage params routing (#264) * slippage params routing * tests * PR comment * exact out tests --- ...p_settleFromCaller_takeAllToMsgSender.snap | 2 +- ...eFromCaller_takeAllToSpecifiedAddress.snap | 2 +- ..._settleWithBalance_takeAllToMsgSender.snap | 2 +- ...WithBalance_takeAllToSpecifiedAddress.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- ...Router_ExactOut1Hop_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOut1Hop_nativeOut.snap | 2 +- .../V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .../V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .../V4Router_ExactOut2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOut3Hops_nativeIn.snap | 2 +- .../V4Router_ExactOut3Hops_nativeOut.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- ...r_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/V4Router.sol | 30 ++++--- src/interfaces/IV4Router.sol | 4 +- src/libraries/Actions.sol | 15 ++-- test/router/Payments.gas.t.sol | 12 +-- test/router/Payments.t.sol | 90 +++++++++++++++++-- test/router/V4Router.t.sol | 14 +-- test/shared/Planner.sol | 8 +- test/shared/RoutingTestHelpers.sol | 3 + 36 files changed, 160 insertions(+), 72 deletions(-) diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index 762bc2b1..e30ad0bd 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -134520 \ No newline at end of file +133519 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index 460e1677..7d2a0de8 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -134658 \ No newline at end of file +135555 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index aec2b78e..86aaa87a 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -127708 \ No newline at end of file +128029 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index 424c0794..3cee4098 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -127846 \ No newline at end of file +128167 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 2ba531ff..cef38079 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8165 \ No newline at end of file +8438 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index 923bb90b..32d9c91a 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -120569 \ No newline at end of file +119534 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index d4cf2340..516f2efe 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -119764 \ No newline at end of file +118729 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index dfc10e53..b98ed60b 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -128636 \ No newline at end of file +127601 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index e3bda2b6..1b8b982f 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135466 \ No newline at end of file +134431 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 92b96ddc..d96af129 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -186973 \ No newline at end of file +185972 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index c29999bd..66eb073f 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -178908 \ No newline at end of file +177907 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 70ec54f0..17723637 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238505 \ No newline at end of file +237494 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index 469d47f6..53ae189d 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -230464 \ No newline at end of file +229453 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index 762bc2b1..e30ad0bd 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134520 \ No newline at end of file +133519 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index 2e31b245..954557e3 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119623 \ No newline at end of file +118622 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index 4db689f3..faf9096a 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118801 \ No newline at end of file +117800 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index b8ac3ed6..44f276e8 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -126227 \ No newline at end of file +125192 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index 3e4bc907..fba2e6e3 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -120505 \ No newline at end of file +119470 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index 1dc41920..3e0079ae 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -129377 \ No newline at end of file +128342 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index bcbd95de..b144de14 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -134178 \ No newline at end of file +133143 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index b807f27b..ce8f4467 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186288 \ No newline at end of file +185287 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index a5edf43b..4539caa7 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -183138 \ No newline at end of file +182137 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index ce17195c..de954cf9 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -238438 \ No newline at end of file +237427 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index f82bf262..0012a090 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -235312 \ No newline at end of file +234301 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index 8a909d9b..2028f219 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -229590 \ No newline at end of file +228579 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index 04fea4fd..107eb18c 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -132941 \ No newline at end of file +131940 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index 95370c23..2d74c064 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -124990 \ No newline at end of file +123989 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index 23c6d01a..e3baa57f 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -119281 \ No newline at end of file +118280 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 168846f6..8f32d51d 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -30,10 +30,9 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { constructor(IPoolManager _poolManager) BaseActionsRouter(_poolManager) {} - // TODO native support !! function _handleAction(uint256 action, bytes calldata params) internal override { // swap actions and payment actions in different blocks for gas efficiency - if (action < Actions.SETTLE_ALL) { + if (action < Actions.SETTLE) { if (action == Actions.SWAP_EXACT_IN) { IV4Router.ExactInputParams calldata swapParams = params.decodeSwapExactInParams(); _swapExactInput(swapParams); @@ -50,13 +49,20 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { revert UnsupportedAction(action); } } else { - if (action == Actions.SETTLE_ALL) { - Currency currency = params.decodeCurrency(); - // TODO should it have a maxAmountOut added slippage protection? - _settle(currency, msgSender(), _getFullDebt(currency)); + if (action == Actions.SETTLE_TAKE_PAIR) { + (Currency settleCurrency, Currency takeCurrency) = params.decodeCurrencyPair(); + _settle(settleCurrency, msgSender(), _getFullDebt(settleCurrency)); + _take(takeCurrency, msgSender(), _getFullCredit(takeCurrency)); + } else if (action == Actions.SETTLE_ALL) { + (Currency currency, uint256 maxAmount) = params.decodeCurrencyAndUint256(); + uint256 amount = _getFullDebt(currency); + if (amount > maxAmount) revert V4TooMuchRequested(); + _settle(currency, msgSender(), amount); } else if (action == Actions.TAKE_ALL) { - (Currency currency, address recipient) = params.decodeCurrencyAndAddress(); - _take(currency, _mapRecipient(recipient), _getFullCredit(currency)); + (Currency currency, uint256 minAmount) = params.decodeCurrencyAndUint256(); + uint256 amount = _getFullCredit(currency); + if (amount < minAmount) revert V4TooLittleReceived(); + _take(currency, msgSender(), amount); } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); @@ -78,7 +84,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { uint128 amountOut = _swap( params.poolKey, params.zeroForOne, int256(-int128(amountIn)), params.sqrtPriceLimitX96, params.hookData ).toUint128(); - if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); + if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(); } function _swapExactInput(IV4Router.ExactInputParams calldata params) private { @@ -100,7 +106,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { currencyIn = pathKey.intermediateCurrency; } - if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); + if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(); } } @@ -114,7 +120,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { params.hookData ) ).toUint128(); - if (amountIn > params.amountInMaximum) revert TooMuchRequested(); + if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(); } function _swapExactOutput(IV4Router.ExactOutputParams calldata params) private { @@ -135,7 +141,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { amountOut = amountIn; currencyOut = pathKey.intermediateCurrency; } - if (amountIn > params.amountInMaximum) revert TooMuchRequested(); + if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(); } } diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index 3dfc4d88..6991d72d 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -9,9 +9,9 @@ import {PathKey} from "../libraries/PathKey.sol"; /// @notice Interface containing all the structs and errors for different v4 swap types interface IV4Router { /// @notice Emitted when an exactInput swap does not receive its minAmountOut - error TooLittleReceived(); + error V4TooLittleReceived(); /// @notice Emitted when an exactOutput is asked for more than its maxAmountIn - error TooMuchRequested(); + error V4TooMuchRequested(); /// @notice Parameters for a single-hop exact-input swap struct ExactInputSingleParams { diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 542c935a..0b2592dd 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -20,8 +20,8 @@ library Actions { // closing deltas on the pool manager // settling - uint256 constant SETTLE_ALL = 0x09; - uint256 constant SETTLE = 0x10; + uint256 constant SETTLE = 0x09; + uint256 constant SETTLE_ALL = 0x10; uint256 constant SETTLE_PAIR = 0x11; // taking uint256 constant TAKE = 0x12; @@ -29,11 +29,12 @@ library Actions { uint256 constant TAKE_PORTION = 0x14; uint256 constant TAKE_PAIR = 0x15; - uint256 constant CLOSE_CURRENCY = 0x16; - uint256 constant CLEAR_OR_TAKE = 0x17; - uint256 constant SWEEP = 0x18; + uint256 constant SETTLE_TAKE_PAIR = 0x16; + uint256 constant CLOSE_CURRENCY = 0x17; + uint256 constant CLEAR_OR_TAKE = 0x18; + uint256 constant SWEEP = 0x19; // minting/burning 6909s to close deltas - uint256 constant MINT_6909 = 0x19; - uint256 constant BURN_6909 = 0x20; + uint256 constant MINT_6909 = 0x20; + uint256 constant BURN_6909 = 0x21; } diff --git a/test/router/Payments.gas.t.sol b/test/router/Payments.gas.t.sol index ef96ed31..9717fdad 100644 --- a/test/router/Payments.gas.t.sol +++ b/test/router/Payments.gas.t.sol @@ -25,8 +25,8 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, MAX_SETTLE_AMOUNT)); + plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, address(this), ActionConstants.OPEN_DELTA)); bytes memory data = plan.encode(); router.executeActions(data); @@ -39,8 +39,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, ActionConstants.MSG_SENDER)); + plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); bytes memory data = plan.encode(); router.executeActions(data); @@ -57,7 +56,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, address(this), ActionConstants.OPEN_DELTA)); bytes memory data = plan.encode(); router.executeActions(data); @@ -74,7 +73,8 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE, abi.encode(currency0, ActionConstants.CONTRACT_BALANCE, false)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, ActionConstants.MSG_SENDER)); + plan = + plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA)); bytes memory data = plan.encode(); router.executeActions(data); diff --git a/test/router/Payments.t.sol b/test/router/Payments.t.sol index acf2dfa4..3aa077ef 100644 --- a/test/router/Payments.t.sol +++ b/test/router/Payments.t.sol @@ -22,15 +22,14 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = Planner.init(); } - function test_settleFromCaller_takeAll() public { + function test_settleTakePair() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); @@ -52,6 +51,83 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); } + function test_exactIn_settleAll_revertsSlippage() public { + uint256 amountIn = 1 ether; + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, amountIn - 1)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, MIN_TAKE_AMOUNT)); + + bytes memory data = plan.encode(); + vm.expectRevert(IV4Router.V4TooMuchRequested.selector); + router.executeActions(data); + } + + function test_exactIn_takeAll_revertsSlippage() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, MAX_SETTLE_AMOUNT)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, expectedAmountOut + 1)); + + bytes memory data = plan.encode(); + vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + router.executeActions(data); + } + + function test_exactOut_settleAll_revertsSlippage() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountIn - 1)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, MIN_TAKE_AMOUNT)); + + bytes memory data = plan.encode(); + vm.expectRevert(IV4Router.V4TooMuchRequested.selector); + router.executeActions(data); + } + + function test_exactOut_takeAll_revertsSlippage() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, MAX_SETTLE_AMOUNT)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, amountOut + 1)); + + bytes memory data = plan.encode(); + vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + router.executeActions(data); + } + + function test_exactOut_takeAll_settleAll_succeedsExactAmount() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), uint128(expectedAmountIn), 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountIn)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency0, amountOut)); + + bytes memory data = plan.encode(); + vm.expectRevert(IV4Router.V4TooLittleReceived.selector); + router.executeActions(data); + } + function test_settleFromRouter_takeAll() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -70,7 +146,7 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, MIN_TAKE_AMOUNT)); bytes memory data = plan.encode(); router.executeActions(data); @@ -93,10 +169,9 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); // take 15 bips to Bob plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, 15)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); uint256 inputBalanceBefore = key0.currency0.balanceOfSelf(); uint256 outputBalanceBefore = key0.currency1.balanceOfSelf(); @@ -130,10 +205,9 @@ contract PaymentsTests is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); // bips is larger than maximum bips plan = plan.add(Actions.TAKE_PORTION, abi.encode(key0.currency1, bob, BipsLibrary.BPS_DENOMINATOR + 1)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(key0.currency0, key0.currency1)); bytes memory data = plan.encode(); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index 9c926431..f6d63805 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -35,7 +35,7 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.TooLittleReceived.selector); + vm.expectRevert(IV4Router.V4TooLittleReceived.selector); router.executeActions(data); } @@ -121,7 +121,7 @@ contract V4RouterTest is RoutingTestHelpers { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0)); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountOut * 12 / 10)); // take the entire open delta to the router's address plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); @@ -176,7 +176,7 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.TooLittleReceived.selector); + vm.expectRevert(IV4Router.V4TooLittleReceived.selector); router.executeActions(data); } @@ -282,7 +282,7 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, address(this))); + plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, MIN_TAKE_AMOUNT)); bytes memory data = plan.encode(); @@ -450,7 +450,7 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, ActionConstants.CONTRACT_BALANCE, false)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(nativeKey.currency1, address(this))); + plan = plan.add(Actions.TAKE_ALL, abi.encode(nativeKey.currency1, MIN_TAKE_AMOUNT)); bytes memory data = plan.encode(); @@ -484,7 +484,7 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.TooMuchRequested.selector); + vm.expectRevert(IV4Router.V4TooMuchRequested.selector); router.executeActions(data); } @@ -540,7 +540,7 @@ contract V4RouterTest is RoutingTestHelpers { plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER); - vm.expectRevert(IV4Router.TooMuchRequested.selector); + vm.expectRevert(IV4Router.V4TooMuchRequested.selector); router.executeActions(data); } diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 18876490..979f1215 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -85,8 +85,12 @@ library Planner { pure returns (bytes memory) { - plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, takeRecipient)); + if (takeRecipient == ActionConstants.MSG_SENDER) { + plan = plan.add(Actions.SETTLE_TAKE_PAIR, abi.encode(inputCurrency, outputCurrency)); + } else { + plan = plan.add(Actions.SETTLE, abi.encode(inputCurrency, ActionConstants.OPEN_DELTA, true)); + plan = plan.add(Actions.TAKE, abi.encode(outputCurrency, takeRecipient, ActionConstants.OPEN_DELTA)); + } return plan.encode(); } } diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index c0535b7c..e06cc3cc 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -27,6 +27,9 @@ contract RoutingTestHelpers is Test, Deployers { PoolModifyLiquidityTest positionManager; MockV4Router router; + uint256 MAX_SETTLE_AMOUNT = type(uint256).max; + uint256 MIN_TAKE_AMOUNT = 0; + // nativeKey is already defined in Deployers.sol PoolKey key0; PoolKey key1; From 41bbc7ddb2ae9bcec1071a51bf2b6eb2147a4aea Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:58:44 -0400 Subject: [PATCH 29/52] add liquidity view (#270) * add liquidity view * comment --- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/PositionManager_permit.snap | 2 +- ...PositionManager_permit_secondPosition.snap | 2 +- .../PositionManager_permit_twice.snap | 2 +- src/PositionManager.sol | 22 +++---- src/interfaces/IPositionManager.sol | 10 ++++ test/position-managers/Execute.t.sol | 26 +++----- .../position-managers/IncreaseLiquidity.t.sol | 12 ++-- test/position-managers/NativeToken.t.sol | 60 ++++++------------- test/position-managers/Permit.t.sol | 13 ++-- .../PositionManager.multicall.t.sol | 12 +--- .../PositionManager.notifier.t.sol | 8 +-- test/position-managers/PositionManager.t.sol | 40 ++++--------- 21 files changed, 81 insertions(+), 146 deletions(-) diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 492cae30..856ec3b4 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47167 \ No newline at end of file +47158 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 6ad16fc4..c3b5ff40 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46984 \ No newline at end of file +46976 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 130b6ee7..9cd54e27 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -122980 \ No newline at end of file +122971 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 1cddf5e5..d06ed4e0 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122689 \ No newline at end of file +122680 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 211bfaf9..44efeb87 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130058 \ No newline at end of file +130049 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 58bede9f..75b82d5a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129768 \ No newline at end of file +129759 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index de137414..02110f25 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134137 \ No newline at end of file +134128 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index ab7b40f5..c9a2b5b8 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126876 \ No newline at end of file +126868 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 93b17f36..993285bf 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416486 \ No newline at end of file +416464 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap index 296e82ca..ee34c59c 100644 --- a/.forge-snapshots/PositionManager_permit.snap +++ b/.forge-snapshots/PositionManager_permit.snap @@ -1 +1 @@ -79467 \ No newline at end of file +79445 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap index 3f96aa23..0b63319b 100644 --- a/.forge-snapshots/PositionManager_permit_secondPosition.snap +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -1 +1 @@ -62355 \ No newline at end of file +62333 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index 913eee8f..7772c13d 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -45243 \ No newline at end of file +45221 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 720275d4..c8a43f3b 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -302,7 +302,7 @@ contract PositionManager is uint128 amount1Min, bytes calldata hookData ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { - uint256 liquidity = uint256(_getPositionLiquidity(config, tokenId)); + uint256 liquidity = uint256(getPositionLiquidity(tokenId, config)); BalanceDelta liquidityDelta; // Can only call modify if there is non zero liquidity. @@ -338,16 +338,6 @@ contract PositionManager is } } - function _getPositionLiquidity(PositionConfig calldata config, uint256 tokenId) - internal - view - returns (uint128 liquidity) - { - bytes32 positionId = - Position.calculatePositionKey(address(this), config.tickLower, config.tickUpper, bytes32(tokenId)); - liquidity = poolManager.getPositionLiquidity(config.poolKey.toId(), positionId); - } - /// @notice Sweeps the entire contract balance of specified currency to the recipient function _sweep(Currency currency, address to) internal { uint256 balance = currency.balanceOfSelf(); @@ -370,6 +360,16 @@ contract PositionManager is if (positionConfigs.hasSubscriber(id)) _notifyTransfer(id, from, to); } + function getPositionLiquidity(uint256 tokenId, PositionConfig calldata config) + public + view + returns (uint128 liquidity) + { + bytes32 positionId = + Position.calculatePositionKey(address(this), config.tickLower, config.tickUpper, bytes32(tokenId)); + liquidity = poolManager.getPositionLiquidity(config.poolKey.toId(), positionId); + } + /// @inheritdoc IPositionManager function getPositionConfigId(uint256 tokenId) external view returns (bytes32) { return positionConfigs.getConfigId(tokenId); diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 0fde8d62..3c45df7d 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.24; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PositionConfig} from "../libraries/PositionConfig.sol"; import {INotifier} from "./INotifier.sol"; @@ -22,4 +23,13 @@ interface IPositionManager is INotifier { /// @return configId a truncated hash of the position's poolkey, tickLower, and tickUpper /// @dev truncates the least significant bit of the hash function getPositionConfigId(uint256 tokenId) external view returns (bytes32 configId); + + /// @param tokenId the ERC721 tokenId + /// @param config the corresponding PositionConfig for the tokenId + /// @return liquidity the position's liquidity, as a liquidityAmount + /// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library + function getPositionLiquidity(uint256 tokenId, PositionConfig calldata config) + external + view + returns (uint128 liquidity); } diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index c443d173..5417c71a 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -72,9 +72,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, initialLiquidity + liquidityToAdd); } @@ -104,9 +102,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2); } @@ -136,9 +132,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2); } @@ -171,9 +165,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, initialLiquidity + liquidityToAdd); } @@ -242,9 +234,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { { // old position has no liquidity - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - uint128 liquidity = manager.getPositionLiquidity(config.poolKey.toId(), positionId); + uint128 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 0); // new token was minted @@ -252,10 +242,8 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(lpm.ownerOf(newTokenId), address(this)); // new token has expected liquidity - positionId = Position.calculatePositionKey( - address(lpm), newConfig.tickLower, newConfig.tickUpper, bytes32(newTokenId) - ); - liquidity = manager.getPositionLiquidity(config.poolKey.toId(), positionId); + + liquidity = lpm.getPositionLiquidity(newTokenId, newConfig); assertEq(liquidity, newLiquidity); } } diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index 018765e5..f1a65efd 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -305,16 +305,14 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { mint(config, liquidityAlice, alice, ZERO_BYTES); vm.stopPrank(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenIdAlice)); - uint128 oldLiquidity = StateLibrary.getPositionLiquidity(manager, config.poolKey.toId(), positionId); + uint128 oldLiquidity = lpm.getPositionLiquidity(tokenIdAlice, config); // bob can increase liquidity for alice even though he is not the owner / not approved vm.startPrank(bob); increaseLiquidity(tokenIdAlice, config, 100e18, ZERO_BYTES); vm.stopPrank(); - uint128 newLiquidity = StateLibrary.getPositionLiquidity(manager, config.poolKey.toId(), positionId); + uint128 newLiquidity = lpm.getPositionLiquidity(tokenIdAlice, config); // assert liqudity increased by the correct amount assertEq(newLiquidity, oldLiquidity + uint128(100e18)); @@ -645,9 +643,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { mint(config, liquidityAlice, alice, ZERO_BYTES); uint256 tokenIdAlice = lpm.nextTokenId() - 1; - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenIdAlice)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice, config); assertEq(liquidity, liquidityAlice); // alice increases with the balance in the position manager @@ -682,7 +678,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { uint256 amount0 = uint128(-delta.amount0()); uint256 amount1 = uint128(-delta.amount1()); - (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + liquidity = lpm.getPositionLiquidity(tokenIdAlice, config); assertEq(liquidity, 2 * liquidityAlice); // The balances were swept back to this address. diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index ee3779da..691a1445 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -91,9 +91,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities{value: amount0 + 1}(calls, _deadline); BalanceDelta delta = getLastDelta(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0())), "incorrect amount0"); @@ -145,9 +143,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities{value: amount0 * 2 + 1}(calls, _deadline); BalanceDelta delta = getLastDelta(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); // only paid the delta amount, with excess tokens returned to caller @@ -192,9 +188,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities{value: amount0 * 2 + 1}(calls, _deadline); BalanceDelta delta = getLastDelta(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); // only paid the delta amount, with excess tokens returned to caller @@ -218,9 +212,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -235,7 +227,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // No decrease/modifyLiq call will actually happen on the call to burn so the deltas array will be the same length. assertEq(numDeltas, hook.numberDeltasReturned()); - (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -273,9 +265,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -295,7 +285,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // No decrease/modifyLiq call will actually happen on the call to burn so the deltas array will be the same length. assertEq(numDeltas, hook.numberDeltasReturned()); - (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -333,9 +323,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -345,7 +333,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { burn(tokenId, config, ZERO_BYTES); BalanceDelta deltaBurn = getLastDelta(); - (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -383,9 +371,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 tokenId = lpm.nextTokenId(); mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); // burn liquidity @@ -400,7 +386,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities(calls, _deadline); BalanceDelta deltaBurn = getLastDelta(); - (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 0); // TODO: slightly off by 1 bip (0.0001%) @@ -451,9 +437,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta delta = getLastDelta(); // verify position liquidity increased - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled // verify native token balances changed as expected @@ -505,9 +489,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta delta = getLastDelta(); // verify position liquidity increased - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled // verify native token balances changed as expected, with overpaid tokens returned @@ -557,9 +539,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta delta = getLastDelta(); // verify position liquidity increased - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled // verify native token balances changed as expected, with overpaid tokens returned @@ -596,9 +576,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); BalanceDelta delta = getLastDelta(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); // verify native token balances changed as expected @@ -643,9 +621,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities(calls, _deadline); BalanceDelta delta = getLastDelta(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); // verify native token balances changed as expected @@ -815,9 +791,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.multicall{value: 10e18}(calls); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 100e18); assertEq(sub.notifySubscribeCount(), 1); diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol index 8de6b6c5..22f887f0 100644 --- a/test/position-managers/Permit.t.sol +++ b/test/position-managers/Permit.t.sol @@ -88,9 +88,7 @@ contract PermitTest is Test, PosmTestSetup { vm.stopPrank(); // alice's position decreased liquidity - bytes32 positionId = - keccak256(abi.encodePacked(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenIdAlice))); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice, config); assertEq(liquidity, liquidityAlice - liquidityToRemove); } @@ -209,9 +207,7 @@ contract PermitTest is Test, PosmTestSetup { decreaseLiquidity(tokenId, config, liquidityToRemove, ZERO_BYTES); vm.stopPrank(); - bytes32 positionId = - keccak256(abi.encodePacked(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId))); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, liquidityAlice - liquidityToRemove); } @@ -240,9 +236,8 @@ contract PermitTest is Test, PosmTestSetup { decreaseLiquidity(tokenId, config, liquidityToRemove, ZERO_BYTES); vm.stopPrank(); - bytes32 positionId = - keccak256(abi.encodePacked(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId))); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + assertEq(liquidity, liquidityAlice - liquidityToRemove); } } diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index 1106f733..da9734c0 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -215,9 +215,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest vm.prank(bob); lpm.multicall(calls); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, liquidityAlice - liquidityToRemove); } @@ -258,9 +256,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest vm.prank(bob); lpm.multicall(calls); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); (_amount,,) = permit2.allowance(address(bob), Currency.unwrap(currency0), address(lpm)); @@ -316,9 +312,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest vm.prank(bob); lpm.multicall(calls); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); (_amount0,,) = permit2.allowance(address(bob), Currency.unwrap(currency0), address(lpm)); (_amount1,,) = permit2.allowance(address(bob), Currency.unwrap(currency1), address(lpm)); diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol index a1544f13..9a15ebad 100644 --- a/test/position-managers/PositionManager.notifier.t.sol +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -234,9 +234,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.multicall(calls); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 100e18); assertEq(sub.notifySubscribeCount(), 1); @@ -272,9 +270,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.multicall(calls); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 110e18); assertEq(sub.notifySubscribeCount(), 1); diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index 64e4045a..6daf517d 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -115,9 +115,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(lpm.nextTokenId(), 2); assertEq(lpm.ownerOf(tokenId), address(this)); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0())), "incorrect amount0"); @@ -322,9 +320,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(tokenId, 1); assertEq(lpm.ownerOf(1), address(this)); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); @@ -339,7 +335,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { burn(tokenId, config, ZERO_BYTES); assertEq(numDeltas, hook.numberDeltasReturned()); - (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 0); @@ -368,9 +364,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(tokenId, 1); assertEq(lpm.ownerOf(1), address(this)); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta)); @@ -392,7 +386,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(uint256(int256(deltaBurn.amount0())), amount0); assertEq(uint256(int256(deltaBurn.amount1())), amount1); - (liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, 0); @@ -484,9 +478,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); BalanceDelta delta = getLastDelta(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); assertEq(currency0.balanceOfSelf(), balance0Before + uint256(uint128(delta.amount0()))); @@ -522,9 +514,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { lpm.modifyLiquidities(calls, _deadline); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); // did not recieve tokens, as they were forfeited with CLEAR @@ -599,9 +589,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1Before = currency1.balanceOfSelf(); decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); @@ -702,9 +690,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { decreaseLiquidity(tokenId, config, decreaseLiquidityDelta, ZERO_BYTES); BalanceDelta delta = getLastDelta(); - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); assertEq(liquidity, uint256(params.liquidityDelta) - decreaseLiquidityDelta); @@ -789,9 +775,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { vm.stopPrank(); // position liquidity increased - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 newLiq,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 newLiq = lpm.getPositionLiquidity(tokenId, config); assertEq(newLiq, liquidity + liquidityToAdd); // alice paid the tokens @@ -833,9 +817,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { { // position liquidity decreased - bytes32 positionId = - Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); - (uint256 newLiq,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + uint256 newLiq = lpm.getPositionLiquidity(tokenId, config); assertEq(newLiq, liquidity - liquidityToRemove); } From ea5f9ec38dbe9faeac0c0d80820dc02b86979a24 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:14:08 -0400 Subject: [PATCH 30/52] add bytes, clean up compiliation (#269) * add bytes, clean up compiliation * comments --- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 8 +-- src/base/Notifier.sol | 10 +-- src/interfaces/INotifier.sol | 8 ++- src/interfaces/ISubscriber.sol | 17 ++++- test/ERC721Permit.t.sol | 4 +- test/libraries/BipsLibrary.t.sol | 6 +- test/mocks/MockBadSubscribers.sol | 11 ++- test/mocks/MockSubscriber.sol | 16 +++-- test/position-managers/NativeToken.t.sol | 2 +- .../PositionManager.notifier.t.sol | 70 +++++++++++++++---- 44 files changed, 139 insertions(+), 81 deletions(-) diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 856ec3b4..4a8a8af9 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47158 \ No newline at end of file +47176 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index c3b5ff40..5f5388bf 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46976 \ No newline at end of file +46993 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 9cd54e27..0deea6f3 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -122971 \ No newline at end of file +122988 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index d06ed4e0..f0ffb8d7 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122680 \ No newline at end of file +122698 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 44efeb87..03487c9a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130049 \ No newline at end of file +130067 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 75b82d5a..6f25fa6c 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129759 \ No newline at end of file +129776 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 3b272ddf..4e0b3e67 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141357 \ No newline at end of file +141379 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 3352dfe7..0ac9adb4 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150205 \ No newline at end of file +150227 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index 3352dfe7..0ac9adb4 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150205 \ No newline at end of file +150227 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index d188e1fa..dc5b485f 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149830 \ No newline at end of file +149852 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 17b24e44..19a44cb4 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108560 \ No newline at end of file +108578 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index cbd40ace..54ee2317 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115748 \ No newline at end of file +115770 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index 1c42c2a2..241fc1a5 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115373 \ No newline at end of file +115395 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 02110f25..39f7f641 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134128 \ No newline at end of file +134146 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index c9a2b5b8..d76b2366 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126868 \ No newline at end of file +126885 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 07d19a82..d0934067 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128464 \ No newline at end of file +128486 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index 120eefd1..b7a40909 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116603 \ No newline at end of file +116625 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 66fe2ca8..56522a67 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152311 \ No newline at end of file +152333 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index d3de4546..3a72622d 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151566 \ No newline at end of file +151588 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index a93023c8..ed79ff90 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134111 \ No newline at end of file +134133 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 87776e95..89b7d1b0 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130350 \ No newline at end of file +130372 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index a4c4b715..d08ced17 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -170970 \ No newline at end of file +170992 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index bafd15d6..06871ca3 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140950 \ No newline at end of file +140972 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index d2181e08..26a0e433 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336789 \ No newline at end of file +336811 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index db016355..2225fef6 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345258 \ No newline at end of file +345280 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index acddf123..4658e17d 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344813 \ No newline at end of file +344835 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index dfd3f6c8..fa5c26f0 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314771 \ No newline at end of file +314793 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 04f38148..856ad2de 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315413 \ No newline at end of file +315435 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index b0645399..dbdfcf61 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -240995 \ No newline at end of file +241017 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index e6d8ab99..2262310e 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370951 \ No newline at end of file +370973 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index bdad41bc..4428bae1 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320789 \ No newline at end of file +320811 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 6146a741..430eef12 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372089 \ No newline at end of file +372111 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 6fcfb86a..c3044f8c 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371482 \ No newline at end of file +371504 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 993285bf..93b17f36 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416464 \ No newline at end of file +416486 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index c8a43f3b..32e95810 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -105,7 +105,7 @@ contract PositionManager is } /// @inheritdoc INotifier - function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber) + function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber, bytes calldata data) external payable onlyIfApproved(msg.sender, tokenId) @@ -113,18 +113,18 @@ contract PositionManager is { // call to _subscribe will revert if the user already has a sub positionConfigs.setSubscribe(tokenId); - _subscribe(tokenId, config, subscriber); + _subscribe(tokenId, config, subscriber, data); } /// @inheritdoc INotifier - function unsubscribe(uint256 tokenId, PositionConfig calldata config) + function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) external payable onlyIfApproved(msg.sender, tokenId) onlyValidConfig(tokenId, config) { positionConfigs.setUnsubscribe(tokenId); - _unsubscribe(tokenId, config); + _unsubscribe(tokenId, config, data); } function _handleAction(uint256 action, bytes calldata params) internal virtual override { diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 174d8ea1..556ea308 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -25,23 +25,25 @@ abstract contract Notifier is INotifier { mapping(uint256 tokenId => ISubscriber subscriber) public subscriber; - function _subscribe(uint256 tokenId, PositionConfig memory config, address newSubscriber) internal { + function _subscribe(uint256 tokenId, PositionConfig memory config, address newSubscriber, bytes memory data) + internal + { ISubscriber _subscriber = subscriber[tokenId]; if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(address(_subscriber)); subscriber[tokenId] = ISubscriber(newSubscriber); - ISubscriber(newSubscriber).notifySubscribe(tokenId, config); + ISubscriber(newSubscriber).notifySubscribe(tokenId, config, data); emit Subscribed(tokenId, address(newSubscriber)); } /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. - function _unsubscribe(uint256 tokenId, PositionConfig memory config) internal { + function _unsubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) internal { ISubscriber _subscriber = subscriber[tokenId]; uint256 subscriberGasLimit = block.gaslimit.calculatePortion(BLOCK_LIMIT_BPS); - try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config) {} catch {} + try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config, data) {} catch {} delete subscriber[tokenId]; emit Unsubscribed(tokenId, address(_subscriber)); diff --git a/src/interfaces/INotifier.sol b/src/interfaces/INotifier.sol index 6fced60e..33ec63f8 100644 --- a/src/interfaces/INotifier.sol +++ b/src/interfaces/INotifier.sol @@ -9,15 +9,19 @@ interface INotifier { /// @param tokenId the ERC721 tokenId /// @param config the corresponding PositionConfig for the tokenId /// @param subscriber the address to notify + /// @param data caller-provided data that's forwarded to the subscriber contract /// @dev Calling subscribe when a position is already subscribed will revert /// @dev payable so it can be multicalled with NATIVE related actions - function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber) external payable; + function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber, bytes calldata data) + external + payable; /// @notice Removes the subscriber from receiving notifications for a respective position /// @param tokenId the ERC721 tokenId /// @param config the corresponding PositionConfig for the tokenId + /// @param data caller-provided data that's forwarded to the subscriber contract /// @dev payable so it can be multicalled with NATIVE related actions - function unsubscribe(uint256 tokenId, PositionConfig calldata config) external payable; + function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) external payable; /// @notice Returns whether a a position should call out to notify a subscribing contract on modification or transfer /// @param tokenId the ERC721 tokenId diff --git a/src/interfaces/ISubscriber.sol b/src/interfaces/ISubscriber.sol index a74efeeb..81a68c0d 100644 --- a/src/interfaces/ISubscriber.sol +++ b/src/interfaces/ISubscriber.sol @@ -3,9 +3,22 @@ pragma solidity ^0.8.24; import {PositionConfig} from "../libraries/PositionConfig.sol"; +/// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager interface ISubscriber { - function notifySubscribe(uint256 tokenId, PositionConfig memory config) external; - function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config) external; + /// @param tokenId the token ID of the position + /// @param config details about the position + /// @param data additional data passed in by the caller + function notifySubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; + /// @param tokenId the token ID of the position + /// @param config details about the position + /// @param data additional data passed in by the caller + function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; + /// @param tokenId the token ID of the position + /// @param config details about the position + /// @param liquidityChange the change in liquidity on the underlying position function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) external; + /// @param tokenId the token ID of the position + /// @param previousOwner address of the old owner + /// @param newOwner address of the new owner function notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) external; } diff --git a/test/ERC721Permit.t.sol b/test/ERC721Permit.t.sol index 726709b1..0dc633d3 100644 --- a/test/ERC721Permit.t.sol +++ b/test/ERC721Permit.t.sol @@ -59,14 +59,14 @@ contract ERC721PermitTest is Test { } // --- Test the signature-based approvals (permit) --- - function test_permitTypeHash() public view { + function test_permitTypeHash() public pure { assertEq( ERC721PermitHashLibrary.PERMIT_TYPEHASH, keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)") ); } - function test_fuzz_permitHash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) public view { + function test_fuzz_permitHash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) public pure { bytes32 expectedHash = keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); assertEq(expectedHash, ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline)); diff --git a/test/libraries/BipsLibrary.t.sol b/test/libraries/BipsLibrary.t.sol index df9f815a..f895cf4d 100644 --- a/test/libraries/BipsLibrary.t.sol +++ b/test/libraries/BipsLibrary.t.sol @@ -27,17 +27,17 @@ contract PositionConfigTest is Test { } } - function test_gasLimit_100_percent() public { + function test_gasLimit_100_percent() public view { assertEq(block.gaslimit, block.gaslimit.calculatePortion(10_000)); } - function test_gasLimit_1_percent() public { + function test_gasLimit_1_percent() public view { /// 100 bps = 1% // 1% of 3_000_000_000 is 30_000_000 assertEq(30_000_000, block.gaslimit.calculatePortion(100)); } - function test_gasLimit_1BP() public { + function test_gasLimit_1BP() public view { /// 1bp is 0.01% assertEq(300_000, block.gaslimit.calculatePortion(1)); } diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol index 2fa517a1..d619ff63 100644 --- a/test/mocks/MockBadSubscribers.sol +++ b/test/mocks/MockBadSubscribers.sol @@ -29,11 +29,11 @@ contract MockReturnDataSubscriber is ISubscriber { _; } - function notifySubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + function notifySubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { notifySubscribeCount++; } - function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { notifyUnsubscribeCount++; uint256 _memPtr = memPtr; assembly { @@ -44,14 +44,11 @@ contract MockReturnDataSubscriber is ISubscriber { } } - function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) - external - onlyByPosm - { + function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { notifyModifyLiquidityCount++; } - function notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) external onlyByPosm { + function notifyTransfer(uint256, address, address) external onlyByPosm { notifyTransferCount++; } diff --git a/test/mocks/MockSubscriber.sol b/test/mocks/MockSubscriber.sol index b6c92cad..1e317ad1 100644 --- a/test/mocks/MockSubscriber.sol +++ b/test/mocks/MockSubscriber.sol @@ -14,6 +14,9 @@ contract MockSubscriber is ISubscriber { uint256 public notifyModifyLiquidityCount; uint256 public notifyTransferCount; + bytes public subscribeData; + bytes public unsubscribeData; + error NotAuthorizedNotifer(address sender); error NotImplemented(); @@ -27,22 +30,21 @@ contract MockSubscriber is ISubscriber { _; } - function notifySubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + function notifySubscribe(uint256, PositionConfig memory, bytes memory data) external onlyByPosm { notifySubscribeCount++; + subscribeData = data; } - function notifyUnsubscribe(uint256 tokenId, PositionConfig memory config) external onlyByPosm { + function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory data) external onlyByPosm { notifyUnsubscribeCount++; + unsubscribeData = data; } - function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) - external - onlyByPosm - { + function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { notifyModifyLiquidityCount++; } - function notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) external onlyByPosm { + function notifyTransfer(uint256, address, address) external onlyByPosm { notifyTransferCount++; } } diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index 691a1445..662ddda1 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -787,7 +787,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); - calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub, ZERO_BYTES); lpm.multicall{value: 10e18}(calls); diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol index 9a15ebad..06dd5d25 100644 --- a/test/position-managers/PositionManager.notifier.t.sol +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -49,7 +49,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { function test_subscribe_revertsWithEmptyPositionConfig() public { uint256 tokenId = lpm.nextTokenId(); vm.expectRevert("NOT_MINTED"); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); } function test_subscribe_revertsWhenNotApproved() public { @@ -59,7 +59,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { // this contract is not approved to operate on alice's liq vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(this))); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); } function test_subscribe_reverts_withIncorrectConfig() public { @@ -74,7 +74,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { PositionConfig memory incorrectConfig = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 301}); vm.expectRevert(abi.encodeWithSelector(IPositionManager.IncorrectPositionConfigForTokenId.selector, tokenId)); - lpm.subscribe(tokenId, incorrectConfig, address(sub)); + lpm.subscribe(tokenId, incorrectConfig, address(sub), ZERO_BYTES); } function test_subscribe_succeeds() public { @@ -86,7 +86,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); assertEq(lpm.hasSubscriber(tokenId), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); @@ -102,7 +102,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); assertEq(lpm.hasSubscriber(tokenId), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); @@ -131,7 +131,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); assertEq(lpm.hasSubscriber(tokenId), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); @@ -150,7 +150,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); assertEq(lpm.hasSubscriber(tokenId), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); @@ -169,7 +169,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); assertEq(lpm.hasSubscriber(tokenId), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); @@ -188,9 +188,9 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(sub)); + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); - lpm.unsubscribe(tokenId, config); + lpm.unsubscribe(tokenId, config, ZERO_BYTES); assertEq(sub.notifyUnsubscribeCount(), 1); assertEq(lpm.hasSubscriber(tokenId), false); @@ -206,10 +206,10 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { lpm.approve(address(this), tokenId); vm.stopPrank(); - lpm.subscribe(tokenId, config, address(badSubscriber)); + lpm.subscribe(tokenId, config, address(badSubscriber), ZERO_BYTES); MockReturnDataSubscriber(badSubscriber).setReturnDataSize(0x600000); - lpm.unsubscribe(tokenId, config); + lpm.unsubscribe(tokenId, config, ZERO_BYTES); // the subscriber contract call failed bc it used too much gas assertEq(MockReturnDataSubscriber(badSubscriber).notifyUnsubscribeCount(), 0); @@ -230,7 +230,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); - calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub, ZERO_BYTES); lpm.multicall(calls); @@ -265,7 +265,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { bytes[] memory calls = new bytes[](3); calls[0] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions, _deadline); - calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub); + calls[1] = abi.encodeWithSelector(lpm.subscribe.selector, tokenId, config, sub, ZERO_BYTES); calls[2] = abi.encodeWithSelector(lpm.modifyLiquidities.selector, actions2, _deadline); lpm.multicall(calls); @@ -289,6 +289,46 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { vm.stopPrank(); vm.expectRevert(); - lpm.unsubscribe(tokenId, config); + lpm.unsubscribe(tokenId, config, ZERO_BYTES); + } + + function test_subscribe_withData() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + bytes memory subData = abi.encode(address(this)); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub), subData); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + assertEq(sub.notifySubscribeCount(), 1); + assertEq(abi.decode(sub.subscribeData(), (address)), address(this)); + } + + function test_unsubscribe_withData() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + bytes memory subData = abi.encode(address(this)); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + + lpm.unsubscribe(tokenId, config, subData); + + assertEq(sub.notifyUnsubscribeCount(), 1); + assertEq(lpm.hasSubscriber(tokenId), false); + assertEq(address(lpm.subscriber(tokenId)), address(0)); + assertEq(abi.decode(sub.unsubscribeData(), (address)), address(this)); } } From f402aa7eb77f18df3da603ad28e796dddfaf82e9 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Mon, 5 Aug 2024 02:59:21 +0100 Subject: [PATCH 31/52] actions with no unlock (#231) * actions with no unlock * Test lockless posm via hooks (#266) * test hook which modifiesLiquidities in beforeSwap * test hook modifying liquidity * minor cleanups * test that hooks cannot re-enter modifyLiquidities * hook mints liquidity with modifyLiquidities * PR cmments * rename * rename * Update src/interfaces/IPositionManager.sol Co-authored-by: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> * misc code comments --------- Co-authored-by: saucepoint Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> Co-authored-by: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> --- .../BaseActionsRouter_mock10commands.snap | 2 +- ...p_settleFromCaller_takeAllToMsgSender.snap | 2 +- ...eFromCaller_takeAllToSpecifiedAddress.snap | 2 +- ..._settleWithBalance_takeAllToMsgSender.snap | 2 +- ...WithBalance_takeAllToSpecifiedAddress.snap | 2 +- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- ...Router_ExactOut1Hop_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOut1Hop_nativeOut.snap | 2 +- .../V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .../V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .../V4Router_ExactOut2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOut3Hops_nativeIn.snap | 2 +- .../V4Router_ExactOut3Hops_nativeOut.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- ...r_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/PositionManager.sol | 12 +- src/base/BaseActionsRouter.sol | 6 +- src/interfaces/IPositionManager.sol | 11 +- .../PositionManager.modifyLiquidities.t.sol | 282 ++++++++++++++++++ .../PositionManager.multicall.t.sol | 2 +- test/position-managers/PositionManager.t.sol | 2 +- test/shared/HookModifyLiquidities.sol | 75 +++++ test/shared/LiquidityOperations.sol | 2 +- test/shared/PosmTestSetup.sol | 19 ++ 72 files changed, 466 insertions(+), 71 deletions(-) create mode 100644 test/position-managers/PositionManager.modifyLiquidities.t.sol create mode 100644 test/shared/HookModifyLiquidities.sol diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index 34a072bb..031ba1de 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -61756 \ No newline at end of file +61794 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index e30ad0bd..cef56fcc 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -133519 \ No newline at end of file +133557 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index 7d2a0de8..69ef47b0 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -135555 \ No newline at end of file +135593 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index 86aaa87a..b1101ea4 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -128029 \ No newline at end of file +128067 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index 3cee4098..80fd74db 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -128167 \ No newline at end of file +128205 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 4a8a8af9..0d1315e6 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47176 \ No newline at end of file +47224 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 5f5388bf..db13bb26 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46993 \ No newline at end of file +47041 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 0deea6f3..5ad60d21 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -122988 \ No newline at end of file +123036 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index f0ffb8d7..91c7e47b 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122698 \ No newline at end of file +122746 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 03487c9a..79fc0eaf 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130067 \ No newline at end of file +130115 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 6f25fa6c..b1129816 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129776 \ No newline at end of file +129824 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 4e0b3e67..09a04fb9 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141379 \ No newline at end of file +141439 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 0ac9adb4..cbbed084 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150227 \ No newline at end of file +150287 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index 0ac9adb4..cbbed084 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150227 \ No newline at end of file +150287 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index dc5b485f..580c9abe 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149852 \ No newline at end of file +149912 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 19a44cb4..6adda5c6 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108578 \ No newline at end of file +108626 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index 54ee2317..a7b42e47 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115770 \ No newline at end of file +115830 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index 241fc1a5..d3554272 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115395 \ No newline at end of file +115455 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 39f7f641..0a3cc22e 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134146 \ No newline at end of file +134194 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index d76b2366..13c3c5a8 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126885 \ No newline at end of file +126933 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index d0934067..ee5cb915 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128486 \ No newline at end of file +128546 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index b7a40909..cce4c7e7 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116625 \ No newline at end of file +116685 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 56522a67..b5fb9a28 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152333 \ No newline at end of file +152393 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 3a72622d..a0c16300 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151588 \ No newline at end of file +151648 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index ed79ff90..eb10283f 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134133 \ No newline at end of file +134193 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 89b7d1b0..de8ee99c 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130372 \ No newline at end of file +130432 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index d08ced17..2b324d60 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -170992 \ No newline at end of file +171052 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 06871ca3..903117b0 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140972 \ No newline at end of file +141032 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 26a0e433..464fbbb0 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336811 \ No newline at end of file +336871 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 2225fef6..93e4c662 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345280 \ No newline at end of file +345340 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 4658e17d..0bb4febe 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344835 \ No newline at end of file +344895 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index fa5c26f0..217a8ffb 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314793 \ No newline at end of file +314853 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 856ad2de..64083d11 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315435 \ No newline at end of file +315495 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index dbdfcf61..c99faa84 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241017 \ No newline at end of file +241077 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 2262310e..4400b3b4 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370973 \ No newline at end of file +371033 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 4428bae1..5447d5b8 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320811 \ No newline at end of file +320871 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 430eef12..0ce5b2aa 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372111 \ No newline at end of file +372171 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index c3044f8c..39b89b70 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371504 \ No newline at end of file +371564 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 93b17f36..aa0725bd 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416486 \ No newline at end of file +416622 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index cef38079..4b0fd4c3 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8438 \ No newline at end of file +8446 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index 32d9c91a..141ee629 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -119534 \ No newline at end of file +119572 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index 516f2efe..4dc9f6ce 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -118729 \ No newline at end of file +118767 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index b98ed60b..8b53041e 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -127601 \ No newline at end of file +127639 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 1b8b982f..fb68412c 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -134431 \ No newline at end of file +134469 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index d96af129..a494c349 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -185972 \ No newline at end of file +186010 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index 66eb073f..428b15cc 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -177907 \ No newline at end of file +177945 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 17723637..838a2bb4 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -237494 \ No newline at end of file +237532 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index 53ae189d..f0c5a684 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -229453 \ No newline at end of file +229491 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index e30ad0bd..cef56fcc 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -133519 \ No newline at end of file +133557 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index 954557e3..4e4ef13d 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -118622 \ No newline at end of file +118660 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index faf9096a..4409062d 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -117800 \ No newline at end of file +117838 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap index 44f276e8..33a2bacb 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeIn_sweepETH.snap @@ -1 +1 @@ -125192 \ No newline at end of file +125230 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap index fba2e6e3..089e6949 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_nativeOut.snap @@ -1 +1 @@ -119470 \ No newline at end of file +119508 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index 3e0079ae..5cbd7ccc 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -128342 \ No newline at end of file +128380 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index b144de14..5c8ee509 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -133143 \ No newline at end of file +133181 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index ce8f4467..a3913eb5 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -185287 \ No newline at end of file +185325 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap index 4539caa7..cb7a0522 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops_nativeIn.snap @@ -1 +1 @@ -182137 \ No newline at end of file +182175 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index de954cf9..dbc8b942 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -237427 \ No newline at end of file +237465 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap index 0012a090..a0c789c3 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeIn.snap @@ -1 +1 @@ -234301 \ No newline at end of file +234339 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap index 2028f219..68126bb6 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops_nativeOut.snap @@ -1 +1 @@ -228579 \ No newline at end of file +228617 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index 107eb18c..91008315 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -131940 \ No newline at end of file +131978 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index 2d74c064..7a8a1df5 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -123989 \ No newline at end of file +124027 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index e3baa57f..0db6b17a 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -118280 \ No newline at end of file +118318 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 32e95810..2630f4e5 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -93,8 +93,7 @@ contract PositionManager is _; } - /// @param unlockData is an encoding of actions, params, and currencies - /// @param deadline is the timestamp at which the unlockData will no longer be valid + /// @inheritdoc IPositionManager function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable @@ -104,6 +103,15 @@ contract PositionManager is _executeActions(unlockData); } + /// @inheritdoc IPositionManager + function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) + external + payable + isNotLocked + { + _executeActionsWithoutUnlock(actions, params); + } + /// @inheritdoc INotifier function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber, bytes calldata data) external diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index 821fa0e0..85bca0e8 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -32,7 +32,11 @@ abstract contract BaseActionsRouter is SafeCallback { function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { // abi.decode(data, (bytes, bytes[])); (bytes calldata actions, bytes[] calldata params) = data.decodeActionsRouterParams(); + _executeActionsWithoutUnlock(actions, params); + return ""; + } + function _executeActionsWithoutUnlock(bytes calldata actions, bytes[] calldata params) internal { uint256 numActions = actions.length; if (numActions != params.length) revert InputLengthMismatch(); @@ -41,8 +45,6 @@ abstract contract BaseActionsRouter is SafeCallback { _handleAction(action, params[actionIndex]); } - - return ""; } /// @notice function to handle the parsing and execution of an action and its parameters diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 3c45df7d..1061877b 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -12,11 +12,20 @@ interface IPositionManager is INotifier { error DeadlinePassed(); error IncorrectPositionConfigForTokenId(uint256 tokenId); - /// @notice Batches many liquidity modification calls to pool manager + /// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity + /// @dev This is the standard entrypoint for the PositionManager /// @param payload is an encoding of actions, and parameters for those actions /// @param deadline is the deadline for the batched actions to be executed function modifyLiquidities(bytes calldata payload, uint256 deadline) external payable; + /// @notice Batches actions for modifying liquidity without unlocking v4 PoolManager + /// @dev This must be called by a contract that has already unlocked the v4 PoolManager + /// @param actions the actions to perform + /// @param params the parameters to provide for the actions + function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable; + + /// @notice Used to get the ID that will be used for the next minted liquidity position + /// @return uint256 The next token ID function nextTokenId() external view returns (uint256); /// @param tokenId the ERC721 tokenId diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol new file mode 100644 index 00000000..6244c456 --- /dev/null +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; + +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {ReentrancyLock} from "../../src/base/ReentrancyLock.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; +import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; + +import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; +import {Planner, Plan} from "../shared/Planner.sol"; +import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; + +contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityFuzzers { + using StateLibrary for IPoolManager; + using PoolIdLibrary for PoolKey; + + PoolId poolId; + address alice; + uint256 alicePK; + address bob; + + PositionConfig config; + + function setUp() public { + (alice, alicePK) = makeAddrAndKey("ALICE"); + (bob,) = makeAddrAndKey("BOB"); + + deployFreshManagerAndRouters(); + deployMintAndApprove2Currencies(); + + // Requires currency0 and currency1 to be set in base Deployers contract. + deployAndApprovePosm(manager); + + seedBalance(alice); + approvePosmFor(alice); + + // must deploy after posm + // Deploys a hook which can accesses IPositionManager.modifyLiquiditiesWithoutUnlock + deployPosmHookModifyLiquidities(); + seedBalance(address(hookModifyLiquidities)); + + (key, poolId) = initPool(currency0, currency1, IHooks(hookModifyLiquidities), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + + config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); + } + + /// @dev minting liquidity without approval is allowable + function test_hook_mint() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook mints a new position in beforeSwap via hookData + uint256 hookTokenId = lpm.nextTokenId(); + uint256 newLiquidity = 10e18; + bytes memory calls = getMintEncoded(config, newLiquidity, address(hookModifyLiquidities), ZERO_BYTES); + + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + + // original liquidity unchanged + assertEq(liquidity, initialLiquidity); + + // hook minted its own position + liquidity = lpm.getPositionLiquidity(hookTokenId, config); + assertEq(liquidity, newLiquidity); + + assertEq(lpm.ownerOf(tokenId), address(this)); // original position owned by this contract + assertEq(lpm.ownerOf(hookTokenId), address(hookModifyLiquidities)); // hook position owned by hook + } + + /// @dev increasing liquidity without approval is allowable + function test_hook_increaseLiquidity() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook increases liquidity in beforeSwap via hookData + uint256 newLiquidity = 10e18; + bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, ZERO_BYTES); + + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + + assertEq(liquidity, initialLiquidity + newLiquidity); + } + + /// @dev hook can decrease liquidity with approval + function test_hook_decreaseLiquidity() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // approve the hook for decreasing liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + + // hook decreases liquidity in beforeSwap via hookData + uint256 liquidityToDecrease = 10e18; + bytes memory calls = getDecreaseEncoded(tokenId, config, liquidityToDecrease, ZERO_BYTES); + + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + + assertEq(liquidity, initialLiquidity - liquidityToDecrease); + } + + /// @dev hook can collect liquidity with approval + function test_hook_collect() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // approve the hook for collecting liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + + // donate to generate revenue + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate(config.poolKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + uint256 balance0HookBefore = currency0.balanceOf(address(hookModifyLiquidities)); + uint256 balance1HookBefore = currency1.balanceOf(address(hookModifyLiquidities)); + + // hook collects liquidity in beforeSwap via hookData + bytes memory calls = getCollectEncoded(tokenId, config, ZERO_BYTES); + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + + // liquidity unchanged + assertEq(liquidity, initialLiquidity); + + // hook collected the fee revenue + assertEq(currency0.balanceOf(address(hookModifyLiquidities)), balance0HookBefore + feeRevenue0 - 1 wei); // imprecision, core is keeping 1 wei + assertEq(currency1.balanceOf(address(hookModifyLiquidities)), balance1HookBefore + feeRevenue1 - 1 wei); + } + + /// @dev hook can burn liquidity with approval + function test_hook_burn() public { + // mint some liquidity that is NOT burned in beforeSwap + mint(config, 100e18, address(this), ZERO_BYTES); + + // the position to be burned by the hook + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + // TODO: make this less jank since HookModifyLiquidites also has delta saving capabilities + // BalanceDelta mintDelta = getLastDelta(); + BalanceDelta mintDelta = hookModifyLiquidities.deltas(hookModifyLiquidities.numberDeltasReturned() - 1); + + // approve the hook for burning liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + + uint256 balance0HookBefore = currency0.balanceOf(address(hookModifyLiquidities)); + uint256 balance1HookBefore = currency1.balanceOf(address(hookModifyLiquidities)); + + // hook burns liquidity in beforeSwap via hookData + bytes memory calls = getBurnEncoded(tokenId, config, ZERO_BYTES); + swap(key, true, -1e18, calls); + + uint256 liquidity = lpm.getPositionLiquidity(tokenId, config); + + // liquidity burned + assertEq(liquidity, 0); + // 721 will revert if the token does not exist + vm.expectRevert(); + lpm.ownerOf(tokenId); + + // hook claimed the burned liquidity + assertEq( + currency0.balanceOf(address(hookModifyLiquidities)), + balance0HookBefore + uint128(-mintDelta.amount0() - 1 wei) // imprecision since core is keeping 1 wei + ); + assertEq( + currency1.balanceOf(address(hookModifyLiquidities)), + balance1HookBefore + uint128(-mintDelta.amount1() - 1 wei) + ); + } + + // --- Revert Scenarios --- // + /// @dev Hook does not have approval so decreasingly liquidity should revert + function test_hook_decreaseLiquidity_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook decreases liquidity in beforeSwap via hookData + uint256 liquidityToDecrease = 10e18; + bytes memory calls = getDecreaseEncoded(tokenId, config, liquidityToDecrease, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + Hooks.FailedHookCall.selector, + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev hook does not have approval so collecting liquidity should revert + function test_hook_collect_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // donate to generate revenue + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate(config.poolKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + // hook collects liquidity in beforeSwap via hookData + bytes memory calls = getCollectEncoded(tokenId, config, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + Hooks.FailedHookCall.selector, + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev hook does not have approval so burning liquidity should revert + function test_hook_burn_revert() public { + // the position to be burned by the hook + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook burns liquidity in beforeSwap via hookData + bytes memory calls = getBurnEncoded(tokenId, config, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + Hooks.FailedHookCall.selector, + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev hook cannot re-enter modifyLiquiditiesWithoutUnlock in beforeRemoveLiquidity + function test_hook_increaseLiquidity_reenter_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + uint256 newLiquidity = 10e18; + + // to be provided as hookData, so beforeAddLiquidity attempts to increase liquidity + bytes memory hookCall = getIncreaseEncoded(tokenId, config, newLiquidity, ZERO_BYTES); + bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, hookCall); + + // should revert because hook is re-entering modifyLiquiditiesWithoutUnlock + vm.expectRevert( + abi.encodeWithSelector( + Hooks.FailedHookCall.selector, abi.encodeWithSelector(ReentrancyLock.ContractLocked.selector) + ) + ); + lpm.modifyLiquidities(calls, _deadline); + } +} diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index da9734c0..4f0ef8fa 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -210,7 +210,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest ); uint256 liquidityToRemove = 0.4444e18; bytes memory actions = getDecreaseEncoded(tokenId, config, liquidityToRemove, ZERO_BYTES); - calls[1] = abi.encodeWithSelector(PositionManager(lpm).modifyLiquidities.selector, actions, _deadline); + calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); vm.prank(bob); lpm.multicall(calls); diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index 6daf517d..ca415468 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -342,7 +342,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(currency0.balanceOfSelf(), balance0BeforeBurn + uint256(int256(deltaDecrease.amount0()))); assertEq(currency1.balanceOfSelf(), balance1BeforeBurn + uint256(uint128(deltaDecrease.amount1()))); - // OZ 721 will revert if the token does not exist + // 721 will revert if the token does not exist vm.expectRevert(); lpm.ownerOf(1); diff --git a/test/shared/HookModifyLiquidities.sol b/test/shared/HookModifyLiquidities.sol new file mode 100644 index 00000000..2e9b5ecf --- /dev/null +++ b/test/shared/HookModifyLiquidities.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; + +import {HookSavesDelta} from "./HookSavesDelta.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; + +/// @notice This contract is NOT a production use contract. It is meant to be used in testing to verify that external contracts can modify liquidity without a lock (IPositionManager.modifyLiquiditiesWithoutUnlock) +/// @dev a hook that can modify liquidity in beforeSwap +contract HookModifyLiquidities is HookSavesDelta { + IPositionManager posm; + IAllowanceTransfer permit2; + + function setAddresses(IPositionManager _posm, IAllowanceTransfer _permit2) external { + posm = _posm; + permit2 = _permit2; + } + + function beforeSwap( + address, /* sender **/ + PoolKey calldata key, /* key **/ + IPoolManager.SwapParams calldata, /* params **/ + bytes calldata hookData + ) external override returns (bytes4, BeforeSwapDelta, uint24) { + approvePosmCurrency(key.currency0); + approvePosmCurrency(key.currency1); + + (bytes memory actions, bytes[] memory params) = abi.decode(hookData, (bytes, bytes[])); + posm.modifyLiquiditiesWithoutUnlock(actions, params); + return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function beforeAddLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + IPoolManager.ModifyLiquidityParams calldata, /* params **/ + bytes calldata hookData + ) external override returns (bytes4) { + if (hookData.length > 0) { + (bytes memory actions, bytes[] memory params) = abi.decode(hookData, (bytes, bytes[])); + posm.modifyLiquiditiesWithoutUnlock(actions, params); + } + return this.beforeAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + IPoolManager.ModifyLiquidityParams calldata, /* params **/ + bytes calldata hookData + ) external override returns (bytes4) { + if (hookData.length > 0) { + (bytes memory actions, bytes[] memory params) = abi.decode(hookData, (bytes, bytes[])); + posm.modifyLiquiditiesWithoutUnlock(actions, params); + } + return this.beforeRemoveLiquidity.selector; + } + + function approvePosmCurrency(Currency currency) internal { + // Because POSM uses permit2, we must execute 2 permits/approvals. + // 1. First, the caller must approve permit2 on the token. + IERC20(Currency.unwrap(currency)).approve(address(permit2), type(uint256).max); + // 2. Then, the caller must approve POSM as a spender of permit2. TODO: This could also be a signature. + permit2.approve(Currency.unwrap(currency), address(posm), type(uint160).max, type(uint48).max); + } +} diff --git a/test/shared/LiquidityOperations.sol b/test/shared/LiquidityOperations.sol index 026abe8c..65ed085d 100644 --- a/test/shared/LiquidityOperations.sol +++ b/test/shared/LiquidityOperations.sol @@ -81,7 +81,7 @@ abstract contract LiquidityOperations is CommonBase { lpm.modifyLiquidities(calls, _deadline); } - // Helper functions for getting encoded calldata for .modifyLiquidities + // Helper functions for getting encoded calldata for .modifyLiquidities() or .modifyLiquiditiesWithoutUnlock() function getMintEncoded(PositionConfig memory config, uint256 liquidity, address recipient, bytes memory hookData) internal pure diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol index 3ae10590..345726c4 100644 --- a/test/shared/PosmTestSetup.sol +++ b/test/shared/PosmTestSetup.sol @@ -14,6 +14,7 @@ import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; import {HookSavesDelta} from "./HookSavesDelta.sol"; +import {HookModifyLiquidities} from "./HookModifyLiquidities.sol"; import {ERC721PermitHashLibrary} from "../../src/libraries/ERC721PermitHash.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. @@ -24,12 +25,30 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { HookSavesDelta hook; address hookAddr = address(uint160(Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG)); + HookModifyLiquidities hookModifyLiquidities; + address hookModifyLiquiditiesAddr = address( + uint160( + Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG + ) + ); + function deployPosmHookSavesDelta() public { HookSavesDelta impl = new HookSavesDelta(); vm.etch(hookAddr, address(impl).code); hook = HookSavesDelta(hookAddr); } + /// @dev deploys a special test hook where beforeSwap hookData is used to modify liquidity + function deployPosmHookModifyLiquidities() public { + HookModifyLiquidities impl = new HookModifyLiquidities(); + vm.etch(hookModifyLiquiditiesAddr, address(impl).code); + hookModifyLiquidities = HookModifyLiquidities(hookModifyLiquiditiesAddr); + + // set posm address since constructor args are not easily copied by vm.etch + hookModifyLiquidities.setAddresses(lpm, permit2); + } + function deployAndApprovePosm(IPoolManager poolManager) public { deployPosm(poolManager); approvePosm(); From d1f9005065f9e86b358d1e388c820dd8fcdcc33f Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:57:22 -0400 Subject: [PATCH 32/52] ERC721Permit - PermitForAll (#271) * initial impl * hashing tests * setApprovalForAll override tests * reorganize ERC721Permit file * separate out tests for permit and permitForAll * tests for permitForAll * regenerate gas * add to interface * pr feedback * rename variable --- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/PositionManager_permit.snap | 2 +- ...PositionManager_permit_secondPosition.snap | 2 +- .../PositionManager_permit_twice.snap | 2 +- src/base/ERC721Permit_v4.sol | 52 ++- src/interfaces/IERC721Permit_v4.sol | 18 +- src/libraries/ERC721PermitHash.sol | 22 +- .../ERC721Permit.permit.t.sol} | 30 +- .../ERC721Permit.permitForAll.t.sol | 348 ++++++++++++++++++ 9 files changed, 452 insertions(+), 26 deletions(-) rename test/{ERC721Permit.t.sol => erc721Permit/ERC721Permit.permit.t.sol} (89%) create mode 100644 test/erc721Permit/ERC721Permit.permitForAll.t.sol diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index aa0725bd..29d08734 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416622 \ No newline at end of file +416667 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit.snap b/.forge-snapshots/PositionManager_permit.snap index ee34c59c..81715a67 100644 --- a/.forge-snapshots/PositionManager_permit.snap +++ b/.forge-snapshots/PositionManager_permit.snap @@ -1 +1 @@ -79445 \ No newline at end of file +79484 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_secondPosition.snap b/.forge-snapshots/PositionManager_permit_secondPosition.snap index 0b63319b..bd214aa7 100644 --- a/.forge-snapshots/PositionManager_permit_secondPosition.snap +++ b/.forge-snapshots/PositionManager_permit_secondPosition.snap @@ -1 +1 @@ -62333 \ No newline at end of file +62372 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index 7772c13d..c0e053f6 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -45221 \ No newline at end of file +45260 \ No newline at end of file diff --git a/src/base/ERC721Permit_v4.sol b/src/base/ERC721Permit_v4.sol index 7c35a570..0485ca26 100644 --- a/src/base/ERC721Permit_v4.sol +++ b/src/base/ERC721Permit_v4.sol @@ -17,23 +17,61 @@ abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, Unorde /// @notice Computes the nameHash and versionHash constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) EIP712_v4(name_) {} + modifier checkSignatureDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert SignatureDeadlineExpired(); + _; + } + /// @inheritdoc IERC721Permit_v4 function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) external payable + checkSignatureDeadline(deadline) { - if (block.timestamp > deadline) revert DeadlineExpired(); - address owner = ownerOf(tokenId); - if (spender == owner) revert NoSelfPermit(); + _checkNoSelfPermit(owner, spender); - bytes32 hash = ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline); - signature.verify(_hashTypedData(hash), owner); + bytes32 digest = ERC721PermitHashLibrary.hashPermit(spender, tokenId, nonce, deadline); + signature.verify(_hashTypedData(digest), owner); _useUnorderedNonce(owner, nonce); _approve(owner, spender, tokenId); } + /// @inheritdoc IERC721Permit_v4 + function permitForAll( + address owner, + address operator, + bool approved, + uint256 deadline, + uint256 nonce, + bytes calldata signature + ) external payable checkSignatureDeadline(deadline) { + _checkNoSelfPermit(owner, operator); + + bytes32 digest = ERC721PermitHashLibrary.hashPermitForAll(operator, approved, nonce, deadline); + signature.verify(_hashTypedData(digest), owner); + + _useUnorderedNonce(owner, nonce); + _approveForAll(owner, operator, approved); + } + + /// @notice Enable or disable approval for a third party ("operator") to manage + /// all of `msg.sender`'s assets + /// @dev Emits the ApprovalForAll event. The contract MUST allow + /// multiple operators per owner. + /// @dev Override Solmate's ERC721 setApprovalForAll so setApprovalForAll() and permit() share the _approveForAll method + /// @param operator Address to add to the set of authorized operators + /// @param approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address operator, bool approved) public override { + _approveForAll(msg.sender, operator, approved); + } + + function _approveForAll(address owner, address operator, bool approved) internal { + isApprovedForAll[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + /// @notice Change or reaffirm the approved address for an NFT /// @dev override Solmate's ERC721 approve so approve() and permit() share the _approve method /// The zero address indicates there is no approved address @@ -58,4 +96,8 @@ abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, Unorde return spender == ownerOf(tokenId) || getApproved[tokenId] == spender || isApprovedForAll[ownerOf(tokenId)][spender]; } + + function _checkNoSelfPermit(address owner, address permitted) internal pure { + if (owner == permitted) revert NoSelfPermit(); + } } diff --git a/src/interfaces/IERC721Permit_v4.sol b/src/interfaces/IERC721Permit_v4.sol index 3fa2894f..02b3eb97 100644 --- a/src/interfaces/IERC721Permit_v4.sol +++ b/src/interfaces/IERC721Permit_v4.sol @@ -4,7 +4,7 @@ pragma solidity >=0.7.5; /// @title ERC721 with permit /// @notice Extension to ERC721 that includes a permit function for signature based approvals interface IERC721Permit_v4 { - error DeadlineExpired(); + error SignatureDeadlineExpired(); error NoSelfPermit(); error Unauthorized(); @@ -17,4 +17,20 @@ interface IERC721Permit_v4 { function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) external payable; + + /// @notice Set an operator with full permission to an owner's tokens via signature + /// @param owner The address that is setting the operator + /// @param operator The address that will be set as an operator for the owner + /// @param approved The permission to set on the operator + /// @param deadline The deadline timestamp by which the call must be mined for the approve to work + /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v) + /// @dev payable so it can be multicalled with NATIVE related actions + function permitForAll( + address owner, + address operator, + bool approved, + uint256 deadline, + uint256 nonce, + bytes calldata signature + ) external payable; } diff --git a/src/libraries/ERC721PermitHash.sol b/src/libraries/ERC721PermitHash.sol index 9a36d6de..4301dbfb 100644 --- a/src/libraries/ERC721PermitHash.sol +++ b/src/libraries/ERC721PermitHash.sol @@ -5,7 +5,10 @@ library ERC721PermitHashLibrary { /// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); bytes32 constant PERMIT_TYPEHASH = 0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad; - function hash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + /// @dev Value is equal to keccak256("PermitForAll(address operator,bool approved,uint256 nonce,uint256 deadline)"); + bytes32 constant PERMIT_FOR_ALL_TYPEHASH = 0x6673cb397ee2a50b6b8401653d3638b4ac8b3db9c28aa6870ffceb7574ec2f76; + + function hashPermit(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) internal pure returns (bytes32 digest) @@ -21,4 +24,21 @@ library ERC721PermitHashLibrary { digest := keccak256(fmp, 0xa0) } } + + function hashPermitForAll(address operator, bool approved, uint256 nonce, uint256 deadline) + internal + pure + returns (bytes32 digest) + { + // equivalent to: keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)); + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, PERMIT_FOR_ALL_TYPEHASH) + mstore(add(fmp, 0x20), operator) + mstore(add(fmp, 0x40), approved) + mstore(add(fmp, 0x60), nonce) + mstore(add(fmp, 0x80), deadline) + digest := keccak256(fmp, 0xa0) + } + } } diff --git a/test/ERC721Permit.t.sol b/test/erc721Permit/ERC721Permit.permit.t.sol similarity index 89% rename from test/ERC721Permit.t.sol rename to test/erc721Permit/ERC721Permit.permit.t.sol index 0dc633d3..654c20c9 100644 --- a/test/ERC721Permit.t.sol +++ b/test/erc721Permit/ERC721Permit.permit.t.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; -import {ERC721PermitHashLibrary} from "../src/libraries/ERC721PermitHash.sol"; -import {MockERC721Permit} from "./mocks/MockERC721Permit.sol"; -import {IERC721Permit_v4} from "../src/interfaces/IERC721Permit_v4.sol"; +import {ERC721PermitHashLibrary} from "../../src/libraries/ERC721PermitHash.sol"; +import {MockERC721Permit} from "../mocks/MockERC721Permit.sol"; +import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; import {IERC721} from "forge-std/interfaces/IERC721.sol"; -import {UnorderedNonce} from "../src/base/UnorderedNonce.sol"; +import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; contract ERC721PermitTest is Test { MockERC721Permit erc721Permit; @@ -69,7 +69,7 @@ contract ERC721PermitTest is Test { function test_fuzz_permitHash(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) public pure { bytes32 expectedHash = keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); - assertEq(expectedHash, ERC721PermitHashLibrary.hash(spender, tokenId, nonce, deadline)); + assertEq(expectedHash, ERC721PermitHashLibrary.hashPermit(spender, tokenId, nonce, deadline)); } function test_domainSeparator() public view { @@ -93,7 +93,7 @@ contract ERC721PermitTest is Test { uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; - bytes32 digest = _getDigest(spender, tokenId, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(spender, tokenId, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -127,7 +127,7 @@ contract ERC721PermitTest is Test { uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; - bytes32 digest = _getDigest(spender, tokenId, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(spender, tokenId, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -163,7 +163,7 @@ contract ERC721PermitTest is Test { _permit(alicePK, tokenIdAlice, bob, nonce); // alice cannot reuse the nonce - bytes32 digest = _getDigest(bob, tokenIdAlice, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(bob, tokenIdAlice, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -186,7 +186,7 @@ contract ERC721PermitTest is Test { _permit(alicePK, tokenIdAlice, bob, nonce); // alice cannot reuse the nonce for the second token - bytes32 digest = _getDigest(bob, tokenIdAlice2, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(bob, tokenIdAlice2, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -202,7 +202,7 @@ contract ERC721PermitTest is Test { uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; - bytes32 digest = _getDigest(bob, tokenId, nonce, block.timestamp); + bytes32 digest = _getPermitDigest(bob, tokenId, nonce, block.timestamp); // bob attempts signing an approval for himself (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPK, digest); @@ -229,13 +229,13 @@ contract ERC721PermitTest is Test { assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); } - function test_fuzz_erc721Permit_deadlineExpired(address spender) public { + function test_fuzz_erc721Permit_SignatureDeadlineExpired(address spender) public { vm.prank(alice); uint256 tokenId = erc721Permit.mint(); uint256 nonce = 1; uint256 deadline = block.timestamp; - bytes32 digest = _getDigest(spender, tokenId, nonce, deadline); + bytes32 digest = _getPermitDigest(spender, tokenId, nonce, deadline); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -252,7 +252,7 @@ contract ERC721PermitTest is Test { // -- Permit but deadline expired -- // vm.startPrank(spender); - vm.expectRevert(IERC721Permit_v4.DeadlineExpired.selector); + vm.expectRevert(IERC721Permit_v4.SignatureDeadlineExpired.selector); erc721Permit.permit(spender, tokenId, deadline, nonce, signature); vm.stopPrank(); @@ -266,7 +266,7 @@ contract ERC721PermitTest is Test { // Helpers related to permit function _permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { - bytes32 digest = _getDigest(operator, tokenId, 1, block.timestamp); + bytes32 digest = _getPermitDigest(operator, tokenId, nonce, block.timestamp); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); bytes memory signature = abi.encodePacked(r, s, v); @@ -275,7 +275,7 @@ contract ERC721PermitTest is Test { erc721Permit.permit(operator, tokenId, block.timestamp, nonce, signature); } - function _getDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + function _getPermitDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) internal view returns (bytes32 digest) diff --git a/test/erc721Permit/ERC721Permit.permitForAll.t.sol b/test/erc721Permit/ERC721Permit.permitForAll.t.sol new file mode 100644 index 00000000..6d5f4be3 --- /dev/null +++ b/test/erc721Permit/ERC721Permit.permitForAll.t.sol @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; + +import {ERC721PermitHashLibrary} from "../../src/libraries/ERC721PermitHash.sol"; +import {MockERC721Permit} from "../mocks/MockERC721Permit.sol"; +import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; +import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; + +contract ERC721PermitForAllTest is Test { + MockERC721Permit erc721Permit; + address alice; + uint256 alicePK; + address bob; + uint256 bobPK; + + string constant name = "Mock ERC721Permit_v4"; + string constant symbol = "MOCK721"; + + function setUp() public { + (alice, alicePK) = makeAddrAndKey("ALICE"); + (bob, bobPK) = makeAddrAndKey("BOB"); + + erc721Permit = new MockERC721Permit(name, symbol); + } + + // --- Test the overriden setApprovalForAll --- + function test_fuzz_setApprovalForAll(address operator) public { + assertEq(erc721Permit.isApprovedForAll(address(this), operator), false); + + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(address(this), operator, true); + erc721Permit.setApprovalForAll(operator, true); + assertEq(erc721Permit.isApprovedForAll(address(this), operator), true); + } + + function test_fuzz_setApprovalForAll_revoke(address operator) public { + assertEq(erc721Permit.isApprovedForAll(address(this), operator), false); + erc721Permit.setApprovalForAll(operator, true); + assertEq(erc721Permit.isApprovedForAll(address(this), operator), true); + + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(address(this), operator, false); + erc721Permit.setApprovalForAll(operator, false); + assertEq(erc721Permit.isApprovedForAll(address(this), operator), false); + } + + // --- Test the signature-based approvals (permitForAll) --- + function test_permitForAllTypeHash() public pure { + assertEq( + ERC721PermitHashLibrary.PERMIT_FOR_ALL_TYPEHASH, + keccak256("PermitForAll(address operator,bool approved,uint256 nonce,uint256 deadline)") + ); + } + + function test_fuzz_permitForAllHash(address operator, bool approved, uint256 nonce, uint256 deadline) public pure { + bytes32 expectedHash = + keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)); + assertEq(expectedHash, ERC721PermitHashLibrary.hashPermitForAll(operator, approved, nonce, deadline)); + } + + /// @dev operator uses alice's signature to approve itself + function test_fuzz_erc721permitForAll_operator(address operator) public { + vm.assume(operator != alice); + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, block.timestamp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // -- PermitForAll -- // + vm.startPrank(operator); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(alice, operator, true); + erc721Permit.permitForAll(alice, operator, true, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals set + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), true); + + // nonce was spent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 2); // 2 = 0010 + } + + /// @dev a third party caller uses alice's signature to give `operator` the approval + function test_fuzz_erc721permitForAll_caller(address caller, address operator) public { + vm.assume(operator != alice); + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, block.timestamp); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // -- PermitForAll -- // + vm.startPrank(caller); + vm.expectEmit(true, true, true, true, address(erc721Permit)); + emit IERC721.ApprovalForAll(alice, operator, true); + erc721Permit.permitForAll(alice, operator, true, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals set + assertEq(erc721Permit.getApproved(tokenId), address(0)); + assertEq(erc721Permit.isApprovedForAll(alice, operator), true); + + // nonce was spent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 2); // 2 = 0010 + } + + function test_fuzz_erc721permitForAll_nonceAlreadyUsed(uint256 nonce) public { + // alice gives bob operator permissions + _permitForAll(alicePK, alice, bob, true, nonce); + + // alice cannot reuse the nonce + bytes32 digest = _getPermitForAllDigest(bob, true, nonce, block.timestamp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.startPrank(alice); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permitForAll(alice, bob, true, block.timestamp, nonce, signature); + vm.stopPrank(); + } + + function test_fuzz_erc721permitForAll_invalidSigner(uint256 nonce) public { + bytes32 digest = _getPermitForAllDigest(bob, true, nonce, block.timestamp); + + // bob attempts signing an approval for himself + (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + erc721Permit.permitForAll(alice, bob, true, block.timestamp, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + function test_fuzz_erc721permitForAll_SignatureDeadlineExpired(address operator) public { + uint256 nonce = 1; + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // no approvals existed + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // fast forward to exceed deadline + skip(1); + + // -- PermitForAll but deadline expired -- // + vm.startPrank(operator); + vm.expectRevert(IERC721Permit_v4.SignatureDeadlineExpired.selector); + erc721Permit.permitForAll(alice, operator, true, deadline, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, operator), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + /// @dev a signature for permit() cannot be used for permitForAll() + function test_fuzz_erc721Permit_invalidSignatureForAll(address operator) public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitDigest(operator, tokenId, nonce, deadline); + + // alice signs a permit for operator + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // signature does not work with permitForAll + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + /// @dev a signature for permitForAll() cannot be used for permit() + function test_fuzz_erc721PermitForAll_invalidSignatureForPermit(address operator) public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 nonce = 1; + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitForAllDigest(operator, true, nonce, deadline); + + // alice signs a permit for operator + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // approvals unset + assertEq(erc721Permit.getApproved(tokenId), address(0)); + + // nonce was unspent + (uint256 wordPos, uint256 bitPos) = _getBitmapFromNonce(nonce); + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + + // signature does not work with permit + vm.startPrank(bob); + vm.expectRevert(SignatureVerification.InvalidSigner.selector); + erc721Permit.permit(bob, tokenId, deadline, nonce, signature); + vm.stopPrank(); + + // approvals unset + assertEq(erc721Permit.getApproved(tokenId), address(0)); + + // nonce was unspent + assertEq(erc721Permit.nonces(alice, wordPos) & (1 << bitPos), 0); + } + + /// @dev a nonce used in permit is unusable for permitForAll + function test_fuzz_erc721PermitForAll_permitNonceUsed(uint256 nonce) public { + vm.prank(alice); + uint256 tokenId = erc721Permit.mint(); + + uint256 deadline = block.timestamp; + bytes32 digest = _getPermitDigest(bob, tokenId, nonce, deadline); + // alice signs a permit for bob + (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + // bob gives himself approval + vm.prank(bob); + erc721Permit.permit(bob, tokenId, deadline, nonce, signature); + assertEq(erc721Permit.getApproved(tokenId), bob); + assertEq(erc721Permit.isApprovedForAll(alice, bob), false); + + // alice tries re-using the nonce for permitForAll + digest = _getPermitForAllDigest(bob, true, nonce, deadline); + (v, r, s) = vm.sign(alicePK, digest); + signature = abi.encodePacked(r, s, v); + + // Nonce does not work with permitForAll + vm.startPrank(bob); + vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); + vm.stopPrank(); + } + + // Helpers related to permitForAll + function _permitForAll(uint256 privateKey, address owner, address operator, bool approved, uint256 nonce) + internal + { + bytes32 digest = _getPermitForAllDigest(operator, approved, nonce, block.timestamp); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.prank(operator); + erc721Permit.permitForAll(owner, operator, approved, block.timestamp, nonce, signature); + } + + function _getPermitForAllDigest(address operator, bool approved, uint256 nonce, uint256 deadline) + internal + view + returns (bytes32 digest) + { + digest = keccak256( + abi.encodePacked( + "\x19\x01", + erc721Permit.DOMAIN_SEPARATOR(), + keccak256( + abi.encode(ERC721PermitHashLibrary.PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline) + ) + ) + ); + } + + function _getPermitDigest(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + internal + view + returns (bytes32 digest) + { + digest = keccak256( + abi.encodePacked( + "\x19\x01", + erc721Permit.DOMAIN_SEPARATOR(), + keccak256(abi.encode(ERC721PermitHashLibrary.PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)) + ) + ); + } + + // copied the private function from UnorderedNonce.sol + function _getBitmapFromNonce(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) { + wordPos = uint248(nonce >> 8); + bitPos = uint8(nonce); + } +} From 3b93674aaf6d7aaaf9cf41622aaa6f30f11e1f8f Mon Sep 17 00:00:00 2001 From: Daniel Gretzke Date: Mon, 5 Aug 2024 06:50:46 +0200 Subject: [PATCH 33/52] Wrap reverts thrown by subscribers (#273) * Wrap reverts thrown by subscribers * add tests * comment --------- Co-authored-by: Sara Reynolds --- ...ger_increase_autocompound_clearExcess.snap | 2 +- src/base/Notifier.sol | 23 +++- src/interfaces/INotifier.sol | 7 ++ test/mocks/MockBadSubscribers.sol | 42 +++++++ .../PositionManager.notifier.t.sol | 119 +++++++++++++++++- 5 files changed, 186 insertions(+), 7 deletions(-) diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 903117b0..3dd89106 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -141032 \ No newline at end of file +141010 \ No newline at end of file diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 556ea308..2b94efc9 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -4,12 +4,14 @@ pragma solidity ^0.8.24; import {ISubscriber} from "../interfaces/ISubscriber.sol"; import {PositionConfig} from "../libraries/PositionConfig.sol"; import {BipsLibrary} from "../libraries/BipsLibrary.sol"; - -import "../interfaces/INotifier.sol"; +import {INotifier, PositionConfig} from "../interfaces/INotifier.sol"; +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; /// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers +/// TODO: Use CustomRevert library when it supports subcontext's addresss abstract contract Notifier is INotifier { using BipsLibrary for uint256; + using CustomRevert for bytes4; error AlreadySubscribed(address subscriber); @@ -33,7 +35,10 @@ abstract contract Notifier is INotifier { if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(address(_subscriber)); subscriber[tokenId] = ISubscriber(newSubscriber); - ISubscriber(newSubscriber).notifySubscribe(tokenId, config, data); + try ISubscriber(newSubscriber).notifySubscribe(tokenId, config, data) {} + catch (bytes memory reason) { + revert Wrap__SubsciptionReverted(newSubscriber, reason); + } emit Subscribed(tokenId, address(newSubscriber)); } @@ -50,10 +55,18 @@ abstract contract Notifier is INotifier { } function _notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) internal { - subscriber[tokenId].notifyModifyLiquidity(tokenId, config, liquidityChange); + ISubscriber _subscriber = subscriber[tokenId]; + try _subscriber.notifyModifyLiquidity(tokenId, config, liquidityChange) {} + catch (bytes memory reason) { + revert Wrap__ModifyLiquidityNotificationReverted(address(_subscriber), reason); + } } function _notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) internal { - subscriber[tokenId].notifyTransfer(tokenId, previousOwner, newOwner); + ISubscriber _subscriber = subscriber[tokenId]; + try _subscriber.notifyTransfer(tokenId, previousOwner, newOwner) {} + catch (bytes memory reason) { + revert Wrap__TransferNotificationReverted(address(_subscriber), reason); + } } } diff --git a/src/interfaces/INotifier.sol b/src/interfaces/INotifier.sol index 33ec63f8..4aa05ae6 100644 --- a/src/interfaces/INotifier.sol +++ b/src/interfaces/INotifier.sol @@ -5,6 +5,13 @@ import {PositionConfig} from "../libraries/PositionConfig.sol"; /// @notice This interface is used to opt in to sending updates to external contracts about position modifications or transfers interface INotifier { + /// @notice Wraps the revert message of the subscriber contract on a reverting subscription + error Wrap__SubsciptionReverted(address subscriber, bytes reason); + /// @notice Wraps the revert message of the subscriber contract on a reverting modify liquidity notification + error Wrap__ModifyLiquidityNotificationReverted(address subscriber, bytes reason); + /// @notice Wraps the revert message of the subscriber contract on a reverting transfer notification + error Wrap__TransferNotificationReverted(address subscriber, bytes reason); + /// @notice Enables the subscriber to receive notifications for a respective position /// @param tokenId the ERC721 tokenId /// @param config the corresponding PositionConfig for the tokenId diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol index d619ff63..3dbf9fcf 100644 --- a/test/mocks/MockBadSubscribers.sol +++ b/test/mocks/MockBadSubscribers.sol @@ -56,3 +56,45 @@ contract MockReturnDataSubscriber is ISubscriber { memPtr = _value; } } + +/// @notice A subscriber contract that returns values from the subscriber entrypoints +contract MockRevertSubscriber is ISubscriber { + PositionManager posm; + + error NotAuthorizedNotifer(address sender); + + error TestRevert(string); + + constructor(PositionManager _posm) { + posm = _posm; + } + + bool shouldRevert; + + modifier onlyByPosm() { + if (msg.sender != address(posm)) revert NotAuthorizedNotifer(msg.sender); + _; + } + + function notifySubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { + if (shouldRevert) { + revert TestRevert("notifySubscribe"); + } + } + + function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { + revert TestRevert("notifyUnsubscribe"); + } + + function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { + revert TestRevert("notifyModifyLiquidity"); + } + + function notifyTransfer(uint256, address, address) external onlyByPosm { + revert TestRevert("notifyTransfer"); + } + + function setRevert(bool _shouldRevert) external { + shouldRevert = _shouldRevert; + } +} diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol index 06dd5d25..ab008a6c 100644 --- a/test/position-managers/PositionManager.notifier.t.sol +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -16,7 +16,8 @@ import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {MockReturnDataSubscriber} from "../mocks/MockBadSubscribers.sol"; +import {INotifier} from "../../src/interfaces/INotifier.sol"; +import {MockReturnDataSubscriber, MockRevertSubscriber} from "../mocks/MockBadSubscribers.sol"; contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { using PoolIdLibrary for PoolKey; @@ -26,6 +27,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { MockSubscriber sub; MockReturnDataSubscriber badSubscriber; PositionConfig config; + MockRevertSubscriber revertSubscriber; address alice = makeAddr("ALICE"); address bob = makeAddr("BOB"); @@ -41,6 +43,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { sub = new MockSubscriber(lpm); badSubscriber = new MockReturnDataSubscriber(lpm); + revertSubscriber = new MockRevertSubscriber(lpm); config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); // TODO: Test NATIVE poolKey @@ -331,4 +334,118 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { assertEq(address(lpm.subscriber(tokenId)), address(0)); assertEq(abi.decode(sub.unsubscribeData(), (address)), address(this)); } + + function test_subscribe_wraps_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + revertSubscriber.setRevert(true); + + vm.expectRevert( + abi.encodeWithSelector( + INotifier.Wrap__SubsciptionReverted.selector, + address(revertSubscriber), + abi.encodeWithSelector(MockRevertSubscriber.TestRevert.selector, "notifySubscribe") + ) + ); + lpm.subscribe(tokenId, config, address(revertSubscriber), ZERO_BYTES); + } + + function test_notifyModifyLiquidiy_wraps_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(revertSubscriber), ZERO_BYTES); + + Plan memory plan = Planner.init(); + for (uint256 i = 0; i < 10; i++) { + plan.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, 10e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + } + + bytes memory calls = plan.finalizeModifyLiquidityWithSettlePair(config.poolKey); + vm.expectRevert( + abi.encodeWithSelector( + INotifier.Wrap__ModifyLiquidityNotificationReverted.selector, + address(revertSubscriber), + abi.encodeWithSelector(MockRevertSubscriber.TestRevert.selector, "notifyModifyLiquidity") + ) + ); + lpm.modifyLiquidities(calls, _deadline); + } + + function test_notifyTransfer_withTransferFrom_wraps_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(revertSubscriber), ZERO_BYTES); + + vm.expectRevert( + abi.encodeWithSelector( + INotifier.Wrap__TransferNotificationReverted.selector, + address(revertSubscriber), + abi.encodeWithSelector(MockRevertSubscriber.TestRevert.selector, "notifyTransfer") + ) + ); + lpm.transferFrom(alice, bob, tokenId); + } + + function test_notifyTransfer_withSafeTransferFrom_wraps_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(revertSubscriber), ZERO_BYTES); + + vm.expectRevert( + abi.encodeWithSelector( + INotifier.Wrap__TransferNotificationReverted.selector, + address(revertSubscriber), + abi.encodeWithSelector(MockRevertSubscriber.TestRevert.selector, "notifyTransfer") + ) + ); + lpm.safeTransferFrom(alice, bob, tokenId); + } + + function test_notifyTransfer_withSafeTransferFromData_wraps_revert() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(revertSubscriber), ZERO_BYTES); + + vm.expectRevert( + abi.encodeWithSelector( + INotifier.Wrap__TransferNotificationReverted.selector, + address(revertSubscriber), + abi.encodeWithSelector(MockRevertSubscriber.TestRevert.selector, "notifyTransfer") + ) + ); + lpm.safeTransferFrom(alice, bob, tokenId, ""); + } } From df47aa9ba521fc15ffd339dc773d32f5fc4c91fc Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Mon, 5 Aug 2024 01:56:47 -0400 Subject: [PATCH 34/52] Some cleanup (#276) * move tokenURI * reorder operations, fix mock * inheritdoc, fix compiler warnings --- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 118 +++++++++--------- src/base/ERC721Permit_v4.sol | 5 + test/mocks/MockBadSubscribers.sol | 8 +- test/mocks/MockERC721Permit.sol | 4 - 35 files changed, 97 insertions(+), 100 deletions(-) diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 5ad60d21..4befe739 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123036 \ No newline at end of file +123166 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 91c7e47b..3755112a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122746 \ No newline at end of file +122673 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 79fc0eaf..599e47f1 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130115 \ No newline at end of file +130244 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index b1129816..b4fa389a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129824 \ No newline at end of file +129752 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 09a04fb9..f5ec4d56 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141439 \ No newline at end of file +141601 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index cbbed084..7afc1160 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150287 \ No newline at end of file +150449 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index cbbed084..7afc1160 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150287 \ No newline at end of file +150449 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 580c9abe..6e0b35e3 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149912 \ No newline at end of file +149821 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 6adda5c6..e9d5fd44 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108626 \ No newline at end of file +108756 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index a7b42e47..15a83720 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115830 \ No newline at end of file +115992 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index d3554272..dc7483fe 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115455 \ No newline at end of file +115364 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 0a3cc22e..79627981 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134194 \ No newline at end of file +134324 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 13c3c5a8..3dd50c16 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126933 \ No newline at end of file +127063 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index ee5cb915..89c4f31b 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128546 \ No newline at end of file +128708 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index cce4c7e7..e3148278 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116685 \ No newline at end of file +116525 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index b5fb9a28..9cd7d780 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152393 \ No newline at end of file +152577 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index a0c16300..523bb689 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151648 \ No newline at end of file +151579 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index eb10283f..c0bfde57 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134193 \ No newline at end of file +134377 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 2b324d60..f1dec53b 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171052 \ No newline at end of file +171214 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 3dd89106..bf77b1d4 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -141010 \ No newline at end of file +141216 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 464fbbb0..cb159162 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336871 \ No newline at end of file +337055 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 93e4c662..ffb34760 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345340 \ No newline at end of file +345547 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 0bb4febe..dc1863f6 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344895 \ No newline at end of file +344849 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 217a8ffb..4cb0e230 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314853 \ No newline at end of file +315037 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 64083d11..5a3de435 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315495 \ No newline at end of file +315679 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index c99faa84..fed34f54 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241077 \ No newline at end of file +241261 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 4400b3b4..c0e383ac 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -371033 \ No newline at end of file +371079 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 5447d5b8..b6a183b4 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320871 \ No newline at end of file +321055 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 0ce5b2aa..ecdecab4 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372171 \ No newline at end of file +372355 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 39b89b70..06e315dc 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371564 \ No newline at end of file +371495 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 29d08734..2d3830c7 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416667 \ No newline at end of file +416851 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 2630f4e5..79eb21c8 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -70,11 +70,6 @@ contract PositionManager is _; } - // TODO: to be implemented after audits - function tokenURI(uint256) public pure override returns (string memory) { - return "https://example.com"; - } - /// @notice Reverts if the caller is not the owner or approved for the ERC721 token /// @param caller The address of the caller /// @param tokenId the unique identifier of the ERC721 token @@ -135,6 +130,10 @@ contract PositionManager is _unsubscribe(tokenId, config, data); } + function msgSender() public view override returns (address) { + return _getLocker(); + } + function _handleAction(uint256 action, bytes calldata params) internal virtual override { if (action < Actions.SETTLE) { if (action == Actions.INCREASE_LIQUIDITY) { @@ -181,37 +180,33 @@ contract PositionManager is revert UnsupportedAction(action); } } else { - if (action == Actions.CLOSE_CURRENCY) { - Currency currency = params.decodeCurrency(); - _close(currency); - } else if (action == Actions.CLEAR_OR_TAKE) { - (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); - _clearOrTake(currency, amountMax); - } else if (action == Actions.SETTLE) { - (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); - _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); - } else if (action == Actions.SETTLE_PAIR) { + if (action == Actions.SETTLE_PAIR) { (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); _settlePair(currency0, currency1); } else if (action == Actions.TAKE_PAIR) { (Currency currency0, Currency currency1, address to) = params.decodeCurrencyPairAndAddress(); _takePair(currency0, currency1, to); - } else if (action == Actions.SWEEP) { - (Currency currency, address to) = params.decodeCurrencyAndAddress(); - _sweep(currency, _mapRecipient(to)); + } else if (action == Actions.SETTLE) { + (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); + _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); } else if (action == Actions.TAKE) { (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + } else if (action == Actions.CLOSE_CURRENCY) { + Currency currency = params.decodeCurrency(); + _close(currency); + } else if (action == Actions.CLEAR_OR_TAKE) { + (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); + _clearOrTake(currency, amountMax); + } else if (action == Actions.SWEEP) { + (Currency currency, address to) = params.decodeCurrencyAndAddress(); + _sweep(currency, _mapRecipient(to)); } else { revert UnsupportedAction(action); } } } - function msgSender() public view override returns (address) { - return _getLocker(); - } - /// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position function _increase( uint256 tokenId, @@ -262,6 +257,41 @@ contract PositionManager is positionConfigs.setConfigId(tokenId, config); } + /// @dev this is overloaded with ERC721Permit_v4._burn + function _burn( + uint256 tokenId, + PositionConfig calldata config, + uint128 amount0Min, + uint128 amount1Min, + bytes calldata hookData + ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { + uint256 liquidity = uint256(getPositionLiquidity(tokenId, config)); + + BalanceDelta liquidityDelta; + // Can only call modify if there is non zero liquidity. + if (liquidity > 0) { + liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); + liquidityDelta.validateMinOut(amount0Min, amount1Min); + } + + delete positionConfigs[tokenId]; + // Burn the token. + _burn(tokenId); + } + + function _settlePair(Currency currency0, Currency currency1) internal { + // the locker is the payer when settling + address caller = msgSender(); + _settle(currency0, caller, _getFullDebt(currency0)); + _settle(currency1, caller, _getFullDebt(currency1)); + } + + function _takePair(Currency currency0, Currency currency1, address to) internal { + address recipient = _mapRecipient(to); + _take(currency0, recipient, _getFullCredit(currency0)); + _take(currency1, recipient, _getFullCredit(currency1)); + } + function _close(Currency currency) internal { // this address has applied all deltas on behalf of the user/owner // it is safe to close this entire delta because of slippage checks throughout the batched calls. @@ -289,39 +319,10 @@ contract PositionManager is } } - function _settlePair(Currency currency0, Currency currency1) internal { - // the locker is the payer when settling - address caller = msgSender(); - _settle(currency0, caller, _getFullDebt(currency0)); - _settle(currency1, caller, _getFullDebt(currency1)); - } - - function _takePair(Currency currency0, Currency currency1, address to) internal { - address recipient = _mapRecipient(to); - _take(currency0, recipient, _getFullCredit(currency0)); - _take(currency1, recipient, _getFullCredit(currency1)); - } - - /// @dev this is overloaded with ERC721Permit_v4._burn - function _burn( - uint256 tokenId, - PositionConfig calldata config, - uint128 amount0Min, - uint128 amount1Min, - bytes calldata hookData - ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { - uint256 liquidity = uint256(getPositionLiquidity(tokenId, config)); - - BalanceDelta liquidityDelta; - // Can only call modify if there is non zero liquidity. - if (liquidity > 0) { - liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); - liquidityDelta.validateMinOut(amount0Min, amount1Min); - } - - delete positionConfigs[tokenId]; - // Burn the token. - _burn(tokenId); + /// @notice Sweeps the entire contract balance of specified currency to the recipient + function _sweep(Currency currency, address to) internal { + uint256 balance = currency.balanceOfSelf(); + if (balance > 0) currency.transfer(to, balance); } function _modifyLiquidity( @@ -346,12 +347,6 @@ contract PositionManager is } } - /// @notice Sweeps the entire contract balance of specified currency to the recipient - function _sweep(Currency currency, address to) internal { - uint256 balance = currency.balanceOfSelf(); - if (balance > 0) currency.transfer(to, balance); - } - // implementation of abstract function DeltaResolver._pay function _pay(Currency currency, address payer, uint256 amount) internal override { if (payer == address(this)) { @@ -368,6 +363,7 @@ contract PositionManager is if (positionConfigs.hasSubscriber(id)) _notifyTransfer(id, from, to); } + /// @inheritdoc IPositionManager function getPositionLiquidity(uint256 tokenId, PositionConfig calldata config) public view diff --git a/src/base/ERC721Permit_v4.sol b/src/base/ERC721Permit_v4.sol index 0485ca26..3f03a03b 100644 --- a/src/base/ERC721Permit_v4.sol +++ b/src/base/ERC721Permit_v4.sol @@ -100,4 +100,9 @@ abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, Unorde function _checkNoSelfPermit(address owner, address permitted) internal pure { if (owner == permitted) revert NoSelfPermit(); } + + // TODO: to be implemented after audits + function tokenURI(uint256) public pure override returns (string memory) { + return "https://example.com"; + } } diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol index 3dbf9fcf..b8db89aa 100644 --- a/test/mocks/MockBadSubscribers.sol +++ b/test/mocks/MockBadSubscribers.sol @@ -76,21 +76,21 @@ contract MockRevertSubscriber is ISubscriber { _; } - function notifySubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { + function notifySubscribe(uint256, PositionConfig memory, bytes memory) external view onlyByPosm { if (shouldRevert) { revert TestRevert("notifySubscribe"); } } - function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory) external onlyByPosm { + function notifyUnsubscribe(uint256, PositionConfig memory, bytes memory) external view onlyByPosm { revert TestRevert("notifyUnsubscribe"); } - function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { + function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external view onlyByPosm { revert TestRevert("notifyModifyLiquidity"); } - function notifyTransfer(uint256, address, address) external onlyByPosm { + function notifyTransfer(uint256, address, address) external view onlyByPosm { revert TestRevert("notifyTransfer"); } diff --git a/test/mocks/MockERC721Permit.sol b/test/mocks/MockERC721Permit.sol index b76bc547..6056a638 100644 --- a/test/mocks/MockERC721Permit.sol +++ b/test/mocks/MockERC721Permit.sol @@ -8,10 +8,6 @@ contract MockERC721Permit is ERC721Permit_v4 { constructor(string memory name, string memory symbol) ERC721Permit_v4(name, symbol) {} - function tokenURI(uint256) public pure override returns (string memory) { - return ""; - } - function mint() external returns (uint256 tokenId) { tokenId = ++lastTokenId; _mint(msg.sender, tokenId); From 20718d51fb1ff4081e3084c017f953077a235755 Mon Sep 17 00:00:00 2001 From: Brock Miller Date: Mon, 5 Aug 2024 13:26:01 -0400 Subject: [PATCH 35/52] Make PositionManager.transferFrom virtual (#278) --- src/PositionManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 79eb21c8..01ad80ae 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -358,7 +358,7 @@ contract PositionManager is } /// @dev overrides solmate transferFrom in case a notification to subscribers is needed - function transferFrom(address from, address to, uint256 id) public override { + function transferFrom(address from, address to, uint256 id) public virtual override { super.transferFrom(from, to, id); if (positionConfigs.hasSubscriber(id)) _notifyTransfer(id, from, to); } From cf4e2adf816073dbb9de5f864748e8461f880f69 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:39:26 -0400 Subject: [PATCH 36/52] Use custom revert (#277) * use v4-core latest * use custom revert --- .../BaseActionsRouter_mock10commands.snap | 2 +- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- ...tateView_extsload_getFeeGrowthGlobals.snap | 2 +- ...StateView_extsload_getFeeGrowthInside.snap | 2 +- .../StateView_extsload_getLiquidity.snap | 2 +- .../StateView_extsload_getPositionInfo.snap | 2 +- ...ateView_extsload_getPositionLiquidity.snap | 2 +- .../StateView_extsload_getSlot0.snap | 2 +- .../StateView_extsload_getTickBitmap.snap | 2 +- ...View_extsload_getTickFeeGrowthOutside.snap | 2 +- .../StateView_extsload_getTickInfo.snap | 2 +- .../StateView_extsload_getTickLiquidity.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- lib/v4-core | 2 +- src/base/Notifier.sol | 41 ++++++++++++++----- .../PositionManager.modifyLiquidities.t.sol | 13 ++++-- 49 files changed, 86 insertions(+), 62 deletions(-) diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index 031ba1de..2b46e583 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -61794 \ No newline at end of file +61749 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 0d1315e6..10da4419 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47224 \ No newline at end of file +47170 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index db13bb26..7cf4184e 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -47041 \ No newline at end of file +46988 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 4befe739..6fde3500 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123166 \ No newline at end of file +123220 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 3755112a..3ce8a31d 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122673 \ No newline at end of file +122727 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 599e47f1..e6c9c985 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130244 \ No newline at end of file +130298 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index b4fa389a..d879fbb6 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129752 \ No newline at end of file +129805 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index f5ec4d56..91993146 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141601 \ No newline at end of file +141690 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 7afc1160..76901910 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150449 \ No newline at end of file +150538 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index 7afc1160..76901910 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150449 \ No newline at end of file +150538 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 6e0b35e3..0e2be24e 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149821 \ No newline at end of file +149910 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index e9d5fd44..db298b27 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108756 \ No newline at end of file +108827 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index 15a83720..fc743866 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -115992 \ No newline at end of file +116081 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index dc7483fe..80e95ab1 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115364 \ No newline at end of file +115453 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 79627981..c0bfde57 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134324 \ No newline at end of file +134377 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 3dd50c16..db56dc9c 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -127063 \ No newline at end of file +127116 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 89c4f31b..1c3eba40 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128708 \ No newline at end of file +128797 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index e3148278..dd61f1d3 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116525 \ No newline at end of file +116614 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 9cd7d780..9435d15a 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152577 \ No newline at end of file +152488 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 523bb689..e7d1e62a 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151579 \ No newline at end of file +151490 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index c0bfde57..b17d1e2c 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134377 \ No newline at end of file +134288 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index de8ee99c..e65873e3 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130432 \ No newline at end of file +130387 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index f1dec53b..32c25b70 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171214 \ No newline at end of file +171303 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index bf77b1d4..888d36a1 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -141216 \ No newline at end of file +141259 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index cb159162..db0827b0 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -337055 \ No newline at end of file +336966 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index ffb34760..e71077c7 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345547 \ No newline at end of file +345458 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index dc1863f6..f2320def 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344849 \ No newline at end of file +344760 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 4cb0e230..97e62f97 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -315037 \ No newline at end of file +314948 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 5a3de435..37c9d6ac 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315679 \ No newline at end of file +315590 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index fed34f54..e54e9b30 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241261 \ No newline at end of file +241172 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index c0e383ac..79e09fec 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -371079 \ No newline at end of file +370990 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index b6a183b4..c0a720aa 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -321055 \ No newline at end of file +320966 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index ecdecab4..03b99010 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372355 \ No newline at end of file +372266 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 06e315dc..ccc8bbcc 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371495 \ No newline at end of file +371406 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 2d3830c7..57a0fec0 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416851 \ No newline at end of file +416740 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap b/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap index 0cd7e117..d659d28b 100644 --- a/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap +++ b/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap @@ -1 +1 @@ -2398 \ No newline at end of file +2376 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap b/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap index d10ca668..37017731 100644 --- a/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap +++ b/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap @@ -1 +1 @@ -8543 \ No newline at end of file +8455 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getLiquidity.snap b/.forge-snapshots/StateView_extsload_getLiquidity.snap index 5303ac12..5ca63cd4 100644 --- a/.forge-snapshots/StateView_extsload_getLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getLiquidity.snap @@ -1 +1 @@ -1509 \ No newline at end of file +1487 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getPositionInfo.snap b/.forge-snapshots/StateView_extsload_getPositionInfo.snap index 9d57e9ad..6f995456 100644 --- a/.forge-snapshots/StateView_extsload_getPositionInfo.snap +++ b/.forge-snapshots/StateView_extsload_getPositionInfo.snap @@ -1 +1 @@ -2927 \ No newline at end of file +2905 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap b/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap index 280f1a09..2dd44763 100644 --- a/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap @@ -1 +1 @@ -1746 \ No newline at end of file +1724 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getSlot0.snap b/.forge-snapshots/StateView_extsload_getSlot0.snap index 38ca8416..a35ae730 100644 --- a/.forge-snapshots/StateView_extsload_getSlot0.snap +++ b/.forge-snapshots/StateView_extsload_getSlot0.snap @@ -1 +1 @@ -1606 \ No newline at end of file +1584 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickBitmap.snap b/.forge-snapshots/StateView_extsload_getTickBitmap.snap index dfd4d9fc..7f29ad28 100644 --- a/.forge-snapshots/StateView_extsload_getTickBitmap.snap +++ b/.forge-snapshots/StateView_extsload_getTickBitmap.snap @@ -1 +1 @@ -1704 \ No newline at end of file +1682 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap b/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap index f26febc1..00c1535a 100644 --- a/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap +++ b/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap @@ -1 +1 @@ -2756 \ No newline at end of file +2734 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickInfo.snap b/.forge-snapshots/StateView_extsload_getTickInfo.snap index 90a81289..a6fea8d1 100644 --- a/.forge-snapshots/StateView_extsload_getTickInfo.snap +++ b/.forge-snapshots/StateView_extsload_getTickInfo.snap @@ -1 +1 @@ -3090 \ No newline at end of file +3068 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickLiquidity.snap b/.forge-snapshots/StateView_extsload_getTickLiquidity.snap index ff353461..fdef1d02 100644 --- a/.forge-snapshots/StateView_extsload_getTickLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getTickLiquidity.snap @@ -1 +1 @@ -1901 \ No newline at end of file +1879 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 4b0fd4c3..cd52958e 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8446 \ No newline at end of file +8454 \ No newline at end of file diff --git a/lib/v4-core b/lib/v4-core index 799dd2cb..d3be0026 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 799dd2cb980319a8d3b827b6a7aa59a606634553 +Subproject commit d3be0026a60a429c76d302b3aebbc20000191256 diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 2b94efc9..2d6669a8 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -4,11 +4,10 @@ pragma solidity ^0.8.24; import {ISubscriber} from "../interfaces/ISubscriber.sol"; import {PositionConfig} from "../libraries/PositionConfig.sol"; import {BipsLibrary} from "../libraries/BipsLibrary.sol"; -import {INotifier, PositionConfig} from "../interfaces/INotifier.sol"; +import {INotifier} from "../interfaces/INotifier.sol"; import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; /// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers -/// TODO: Use CustomRevert library when it supports subcontext's addresss abstract contract Notifier is INotifier { using BipsLibrary for uint256; using CustomRevert for bytes4; @@ -35,10 +34,14 @@ abstract contract Notifier is INotifier { if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(address(_subscriber)); subscriber[tokenId] = ISubscriber(newSubscriber); - try ISubscriber(newSubscriber).notifySubscribe(tokenId, config, data) {} - catch (bytes memory reason) { - revert Wrap__SubsciptionReverted(newSubscriber, reason); + bool success = _call( + address(newSubscriber), abi.encodeWithSelector(ISubscriber.notifySubscribe.selector, tokenId, config, data) + ); + + if (!success) { + Wrap__SubsciptionReverted.selector.bubbleUpAndRevertWith(address(newSubscriber)); } + emit Subscribed(tokenId, address(newSubscriber)); } @@ -56,17 +59,33 @@ abstract contract Notifier is INotifier { function _notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) internal { ISubscriber _subscriber = subscriber[tokenId]; - try _subscriber.notifyModifyLiquidity(tokenId, config, liquidityChange) {} - catch (bytes memory reason) { - revert Wrap__ModifyLiquidityNotificationReverted(address(_subscriber), reason); + + bool success = _call( + address(_subscriber), + abi.encodeWithSelector(ISubscriber.notifyModifyLiquidity.selector, tokenId, config, liquidityChange) + ); + + if (!success) { + Wrap__ModifyLiquidityNotificationReverted.selector.bubbleUpAndRevertWith(address(_subscriber)); } } function _notifyTransfer(uint256 tokenId, address previousOwner, address newOwner) internal { ISubscriber _subscriber = subscriber[tokenId]; - try _subscriber.notifyTransfer(tokenId, previousOwner, newOwner) {} - catch (bytes memory reason) { - revert Wrap__TransferNotificationReverted(address(_subscriber), reason); + + bool success = _call( + address(_subscriber), + abi.encodeWithSelector(ISubscriber.notifyTransfer.selector, tokenId, previousOwner, newOwner) + ); + + if (!success) { + Wrap__TransferNotificationReverted.selector.bubbleUpAndRevertWith(address(_subscriber)); + } + } + + function _call(address target, bytes memory encodedCall) internal returns (bool success) { + assembly ("memory-safe") { + success := call(gas(), target, 0, add(encodedCall, 0x20), mload(encodedCall), 0, 0) } } } diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index 6244c456..702b0952 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -208,7 +208,8 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // should revert because hook is not approved vm.expectRevert( abi.encodeWithSelector( - Hooks.FailedHookCall.selector, + Hooks.Wrap__FailedHookCall.selector, + address(hookModifyLiquidities), abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)) ) ); @@ -232,7 +233,8 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // should revert because hook is not approved vm.expectRevert( abi.encodeWithSelector( - Hooks.FailedHookCall.selector, + Hooks.Wrap__FailedHookCall.selector, + address(hookModifyLiquidities), abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)) ) ); @@ -252,7 +254,8 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // should revert because hook is not approved vm.expectRevert( abi.encodeWithSelector( - Hooks.FailedHookCall.selector, + Hooks.Wrap__FailedHookCall.selector, + address(hookModifyLiquidities), abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)) ) ); @@ -274,7 +277,9 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // should revert because hook is re-entering modifyLiquiditiesWithoutUnlock vm.expectRevert( abi.encodeWithSelector( - Hooks.FailedHookCall.selector, abi.encodeWithSelector(ReentrancyLock.ContractLocked.selector) + Hooks.Wrap__FailedHookCall.selector, + address(hookModifyLiquidities), + abi.encodeWithSelector(ReentrancyLock.ContractLocked.selector) ) ); lpm.modifyLiquidities(calls, _deadline); From af688af3f5b620843670eb64d2ae9d8b82fccf79 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:11:16 -0400 Subject: [PATCH 37/52] add mint position event (#279) * add mint position event * merge main --- .forge-snapshots/PositionManager_mint_native.snap | 2 +- .../PositionManager_mint_nativeWithSweep_withClose.snap | 2 +- ...itionManager_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .forge-snapshots/PositionManager_mint_sameRange.snap | 2 +- .../PositionManager_mint_settleWithBalance_sweep.snap | 2 +- .../PositionManager_mint_warmedPool_differentRange.snap | 2 +- .forge-snapshots/PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- .../PositionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 2 ++ src/interfaces/IPositionManager.sol | 2 ++ test/position-managers/PositionManager.t.sol | 9 +++++++++ 14 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index db0827b0..1fff7e94 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336966 \ No newline at end of file +340631 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index e71077c7..7ec221a4 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -345458 \ No newline at end of file +349123 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index f2320def..80f6525a 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -344760 \ No newline at end of file +348425 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 97e62f97..14dd1b79 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314948 \ No newline at end of file +318613 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 37c9d6ac..aba6356f 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315590 \ No newline at end of file +319255 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index e54e9b30..a6cf5393 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -241172 \ No newline at end of file +244837 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 79e09fec..095bb62b 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370990 \ No newline at end of file +374655 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index c0a720aa..62ccd91f 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320966 \ No newline at end of file +324631 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 03b99010..27da45cd 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -372266 \ No newline at end of file +375931 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index ccc8bbcc..4ee5b345 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -371406 \ No newline at end of file +375071 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 57a0fec0..0a30edb5 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416740 \ No newline at end of file +420405 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 01ad80ae..9be9413f 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -255,6 +255,8 @@ contract PositionManager is BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); liquidityDelta.validateMaxIn(amount0Max, amount1Max); positionConfigs.setConfigId(tokenId, config); + + emit MintPosition(tokenId, config); } /// @dev this is overloaded with ERC721Permit_v4._burn diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 1061877b..03213c74 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -12,6 +12,8 @@ interface IPositionManager is INotifier { error DeadlinePassed(); error IncorrectPositionConfigForTokenId(uint256 tokenId); + event MintPosition(uint256 indexed tokenId, PositionConfig config); + /// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity /// @dev This is the standard entrypoint for the PositionManager /// @param payload is an encoding of actions, and parameters for those actions diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index ca415468..ab4be391 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -935,5 +935,14 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertLt(uint256(int256(deltaDecrease.amount1())), uint256(int256(-deltaMint.amount1()))); // amount1 in the second position was greater than amount1 in the first position } + function test_mint_emits_event() public { + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); + uint256 tokenId = lpm.nextTokenId(); + + vm.expectEmit(true, false, false, true, address(lpm)); + emit IPositionManager.MintPosition(tokenId, config); + mint(config, 1e18, ActionConstants.MSG_SENDER, ZERO_BYTES); + } + function test_mint_slippageRevert() public {} } From bf3b8adffd2dff33807a70177f84048ae253fcc0 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Tue, 6 Aug 2024 18:04:19 -0400 Subject: [PATCH 38/52] Provide feesAccrued to subscriber.notifyModifyLiquidity (#282) * provide delta info to subscibers * notifyModifyLiquidity parameters provided correctly * do not provide liquidity delta to subscribers --- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 5 ++-- src/base/Notifier.sol | 12 ++++++-- src/interfaces/ISubscriber.sol | 9 +++++- test/mocks/MockBadSubscribers.sol | 5 ++-- test/mocks/MockSubscriber.sol | 10 ++++++- .../PositionManager.notifier.t.sol | 29 +++++++++++++++++++ 38 files changed, 94 insertions(+), 40 deletions(-) diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 6fde3500..0c46e3d1 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123220 \ No newline at end of file +123226 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 3ce8a31d..4daa5df5 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122727 \ No newline at end of file +122733 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index e6c9c985..4e8dd523 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130298 \ No newline at end of file +130304 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index d879fbb6..695f0ba1 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129805 \ No newline at end of file +129812 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 91993146..a29149e6 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141690 \ No newline at end of file +141698 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 76901910..d0c0d21a 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150538 \ No newline at end of file +150546 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index 76901910..d0c0d21a 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150538 \ No newline at end of file +150546 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 0e2be24e..6654e101 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149910 \ No newline at end of file +149918 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index db298b27..bc5857fa 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108827 \ No newline at end of file +108833 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index fc743866..d4154674 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -116081 \ No newline at end of file +116089 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index 80e95ab1..cfe262e2 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115453 \ No newline at end of file +115461 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index c0bfde57..bf8a89f1 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134377 \ No newline at end of file +134384 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index db56dc9c..64fa2092 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -127116 \ No newline at end of file +127123 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 1c3eba40..3df0c73c 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128797 \ No newline at end of file +128805 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index dd61f1d3..afeb3089 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116614 \ No newline at end of file +116622 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 9435d15a..8ed74edd 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152488 \ No newline at end of file +152496 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index e7d1e62a..768175ec 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151490 \ No newline at end of file +151498 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index b17d1e2c..21ace784 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134288 \ No newline at end of file +134296 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index e65873e3..7eea1984 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130387 \ No newline at end of file +130395 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 32c25b70..3be3e37d 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171303 \ No newline at end of file +171311 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 888d36a1..c1a17b31 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -141259 \ No newline at end of file +141267 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 1fff7e94..b9627da9 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -340631 \ No newline at end of file +340639 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 7ec221a4..5c77d4aa 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -349123 \ No newline at end of file +349131 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 80f6525a..aa3817d2 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -348425 \ No newline at end of file +348433 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 14dd1b79..2d86e061 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -318613 \ No newline at end of file +318621 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index aba6356f..ff727a45 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -319255 \ No newline at end of file +319263 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index a6cf5393..d8c6c3a6 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -244837 \ No newline at end of file +244845 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 095bb62b..99790fc7 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -374655 \ No newline at end of file +374663 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 62ccd91f..780d8a42 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -324631 \ No newline at end of file +324639 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 27da45cd..8dab44ba 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -375931 \ No newline at end of file +375939 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 4ee5b345..6a830e87 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -375071 \ No newline at end of file +375079 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 0a30edb5..5a4056a9 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -420405 \ No newline at end of file +420413 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 9be9413f..b19a0425 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -333,7 +333,8 @@ contract PositionManager is bytes32 salt, bytes calldata hookData ) internal returns (BalanceDelta liquidityDelta) { - (liquidityDelta,) = poolManager.modifyLiquidity( + BalanceDelta feesAccrued; + (liquidityDelta, feesAccrued) = poolManager.modifyLiquidity( config.poolKey, IPoolManager.ModifyLiquidityParams({ tickLower: config.tickLower, @@ -345,7 +346,7 @@ contract PositionManager is ); if (positionConfigs.hasSubscriber(uint256(salt))) { - _notifyModifyLiquidity(uint256(salt), config, liquidityChange); + _notifyModifyLiquidity(uint256(salt), config, liquidityChange, feesAccrued); } } diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 2d6669a8..09a542d3 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -6,6 +6,7 @@ import {PositionConfig} from "../libraries/PositionConfig.sol"; import {BipsLibrary} from "../libraries/BipsLibrary.sol"; import {INotifier} from "../interfaces/INotifier.sol"; import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; /// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers abstract contract Notifier is INotifier { @@ -57,12 +58,19 @@ abstract contract Notifier is INotifier { emit Unsubscribed(tokenId, address(_subscriber)); } - function _notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) internal { + function _notifyModifyLiquidity( + uint256 tokenId, + PositionConfig memory config, + int256 liquidityChange, + BalanceDelta feesAccrued + ) internal { ISubscriber _subscriber = subscriber[tokenId]; bool success = _call( address(_subscriber), - abi.encodeWithSelector(ISubscriber.notifyModifyLiquidity.selector, tokenId, config, liquidityChange) + abi.encodeWithSelector( + ISubscriber.notifyModifyLiquidity.selector, tokenId, config, liquidityChange, feesAccrued + ) ); if (!success) { diff --git a/src/interfaces/ISubscriber.sol b/src/interfaces/ISubscriber.sol index 81a68c0d..18d428c7 100644 --- a/src/interfaces/ISubscriber.sol +++ b/src/interfaces/ISubscriber.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {PositionConfig} from "../libraries/PositionConfig.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; /// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager interface ISubscriber { @@ -16,7 +17,13 @@ interface ISubscriber { /// @param tokenId the token ID of the position /// @param config details about the position /// @param liquidityChange the change in liquidity on the underlying position - function notifyModifyLiquidity(uint256 tokenId, PositionConfig memory config, int256 liquidityChange) external; + /// @param feesAccrued the fees to be collected from the position as a result of the modifyLiquidity call + function notifyModifyLiquidity( + uint256 tokenId, + PositionConfig memory config, + int256 liquidityChange, + BalanceDelta feesAccrued + ) external; /// @param tokenId the token ID of the position /// @param previousOwner address of the old owner /// @param newOwner address of the new owner diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol index b8db89aa..30bafba7 100644 --- a/test/mocks/MockBadSubscribers.sol +++ b/test/mocks/MockBadSubscribers.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {PositionManager} from "../../src/PositionManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; /// @notice A subscriber contract that returns values from the subscriber entrypoints contract MockReturnDataSubscriber is ISubscriber { @@ -44,7 +45,7 @@ contract MockReturnDataSubscriber is ISubscriber { } } - function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { + function notifyModifyLiquidity(uint256, PositionConfig memory, int256, BalanceDelta) external onlyByPosm { notifyModifyLiquidityCount++; } @@ -86,7 +87,7 @@ contract MockRevertSubscriber is ISubscriber { revert TestRevert("notifyUnsubscribe"); } - function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external view onlyByPosm { + function notifyModifyLiquidity(uint256, PositionConfig memory, int256, BalanceDelta) external view onlyByPosm { revert TestRevert("notifyModifyLiquidity"); } diff --git a/test/mocks/MockSubscriber.sol b/test/mocks/MockSubscriber.sol index 1e317ad1..032bea86 100644 --- a/test/mocks/MockSubscriber.sol +++ b/test/mocks/MockSubscriber.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; import {PositionConfig} from "../../src/libraries/PositionConfig.sol"; import {PositionManager} from "../../src/PositionManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; /// @notice A subscriber contract that ingests updates from the v4 position manager contract MockSubscriber is ISubscriber { @@ -13,6 +14,8 @@ contract MockSubscriber is ISubscriber { uint256 public notifyUnsubscribeCount; uint256 public notifyModifyLiquidityCount; uint256 public notifyTransferCount; + int256 public liquidityChange; + BalanceDelta public feesAccrued; bytes public subscribeData; bytes public unsubscribeData; @@ -40,8 +43,13 @@ contract MockSubscriber is ISubscriber { unsubscribeData = data; } - function notifyModifyLiquidity(uint256, PositionConfig memory, int256) external onlyByPosm { + function notifyModifyLiquidity(uint256, PositionConfig memory, int256 _liquidityChange, BalanceDelta _feesAccrued) + external + onlyByPosm + { notifyModifyLiquidityCount++; + liquidityChange = _liquidityChange; + feesAccrued = _feesAccrued; } function notifyTransfer(uint256, address, address) external onlyByPosm { diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol index ab008a6c..28f5813f 100644 --- a/test/position-managers/PositionManager.notifier.t.sol +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -18,6 +18,7 @@ import {Plan, Planner} from "../shared/Planner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {INotifier} from "../../src/interfaces/INotifier.sol"; import {MockReturnDataSubscriber, MockRevertSubscriber} from "../mocks/MockBadSubscribers.sol"; +import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { using PoolIdLibrary for PoolKey; @@ -125,6 +126,34 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup, GasSnapshot { assertEq(sub.notifyModifyLiquidityCount(), 10); } + function test_notifyModifyLiquidity_args() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 100e18, alice, ZERO_BYTES); + + // donate to generate fee revenue, to be checked in subscriber + uint256 feeRevenue0 = 1e18; + uint256 feeRevenue1 = 0.1e18; + donateRouter.donate(config.poolKey, feeRevenue0, feeRevenue1, ZERO_BYTES); + + // approve this contract to operate on alices liq + vm.startPrank(alice); + lpm.approve(address(this), tokenId); + vm.stopPrank(); + + lpm.subscribe(tokenId, config, address(sub), ZERO_BYTES); + + assertEq(lpm.hasSubscriber(tokenId), true); + assertEq(address(lpm.subscriber(tokenId)), address(sub)); + + uint256 liquidityToAdd = 10e18; + increaseLiquidity(tokenId, config, liquidityToAdd, ZERO_BYTES); + + assertEq(sub.notifyModifyLiquidityCount(), 1); + assertEq(sub.liquidityChange(), int256(liquidityToAdd)); + assertEq(int256(sub.feesAccrued().amount0()), int256(feeRevenue0) - 1 wei); + assertEq(int256(sub.feesAccrued().amount1()), int256(feeRevenue1) - 1 wei); + } + function test_notifyTransfer_withTransferFrom_succeeds() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 100e18, alice, ZERO_BYTES); From 17f1a49e10ed58915b0e14cb07543343820bb109 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:17:44 -0400 Subject: [PATCH 39/52] OZ: posm - restore permissioning on increase (#290) * restore permissioning on increase * fix comment * fix code comments --- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- src/PositionManager.sol | 2 +- .../position-managers/IncreaseLiquidity.t.sol | 24 ----------- test/position-managers/Permit.t.sol | 40 +++++++++++++++++-- .../PositionManager.modifyLiquidities.t.sol | 28 ++++++++++++- 10 files changed, 70 insertions(+), 36 deletions(-) diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 8ed74edd..b3a45767 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -152496 \ No newline at end of file +154942 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 768175ec..6423f6ee 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -151498 \ No newline at end of file +153944 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 21ace784..9aed0319 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -134296 \ No newline at end of file +136742 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 7eea1984..d1ccfa88 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130395 \ No newline at end of file +132841 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 3be3e37d..55dddada 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -171311 \ No newline at end of file +173757 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index c1a17b31..9bac5046 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -141267 \ No newline at end of file +143713 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index b19a0425..d76b08ff 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -215,7 +215,7 @@ contract PositionManager is uint128 amount0Max, uint128 amount1Max, bytes calldata hookData - ) internal onlyValidConfig(tokenId, config) { + ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); liquidityDelta.validateMaxInNegative(amount0Max, amount1Max); diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index f1a65efd..e9aa4946 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -294,30 +294,6 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { assertEq(currency1.balanceOf(alice), balance1BeforeAlice); } - function test_increaseLiquidity_withUnapprovedCaller() public { - // Alice provides liquidity - // Bob increases Alice's liquidity without being approved - uint256 liquidityAlice = 3_000e18; - - // alice provides liquidity - vm.startPrank(alice); - uint256 tokenIdAlice = lpm.nextTokenId(); - mint(config, liquidityAlice, alice, ZERO_BYTES); - vm.stopPrank(); - - uint128 oldLiquidity = lpm.getPositionLiquidity(tokenIdAlice, config); - - // bob can increase liquidity for alice even though he is not the owner / not approved - vm.startPrank(bob); - increaseLiquidity(tokenIdAlice, config, 100e18, ZERO_BYTES); - vm.stopPrank(); - - uint128 newLiquidity = lpm.getPositionLiquidity(tokenIdAlice, config); - - // assert liqudity increased by the correct amount - assertEq(newLiquidity, oldLiquidity + uint128(100e18)); - } - function test_increaseLiquidity_sameRange_withExcessFees() public { // Alice and Bob provide liquidity on the same range // Alice uses half her fees to increase liquidity. The other half are collected to her wallet. diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol index 22f887f0..6cf03c3f 100644 --- a/test/position-managers/Permit.t.sol +++ b/test/position-managers/Permit.t.sol @@ -72,6 +72,27 @@ contract PermitTest is Test, PosmTestSetup { ); } + function test_permit_increaseLiquidity() public { + uint256 liquidityAlice = 1e18; + uint256 tokenIdAlice = lpm.nextTokenId(); + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + + // alice gives bob permissions + permit(alicePK, tokenIdAlice, bob, 1); + + // bob can increase liquidity on alice's token + uint256 liquidityToAdd = 0.4444e18; + vm.startPrank(bob); + increaseLiquidity(tokenIdAlice, config, liquidityToAdd, ZERO_BYTES); + vm.stopPrank(); + + // alice's position increased liquidity + uint256 liquidity = lpm.getPositionLiquidity(tokenIdAlice, config); + + assertEq(liquidity, liquidityAlice + liquidityToAdd); + } + function test_permit_decreaseLiquidity() public { uint256 liquidityAlice = 1e18; vm.prank(alice); @@ -143,9 +164,22 @@ contract PermitTest is Test, PosmTestSetup { vm.stopPrank(); } - // unapproved callers can increase others' positions - // see `test_increaseLiquidity_withUnapprovedCaller()` - // function test_noPermit_increaseLiquidityRevert() public {} + /// @dev unapproved callers CANNOT increase others' positions + function test_noPermit_increaseLiquidityRevert() public { + // increase fails if the owner did not permit + uint256 liquidityAlice = 1e18; + vm.prank(alice); + mint(config, liquidityAlice, alice, ZERO_BYTES); + uint256 tokenIdAlice = lpm.nextTokenId() - 1; + + // bob cannot increase liquidity on alice's token + uint256 liquidityToAdd = 0.4444e18; + bytes memory decrease = getIncreaseEncoded(tokenIdAlice, config, liquidityToAdd, ZERO_BYTES); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(bob))); + lpm.modifyLiquidities(decrease, _deadline); + vm.stopPrank(); + } function test_noPermit_decreaseLiquidityRevert() public { // decreaseLiquidity fails if the owner did not permit diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index 702b0952..33a8fff7 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -84,12 +84,15 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertEq(lpm.ownerOf(hookTokenId), address(hookModifyLiquidities)); // hook position owned by hook } - /// @dev increasing liquidity without approval is allowable + /// @dev hook must be approved to increase liquidity function test_hook_increaseLiquidity() public { uint256 initialLiquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); mint(config, initialLiquidity, address(this), ZERO_BYTES); + // approve the hook for increasing liquidity + lpm.approve(address(hookModifyLiquidities), tokenId); + // hook increases liquidity in beforeSwap via hookData uint256 newLiquidity = 10e18; bytes memory calls = getIncreaseEncoded(tokenId, config, newLiquidity, ZERO_BYTES); @@ -195,7 +198,28 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF } // --- Revert Scenarios --- // - /// @dev Hook does not have approval so decreasingly liquidity should revert + /// @dev Hook does not have approval so increasing liquidity should revert + function test_hook_increaseLiquidity_revert() public { + uint256 initialLiquidity = 100e18; + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + // hook decreases liquidity in beforeSwap via hookData + uint256 liquidityToAdd = 10e18; + bytes memory calls = getIncreaseEncoded(tokenId, config, liquidityToAdd, ZERO_BYTES); + + // should revert because hook is not approved + vm.expectRevert( + abi.encodeWithSelector( + Hooks.Wrap__FailedHookCall.selector, + address(hookModifyLiquidities), + abi.encodeWithSelector(IPositionManager.NotApproved.selector, address(hookModifyLiquidities)) + ) + ); + swap(key, true, -1e18, calls); + } + + /// @dev Hook does not have approval so decreasing liquidity should revert function test_hook_decreaseLiquidity_revert() public { uint256 initialLiquidity = 100e18; uint256 tokenId = lpm.nextTokenId(); From 7cad2f668dc9954796326814d5a28ea165250c3c Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Thu, 8 Aug 2024 05:02:04 -0400 Subject: [PATCH 40/52] fix: slippage checks (#285) * update amount checks * take test after increase * make natspec better, comments --------- Co-authored-by: Alice Henshaw --- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 28 ++++--- src/libraries/SlippageCheck.sol | 33 ++++++--- .../position-managers/IncreaseLiquidity.t.sol | 74 ++++++++++++++++++- 37 files changed, 145 insertions(+), 58 deletions(-) diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 10da4419..492cae30 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47170 \ No newline at end of file +47167 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 7cf4184e..6ad16fc4 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46988 \ No newline at end of file +46984 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 0c46e3d1..d9403287 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -123226 \ No newline at end of file +123586 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 4daa5df5..003ba99a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -122733 \ No newline at end of file +123093 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 4e8dd523..02edc4f7 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -130304 \ No newline at end of file +130664 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 695f0ba1..108d06a6 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -129812 \ No newline at end of file +130172 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index a29149e6..1d9f0b2e 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141698 \ No newline at end of file +142147 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index d0c0d21a..a81b3e19 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150546 \ No newline at end of file +150995 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index d0c0d21a..a81b3e19 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -150546 \ No newline at end of file +150995 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 6654e101..f1001220 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -149918 \ No newline at end of file +150367 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index bc5857fa..624e0da8 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108833 \ No newline at end of file +109192 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index d4154674..dbe6c306 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -116089 \ No newline at end of file +116538 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index cfe262e2..d421dfa0 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -115461 \ No newline at end of file +115910 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index bf8a89f1..1f43cf81 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134384 \ No newline at end of file +134740 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 64fa2092..b4eabb29 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -127123 \ No newline at end of file +127479 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 3df0c73c..062922cd 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128805 \ No newline at end of file +129254 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index afeb3089..56793a69 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -116622 \ No newline at end of file +117071 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index b3a45767..f54d79a2 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -154942 \ No newline at end of file +155245 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap index 6423f6ee..9a32f688 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -153944 \ No newline at end of file +154247 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 9aed0319..61c51099 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -136742 \ No newline at end of file +137045 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index d1ccfa88..b499b44a 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -132841 \ No newline at end of file +133390 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 55dddada..5c2d6ce7 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -173757 \ No newline at end of file +174306 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 9bac5046..0aff3df0 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -143713 \ No newline at end of file +144262 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index b9627da9..ec5fa61b 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -340639 \ No newline at end of file +341062 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 5c77d4aa..0eb07673 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -349131 \ No newline at end of file +349554 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index aa3817d2..7cc33796 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -348433 \ No newline at end of file +348856 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 2d86e061..dd01e09b 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -318621 \ No newline at end of file +319044 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index ff727a45..ecb6919a 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -319263 \ No newline at end of file +319686 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index d8c6c3a6..4b788a7b 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -244845 \ No newline at end of file +245268 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 99790fc7..04ed7a3f 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -374663 \ No newline at end of file +375086 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 780d8a42..8ab21745 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -324639 \ No newline at end of file +325062 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 8dab44ba..2c29315b 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -375939 \ No newline at end of file +376362 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 6a830e87..a151e59d 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -375079 \ No newline at end of file +375502 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 5a4056a9..ddaa376d 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -420413 \ No newline at end of file +420836 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index d76b08ff..d56f30aa 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -217,8 +217,10 @@ contract PositionManager is bytes calldata hookData ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. - BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); - liquidityDelta.validateMaxInNegative(amount0Max, amount1Max); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); } /// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position @@ -231,8 +233,10 @@ contract PositionManager is bytes calldata hookData ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { // Note: the tokenId is used as the salt. - BalanceDelta liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); - liquidityDelta.validateMinOut(amount0Min, amount1Min); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); } function _mint( @@ -252,8 +256,10 @@ contract PositionManager is _mint(owner, tokenId); // _beforeModify is not called here because the tokenId is newly minted - BalanceDelta liquidityDelta = _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); - liquidityDelta.validateMaxIn(amount0Max, amount1Max); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); positionConfigs.setConfigId(tokenId, config); emit MintPosition(tokenId, config); @@ -269,11 +275,12 @@ contract PositionManager is ) internal onlyIfApproved(msgSender(), tokenId) onlyValidConfig(tokenId, config) { uint256 liquidity = uint256(getPositionLiquidity(tokenId, config)); - BalanceDelta liquidityDelta; // Can only call modify if there is non zero liquidity. if (liquidity > 0) { - liquidityDelta = _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); - liquidityDelta.validateMinOut(amount0Min, amount1Min); + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(config, -(liquidity.toInt256()), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); } delete positionConfigs[tokenId]; @@ -332,8 +339,7 @@ contract PositionManager is int256 liquidityChange, bytes32 salt, bytes calldata hookData - ) internal returns (BalanceDelta liquidityDelta) { - BalanceDelta feesAccrued; + ) internal returns (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) { (liquidityDelta, feesAccrued) = poolManager.modifyLiquidity( config.poolKey, IPoolManager.ModifyLiquidityParams({ diff --git a/src/libraries/SlippageCheck.sol b/src/libraries/SlippageCheck.sol index efeafca9..00d9d23b 100644 --- a/src/libraries/SlippageCheck.sol +++ b/src/libraries/SlippageCheck.sol @@ -2,33 +2,42 @@ pragma solidity ^0.8.0; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCastTemp} from "./SafeCast.sol"; /// @title Slippage Check Library /// @notice a library for checking if a delta exceeds a maximum ceiling or fails to meet a minimum floor library SlippageCheckLibrary { + using SafeCastTemp for int128; + error MaximumAmountExceeded(); error MinimumAmountInsufficient(); /// @notice Revert if one or both deltas does not meet a minimum output - /// @dev to be used when removing liquidity to guarantee a minimum output + /// @param delta The principal amount of tokens to be removed, does not include any fees accrued + /// @param amount0Min The minimum amount of token0 to receive + /// @param amount1Min The minimum amount of token1 to receive + /// @dev This should be called when removing liquidity (burn or decrease) function validateMinOut(BalanceDelta delta, uint128 amount0Min, uint128 amount1Min) internal pure { - if (uint128(delta.amount0()) < amount0Min || uint128(delta.amount1()) < amount1Min) { + // Called on burn or decrease, where we expect the returned delta to be positive. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be negative. + // Because we use SafeCast, this will revert in those cases when the delta is negative. + // This means this contract will NOT support pools where the hook returns a negative delta on burn/decrease. + if (delta.amount0().toUint128() < amount0Min || delta.amount1().toUint128() < amount1Min) { revert MinimumAmountInsufficient(); } } /// @notice Revert if one or both deltas exceeds a maximum input - /// @dev to be used when minting liquidity to guarantee a maximum input + /// @param delta The principal amount of tokens to be added, does not include any fees accrued (which is possible on increase) + /// @param amount0Max The maximum amount of token0 to spend + /// @param amount1Max The maximum amount of token1 to spend + /// @dev This should be called when adding liquidity (mint or increase) function validateMaxIn(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure { - if (uint128(-delta.amount0()) > amount0Max || uint128(-delta.amount1()) > amount1Max) { - revert MaximumAmountExceeded(); - } - } - - /// @notice Revert if one or both deltas exceeds a maximum input - /// @dev When increasing liquidity, delta can be positive when excess fees need to be collected - /// in those cases, slippage checks are not required - function validateMaxInNegative(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure { + // Called on mint or increase, where we expect the returned delta to be negative. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be positive (even after discounting fees accrued). + // Thus, we only cast the delta if it is guaranteed to be negative. + // And we do NOT revert in the positive delta case. Since a positive delta means the hook is crediting tokens to the user for minting/increasing liquidity, we do not check slippage. + // This means this contract will NOT support _positive_ slippage checks (minAmountOut checks) on pools where the hook returns a positive delta on mint/increase. if ( delta.amount0() < 0 && amount0Max < uint128(-delta.amount0()) || delta.amount1() < 0 && amount1Max < uint128(-delta.amount1()) diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index e9aa4946..1feef6bc 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -75,6 +75,75 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { config = PositionConfig({poolKey: key, tickLower: -300, tickUpper: 300}); } + /// @notice Increase liquidity by less than the amount of liquidity the position has earned, requiring a take + function test_increaseLiquidity_withCollection_takePair() public { + // Alice and Bob provide liquidity on the range + // Alice uses her exact fees to increase liquidity (compounding) + + uint256 liquidityAlice = 3_000e18; + uint256 liquidityBob = 1_000e18; + + // alice provides liquidity + vm.startPrank(alice); + uint256 tokenIdAlice = lpm.nextTokenId(); + mint(config, liquidityAlice, alice, ZERO_BYTES); + vm.stopPrank(); + + // bob provides liquidity + vm.startPrank(bob); + mint(config, liquidityBob, bob, ZERO_BYTES); + vm.stopPrank(); + + // donate to create fees + uint256 amountDonate = 0.1e18; + donateRouter.donate(key, amountDonate, amountDonate, ZERO_BYTES); + + // alice uses her half her fees to increase liquidity + // Slight error in this calculation vs. actual fees.. TODO: Fix this. + BalanceDelta feesOwedAlice = IPositionManager(lpm).getFeesOwed(manager, config, tokenIdAlice); + // Note: You can alternatively calculate Alice's fees owed from the swap amount, fee on the pool, and total liquidity in that range. + // swapAmount.mulWadDown(FEE_WAD).mulDivDown(liquidityAlice, liquidityAlice + liquidityBob); + + (uint160 sqrtPriceX96,,,) = StateLibrary.getSlot0(manager, config.poolKey.toId()); + uint256 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(config.tickLower), + TickMath.getSqrtPriceAtTick(config.tickUpper), + uint256(int256(feesOwedAlice.amount0() / 2)), + uint256(int256(feesOwedAlice.amount1() / 2)) + ); + + uint256 balance0BeforeAlice = currency0.balanceOf(alice); + uint256 balance1BeforeAlice = currency1.balanceOf(alice); + + // Set the slippage amounts to be exactly half the fees that alice is reinvesting. + + Plan memory planner = Planner.init(); + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode( + tokenIdAlice, + config, + liquidityDelta, + feesOwedAlice.amount0() / 2, + feesOwedAlice.amount1() / 2, + ZERO_BYTES + ) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithTakePair(config.poolKey, address(alice)); + vm.startPrank(alice); + lpm.modifyLiquidities(calls, _deadline); + vm.stopPrank(); + + // alices current balance is the balanceBefore plus half of her fees owed + assertApproxEqAbs( + currency0.balanceOf(alice), balance0BeforeAlice + uint256(int256(feesOwedAlice.amount0() / 2)), tolerance + ); + assertApproxEqAbs( + currency1.balanceOf(alice), balance1BeforeAlice + uint256(int256(feesOwedAlice.amount1() / 2)), tolerance + ); + } + /// @notice Increase liquidity with exact fees, taking dust function test_increaseLiquidity_withExactFees_take() public { // Alice and Bob provide liquidity on the range @@ -119,7 +188,10 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { Plan memory planner = Planner.init(); planner.add( - Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, 0 wei, 0 wei, ZERO_BYTES) + Actions.INCREASE_LIQUIDITY, + abi.encode( + tokenIdAlice, config, liquidityDelta, feesOwedAlice.amount0(), feesOwedAlice.amount1(), ZERO_BYTES + ) ); bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.startPrank(alice); From b890da6a07a9ff8dcf72671dd992e4046196d814 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Thu, 8 Aug 2024 05:03:24 -0400 Subject: [PATCH 41/52] nit: make multicall external (#292) --- src/base/Multicall_v4.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base/Multicall_v4.sol b/src/base/Multicall_v4.sol index b6190a33..e648cca8 100644 --- a/src/base/Multicall_v4.sol +++ b/src/base/Multicall_v4.sol @@ -7,7 +7,7 @@ import {IMulticall_v4} from "../interfaces/IMulticall_v4.sol"; /// @notice Enables calling multiple methods in a single call to the contract abstract contract Multicall_v4 is IMulticall_v4 { /// @inheritdoc IMulticall_v4 - function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { + function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { (bool success, bytes memory result) = address(this).delegatecall(data[i]); From 4d566873b559a1ccf6375fb07630a4b22aeb1569 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:12:53 +0100 Subject: [PATCH 42/52] OZ: Remove contract balance swap input (#286) * Remove contract balance swap input * combine take and swap helpers * remove swap helper function --- ...s_swap_settleFromCaller_takeAllToMsgSender.snap | 2 +- ...settleFromCaller_takeAllToSpecifiedAddress.snap | 2 +- ..._swap_settleWithBalance_takeAllToMsgSender.snap | 2 +- ...ettleWithBalance_takeAllToSpecifiedAddress.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_nativeIn.snap | 2 +- .../V4Router_ExactIn1Hop_nativeOut.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .../V4Router_ExactIn2Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactIn3Hops_nativeIn.snap | 2 +- .forge-snapshots/V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- src/V4Router.sol | 11 ++++++++--- src/base/DeltaResolver.sol | 14 -------------- src/libraries/ActionConstants.sol | 1 + 19 files changed, 25 insertions(+), 33 deletions(-) diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap index cef56fcc..8f580036 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToMsgSender.snap @@ -1 +1 @@ -133557 \ No newline at end of file +133206 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap index 69ef47b0..0356d385 100644 --- a/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleFromCaller_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -135593 \ No newline at end of file +135242 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap index b1101ea4..8f190ac5 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToMsgSender.snap @@ -1 +1 @@ -128067 \ No newline at end of file +127716 \ No newline at end of file diff --git a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap index 80fd74db..f7b6cea9 100644 --- a/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap +++ b/.forge-snapshots/Payments_swap_settleWithBalance_takeAllToSpecifiedAddress.snap @@ -1 +1 @@ -128205 \ No newline at end of file +127854 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index cd52958e..e88f6975 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8454 \ No newline at end of file +8389 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap index 141ee629..ce9be634 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeIn.snap @@ -1 +1 @@ -119572 \ No newline at end of file +119501 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap index 4dc9f6ce..726ce00a 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_nativeOut.snap @@ -1 +1 @@ -118767 \ No newline at end of file +118696 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index 8b53041e..4cda0de8 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -127639 \ No newline at end of file +127568 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index fb68412c..988f465e 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -134469 \ No newline at end of file +134398 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index a494c349..0ec74b47 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -186010 \ No newline at end of file +185939 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap index 428b15cc..1ce5a179 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops_nativeIn.snap @@ -1 +1 @@ -177945 \ No newline at end of file +177874 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 838a2bb4..96e4b6a4 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -237532 \ No newline at end of file +237461 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap index f0c5a684..456844ad 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops_nativeIn.snap @@ -1 +1 @@ -229491 \ No newline at end of file +229420 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index cef56fcc..8f580036 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -133557 \ No newline at end of file +133206 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index 4e4ef13d..e8ecc585 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -118660 \ No newline at end of file +118309 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index 4409062d..06d58308 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -117838 \ No newline at end of file +117482 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 8f32d51d..3733cca3 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -16,6 +16,7 @@ import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; import {Actions} from "./libraries/Actions.sol"; import {SafeCastTemp} from "./libraries/SafeCast.sol"; +import {ActionConstants} from "./libraries/ActionConstants.sol"; /// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools @@ -79,8 +80,11 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { } function _swapExactInputSingle(IV4Router.ExactInputSingleParams calldata params) private { - uint128 amountIn = - _mapInputAmount(params.amountIn, params.zeroForOne ? params.poolKey.currency0 : params.poolKey.currency1); + uint128 amountIn = params.amountIn; + if (amountIn == ActionConstants.OPEN_DELTA) { + amountIn = + _getFullCredit(params.zeroForOne ? params.poolKey.currency0 : params.poolKey.currency1).toUint128(); + } uint128 amountOut = _swap( params.poolKey, params.zeroForOne, int256(-int128(amountIn)), params.sqrtPriceLimitX96, params.hookData ).toUint128(); @@ -93,7 +97,8 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { uint256 pathLength = params.path.length; uint128 amountOut; Currency currencyIn = params.currencyIn; - uint128 amountIn = _mapInputAmount(params.amountIn, currencyIn); + uint128 amountIn = params.amountIn; + if (amountIn == ActionConstants.OPEN_DELTA) amountIn = _getFullCredit(currencyIn).toUint128(); PathKey calldata pathKey; for (uint256 i = 0; i < pathLength; i++) { diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 4269092e..8170ae94 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -86,18 +86,4 @@ abstract contract DeltaResolver is ImmutableState { } return amount; } - - /// @notice Calculates the amount for a swap action - /// @dev This is to be used for swaps where the input amount isn't known before the transaction, and - /// isn't possible using a v4 multi-hop command. - /// For example USDC-v2->DAI-v4->USDT. This intermediate DAI amount could be swapped using CONTRACT_BALANCE - /// or settled using CONTRACT_BALANCE then swapped using OPEN_DELTA. - function _mapInputAmount(uint128 amount, Currency currency) internal view returns (uint128) { - if (amount == ActionConstants.CONTRACT_BALANCE) { - return currency.balanceOfSelf().toUint128(); - } else if (amount == ActionConstants.OPEN_DELTA) { - return _getFullCredit(currency).toUint128(); - } - return amount; - } } diff --git a/src/libraries/ActionConstants.sol b/src/libraries/ActionConstants.sol index 504d9052..bb878540 100644 --- a/src/libraries/ActionConstants.sol +++ b/src/libraries/ActionConstants.sol @@ -5,6 +5,7 @@ library ActionConstants { /// @notice used to signal that an action should use the input value of the open delta on the pool manager /// or of the balance that the contract holds uint128 internal constant OPEN_DELTA = 0; + /// @notice used to signal that an action should use the contract's entire balance of a currency /// This value is equivalent to 1<<255, i.e. a singular 1 in the most significant bit. uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000; From 469f8562ac5ed74de0bb4db43479e50f1d04e484 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:44:48 -0400 Subject: [PATCH 43/52] move sub unsub (#287) * move sub unsub * use struct * pass in bytes to setConfigId * ... --- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 54 +++------ src/base/Notifier.sol | 32 ++++- src/interfaces/INotifier.sol | 1 + src/libraries/PositionConfig.sol | 43 +------ src/libraries/PositionConfigId.sol | 39 +++++++ test/libraries/PositionConfig.t.sol | 109 ++++++++++++------ 17 files changed, 171 insertions(+), 129 deletions(-) create mode 100644 src/libraries/PositionConfigId.sol diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index ec5fa61b..e60f75f4 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -341062 \ No newline at end of file +341067 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap index 0eb07673..b0e3dbe4 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -349554 \ No newline at end of file +349559 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap index 7cc33796..3d917e21 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -348856 \ No newline at end of file +348861 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index dd01e09b..6bcd8770 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -319044 \ No newline at end of file +319049 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index ecb6919a..589f7a63 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -319686 \ No newline at end of file +319691 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index 4b788a7b..c1563f13 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -245268 \ No newline at end of file +245273 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index 04ed7a3f..66c788d9 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -375086 \ No newline at end of file +375091 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 8ab21745..13864905 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -325062 \ No newline at end of file +325067 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 2c29315b..f5a59c25 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -376362 \ No newline at end of file +376367 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index a151e59d..1742c9ea 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -375502 \ No newline at end of file +375507 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index ddaa376d..869a05f5 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -420836 \ No newline at end of file +420841 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index d56f30aa..2f4c9bd9 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -10,7 +10,6 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; -import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; @@ -29,6 +28,7 @@ import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; import {INotifier} from "./interfaces/INotifier.sol"; import {Permit2Forwarder} from "./base/Permit2Forwarder.sol"; import {SlippageCheckLibrary} from "./libraries/SlippageCheck.sol"; +import {PositionConfigId, PositionConfigIdLibrary} from "./libraries/PositionConfigId.sol"; contract PositionManager is IPositionManager, @@ -44,18 +44,24 @@ contract PositionManager is using SafeTransferLib for *; using CurrencyLibrary for Currency; using PoolIdLibrary for PoolKey; - using PositionConfigLibrary for *; + using PositionConfigLibrary for PositionConfig; using StateLibrary for IPoolManager; using TransientStateLibrary for IPoolManager; using SafeCast for uint256; using SafeCast for int256; using CalldataDecoder for bytes; using SlippageCheckLibrary for BalanceDelta; + using PositionConfigIdLibrary for PositionConfigId; /// @dev The ID of the next token that will be minted. Skips 0 uint256 public nextTokenId = 1; - mapping(uint256 tokenId => bytes32 config) private positionConfigs; + mapping(uint256 tokenId => PositionConfigId configId) internal positionConfigs; + + /// @notice an internal getter for PositionConfigId to be used by Notifier + function _positionConfigs(uint256 tokenId) internal view override returns (PositionConfigId storage) { + return positionConfigs[tokenId]; + } constructor(IPoolManager _poolManager, IAllowanceTransfer _permit2) BaseActionsRouter(_poolManager) @@ -75,7 +81,7 @@ contract PositionManager is /// @param tokenId the unique identifier of the ERC721 token /// @dev either msg.sender or _msgSender() is passed in as the caller /// _msgSender() should ONLY be used if this is being called from within the unlockCallback - modifier onlyIfApproved(address caller, uint256 tokenId) { + modifier onlyIfApproved(address caller, uint256 tokenId) override { if (!_isApprovedOrOwner(caller, tokenId)) revert NotApproved(caller); _; } @@ -83,8 +89,8 @@ contract PositionManager is /// @notice Reverts if the hash of the config does not equal the saved hash /// @param tokenId the unique identifier of the ERC721 token /// @param config the PositionConfig to check against - modifier onlyValidConfig(uint256 tokenId, PositionConfig calldata config) { - if (positionConfigs.getConfigId(tokenId) != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); + modifier onlyValidConfig(uint256 tokenId, PositionConfig calldata config) override { + if (positionConfigs[tokenId].getConfigId() != config.toId()) revert IncorrectPositionConfigForTokenId(tokenId); _; } @@ -107,29 +113,6 @@ contract PositionManager is _executeActionsWithoutUnlock(actions, params); } - /// @inheritdoc INotifier - function subscribe(uint256 tokenId, PositionConfig calldata config, address subscriber, bytes calldata data) - external - payable - onlyIfApproved(msg.sender, tokenId) - onlyValidConfig(tokenId, config) - { - // call to _subscribe will revert if the user already has a sub - positionConfigs.setSubscribe(tokenId); - _subscribe(tokenId, config, subscriber, data); - } - - /// @inheritdoc INotifier - function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) - external - payable - onlyIfApproved(msg.sender, tokenId) - onlyValidConfig(tokenId, config) - { - positionConfigs.setUnsubscribe(tokenId); - _unsubscribe(tokenId, config, data); - } - function msgSender() public view override returns (address) { return _getLocker(); } @@ -260,7 +243,7 @@ contract PositionManager is _modifyLiquidity(config, liquidity.toInt256(), bytes32(tokenId), hookData); // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); - positionConfigs.setConfigId(tokenId, config); + positionConfigs[tokenId].setConfigId(config.toId()); emit MintPosition(tokenId, config); } @@ -351,7 +334,7 @@ contract PositionManager is hookData ); - if (positionConfigs.hasSubscriber(uint256(salt))) { + if (positionConfigs[uint256(salt)].hasSubscriber()) { _notifyModifyLiquidity(uint256(salt), config, liquidityChange, feesAccrued); } } @@ -369,7 +352,7 @@ contract PositionManager is /// @dev overrides solmate transferFrom in case a notification to subscribers is needed function transferFrom(address from, address to, uint256 id) public virtual override { super.transferFrom(from, to, id); - if (positionConfigs.hasSubscriber(id)) _notifyTransfer(id, from, to); + if (positionConfigs[id].hasSubscriber()) _notifyTransfer(id, from, to); } /// @inheritdoc IPositionManager @@ -385,11 +368,6 @@ contract PositionManager is /// @inheritdoc IPositionManager function getPositionConfigId(uint256 tokenId) external view returns (bytes32) { - return positionConfigs.getConfigId(tokenId); - } - - /// @inheritdoc INotifier - function hasSubscriber(uint256 tokenId) external view returns (bool) { - return positionConfigs.hasSubscriber(tokenId); + return positionConfigs[tokenId].getConfigId(); } } diff --git a/src/base/Notifier.sol b/src/base/Notifier.sol index 09a542d3..10a501a2 100644 --- a/src/base/Notifier.sol +++ b/src/base/Notifier.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.24; import {ISubscriber} from "../interfaces/ISubscriber.sol"; import {PositionConfig} from "../libraries/PositionConfig.sol"; +import {PositionConfigId, PositionConfigIdLibrary} from "../libraries/PositionConfigId.sol"; import {BipsLibrary} from "../libraries/BipsLibrary.sol"; import {INotifier} from "../interfaces/INotifier.sol"; import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; @@ -12,6 +13,7 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; abstract contract Notifier is INotifier { using BipsLibrary for uint256; using CustomRevert for bytes4; + using PositionConfigIdLibrary for PositionConfigId; error AlreadySubscribed(address subscriber); @@ -27,9 +29,20 @@ abstract contract Notifier is INotifier { mapping(uint256 tokenId => ISubscriber subscriber) public subscriber; - function _subscribe(uint256 tokenId, PositionConfig memory config, address newSubscriber, bytes memory data) - internal + modifier onlyIfApproved(address caller, uint256 tokenId) virtual; + modifier onlyValidConfig(uint256 tokenId, PositionConfig calldata config) virtual; + + function _positionConfigs(uint256 tokenId) internal view virtual returns (PositionConfigId storage); + + /// @inheritdoc INotifier + function subscribe(uint256 tokenId, PositionConfig calldata config, address newSubscriber, bytes calldata data) + external + payable + onlyIfApproved(msg.sender, tokenId) + onlyValidConfig(tokenId, config) { + // will revert below if the user already has a subcriber + _positionConfigs(tokenId).setSubscribe(); ISubscriber _subscriber = subscriber[tokenId]; if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(address(_subscriber)); @@ -46,8 +59,14 @@ abstract contract Notifier is INotifier { emit Subscribed(tokenId, address(newSubscriber)); } - /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. - function _unsubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) internal { + /// @inheritdoc INotifier + function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) + external + payable + onlyIfApproved(msg.sender, tokenId) + onlyValidConfig(tokenId, config) + { + _positionConfigs(tokenId).setUnsubscribe(); ISubscriber _subscriber = subscriber[tokenId]; uint256 subscriberGasLimit = block.gaslimit.calculatePortion(BLOCK_LIMIT_BPS); @@ -96,4 +115,9 @@ abstract contract Notifier is INotifier { success := call(gas(), target, 0, add(encodedCall, 0x20), mload(encodedCall), 0, 0) } } + + /// @inheritdoc INotifier + function hasSubscriber(uint256 tokenId) external view returns (bool) { + return _positionConfigs(tokenId).hasSubscriber(); + } } diff --git a/src/interfaces/INotifier.sol b/src/interfaces/INotifier.sol index 4aa05ae6..20c4d7f0 100644 --- a/src/interfaces/INotifier.sol +++ b/src/interfaces/INotifier.sol @@ -28,6 +28,7 @@ interface INotifier { /// @param config the corresponding PositionConfig for the tokenId /// @param data caller-provided data that's forwarded to the subscriber contract /// @dev payable so it can be multicalled with NATIVE related actions + /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. function unsubscribe(uint256 tokenId, PositionConfig calldata config, bytes calldata data) external payable; /// @notice Returns whether a a position should call out to notify a subscribing contract on modification or transfer diff --git a/src/libraries/PositionConfig.sol b/src/libraries/PositionConfig.sol index 7c5d632a..8f3d6ead 100644 --- a/src/libraries/PositionConfig.sol +++ b/src/libraries/PositionConfig.sol @@ -10,49 +10,8 @@ struct PositionConfig { int24 tickUpper; } -/// @notice Library to get and set the PositionConfigId and subscriber status for a given tokenId +/// @notice Library to calculate the PositionConfigId from the PositionConfig struct library PositionConfigLibrary { - using PositionConfigLibrary for PositionConfig; - - bytes32 constant MASK_UPPER_BIT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - bytes32 constant DIRTY_UPPER_BIT = 0x8000000000000000000000000000000000000000000000000000000000000000; - - /// @notice returns the truncated hash of the PositionConfig for a given tokenId - function getConfigId(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) - internal - view - returns (bytes32 configId) - { - configId = positionConfigs[tokenId] & MASK_UPPER_BIT; - } - - function setConfigId( - mapping(uint256 => bytes32) storage positionConfigs, - uint256 tokenId, - PositionConfig calldata config - ) internal { - positionConfigs[tokenId] = config.toId(); - } - - function setSubscribe(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) internal { - positionConfigs[tokenId] |= DIRTY_UPPER_BIT; - } - - function setUnsubscribe(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) internal { - positionConfigs[tokenId] &= MASK_UPPER_BIT; - } - - function hasSubscriber(mapping(uint256 => bytes32) storage positionConfigs, uint256 tokenId) - internal - view - returns (bool subscribed) - { - bytes32 _config = positionConfigs[tokenId]; - assembly ("memory-safe") { - subscribed := shr(255, _config) - } - } - function toId(PositionConfig calldata config) internal pure returns (bytes32 id) { // id = keccak256(abi.encodePacked(currency0, currency1, fee, tickSpacing, hooks, tickLower, tickUpper))) >> 1 assembly ("memory-safe") { diff --git a/src/libraries/PositionConfigId.sol b/src/libraries/PositionConfigId.sol new file mode 100644 index 00000000..40127102 --- /dev/null +++ b/src/libraries/PositionConfigId.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.24; + +/// @notice A configId is set per tokenId +/// The lower 255 bits are used to store the truncated hash of the corresponding PositionConfig +/// The upper bit is used to signal if the tokenId has a subscriber +struct PositionConfigId { + bytes32 id; +} + +library PositionConfigIdLibrary { + bytes32 constant MASK_UPPER_BIT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + bytes32 constant DIRTY_UPPER_BIT = 0x8000000000000000000000000000000000000000000000000000000000000000; + + /// @notice returns the truncated hash of the PositionConfig for a given tokenId + function getConfigId(PositionConfigId storage _configId) internal view returns (bytes32 configId) { + configId = _configId.id & MASK_UPPER_BIT; + } + + /// @dev We only set the config on mint, guaranteeing that the most significant bit is unset, so we can just assign the entire 32 bytes to the id. + function setConfigId(PositionConfigId storage _configId, bytes32 configId) internal { + _configId.id = configId; + } + + function setSubscribe(PositionConfigId storage configId) internal { + configId.id |= DIRTY_UPPER_BIT; + } + + function setUnsubscribe(PositionConfigId storage configId) internal { + configId.id &= MASK_UPPER_BIT; + } + + function hasSubscriber(PositionConfigId storage configId) internal view returns (bool subscribed) { + bytes32 _id = configId.id; + assembly ("memory-safe") { + subscribed := shr(255, _id) + } + } +} diff --git a/test/libraries/PositionConfig.t.sol b/test/libraries/PositionConfig.t.sol index cfec1a54..187ee370 100644 --- a/test/libraries/PositionConfig.t.sol +++ b/test/libraries/PositionConfig.t.sol @@ -4,11 +4,13 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; import {PositionConfig, PositionConfigLibrary} from "../../src/libraries/PositionConfig.sol"; +import {PositionConfigId, PositionConfigIdLibrary} from "../../src/libraries/PositionConfigId.sol"; contract PositionConfigTest is Test { - using PositionConfigLibrary for *; + using PositionConfigLibrary for PositionConfig; + using PositionConfigIdLibrary for PositionConfigId; - mapping(uint256 => bytes32) internal testConfigs; + mapping(uint256 => PositionConfigId) internal testConfigs; bytes32 public constant UPPER_BIT_SET = 0x8000000000000000000000000000000000000000000000000000000000000000; @@ -18,88 +20,127 @@ contract PositionConfigTest is Test { } function test_fuzz_setConfigId(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); + testConfigs[tokenId].setConfigId(config.toId()); bytes32 expectedConfigId = _calculateExpectedId(config); - bytes32 actualConfigId = testConfigs[tokenId]; + bytes32 actualConfigId = testConfigs[tokenId].id; assertEq(expectedConfigId, actualConfigId); } function test_fuzz_getConfigId(uint256 tokenId, PositionConfig calldata config) public { bytes32 expectedId = _calculateExpectedId(config); // set - testConfigs[tokenId] = expectedId; + testConfigs[tokenId] = PositionConfigId({id: expectedId}); - assertEq(expectedId, testConfigs.getConfigId(tokenId)); + assertEq(expectedId, testConfigs[tokenId].getConfigId()); } function test_fuzz_setConfigId_getConfigId(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); + testConfigs[tokenId].setConfigId(config.toId()); bytes32 expectedId = _calculateExpectedId(config); - assertEq(testConfigs.getConfigId(tokenId), testConfigs[tokenId]); - assertEq(testConfigs.getConfigId(tokenId), expectedId); + assertEq(testConfigs[tokenId].getConfigId(), testConfigs[tokenId].id); + assertEq(testConfigs[tokenId].getConfigId(), expectedId); } function test_fuzz_getConfigId_equal_afterSubscribe(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); - testConfigs.setSubscribe(tokenId); + testConfigs[tokenId].setConfigId(config.toId()); + testConfigs[tokenId].setSubscribe(); - assertEq(testConfigs.getConfigId(tokenId), config.toId()); + assertEq(testConfigs[tokenId].getConfigId(), config.toId()); } function test_fuzz_setSubscribe(uint256 tokenId) public { - testConfigs.setSubscribe(tokenId); - bytes32 upperBitSet = testConfigs[tokenId]; + testConfigs[tokenId].setSubscribe(); + bytes32 upperBitSet = testConfigs[tokenId].id; assertEq(upperBitSet, UPPER_BIT_SET); } function test_fuzz_setConfigId_setSubscribe(uint256 tokenId, PositionConfig calldata config) public { - testConfigs.setConfigId(tokenId, config); - testConfigs.setSubscribe(tokenId); + testConfigs[tokenId].setConfigId(config.toId()); + testConfigs[tokenId].setSubscribe(); bytes32 expectedConfig = _calculateExpectedId(config) | UPPER_BIT_SET; - bytes32 _config = testConfigs[tokenId]; + bytes32 _config = testConfigs[tokenId].id; assertEq(_config, expectedConfig); } function test_fuzz_setUnsubscribe(uint256 tokenId) public { - testConfigs.setSubscribe(tokenId); - bytes32 _config = testConfigs[tokenId]; + testConfigs[tokenId].setSubscribe(); + bytes32 _config = testConfigs[tokenId].id; assertEq(_config, UPPER_BIT_SET); - testConfigs.setUnsubscribe(tokenId); - _config = testConfigs[tokenId]; + testConfigs[tokenId].setUnsubscribe(); + _config = testConfigs[tokenId].id; assertEq(_config, 0); } function test_hasSubscriber(uint256 tokenId) public { - testConfigs.setSubscribe(tokenId); - assert(testConfigs.hasSubscriber(tokenId)); - testConfigs.setUnsubscribe(tokenId); - assert(!testConfigs.hasSubscriber(tokenId)); + testConfigs[tokenId].setSubscribe(); + assert(testConfigs[tokenId].hasSubscriber()); + testConfigs[tokenId].setUnsubscribe(); + assert(!testConfigs[tokenId].hasSubscriber()); } function test_fuzz_setConfigId_setSubscribe_setUnsubscribe_getConfigId( uint256 tokenId, PositionConfig calldata config ) public { - assertEq(testConfigs.getConfigId(tokenId), 0); + assertEq(testConfigs[tokenId].getConfigId(), 0); - testConfigs.setConfigId(tokenId, config); - assertEq(testConfigs.getConfigId(tokenId), config.toId()); + testConfigs[tokenId].setConfigId(config.toId()); + assertEq(testConfigs[tokenId].getConfigId(), config.toId()); - testConfigs.setSubscribe(tokenId); - assertEq(testConfigs.getConfigId(tokenId), config.toId()); - assertEq(testConfigs.hasSubscriber(tokenId), true); + testConfigs[tokenId].setSubscribe(); + assertEq(testConfigs[tokenId].getConfigId(), config.toId()); + assertEq(testConfigs[tokenId].hasSubscriber(), true); - testConfigs.setUnsubscribe(tokenId); - assertEq(testConfigs.getConfigId(tokenId), config.toId()); - assertEq(testConfigs.hasSubscriber(tokenId), false); + testConfigs[tokenId].setUnsubscribe(); + assertEq(testConfigs[tokenId].getConfigId(), config.toId()); + assertEq(testConfigs[tokenId].hasSubscriber(), false); + } + + function test_fuzz_setSubscribe_twice(uint256 tokenId, PositionConfig calldata config) public { + assertFalse(testConfigs[tokenId].hasSubscriber()); + + testConfigs[tokenId].setSubscribe(); + testConfigs[tokenId].setSubscribe(); + assertTrue(testConfigs[tokenId].hasSubscriber()); + + // It is known behavior that setting the config id just stores the id directly, meaning the upper most bit is unset. + // This is ok because setConfigId will only ever be called on mint. + testConfigs[tokenId].setConfigId(config.toId()); + assertFalse(testConfigs[tokenId].hasSubscriber()); + + testConfigs[tokenId].setSubscribe(); + testConfigs[tokenId].setSubscribe(); + assertTrue(testConfigs[tokenId].hasSubscriber()); + } + + function test_fuzz_setUnsubscribe_twice(uint256 tokenId, PositionConfig calldata config) public { + assertFalse(testConfigs[tokenId].hasSubscriber()); + + testConfigs[tokenId].setUnsubscribe(); + testConfigs[tokenId].setUnsubscribe(); + assertFalse(testConfigs[tokenId].hasSubscriber()); + + testConfigs[tokenId].setConfigId(config.toId()); + assertFalse(testConfigs[tokenId].hasSubscriber()); + + testConfigs[tokenId].setUnsubscribe(); + testConfigs[tokenId].setUnsubscribe(); + assertFalse(testConfigs[tokenId].hasSubscriber()); + + testConfigs[tokenId].setSubscribe(); + assertTrue(testConfigs[tokenId].hasSubscriber()); + + testConfigs[tokenId].setUnsubscribe(); + testConfigs[tokenId].setUnsubscribe(); + assertFalse(testConfigs[tokenId].hasSubscriber()); } function _calculateExpectedId(PositionConfig calldata config) internal pure returns (bytes32 expectedId) { From 5ad4439e6eec590d7dcd70e62f2565ceb5a8d9ec Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:17:19 -0400 Subject: [PATCH 44/52] add view quoter --- src/interfaces/IViewQuoter.sol | 29 +++++ src/lens/ViewQuoter.sol | 24 ++++ src/libraries/PoolTickBitmap.sol | 69 +++++++++++ src/libraries/QuoterMath.sol | 203 +++++++++++++++++++++++++++++++ test/ViewQuoter.t.sol | 150 +++++++++++++++++++++++ 5 files changed, 475 insertions(+) create mode 100644 src/interfaces/IViewQuoter.sol create mode 100644 src/lens/ViewQuoter.sol create mode 100644 src/libraries/PoolTickBitmap.sol create mode 100644 src/libraries/QuoterMath.sol create mode 100644 test/ViewQuoter.t.sol diff --git a/src/interfaces/IViewQuoter.sol b/src/interfaces/IViewQuoter.sol new file mode 100644 index 00000000..0fb9aca3 --- /dev/null +++ b/src/interfaces/IViewQuoter.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.26; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +/// @title Quoter Interface +/// @notice Supports quoting the calculated amounts from hookless swaps. +interface IViewQuoter { + /// @notice Returns the amount taken or received for a swap of a single pool + /// @param poolKey The poolKey identifying the pool traded against + /// currency0 + /// currency1 + /// fee + /// tickSpacing + /// hooks + /// @param swapParams The parameters used for the swap + /// zeroForOne + /// amountSpecified + /// sqrtPriceLimitX96 + /// @return amount0 the amount of token0 sent in or out of the pool + /// @return amount1 the amount of token1 sent in or out of the pool + /// @return sqrtPriceAfterX96 the price of the pool after the swap + /// @return initializedTicksCrossed the number of initialized ticks LOADED IN + function quoteSingle(PoolKey calldata poolKey, IPoolManager.SwapParams calldata swapParams) + external + view + returns (int256, int256, uint160, uint32); +} diff --git a/src/lens/ViewQuoter.sol b/src/lens/ViewQuoter.sol new file mode 100644 index 00000000..3c0ad3fc --- /dev/null +++ b/src/lens/ViewQuoter.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import {IViewQuoter} from "../interfaces/IViewQuoter.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {QuoterMath} from "../libraries/QuoterMath.sol"; + +contract ViewQuoter is IViewQuoter { + IPoolManager public immutable poolManager; + + constructor(IPoolManager _poolManager) { + poolManager = _poolManager; + } + + function quoteSingle(PoolKey calldata poolKey, IPoolManager.SwapParams calldata swapParams) + public + view + override + returns (int256, int256, uint160, uint32) + { + return QuoterMath.quote(poolManager, poolKey, swapParams); + } +} diff --git a/src/libraries/PoolTickBitmap.sol b/src/libraries/PoolTickBitmap.sol new file mode 100644 index 00000000..08b06bad --- /dev/null +++ b/src/libraries/PoolTickBitmap.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import {IPoolManager} from "lib/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId} from "lib/v4-core/src/types/PoolId.sol"; +import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; + +/// @title Packed tick initialized state library +/// @notice Stores a packed mapping of tick index to its initialized state +/// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word. +library PoolTickBitmap { + using StateLibrary for IPoolManager; + + /// @notice Computes the position in the mapping where the initialized bit for a tick lives + /// @param tick The tick for which to compute the position + /// @return wordPos The key in the mapping containing the word in which the bit is stored + /// @return bitPos The bit position in the word where the flag is stored + function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { + wordPos = int16(tick >> 8); + bitPos = uint8(uint24(tick % 256)); + } + + /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either + /// to the left (less than or equal to) or right (greater than) of the given tick + /// @param poolId The pool id + /// @param tickSpacing the tick spacing of the pool + /// @param tick The starting tick + /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) + /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick + /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks + function nextInitializedTickWithinOneWord( + IPoolManager poolManager, + PoolId poolId, + int24 tickSpacing, + int24 tick, + bool lte + ) internal view returns (int24 next, bool initialized) { + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); + uint256 masked = poolManager.getTickBitmap(poolId, wordPos) & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(compressed + 1); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = poolManager.getTickBitmap(poolId, wordPos) & mask; + + // if there are no initialized ticks to the left of the current tick, return leftmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + 1 + int24(uint24(type(uint8).max) - bitPos)) * tickSpacing; + } + } +} diff --git a/src/libraries/QuoterMath.sol b/src/libraries/QuoterMath.sol new file mode 100644 index 00000000..db67d157 --- /dev/null +++ b/src/libraries/QuoterMath.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.26; + +import {IQuoter} from "../interfaces/IQuoter.sol"; +import {SwapMath} from "@uniswap/v4-core/src/libraries/SwapMath.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {SqrtPriceMath} from "@uniswap/v4-core/src/libraries/SqrtPriceMath.sol"; +import {LiquidityMath} from "@uniswap/v4-core/src/libraries/LiquidityMath.sol"; +import {PoolTickBitmap} from "./PoolTickBitmap.sol"; +import {Slot0, Slot0Library} from "@uniswap/v4-core/src/types/Slot0.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +library QuoterMath { + using SafeCast for uint256; + using SafeCast for int256; + + using Slot0Library for Slot0; + using StateLibrary for IPoolManager; + using PoolIdLibrary for PoolKey; + + struct Slot0Struct { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // tick spacing + int24 tickSpacing; + } + + // used for packing under the stack limit + struct QuoteParams { + bool zeroForOne; + bool exactInput; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + function fillSlot0(IPoolManager poolManager, PoolKey calldata poolKey) + private + view + returns (Slot0Struct memory slot0) + { + (slot0.sqrtPriceX96, slot0.tick,,) = poolManager.getSlot0(poolKey.toId()); + slot0.tickSpacing = poolKey.tickSpacing; + return slot0; + } + + struct SwapCache { + // the protocol fee for the input token + uint8 feeProtocol; + // liquidity at the beginning of the swap + uint128 liquidityStart; + // the timestamp of the current block + uint32 blockTimestamp; + // the current value of the tick accumulator, computed only if we cross an initialized tick + int56 tickCumulative; + // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick + uint160 secondsPerLiquidityCumulativeX128; + // whether we've computed and cached the above two accumulators + bool computedLatestObservation; + } + + // the top level state of the swap, the results of which are recorded in storage at the end + struct SwapState { + // the amount remaining to be swapped in/out of the input/output asset + int256 amountSpecifiedRemaining; + // the amount already swapped out/in of the output/input asset + int256 amountCalculated; + // current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // the global fee growth of the input token + uint256 feeGrowthGlobalX128; + // amount of input token paid as protocol fee + uint128 protocolFee; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + } + + /// @notice Utility function called by the quote functions to + /// calculate the amounts in/out for a hookless v4 swap + /// @param poolManager the Uniswap v4 pool manager + /// @param poolKey The poolKey identifying the pool traded against + /// @param swapParams The parameters used for the swap + /// @return amount0 the amount of token0 sent in or out of the pool + /// @return amount1 the amount of token1 sent in or out of the pool + /// @return sqrtPriceAfterX96 the price of the pool after the swap + /// @return initializedTicksCrossed the number of initialized ticks LOADED IN + function quote(IPoolManager poolManager, PoolKey calldata poolKey, IPoolManager.SwapParams calldata swapParams) + internal + view + returns (int256 amount0, int256 amount1, uint160 sqrtPriceAfterX96, uint32 initializedTicksCrossed) + { + QuoteParams memory quoteParams = QuoteParams( + swapParams.zeroForOne, swapParams.amountSpecified < 0, poolKey.fee, swapParams.sqrtPriceLimitX96 + ); + initializedTicksCrossed = 1; + + Slot0Struct memory slot0 = fillSlot0(poolManager, poolKey); + + SwapState memory state = SwapState({ + amountSpecifiedRemaining: -swapParams.amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0.sqrtPriceX96, + tick: slot0.tick, + feeGrowthGlobalX128: 0, + protocolFee: 0, + liquidity: poolManager.getLiquidity(poolKey.toId()) + }); + + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != quoteParams.sqrtPriceLimitX96) { + StepComputations memory step; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + (step.tickNext, step.initialized) = PoolTickBitmap.nextInitializedTickWithinOneWord( + poolManager, poolKey.toId(), slot0.tickSpacing, state.tick, quoteParams.zeroForOne + ); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + 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( + state.sqrtPriceX96, + ( + quoteParams.zeroForOne + ? step.sqrtPriceNextX96 < quoteParams.sqrtPriceLimitX96 + : step.sqrtPriceNextX96 > quoteParams.sqrtPriceLimitX96 + ) ? quoteParams.sqrtPriceLimitX96 : step.sqrtPriceNextX96, + state.liquidity, + -state.amountSpecifiedRemaining, + quoteParams.fee + ); + + if (quoteParams.exactInput) { + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); + state.amountCalculated = state.amountCalculated + step.amountOut.toInt256(); + } else { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + state.amountCalculated = state.amountCalculated - (step.amountIn + step.feeAmount).toInt256(); + } + + // shift tick if we reached the next price + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + (, int128 liquidityNet,,) = poolManager.getTickInfo(poolKey.toId(), step.tickNext); + + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (quoteParams.zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); + + initializedTicksCrossed++; + } + + state.tick = quoteParams.zeroForOne ? step.tickNext - 1 : step.tickNext; + } 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.getTickAtSqrtPrice(state.sqrtPriceX96); + } + + (amount0, amount1) = quoteParams.zeroForOne == quoteParams.exactInput + ? (state.amountSpecifiedRemaining + swapParams.amountSpecified, state.amountCalculated) + : (state.amountCalculated, state.amountSpecifiedRemaining + swapParams.amountSpecified); + + sqrtPriceAfterX96 = state.sqrtPriceX96; + } + } +} diff --git a/test/ViewQuoter.t.sol b/test/ViewQuoter.t.sol new file mode 100644 index 00000000..fdd14b6c --- /dev/null +++ b/test/ViewQuoter.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import {Vm} from "forge-std/Vm.sol"; +import {Test} from "forge-std/Test.sol"; +import {ViewQuoter} from "src/lens/ViewQuoter.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IHooks} from "lib/v4-core/src/interfaces/IHooks.sol"; +import {BalanceDelta} from "lib/v4-core/src/types/BalanceDelta.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; + +contract ViewViewQuoterTest is Test, Deployers { + using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; + + Vm internal constant _vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + uint24 internal constant TICK_SPACING = 2; + + ViewQuoter public quoter; + + PoolId id; + + function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + + (key, id) = initPoolAndAddLiquidity( + currency0, currency1, IHooks(address(0)), TICK_SPACING * 50, SQRT_PRICE_1_1, ZERO_BYTES + ); + + quoter = new ViewQuoter(manager); + } + + function testQuote() public { + // exact input zero for one + (,,, uint32 initializedTicksCrossed) = _quote(true, -0.001 ether); + assertEq(initializedTicksCrossed, 1); + + // exact output zero for one + (,,, initializedTicksCrossed) = _quote(true, 0.001 ether); + assertEq(initializedTicksCrossed, 1); + + // exact input one for zero + (,,, initializedTicksCrossed) = _quote(false, -0.001 ether); + assertEq(initializedTicksCrossed, 1); + + // exact output one for zero + (,,, initializedTicksCrossed) = _quote(false, 0.001 ether); + assertEq(initializedTicksCrossed, 1); + } + + // these swaps are more than the pool has liquidity for + function testLargeQuote() public { + (,,, uint32 initializedTicksCrossed) = _quote(true, -1 ether); + assertEq(initializedTicksCrossed, 2); + (,,, initializedTicksCrossed) = _quote(false, -1 ether); + assertEq(initializedTicksCrossed, 3); + // the pool has no more liquidity for this quote + (int256 amount0, int256 amount1, uint160 sqrtPriceAfterX96, uint32 initializedTicksCrossed2) = + quoter.quoteSingle(key, IPoolManager.SwapParams(false, -1 ether, MAX_PRICE_LIMIT)); + assertEq(amount0, 0); + assertEq(amount1, 0); + assertEq(sqrtPriceAfterX96, 0); // undefined behavior + assertEq(initializedTicksCrossed2, 1); + } + + // add more liquidity and swap into it + function testAddLiquidityAndSwap() public { + IPoolManager.ModifyLiquidityParams memory params = + IPoolManager.ModifyLiquidityParams({tickLower: 100, tickUpper: 200, liquidityDelta: 2e18, salt: 0}); + modifyLiquidityRouter.modifyLiquidity(key, params, ZERO_BYTES); + (,,, uint32 initializedTicksCrossed) = _quote(false, -1 ether); + assertEq(initializedTicksCrossed, 4); + } + + function testAddLiquidityAndSwap_fuzz( + int24 tickLower, + int24 tickUpper, + uint64 liquidityDelta, + bool zeroForOne, + int64 amountSpecified + ) public { + _vm.assume(liquidityDelta != 0); + _vm.assume(amountSpecified != 0); + _vm.assume(tickLower >= TickMath.MIN_TICK); + _vm.assume(tickUpper <= TickMath.MAX_TICK); + _vm.assume(int64(tickUpper) - int64(tickLower) >= int64(int24(TICK_SPACING))); + + tickLower = _align(tickLower); + tickUpper = _align(tickUpper); + IPoolManager.ModifyLiquidityParams memory params = IPoolManager.ModifyLiquidityParams({ + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: int128(uint128(liquidityDelta)), + salt: 0 + }); + modifyLiquidityRouter.modifyLiquidity(key, params, ZERO_BYTES); + _quote(zeroForOne, amountSpecified); + } + + function testAddLiquidityAndSwapTwice_fuzz( + int24 tickLower, + int24 tickUpper, + uint64 liquidityDelta, + bool zeroForOne, + int64 amountSpecified, + int24 tickLower2, + int24 tickUpper2, + bool zeroForOne2, + int64 amountSpecified2 + ) public { + testAddLiquidityAndSwap_fuzz(tickLower, tickUpper, liquidityDelta, zeroForOne, amountSpecified); + // avoid errors with too little liquidity + uint64 liquidityDelta2 = 1e18; + testAddLiquidityAndSwap_fuzz(tickLower2, tickUpper2, liquidityDelta2, zeroForOne2, amountSpecified2); + } + + /// @notice Aligns a tick to the nearest usable tick + function _align(int24 tick) internal pure returns (int24 alignedTick) { + if (tick < 0) { + tick -= int24(TICK_SPACING); + } + alignedTick = tick / int24(TICK_SPACING) * int24(TICK_SPACING); + } + + /// @notice Quotes a swap and executes it, asserting that the outputs match + function _quote(bool zeroForOne, int256 amountSpecified) + internal + returns (int256 amount0, int256 amount1, uint160 sqrtPriceAfterX96, uint32 initializedTicksCrossed) + { + uint160 sqrtPriceLimitX96 = zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT; + (amount0, amount1, sqrtPriceAfterX96, initializedTicksCrossed) = + quoter.quoteSingle(key, IPoolManager.SwapParams(zeroForOne, amountSpecified, sqrtPriceLimitX96)); + if (amount0 == 0 && amount1 == 0) { + // pool has no liquidity for this quote + vm.expectRevert(); // PriceLimitAlreadyExceeded + swap(key, zeroForOne, amountSpecified, ZERO_BYTES); + } else { + BalanceDelta swapDelta = swap(key, zeroForOne, amountSpecified, ZERO_BYTES); + (uint160 realSqrtPriceX96,,,) = manager.getSlot0(id); + assertEq(swapDelta.amount0(), amount0); + assertEq(swapDelta.amount1(), amount1); + assertEq(sqrtPriceAfterX96, realSqrtPriceX96); + } + } +} From 05ad967d866b6ad0fe0c7088fa8eaaf4ef8b4723 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:00:20 -0400 Subject: [PATCH 45/52] uninheret from middleware-remove --- .../BaseActionsRouter_mock10commands.snap | 2 +- .../MIDDLEWARE_REMOVE-deltas-protected.snap | 2 +- .../MIDDLEWARE_REMOVE-deltas-vanilla.snap | 2 +- .../MIDDLEWARE_REMOVE-fee-protected.snap | 2 +- .../MIDDLEWARE_REMOVE-fee-vanilla.snap | 2 +- .../MIDDLEWARE_REMOVE-override.snap | 2 +- .../MIDDLEWARE_REMOVE-protected.snap | 2 +- .../MIDDLEWARE_REMOVE-vanilla.snap | 2 +- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- .../PositionManager_burn_nonEmpty.snap | 2 +- .../PositionManager_burn_nonEmpty_native.snap | 2 +- .forge-snapshots/PositionManager_collect.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_decreaseLiquidity.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- ...sitionManager_increaseLiquidity_erc20.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .forge-snapshots/PositionManager_mint.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- .../PositionManager_mint_nativeWithSweep.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- ...tateView_extsload_getFeeGrowthGlobals.snap | 2 +- ...StateView_extsload_getFeeGrowthInside.snap | 2 +- .../StateView_extsload_getLiquidity.snap | 2 +- .../StateView_extsload_getPositionInfo.snap | 2 +- ...ateView_extsload_getPositionLiquidity.snap | 2 +- .../StateView_extsload_getSlot0.snap | 2 +- .../StateView_extsload_getTickBitmap.snap | 2 +- ...View_extsload_getTickFeeGrowthOutside.snap | 2 +- .../StateView_extsload_getTickInfo.snap | 2 +- .../StateView_extsload_getTickLiquidity.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../middleware/MiddlewareProtectFactory.sol | 14 -- src/middleware/BaseMiddleware.sol | 2 + .../middleware/MiddlewareProtect.sol | 65 +++----- src/middleware/MiddlewareProtectFactory.sol | 38 +++++ test/MiddlewareProtectFactory.t.sol | 145 ++++++++++-------- test/middleware/FrontrunAdd.sol | 12 +- test/middleware/HooksFrontrun.sol | 15 +- test/middleware/HooksOutOfGas.sol | 5 +- test/middleware/HooksReturnDeltas.sol | 3 +- test/middleware/HooksRevert.sol | 58 ------- 55 files changed, 201 insertions(+), 246 deletions(-) delete mode 100644 contracts/middleware/MiddlewareProtectFactory.sol rename {contracts => src}/middleware/MiddlewareProtect.sol (69%) create mode 100644 src/middleware/MiddlewareProtectFactory.sol diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index 34a072bb..e18320ab 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -61756 \ No newline at end of file +61711 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-protected.snap b/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-protected.snap index 2557522c..3fc265ea 100644 --- a/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-protected.snap @@ -1 +1 @@ -138303 \ No newline at end of file +138348 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-vanilla.snap b/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-vanilla.snap index 9f50530b..61353a6f 100644 --- a/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-vanilla.snap +++ b/.forge-snapshots/MIDDLEWARE_REMOVE-deltas-vanilla.snap @@ -1 +1 @@ -124851 \ No newline at end of file +124896 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_REMOVE-fee-protected.snap b/.forge-snapshots/MIDDLEWARE_REMOVE-fee-protected.snap index 0f4ae3b9..6754592f 100644 --- a/.forge-snapshots/MIDDLEWARE_REMOVE-fee-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_REMOVE-fee-protected.snap @@ -1 +1 @@ -153543 \ No newline at end of file +153544 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_REMOVE-fee-vanilla.snap b/.forge-snapshots/MIDDLEWARE_REMOVE-fee-vanilla.snap index 015afa77..c8e115c3 100644 --- a/.forge-snapshots/MIDDLEWARE_REMOVE-fee-vanilla.snap +++ b/.forge-snapshots/MIDDLEWARE_REMOVE-fee-vanilla.snap @@ -1 +1 @@ -140103 \ No newline at end of file +140104 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_REMOVE-override.snap b/.forge-snapshots/MIDDLEWARE_REMOVE-override.snap index 44729915..2d68a473 100644 --- a/.forge-snapshots/MIDDLEWARE_REMOVE-override.snap +++ b/.forge-snapshots/MIDDLEWARE_REMOVE-override.snap @@ -1 +1 @@ -133820 \ No newline at end of file +133865 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_REMOVE-protected.snap b/.forge-snapshots/MIDDLEWARE_REMOVE-protected.snap index 79afd43e..f25c260d 100644 --- a/.forge-snapshots/MIDDLEWARE_REMOVE-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_REMOVE-protected.snap @@ -1 +1 @@ -135757 \ No newline at end of file +135802 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_REMOVE-vanilla.snap b/.forge-snapshots/MIDDLEWARE_REMOVE-vanilla.snap index 9520d0bd..218811f7 100644 --- a/.forge-snapshots/MIDDLEWARE_REMOVE-vanilla.snap +++ b/.forge-snapshots/MIDDLEWARE_REMOVE-vanilla.snap @@ -1 +1 @@ -124822 \ No newline at end of file +124867 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 4c184642..899ad914 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47059 \ No newline at end of file +47005 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 9230f513..f3512523 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46876 \ No newline at end of file +46823 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty.snap b/.forge-snapshots/PositionManager_burn_nonEmpty.snap index c15f4f2d..c2cc9a9b 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty.snap @@ -1 +1 @@ -129852 \ No newline at end of file +129905 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap index d2ae1c7d..193dbbfb 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native.snap @@ -1 +1 @@ -122773 \ No newline at end of file +122827 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect.snap b/.forge-snapshots/PositionManager_collect.snap index 0cf00f2a..e9389c62 100644 --- a/.forge-snapshots/PositionManager_collect.snap +++ b/.forge-snapshots/PositionManager_collect.snap @@ -1 +1 @@ -149984 \ No newline at end of file +150073 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 1b627db8..c32584dd 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -141136 \ No newline at end of file +141225 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 0cf00f2a..e9389c62 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -149984 \ No newline at end of file +150073 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity.snap b/.forge-snapshots/PositionManager_decreaseLiquidity.snap index c30fc9de..cbf7bc78 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity.snap @@ -1 +1 @@ -115527 \ No newline at end of file +115616 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 3c0e3daf..3daa9125 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -108384 \ No newline at end of file +108455 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 58fe1844..2de98bd3 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -133885 \ No newline at end of file +133939 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 23aac82f..7597a259 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -126624 \ No newline at end of file +126678 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 48ed6687..a43c4a7a 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -128243 \ No newline at end of file +128332 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap index 26723396..e8f290d9 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap @@ -1 +1 @@ -152100 \ No newline at end of file +152011 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 35295eaf..d39cabe9 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -133900 \ No newline at end of file +133811 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 04c138b3..2087e3cf 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -130065 \ No newline at end of file +130020 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index e55d6257..49435c01 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -170759 \ No newline at end of file +170848 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 375f518b..fc3e35cc 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -140581 \ No newline at end of file +140624 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint.snap index 7e0f6688..249112de 100644 --- a/.forge-snapshots/PositionManager_mint.snap +++ b/.forge-snapshots/PositionManager_mint.snap @@ -1 +1 @@ -372012 \ No newline at end of file +371923 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index e708094b..ad2b296c 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -336712 \ No newline at end of file +336623 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap index 15689f38..d187f1de 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap @@ -1 +1 @@ -345244 \ No newline at end of file +345155 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 90fd8fec..17d39b6a 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -314694 \ No newline at end of file +314605 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 9b6845af..68b59d84 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -315336 \ No newline at end of file +315247 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index 597983b5..cee35913 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -240918 \ No newline at end of file +240829 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index a6459af0..e1d7f058 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370018 \ No newline at end of file +369929 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index af4a8aa0..a38a75cf 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -320712 \ No newline at end of file +320623 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 28856ebd..07cf6833 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -416388 \ No newline at end of file +416277 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap b/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap index 0cd7e117..d659d28b 100644 --- a/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap +++ b/.forge-snapshots/StateView_extsload_getFeeGrowthGlobals.snap @@ -1 +1 @@ -2398 \ No newline at end of file +2376 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap b/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap index d10ca668..37017731 100644 --- a/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap +++ b/.forge-snapshots/StateView_extsload_getFeeGrowthInside.snap @@ -1 +1 @@ -8543 \ No newline at end of file +8455 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getLiquidity.snap b/.forge-snapshots/StateView_extsload_getLiquidity.snap index 5303ac12..5ca63cd4 100644 --- a/.forge-snapshots/StateView_extsload_getLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getLiquidity.snap @@ -1 +1 @@ -1509 \ No newline at end of file +1487 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getPositionInfo.snap b/.forge-snapshots/StateView_extsload_getPositionInfo.snap index 9d57e9ad..6f995456 100644 --- a/.forge-snapshots/StateView_extsload_getPositionInfo.snap +++ b/.forge-snapshots/StateView_extsload_getPositionInfo.snap @@ -1 +1 @@ -2927 \ No newline at end of file +2905 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap b/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap index 280f1a09..2dd44763 100644 --- a/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getPositionLiquidity.snap @@ -1 +1 @@ -1746 \ No newline at end of file +1724 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getSlot0.snap b/.forge-snapshots/StateView_extsload_getSlot0.snap index 38ca8416..a35ae730 100644 --- a/.forge-snapshots/StateView_extsload_getSlot0.snap +++ b/.forge-snapshots/StateView_extsload_getSlot0.snap @@ -1 +1 @@ -1606 \ No newline at end of file +1584 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickBitmap.snap b/.forge-snapshots/StateView_extsload_getTickBitmap.snap index dfd4d9fc..7f29ad28 100644 --- a/.forge-snapshots/StateView_extsload_getTickBitmap.snap +++ b/.forge-snapshots/StateView_extsload_getTickBitmap.snap @@ -1 +1 @@ -1704 \ No newline at end of file +1682 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap b/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap index f26febc1..00c1535a 100644 --- a/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap +++ b/.forge-snapshots/StateView_extsload_getTickFeeGrowthOutside.snap @@ -1 +1 @@ -2756 \ No newline at end of file +2734 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickInfo.snap b/.forge-snapshots/StateView_extsload_getTickInfo.snap index 90a81289..a6fea8d1 100644 --- a/.forge-snapshots/StateView_extsload_getTickInfo.snap +++ b/.forge-snapshots/StateView_extsload_getTickInfo.snap @@ -1 +1 @@ -3090 \ No newline at end of file +3068 \ No newline at end of file diff --git a/.forge-snapshots/StateView_extsload_getTickLiquidity.snap b/.forge-snapshots/StateView_extsload_getTickLiquidity.snap index ff353461..fdef1d02 100644 --- a/.forge-snapshots/StateView_extsload_getTickLiquidity.snap +++ b/.forge-snapshots/StateView_extsload_getTickLiquidity.snap @@ -1 +1 @@ -1901 \ No newline at end of file +1879 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index c6e2c74d..0c41010f 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -6942 \ No newline at end of file +6950 \ No newline at end of file diff --git a/contracts/middleware/MiddlewareProtectFactory.sol b/contracts/middleware/MiddlewareProtectFactory.sol deleted file mode 100644 index 511b1260..00000000 --- a/contracts/middleware/MiddlewareProtectFactory.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {BaseMiddlewareFactory} from "./BaseMiddlewareFactory.sol"; -import {MiddlewareProtect} from "./MiddlewareProtect.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; - -contract MiddlewareProtectFactory is BaseMiddlewareFactory { - constructor(IPoolManager _manager) BaseMiddlewareFactory(_manager) {} - - function _deployMiddleware(address implementation, bytes32 salt) internal override returns (address middleware) { - middleware = address(new MiddlewareProtect{salt: salt}(manager, implementation)); - } -} diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 0bc8f797..4dc69744 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -24,6 +24,8 @@ abstract contract BaseMiddleware is Proxy { implementation = _impl; } + receive() external payable {} + function _implementation() internal view override returns (address) { return implementation; } diff --git a/contracts/middleware/MiddlewareProtect.sol b/src/middleware/MiddlewareProtect.sol similarity index 69% rename from contracts/middleware/MiddlewareProtect.sol rename to src/middleware/MiddlewareProtect.sol index f486cbed..f2ef5d81 100644 --- a/contracts/middleware/MiddlewareProtect.sol +++ b/src/middleware/MiddlewareProtect.sol @@ -5,7 +5,7 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; -import {BaseHook} from "../BaseHook.sol"; +import {BaseHook} from "../base/hooks/BaseHook.sol"; import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; @@ -14,14 +14,14 @@ import {NonZeroDeltaCount} from "@uniswap/v4-core/src/libraries/NonZeroDeltaCoun import {IExttload} from "@uniswap/v4-core/src/interfaces/IExttload.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {MiddlewareRemove} from "./MiddlewareRemove.sol"; +import {BaseMiddleware} from "./BaseMiddleware.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; -import {console} from "./../../lib/forge-gas-snapshot/lib/forge-std/src/console.sol"; +import {console} from "forge-std/console.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -contract MiddlewareProtect is MiddlewareRemove { +contract MiddlewareProtect is BaseMiddleware { using CustomRevert for bytes4; using Hooks for IHooks; using StateLibrary for IPoolManager; @@ -30,28 +30,24 @@ contract MiddlewareProtect is MiddlewareRemove { using LPFeeLibrary for uint24; using BalanceDeltaLibrary for BalanceDelta; - error HookModifiedOutput(); - error ForbiddenDynamicFee(); + /// @notice Thrown when hook permissions are forbidden + /// @param hooks The address of this contract + error HookPermissionForbidden(address hooks); + + /// @notice Thrown when both flags match, but deployer must use AFTER_SWAP_FLAG + /// @dev redeploy with AFTER_SWAP_FLAG error MustHaveAfterSwapFlagOnMiddleware(); - // todo: use tstore - BalanceDelta private quote; + /// @notice Thrown when the implementation modified the output of a swap + error HookModifiedOutput(); - uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; - uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1; + bytes internal constant ZERO_BYTES = bytes(""); - constructor(IPoolManager _manager, address _impl) MiddlewareRemove(_manager, _impl) {} + // todo: use tstore + BalanceDelta private quote; - function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, bytes calldata hookData) - external - returns (bytes4) - { - if (key.fee.isDynamicFee()) revert ForbiddenDynamicFee(); - (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); - if (!success) { - _handleRevert(returnData); - } - return abi.decode(returnData, (bytes4)); + constructor(IPoolManager _manager, address _impl) BaseMiddleware(_manager, _impl) { + _ensureValidFlags(); } function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) @@ -62,7 +58,6 @@ contract MiddlewareProtect is MiddlewareRemove { catch (bytes memory reason) { quote = abi.decode(reason, (BalanceDelta)); } - uint160 outputBefore = 0; (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { _handleRevert(returnData); @@ -74,7 +69,7 @@ contract MiddlewareProtect is MiddlewareRemove { external returns (bytes memory) { - BalanceDelta swapDelta = manager.swap(key, params, ZERO_BYTES); + BalanceDelta swapDelta = poolManager.swap(key, params, ZERO_BYTES); bytes memory result = abi.encode(swapDelta); assembly { revert(add(0x20, result), mload(result)) @@ -99,42 +94,26 @@ contract MiddlewareProtect is MiddlewareRemove { return abi.decode(returnData, (bytes4, int128)); } - function beforeAddLiquidity( - address sender, - PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata params, - bytes calldata hookData - ) external returns (bytes4) { - (bool success, bytes memory returnData) = address(this).delegatecall{gas: GAS_LIMIT}( - abi.encodeWithSelector(this._callAndEnsurePrice.selector, msg.data) - ); - if (!success) { - _handleRevert(returnData); - } - return BaseHook.beforeAddLiquidity.selector; - } - function _handleRevert(bytes memory returnData) internal pure { assembly { revert(add(32, returnData), mload(returnData)) } } - function _ensureValidFlags(address _impl) internal view virtual override { + function _ensureValidFlags() internal view { IHooks This = IHooks(address(this)); if (This.hasPermission(Hooks.BEFORE_SWAP_FLAG)) { if ( uint160(address(this)) & Hooks.ALL_HOOK_MASK - != uint160(_impl) & Hooks.ALL_HOOK_MASK | Hooks.AFTER_SWAP_FLAG + != uint160(implementation) & Hooks.ALL_HOOK_MASK | Hooks.AFTER_SWAP_FLAG ) { - if (IHooks(_impl).hasPermission(Hooks.AFTER_SWAP_FLAG)) { + if (IHooks(implementation).hasPermission(Hooks.AFTER_SWAP_FLAG)) { revert FlagsMismatch(); } else { - // both flags match, but dev must enable AFTER_SWAP_FLAG revert MustHaveAfterSwapFlagOnMiddleware(); } } - } else if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(_impl) & Hooks.ALL_HOOK_MASK) { + } else if (uint160(address(this)) & Hooks.ALL_HOOK_MASK != uint160(implementation) & Hooks.ALL_HOOK_MASK) { revert FlagsMismatch(); } if ( diff --git a/src/middleware/MiddlewareProtectFactory.sol b/src/middleware/MiddlewareProtectFactory.sol new file mode 100644 index 00000000..33a3c3d6 --- /dev/null +++ b/src/middleware/MiddlewareProtectFactory.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {MiddlewareProtect} from "./MiddlewareProtect.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +contract MiddlewareProtectFactory { + event MiddlewareCreated(address implementation, address middleware); + + mapping(address => address) private _implementations; + + IPoolManager public immutable poolManager; + + constructor(IPoolManager _poolManager) { + poolManager = _poolManager; + } + + /** + * @notice Get the implementation address for a given middleware. + * @param middleware The address of the middleware. + * @return implementation The address of the implementation. + */ + function getImplementation(address middleware) external view returns (address implementation) { + return _implementations[middleware]; + } + + /** + * @notice Create a new middlewareRemove contract. + * @param implementation The address of the implementation or an existing hook. + * @param salt The salt for deploying to the right flags. + * @return middleware The address of the newly created middlewareRemove contract. + */ + function createMiddleware(address implementation, bytes32 salt) external returns (address middleware) { + middleware = address(new MiddlewareProtect{salt: salt}(poolManager, implementation)); + _implementations[middleware] = implementation; + emit MiddlewareCreated(implementation, middleware); + } +} diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index eb418827..6ae26484 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -4,8 +4,7 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {HooksFrontrun} from "./middleware/HooksFrontrun.sol"; -import {MiddlewareRemove} from "../contracts/middleware/MiddlewareRemove.sol"; -import {MiddlewareProtect} from "../contracts/middleware/MiddlewareProtect.sol"; +import {MiddlewareProtect} from "../src/middleware/MiddlewareProtect.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; @@ -14,18 +13,18 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {console} from "../../../lib/forge-std/src/console.sol"; +import {console} from "forge-std/console.sol"; import {HooksRevert} from "./middleware/HooksRevert.sol"; import {HooksOutOfGas} from "./middleware/HooksOutOfGas.sol"; -import {MiddlewareProtectFactory} from "./../contracts/middleware/MiddlewareProtectFactory.sol"; +import {MiddlewareProtectFactory} from "./../src/middleware/MiddlewareProtectFactory.sol"; import {HookMiner} from "./utils/HookMiner.sol"; import {HooksReturnDeltas} from "./middleware/HooksReturnDeltas.sol"; -import {Counter} from "./middleware/Counter.sol"; -import {SafeCallback} from "./../contracts/base/SafeCallback.sol"; +import {HooksCounter} from "./middleware/HooksCounter.sol"; +import {SafeCallback} from "./../src/base/SafeCallback.sol"; import {FrontrunAdd} from "./middleware/FrontrunAdd.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {BaseMiddleware} from "./../contracts/middleware/BaseMiddleware.sol"; +import {BaseMiddleware} from "./../src/middleware/BaseMiddleware.sol"; contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { HookEnabledSwapRouter router; @@ -33,7 +32,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { TestERC20 token1; MiddlewareProtectFactory factory; - Counter counter; + HooksCounter counter; address middleware; HooksFrontrun hooksFrontrun; @@ -52,8 +51,8 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { token1 = TestERC20(Currency.unwrap(currency1)); factory = new MiddlewareProtectFactory(manager); - counter = Counter(address(COUNTER_FLAGS)); - vm.etch(address(counter), address(new Counter(manager)).code); + counter = HooksCounter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new HooksCounter(manager)).code); token0.approve(address(router), type(uint256).max); token1.approve(address(router), type(uint256).max); @@ -84,7 +83,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { abi.encode(address(manager), address(hooksReturnDeltas)) ); address implementation = address(hooksReturnDeltas); - vm.expectRevert(abi.encodePacked(bytes16(MiddlewareRemove.HookPermissionForbidden.selector), hookAddress)); + vm.expectRevert(abi.encodePacked(bytes16(MiddlewareProtect.HookPermissionForbidden.selector), hookAddress)); factory.createMiddleware(implementation, salt); } @@ -122,7 +121,13 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { (key,) = initPoolAndAddLiquidity( currency0, currency1, IHooks(address(middlewareProtect)), 100, SQRT_PRICE_1_1, ZERO_BYTES ); - vm.expectRevert(MiddlewareProtect.HookModifiedOutput.selector); + vm.expectRevert( + abi.encodeWithSelector( + Hooks.Wrap__FailedHookCall.selector, + address(middlewareProtect), + abi.encodePacked(MiddlewareProtect.HookModifiedOutput.selector) + ) + ); swap(key, true, 0.001 ether, ZERO_BYTES); } @@ -141,7 +146,13 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { ); middleware = factory.createMiddleware(address(hooksRevert), salt); (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); - vm.expectRevert(HooksRevert.AlwaysRevert.selector); + vm.expectRevert( + abi.encodeWithSelector( + Hooks.Wrap__FailedHookCall.selector, + address(middleware), + abi.encodePacked(HooksRevert.AlwaysRevert.selector) + ) + ); swap(key, true, 1, ZERO_BYTES); HooksOutOfGas hooksOutOfGas = HooksOutOfGas(address(flags)); @@ -154,57 +165,57 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { ); middleware = factory.createMiddleware(address(hooksOutOfGas), salt); (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); - vm.expectRevert(Hooks.FailedHookCall.selector); + vm.expectRevert(abi.encodeWithSelector(Hooks.Wrap__FailedHookCall.selector, address(middleware), ZERO_BYTES)); swap(key, true, 1, ZERO_BYTES); } - function testFrontrunAdd() public { - uint160 flags = uint160(Hooks.BEFORE_ADD_LIQUIDITY_FLAG); - FrontrunAdd frontrunAdd = FrontrunAdd(address(flags)); - vm.etch(address(frontrunAdd), address(new FrontrunAdd(manager)).code); - (, bytes32 salt) = HookMiner.find( - address(factory), - flags, - type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(frontrunAdd)) - ); - middleware = factory.createMiddleware(address(frontrunAdd), salt); - currency0.transfer(address(frontrunAdd), 1 ether); - currency1.transfer(address(frontrunAdd), 1 ether); - currency0.transfer(address(middleware), 1 ether); - currency1.transfer(address(middleware), 1 ether); - - (PoolKey memory key,) = - initPoolAndAddLiquidity(currency0, currency1, IHooks(frontrunAdd), 3000, SQRT_PRICE_1_1, ZERO_BYTES); - uint256 initialBalance0 = token0.balanceOf(address(this)); - uint256 initialBalance1 = token1.balanceOf(address(this)); - modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); - uint256 inFrontrun0 = initialBalance0 - token0.balanceOf(address(this)); - uint256 inFrontrun1 = initialBalance1 - token1.balanceOf(address(this)); - - IHooks noHooks = IHooks(address(0)); - (key,) = initPoolAndAddLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); - initialBalance0 = token0.balanceOf(address(this)); - initialBalance1 = token1.balanceOf(address(this)); - modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); - uint256 inNormal0 = initialBalance0 - token0.balanceOf(address(this)); - uint256 inNormal1 = initialBalance1 - token1.balanceOf(address(this)); - - // was frontrun - assertTrue(inFrontrun0 > inNormal0); - assertTrue(inFrontrun1 < inNormal1); - - initialBalance0 = token0.balanceOf(address(this)); - initialBalance1 = token1.balanceOf(address(this)); - (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); - vm.expectRevert(MiddlewareRemove.HookModifiedPrice.selector); - modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); - } - - function testRevertOnDynamicFee() public { - vm.expectRevert(MiddlewareProtect.ForbiddenDynamicFee.selector); - initPool(currency0, currency1, IHooks(middleware), LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1, ZERO_BYTES); - } + // function testFrontrunAdd() public { + // uint160 flags = uint160(Hooks.BEFORE_ADD_LIQUIDITY_FLAG); + // FrontrunAdd frontrunAdd = FrontrunAdd(address(flags)); + // vm.etch(address(frontrunAdd), address(new FrontrunAdd(manager)).code); + // (, bytes32 salt) = HookMiner.find( + // address(factory), + // flags, + // type(MiddlewareProtect).creationCode, + // abi.encode(address(manager), address(frontrunAdd)) + // ); + // middleware = factory.createMiddleware(address(frontrunAdd), salt); + // currency0.transfer(address(frontrunAdd), 1 ether); + // currency1.transfer(address(frontrunAdd), 1 ether); + // currency0.transfer(address(middleware), 1 ether); + // currency1.transfer(address(middleware), 1 ether); + + // (PoolKey memory key,) = + // initPoolAndAddLiquidity(currency0, currency1, IHooks(frontrunAdd), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + // uint256 initialBalance0 = token0.balanceOf(address(this)); + // uint256 initialBalance1 = token1.balanceOf(address(this)); + // modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + // uint256 inFrontrun0 = initialBalance0 - token0.balanceOf(address(this)); + // uint256 inFrontrun1 = initialBalance1 - token1.balanceOf(address(this)); + + // IHooks noHooks = IHooks(address(0)); + // (key,) = initPoolAndAddLiquidity(currency0, currency1, noHooks, 3000, SQRT_PRICE_1_1, ZERO_BYTES); + // initialBalance0 = token0.balanceOf(address(this)); + // initialBalance1 = token1.balanceOf(address(this)); + // modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + // uint256 inNormal0 = initialBalance0 - token0.balanceOf(address(this)); + // uint256 inNormal1 = initialBalance1 - token1.balanceOf(address(this)); + + // // was frontrun + // assertTrue(inFrontrun0 > inNormal0); + // assertTrue(inFrontrun1 < inNormal1); + + // initialBalance0 = token0.balanceOf(address(this)); + // initialBalance1 = token1.balanceOf(address(this)); + // (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + // vm.expectRevert(MiddlewareProtect.HookModifiedPrice.selector); + // modifyLiquidityRouter.modifyLiquidity(key, LIQUIDITY_PARAMS, ZERO_BYTES); + // } + + // function testRevertOnDynamicFee() public { + // vm.expectRevert(MiddlewareProtect.ForbiddenDynamicFee.selector); + // initPool(currency0, currency1, IHooks(middleware), LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1, ZERO_BYTES); + // } function testVariousSwaps() public { (PoolKey memory key, PoolId id) = @@ -309,8 +320,8 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { } function testRevertOnIncorrectFlags() public { - Counter counter2 = Counter(address(COUNTER_FLAGS)); - vm.etch(address(counter), address(new Counter(manager)).code); + HooksCounter counter2 = HooksCounter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new HooksCounter(manager)).code); uint160 incorrectFlags = uint160(Hooks.BEFORE_INITIALIZE_FLAG); (address hookAddress, bytes32 salt) = HookMiner.find( @@ -325,15 +336,15 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { } function testRevertOnIncorrectFlagsMined() public { - Counter counter2 = Counter(address(COUNTER_FLAGS)); - vm.etch(address(counter), address(new Counter(manager)).code); + HooksCounter counter2 = HooksCounter(address(COUNTER_FLAGS)); + vm.etch(address(counter), address(new HooksCounter(manager)).code); address implementation = address(counter2); vm.expectRevert(); factory.createMiddleware(implementation, bytes32("who needs to mine a salt?")); } function testRevertOnIncorrectCaller() public { - vm.expectRevert(SafeCallback.NotManager.selector); + vm.expectRevert(SafeCallback.NotPoolManager.selector); counter.afterDonate(address(this), key, 0, 0, ZERO_BYTES); } @@ -341,7 +352,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { (PoolKey memory key, PoolId id) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); - Counter counterProxy = Counter(middleware); + HooksCounter counterProxy = HooksCounter(middleware); assertEq(counterProxy.beforeInitializeCount(id), 1); assertEq(counterProxy.afterInitializeCount(id), 1); assertEq(counterProxy.beforeSwapCount(id), 0); diff --git a/test/middleware/FrontrunAdd.sol b/test/middleware/FrontrunAdd.sol index 20fcdf34..01c4c5dc 100644 --- a/test/middleware/FrontrunAdd.sol +++ b/test/middleware/FrontrunAdd.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {BaseHook} from "./../../src/base/hooks/BaseHook.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {BaseHook} from "./../../contracts/BaseHook.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; @@ -52,10 +51,11 @@ contract FrontrunAdd is BaseHook { ) external override returns (bytes4) { if (hasLiquidity[key.toId()]) { BalanceDelta swapDelta = - manager.swap(key, IPoolManager.SwapParams(true, SWAP_AMOUNT, MIN_PRICE_LIMIT), ZERO_BYTES); - key.currency0.transfer(address(manager), uint128(-swapDelta.amount0())); - manager.settle(key.currency0); - manager.take(key.currency1, address(this), uint128(swapDelta.amount1())); + poolManager.swap(key, IPoolManager.SwapParams(true, SWAP_AMOUNT, MIN_PRICE_LIMIT), ZERO_BYTES); + key.currency0.transfer(address(poolManager), uint128(-swapDelta.amount0())); + poolManager.sync(key.currency0); + poolManager.settle(); + poolManager.take(key.currency1, address(this), uint128(swapDelta.amount1())); } else { hasLiquidity[key.toId()] = true; } diff --git a/test/middleware/HooksFrontrun.sol b/test/middleware/HooksFrontrun.sol index 238e1aad..9441881a 100644 --- a/test/middleware/HooksFrontrun.sol +++ b/test/middleware/HooksFrontrun.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {BaseHook} from "./../../src/base/hooks/BaseHook.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; @@ -9,7 +9,6 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {BaseHook} from "./../../contracts/BaseHook.sol"; contract HooksFrontrun is BaseHook { using SafeCast for uint256; @@ -48,21 +47,21 @@ contract HooksFrontrun is BaseHook { function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) external override - onlyByManager + onlyByPoolManager returns (bytes4, BeforeSwapDelta, uint24) { swapParams = params; - swapDelta = manager.swap(key, params, ZERO_BYTES); + swapDelta = poolManager.swap(key, params, ZERO_BYTES); return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) external override - onlyByManager + onlyByPoolManager returns (bytes4, int128) { - BalanceDelta afterDelta = manager.swap( + BalanceDelta afterDelta = poolManager.swap( key, IPoolManager.SwapParams( !swapParams.zeroForOne, @@ -75,13 +74,13 @@ contract HooksFrontrun is BaseHook { int256 profit = afterDelta.amount0() + swapDelta.amount0(); if (profit > 0) { // else hook reverts - manager.mint(address(this), key.currency0.toId(), uint256(profit)); + poolManager.mint(address(this), key.currency0.toId(), uint256(profit)); } } else { int256 profit = afterDelta.amount1() + swapDelta.amount1(); if (profit > 0) { // else hook reverts - manager.mint(address(this), key.currency1.toId(), uint256(profit)); + poolManager.mint(address(this), key.currency1.toId(), uint256(profit)); } } return (BaseHook.afterSwap.selector, 0); diff --git a/test/middleware/HooksOutOfGas.sol b/test/middleware/HooksOutOfGas.sol index bde1e9ec..2ee4021d 100644 --- a/test/middleware/HooksOutOfGas.sol +++ b/test/middleware/HooksOutOfGas.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {BaseHook} from "./../../src/base/hooks/BaseHook.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {BaseHook} from "./../../contracts/BaseHook.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; -import {console} from "./../../lib/forge-gas-snapshot/lib/forge-std/src/console.sol"; +import {console} from "forge-std/console.sol"; contract HooksOutOfGas is BaseHook { uint256 counter; diff --git a/test/middleware/HooksReturnDeltas.sol b/test/middleware/HooksReturnDeltas.sol index a4c2567e..68121a19 100644 --- a/test/middleware/HooksReturnDeltas.sol +++ b/test/middleware/HooksReturnDeltas.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {BaseHook} from "./../../contracts/BaseHook.sol"; +import {BaseHook} from "./../../src/base/hooks/BaseHook.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; -import {BaseHook} from "./../../contracts/BaseHook.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; contract HooksReturnDeltas is BaseHook { diff --git a/test/middleware/HooksRevert.sol b/test/middleware/HooksRevert.sol index 68ba6025..362b9aa6 100644 --- a/test/middleware/HooksRevert.sol +++ b/test/middleware/HooksRevert.sol @@ -6,64 +6,6 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; - -contract RemoveReverts is BaseHook { - error AlwaysRevert(); - - constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - - // middleware implementations do not need to be mined - function validateHookAddress(BaseHook _this) internal pure override {} - - function getHookPermissions() public pure override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: false, - afterInitialize: false, - beforeAddLiquidity: false, - afterAddLiquidity: false, - beforeRemoveLiquidity: true, - afterRemoveLiquidity: true, - beforeSwap: false, - afterSwap: false, - beforeDonate: false, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); - } - - function beforeRemoveLiquidity( - address, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - bytes calldata - ) external pure override returns (bytes4) { - revert AlwaysRevert(); - } - - function afterRemoveLiquidity( - address sender, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - BalanceDelta, - bytes calldata - ) external pure override returns (bytes4, BalanceDelta) { - require(sender == address(0), "nobody can remove"); - return (BaseHook.beforeRemoveLiquidity.selector, toBalanceDelta(0, 0)); - } -} - -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -import {BaseHook} from "./../../contracts/BaseHook.sol"; -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {BaseHook} from "./../../contracts/BaseHook.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; contract HooksRevert is BaseHook { From 54c12c8ab518d258416b1ba2fe4981f7f77372f1 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:39:34 -0400 Subject: [PATCH 46/52] add gas tests with revert quoter --- .../MIDDLEWARE_PROTECT-multi-protected.snap | 1 + .../MIDDLEWARE_PROTECT-multi-vanilla.snap | 1 + .../MIDDLEWARE_PROTECT-protected.snap | 1 + .../MIDDLEWARE_PROTECT-vanilla.snap | 1 + test/MiddlewareProtectFactory.t.sol | 29 ++++++++++ test/middleware/BlankSwapHooks.sol | 53 +++++++++++++++++++ 6 files changed, 86 insertions(+) create mode 100644 .forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap create mode 100644 .forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap create mode 100644 .forge-snapshots/MIDDLEWARE_PROTECT-protected.snap create mode 100644 .forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap create mode 100644 test/middleware/BlankSwapHooks.sol diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap new file mode 100644 index 00000000..79ff6983 --- /dev/null +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap @@ -0,0 +1 @@ +872991 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap new file mode 100644 index 00000000..98ddcf3c --- /dev/null +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap @@ -0,0 +1 @@ +475485 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap new file mode 100644 index 00000000..e70c7a11 --- /dev/null +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap @@ -0,0 +1 @@ +229771 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap new file mode 100644 index 00000000..e1b225a9 --- /dev/null +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap @@ -0,0 +1 @@ +147725 \ No newline at end of file diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index 6ae26484..cbe6a675 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -25,6 +25,7 @@ import {FrontrunAdd} from "./middleware/FrontrunAdd.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {BaseMiddleware} from "./../src/middleware/BaseMiddleware.sol"; +import {BlankSwapHooks} from "./middleware/BlankSwapHooks.sol"; contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { HookEnabledSwapRouter router; @@ -379,4 +380,32 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { assertEq(counterProxy.beforeRemoveLiquidityCount(id), 1); assertEq(counterProxy.afterRemoveLiquidityCount(id), 1); } + + function testMiddlewareRemoveGas() public { + uint160 flags = Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG; + BlankSwapHooks blankSwapHooks = BlankSwapHooks(address(flags)); + vm.etch(address(blankSwapHooks), address(new BlankSwapHooks(manager)).code); + (key,) = initPoolAndAddLiquidity( + currency0, currency1, IHooks(address(blankSwapHooks)), 3000, SQRT_PRICE_1_1, ZERO_BYTES + ); + swap(key, true, 0.0001 ether, ZERO_BYTES); + snapLastCall("MIDDLEWARE_PROTECT-vanilla"); + uint160 maxFeeBips = 0; + (, bytes32 salt) = HookMiner.find( + address(factory), + flags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(blankSwapHooks)) + ); + address hookAddress = factory.createMiddleware(address(blankSwapHooks), salt); + (PoolKey memory protectedKey,) = + initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddress), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + swap(protectedKey, true, 0.0001 ether, ZERO_BYTES); + snapLastCall("MIDDLEWARE_PROTECT-protected"); + + swap(key, true, 0.01 ether, ZERO_BYTES); + snapLastCall("MIDDLEWARE_PROTECT-multi-vanilla"); + swap(protectedKey, true, 0.01 ether, ZERO_BYTES); + snapLastCall("MIDDLEWARE_PROTECT-multi-protected"); + } } diff --git a/test/middleware/BlankSwapHooks.sol b/test/middleware/BlankSwapHooks.sol new file mode 100644 index 00000000..6fcd8fdb --- /dev/null +++ b/test/middleware/BlankSwapHooks.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {BaseHook} from "./../../src/base/hooks/BaseHook.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; + +contract BlankSwapHooks is BaseHook { + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + // for testing + function validateHookAddress(BaseHook _this) internal pure override {} + + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) + external + override + onlyByPoolManager + returns (bytes4, BeforeSwapDelta, uint24) + { + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + onlyByPoolManager + returns (bytes4, int128) + { + return (BaseHook.afterSwap.selector, 0); + } +} From 8139431b402c93e3d08acfd81c3be2de801ff372 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:00:48 -0400 Subject: [PATCH 47/52] forge test --isolate --- .forge-snapshots/BaseActionsRouter_mock10commands.snap | 2 +- .forge-snapshots/PositionManager_burn_empty.snap | 2 +- .forge-snapshots/PositionManager_burn_empty_native.snap | 2 +- .forge-snapshots/PositionManager_collect_native.snap | 2 +- .forge-snapshots/PositionManager_collect_sameRange.snap | 2 +- .forge-snapshots/PositionManager_decreaseLiquidity_native.snap | 2 +- .forge-snapshots/PositionManager_decrease_burnEmpty.snap | 2 +- .forge-snapshots/PositionManager_decrease_burnEmpty_native.snap | 2 +- .../PositionManager_decrease_sameRange_allLiquidity.snap | 2 +- .forge-snapshots/PositionManager_increaseLiquidity_native.snap | 2 +- ...PositionManager_increase_autocompoundExactUnclaimedFees.snap | 2 +- .../PositionManager_increase_autocompoundExcessFeesCredit.snap | 2 +- .../PositionManager_increase_autocompound_clearExcess.snap | 2 +- .forge-snapshots/PositionManager_mint_native.snap | 2 +- .forge-snapshots/PositionManager_mint_onSameTickLower.snap | 2 +- .forge-snapshots/PositionManager_mint_onSameTickUpper.snap | 2 +- .forge-snapshots/PositionManager_mint_sameRange.snap | 2 +- .../PositionManager_mint_settleWithBalance_sweep.snap | 2 +- .../PositionManager_mint_warmedPool_differentRange.snap | 2 +- .forge-snapshots/PositionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index 8a30e665..2b46e583 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -61749 +61749 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index dd1b6e7b..492cae30 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -47167 +47167 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_empty_native.snap b/.forge-snapshots/PositionManager_burn_empty_native.snap index 5e00338e..6ad16fc4 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -46984 +46984 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 7febb2cd..1d9f0b2e 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -142147 +142147 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index eb270a10..a81b3e19 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -150995 +150995 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 12e0bf3e..624e0da8 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -109192 +109192 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 31411840..1f43cf81 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -134740 +134740 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap index 64acb256..b4eabb29 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -127479 +127479 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 5bc09bf8..062922cd 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -129254 +129254 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 647e83d9..61c51099 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -137045 +137045 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 8c343d0b..b499b44a 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -133390 +133390 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 220f781d..5c2d6ce7 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -174306 +174306 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap index 3287ca76..0aff3df0 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -144262 +144262 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 5725b98a..e60f75f4 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -341067 +341067 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 7569a6e6..6bcd8770 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -319049 +319049 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 648e557e..589f7a63 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -319691 +319691 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index d79bd66c..c1563f13 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -245273 +245273 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index fbc15b40..66c788d9 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -375091 +375091 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 9d95ffb0..13864905 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -325067 +325067 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index d170d244..869a05f5 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -420841 +420841 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 638eae80..e88f6975 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -8389 +8389 \ No newline at end of file From 81e9692a3e73d6241a39f731c3ba53d44fe9e3f9 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:27:50 -0400 Subject: [PATCH 48/52] use view quoter --- .../MIDDLEWARE_PROTECT-multi-protected.snap | 2 +- .../MIDDLEWARE_PROTECT-protected.snap | 2 +- src/middleware/ABOUT_MIDDLEWARE.md | 1 + src/middleware/MiddlewareProtect.sol | 40 +++++++++---------- src/middleware/MiddlewareProtectFactory.sol | 7 +++- test/MiddlewareProtectFactory.t.sol | 31 ++++++++------ 6 files changed, 46 insertions(+), 37 deletions(-) diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap index 79ff6983..4bf2964e 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap @@ -1 +1 @@ -872991 \ No newline at end of file +914551 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap index e70c7a11..3af65269 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap @@ -1 +1 @@ -229771 \ No newline at end of file +201607 \ No newline at end of file diff --git a/src/middleware/ABOUT_MIDDLEWARE.md b/src/middleware/ABOUT_MIDDLEWARE.md index d7978934..5fce0645 100644 --- a/src/middleware/ABOUT_MIDDLEWARE.md +++ b/src/middleware/ABOUT_MIDDLEWARE.md @@ -17,6 +17,7 @@ Best of all, attaching a middleware to a hook is easy and usually requires no ex ### Caveats - (because of the proxy pattern) constructors will never be called, so it may be necessary to revise the implementation contract to use an initialize function if the constructor needs to set non-immutable variables. - let's say hook A calls a permissioned function on external contract E. a middleware pointing to hook A would then not be able to call contract E. +- be mindful of proxy storage collisions between the middleware and the implementation. ### Deployment Developers should mine a salt to generate the correct flags for the middleware. While not strictly required, it’s recommended to match the hook’s flags with the middleware’s flags. diff --git a/src/middleware/MiddlewareProtect.sol b/src/middleware/MiddlewareProtect.sol index f2ef5d81..a806e698 100644 --- a/src/middleware/MiddlewareProtect.sol +++ b/src/middleware/MiddlewareProtect.sol @@ -20,6 +20,7 @@ import {console} from "forge-std/console.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {IViewQuoter} from "../interfaces/IViewQuoter.sol"; contract MiddlewareProtect is BaseMiddleware { using CustomRevert for bytes4; @@ -43,10 +44,13 @@ contract MiddlewareProtect is BaseMiddleware { bytes internal constant ZERO_BYTES = bytes(""); + IViewQuoter public immutable viewQuoter; + // todo: use tstore - BalanceDelta private quote; + int256 private quote; - constructor(IPoolManager _manager, address _impl) BaseMiddleware(_manager, _impl) { + constructor(IPoolManager _manager, IViewQuoter _viewQuoter, address _impl) BaseMiddleware(_manager, _impl) { + viewQuoter = _viewQuoter; _ensureValidFlags(); } @@ -54,9 +58,10 @@ contract MiddlewareProtect is BaseMiddleware { external returns (bytes4, BeforeSwapDelta, uint24) { - try this._quoteSwapDelta(key, params) {} - catch (bytes memory reason) { - quote = abi.decode(reason, (BalanceDelta)); + if (params.zeroForOne) { + (, quote,,) = viewQuoter.quoteSingle(key, params); + } else { + (quote,,,) = viewQuoter.quoteSingle(key, params); } (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { @@ -65,24 +70,17 @@ contract MiddlewareProtect is BaseMiddleware { return abi.decode(returnData, (bytes4, BeforeSwapDelta, uint24)); } - function _quoteSwapDelta(PoolKey memory key, IPoolManager.SwapParams memory params) - external - returns (bytes memory) - { - BalanceDelta swapDelta = poolManager.swap(key, params, ZERO_BYTES); - bytes memory result = abi.encode(swapDelta); - assembly { - revert(add(0x20, result), mload(result)) - } - } - - function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta delta, bytes calldata) - external - returns (bytes4, int128) - { + function afterSwap( + address, + PoolKey calldata, + IPoolManager.SwapParams calldata params, + BalanceDelta delta, + bytes calldata + ) external returns (bytes4, int128) { IHooks implementation = IHooks(address(implementation)); if (implementation.hasPermission(Hooks.BEFORE_SWAP_FLAG)) { - if (delta != quote) revert HookModifiedOutput(); + int256 amountOut = params.zeroForOne ? delta.amount1() : delta.amount0(); + if (amountOut != quote) revert HookModifiedOutput(); if (!implementation.hasPermission(Hooks.AFTER_SWAP_FLAG)) { return (BaseHook.afterSwap.selector, 0); } diff --git a/src/middleware/MiddlewareProtectFactory.sol b/src/middleware/MiddlewareProtectFactory.sol index 33a3c3d6..aff46d48 100644 --- a/src/middleware/MiddlewareProtectFactory.sol +++ b/src/middleware/MiddlewareProtectFactory.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import {MiddlewareProtect} from "./MiddlewareProtect.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IViewQuoter} from "../interfaces/IViewQuoter.sol"; contract MiddlewareProtectFactory { event MiddlewareCreated(address implementation, address middleware); @@ -10,9 +11,11 @@ contract MiddlewareProtectFactory { mapping(address => address) private _implementations; IPoolManager public immutable poolManager; + IViewQuoter public immutable viewQuoter; - constructor(IPoolManager _poolManager) { + constructor(IPoolManager _poolManager, IViewQuoter _viewQuoter) { poolManager = _poolManager; + viewQuoter = _viewQuoter; } /** @@ -31,7 +34,7 @@ contract MiddlewareProtectFactory { * @return middleware The address of the newly created middlewareRemove contract. */ function createMiddleware(address implementation, bytes32 salt) external returns (address middleware) { - middleware = address(new MiddlewareProtect{salt: salt}(poolManager, implementation)); + middleware = address(new MiddlewareProtect{salt: salt}(poolManager, viewQuoter, implementation)); _implementations[middleware] = implementation; emit MiddlewareCreated(implementation, middleware); } diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index cbe6a675..03ea73c3 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -26,6 +26,8 @@ import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {BaseMiddleware} from "./../src/middleware/BaseMiddleware.sol"; import {BlankSwapHooks} from "./middleware/BlankSwapHooks.sol"; +import {ViewQuoter} from "./../src/lens/ViewQuoter.sol"; +import {IViewQuoter} from "./../src/interfaces/IViewQuoter.sol"; contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { HookEnabledSwapRouter router; @@ -36,6 +38,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { HooksCounter counter; address middleware; HooksFrontrun hooksFrontrun; + IViewQuoter viewQuoter; uint160 COUNTER_FLAGS = uint160( Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG @@ -51,7 +54,8 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - factory = new MiddlewareProtectFactory(manager); + viewQuoter = new ViewQuoter(manager); + factory = new MiddlewareProtectFactory(manager, viewQuoter); counter = HooksCounter(address(COUNTER_FLAGS)); vm.etch(address(counter), address(new HooksCounter(manager)).code); @@ -62,7 +66,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), COUNTER_FLAGS, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(counter)) + abi.encode(address(manager), address(viewQuoter), address(counter)) ); middleware = factory.createMiddleware(address(counter), salt); assertEq(hookAddress, middleware); @@ -81,7 +85,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(hooksReturnDeltas)) + abi.encode(address(manager), address(viewQuoter), address(hooksReturnDeltas)) ); address implementation = address(hooksReturnDeltas); vm.expectRevert(abi.encodePacked(bytes16(MiddlewareProtect.HookPermissionForbidden.selector), hookAddress)); @@ -112,7 +116,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(hooksFrontrun)) + abi.encode(address(manager), address(viewQuoter), address(hooksFrontrun)) ); address implementation = address(hooksFrontrun); address hookAddressCreated = factory.createMiddleware(implementation, salt); @@ -143,7 +147,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(hooksRevert)) + abi.encode(address(manager), address(viewQuoter), address(hooksRevert)) ); middleware = factory.createMiddleware(address(hooksRevert), salt); (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); @@ -162,7 +166,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(hooksOutOfGas)) + abi.encode(address(manager), address(viewQuoter), address(hooksOutOfGas)) ); middleware = factory.createMiddleware(address(hooksOutOfGas), salt); (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); @@ -178,7 +182,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { // address(factory), // flags, // type(MiddlewareProtect).creationCode, - // abi.encode(address(manager), address(frontrunAdd)) + // abi.encode(address(manager), address(viewQuoter), address(frontrunAdd)) // ); // middleware = factory.createMiddleware(address(frontrunAdd), salt); // currency0.transfer(address(frontrunAdd), 1 ether); @@ -296,7 +300,10 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { function testFlagCompatibilities(uint160 thisFlags, uint160 implFlags) internal { (, bytes32 salt) = HookMiner.find( - address(factory), thisFlags, type(MiddlewareProtect).creationCode, abi.encode(address(manager), implFlags) + address(factory), + thisFlags, + type(MiddlewareProtect).creationCode, + abi.encode(address(manager), address(viewQuoter), implFlags) ); factory.createMiddleware(address(implFlags), salt); } @@ -312,7 +319,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(counter)) + abi.encode(address(manager), address(viewQuoter), address(counter)) ); factory.createMiddleware(address(counter), salt); // second deployment should revert @@ -329,7 +336,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), incorrectFlags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(counter2)) + abi.encode(address(manager), address(viewQuoter), address(counter2)) ); address implementation = address(counter2); vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); @@ -381,7 +388,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { assertEq(counterProxy.afterRemoveLiquidityCount(id), 1); } - function testMiddlewareRemoveGas() public { + function testMiddlewareProtectGas() public { uint160 flags = Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG; BlankSwapHooks blankSwapHooks = BlankSwapHooks(address(flags)); vm.etch(address(blankSwapHooks), address(new BlankSwapHooks(manager)).code); @@ -395,7 +402,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(blankSwapHooks)) + abi.encode(address(manager), address(viewQuoter), address(blankSwapHooks)) ); address hookAddress = factory.createMiddleware(address(blankSwapHooks), salt); (PoolKey memory protectedKey,) = From 3f69c05767ebe34964f4579814d3392a13fd02a7 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:37:09 -0400 Subject: [PATCH 49/52] optimize quoter --- .../MIDDLEWARE_PROTECT-multi-protected.snap | 2 +- .../MIDDLEWARE_PROTECT-multi-vanilla.snap | 2 +- .../MIDDLEWARE_PROTECT-protected.snap | 2 +- .../MIDDLEWARE_PROTECT-vanilla.snap | 2 +- src/middleware/CheapQuoter.sol | 190 ++++++++++++++++++ src/middleware/MiddlewareProtect.sol | 18 +- src/middleware/MiddlewareProtectFactory.sol | 10 +- test/MiddlewareProtectFactory.t.sol | 55 +++-- 8 files changed, 242 insertions(+), 39 deletions(-) create mode 100644 src/middleware/CheapQuoter.sol diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap index 4bf2964e..8be44c61 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap @@ -1 +1 @@ -914551 \ No newline at end of file +184020 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap index 98ddcf3c..9d7324da 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap @@ -1 +1 @@ -475485 \ No newline at end of file +143854 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap index 3af65269..afb522c2 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap @@ -1 +1 @@ -201607 \ No newline at end of file +151513 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap index e1b225a9..bec120fa 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap @@ -1 +1 @@ -147725 \ No newline at end of file +124869 \ No newline at end of file diff --git a/src/middleware/CheapQuoter.sol b/src/middleware/CheapQuoter.sol new file mode 100644 index 00000000..caf6bd68 --- /dev/null +++ b/src/middleware/CheapQuoter.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.26; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {SwapMath} from "@uniswap/v4-core/src/libraries/SwapMath.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {LiquidityMath} from "@uniswap/v4-core/src/libraries/LiquidityMath.sol"; +import {PoolTickBitmap} from "../libraries/PoolTickBitmap.sol"; +import {Slot0, Slot0Library} from "@uniswap/v4-core/src/types/Slot0.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +contract CheapQuoter { + IPoolManager public immutable poolManager; + + using SafeCast for uint256; + using SafeCast for int256; + + using Slot0Library for Slot0; + using StateLibrary for IPoolManager; + using PoolIdLibrary for PoolKey; + + struct Slot0Struct { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // tick spacing + int24 tickSpacing; + } + + // used for packing under the stack limit + struct QuoteParams { + bool zeroForOne; + bool exactInput; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + struct SwapCache { + // the protocol fee for the input token + uint8 feeProtocol; + // liquidity at the beginning of the swap + uint128 liquidityStart; + // the timestamp of the current block + uint32 blockTimestamp; + // the current value of the tick accumulator, computed only if we cross an initialized tick + int56 tickCumulative; + // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick + uint160 secondsPerLiquidityCumulativeX128; + // whether we've computed and cached the above two accumulators + bool computedLatestObservation; + } + + // the top level state of the swap, the results of which are recorded in storage at the end + struct SwapState { + // the amount remaining to be swapped in/out of the input/output asset + int256 amountSpecifiedRemaining; + // the amount already swapped out/in of the output/input asset + int256 amountCalculated; + // current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // the global fee growth of the input token + uint256 feeGrowthGlobalX128; + // amount of input token paid as protocol fee + uint128 protocolFee; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + } + + constructor(IPoolManager _poolManager) { + poolManager = _poolManager; + } + + function fillSlot0(PoolKey calldata poolKey) private view returns (Slot0Struct memory slot0) { + (slot0.sqrtPriceX96, slot0.tick,,) = poolManager.getSlot0(poolKey.toId()); + slot0.tickSpacing = poolKey.tickSpacing; + return slot0; + } + + function quote(PoolKey calldata poolKey, IPoolManager.SwapParams calldata swapParams) + external + view + returns (int256 quote) + { + QuoteParams memory quoteParams = QuoteParams( + swapParams.zeroForOne, swapParams.amountSpecified < 0, poolKey.fee, swapParams.sqrtPriceLimitX96 + ); + + Slot0Struct memory slot0 = fillSlot0(poolKey); + + SwapState memory state = SwapState({ + amountSpecifiedRemaining: -swapParams.amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0.sqrtPriceX96, + tick: slot0.tick, + feeGrowthGlobalX128: 0, + protocolFee: 0, + liquidity: poolManager.getLiquidity(poolKey.toId()) + }); + + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != quoteParams.sqrtPriceLimitX96) { + StepComputations memory step; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + (step.tickNext, step.initialized) = PoolTickBitmap.nextInitializedTickWithinOneWord( + poolManager, poolKey.toId(), slot0.tickSpacing, state.tick, quoteParams.zeroForOne + ); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + 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( + state.sqrtPriceX96, + ( + quoteParams.zeroForOne + ? step.sqrtPriceNextX96 < quoteParams.sqrtPriceLimitX96 + : step.sqrtPriceNextX96 > quoteParams.sqrtPriceLimitX96 + ) ? quoteParams.sqrtPriceLimitX96 : step.sqrtPriceNextX96, + state.liquidity, + -state.amountSpecifiedRemaining, + quoteParams.fee + ); + + if (quoteParams.exactInput) { + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); + state.amountCalculated = state.amountCalculated + step.amountOut.toInt256(); + } else { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + state.amountCalculated = state.amountCalculated - (step.amountIn + step.feeAmount).toInt256(); + } + + // shift tick if we reached the next price + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + (, int128 liquidityNet,,) = poolManager.getTickInfo(poolKey.toId(), step.tickNext); + + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (quoteParams.zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); + } + + state.tick = quoteParams.zeroForOne ? step.tickNext - 1 : step.tickNext; + } 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.getTickAtSqrtPrice(state.sqrtPriceX96); + } + + quote = quoteParams.exactInput + ? state.amountCalculated + : state.amountSpecifiedRemaining + swapParams.amountSpecified; + } + } +} diff --git a/src/middleware/MiddlewareProtect.sol b/src/middleware/MiddlewareProtect.sol index a806e698..2d5f8b53 100644 --- a/src/middleware/MiddlewareProtect.sol +++ b/src/middleware/MiddlewareProtect.sol @@ -20,7 +20,7 @@ import {console} from "forge-std/console.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {IViewQuoter} from "../interfaces/IViewQuoter.sol"; +import {CheapQuoter} from "./CheapQuoter.sol"; contract MiddlewareProtect is BaseMiddleware { using CustomRevert for bytes4; @@ -44,25 +44,23 @@ contract MiddlewareProtect is BaseMiddleware { bytes internal constant ZERO_BYTES = bytes(""); - IViewQuoter public immutable viewQuoter; + CheapQuoter public immutable cheapQuoter; // todo: use tstore int256 private quote; - constructor(IPoolManager _manager, IViewQuoter _viewQuoter, address _impl) BaseMiddleware(_manager, _impl) { - viewQuoter = _viewQuoter; + constructor(IPoolManager _poolManager, CheapQuoter _cheapQuoter, address _impl) + BaseMiddleware(_poolManager, _impl) + { + cheapQuoter = _cheapQuoter; _ensureValidFlags(); } - function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) + function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) external returns (bytes4, BeforeSwapDelta, uint24) { - if (params.zeroForOne) { - (, quote,,) = viewQuoter.quoteSingle(key, params); - } else { - (quote,,,) = viewQuoter.quoteSingle(key, params); - } + quote = cheapQuoter.quote(key, params); (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { _handleRevert(returnData); diff --git a/src/middleware/MiddlewareProtectFactory.sol b/src/middleware/MiddlewareProtectFactory.sol index aff46d48..0ae5ef6d 100644 --- a/src/middleware/MiddlewareProtectFactory.sol +++ b/src/middleware/MiddlewareProtectFactory.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {MiddlewareProtect} from "./MiddlewareProtect.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {IViewQuoter} from "../interfaces/IViewQuoter.sol"; +import {CheapQuoter} from "./CheapQuoter.sol"; contract MiddlewareProtectFactory { event MiddlewareCreated(address implementation, address middleware); @@ -11,11 +11,11 @@ contract MiddlewareProtectFactory { mapping(address => address) private _implementations; IPoolManager public immutable poolManager; - IViewQuoter public immutable viewQuoter; + CheapQuoter public immutable cheapQuoter; - constructor(IPoolManager _poolManager, IViewQuoter _viewQuoter) { + constructor(IPoolManager _poolManager, CheapQuoter _cheapQuoter) { poolManager = _poolManager; - viewQuoter = _viewQuoter; + cheapQuoter = _cheapQuoter; } /** @@ -34,7 +34,7 @@ contract MiddlewareProtectFactory { * @return middleware The address of the newly created middlewareRemove contract. */ function createMiddleware(address implementation, bytes32 salt) external returns (address middleware) { - middleware = address(new MiddlewareProtect{salt: salt}(poolManager, viewQuoter, implementation)); + middleware = address(new MiddlewareProtect{salt: salt}(poolManager, cheapQuoter, implementation)); _implementations[middleware] = implementation; emit MiddlewareCreated(implementation, middleware); } diff --git a/test/MiddlewareProtectFactory.t.sol b/test/MiddlewareProtectFactory.t.sol index 03ea73c3..488f8910 100644 --- a/test/MiddlewareProtectFactory.t.sol +++ b/test/MiddlewareProtectFactory.t.sol @@ -26,8 +26,9 @@ import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {BaseMiddleware} from "./../src/middleware/BaseMiddleware.sol"; import {BlankSwapHooks} from "./middleware/BlankSwapHooks.sol"; -import {ViewQuoter} from "./../src/lens/ViewQuoter.sol"; -import {IViewQuoter} from "./../src/interfaces/IViewQuoter.sol"; +import {CheapQuoter} from "./../src/middleware/CheapQuoter.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { HookEnabledSwapRouter router; @@ -38,7 +39,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { HooksCounter counter; address middleware; HooksFrontrun hooksFrontrun; - IViewQuoter viewQuoter; + CheapQuoter cheapQuoter; uint160 COUNTER_FLAGS = uint160( Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG @@ -54,8 +55,8 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - viewQuoter = new ViewQuoter(manager); - factory = new MiddlewareProtectFactory(manager, viewQuoter); + cheapQuoter = new CheapQuoter(manager); + factory = new MiddlewareProtectFactory(manager, cheapQuoter); counter = HooksCounter(address(COUNTER_FLAGS)); vm.etch(address(counter), address(new HooksCounter(manager)).code); @@ -66,7 +67,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), COUNTER_FLAGS, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(counter)) + abi.encode(address(manager), address(cheapQuoter), address(counter)) ); middleware = factory.createMiddleware(address(counter), salt); assertEq(hookAddress, middleware); @@ -85,7 +86,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(hooksReturnDeltas)) + abi.encode(address(manager), address(cheapQuoter), address(hooksReturnDeltas)) ); address implementation = address(hooksReturnDeltas); vm.expectRevert(abi.encodePacked(bytes16(MiddlewareProtect.HookPermissionForbidden.selector), hookAddress)); @@ -116,7 +117,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(hooksFrontrun)) + abi.encode(address(manager), address(cheapQuoter), address(hooksFrontrun)) ); address implementation = address(hooksFrontrun); address hookAddressCreated = factory.createMiddleware(implementation, salt); @@ -147,7 +148,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(hooksRevert)) + abi.encode(address(manager), address(cheapQuoter), address(hooksRevert)) ); middleware = factory.createMiddleware(address(hooksRevert), salt); (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); @@ -166,7 +167,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(hooksOutOfGas)) + abi.encode(address(manager), address(cheapQuoter), address(hooksOutOfGas)) ); middleware = factory.createMiddleware(address(hooksOutOfGas), salt); (key,) = initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES); @@ -182,7 +183,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { // address(factory), // flags, // type(MiddlewareProtect).creationCode, - // abi.encode(address(manager), address(viewQuoter), address(frontrunAdd)) + // abi.encode(address(manager), address(cheapQuoter), address(frontrunAdd)) // ); // middleware = factory.createMiddleware(address(frontrunAdd), salt); // currency0.transfer(address(frontrunAdd), 1 ether); @@ -303,7 +304,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), thisFlags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), implFlags) + abi.encode(address(manager), address(cheapQuoter), implFlags) ); factory.createMiddleware(address(implFlags), salt); } @@ -319,7 +320,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(counter)) + abi.encode(address(manager), address(cheapQuoter), address(counter)) ); factory.createMiddleware(address(counter), salt); // second deployment should revert @@ -336,7 +337,7 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { address(factory), incorrectFlags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(counter2)) + abi.encode(address(manager), address(cheapQuoter), address(counter2)) ); address implementation = address(counter2); vm.expectRevert(BaseMiddleware.FlagsMismatch.selector); @@ -392,27 +393,41 @@ contract MiddlewareProtectFactoryTest is Test, Deployers, GasSnapshot { uint160 flags = Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG; BlankSwapHooks blankSwapHooks = BlankSwapHooks(address(flags)); vm.etch(address(blankSwapHooks), address(new BlankSwapHooks(manager)).code); - (key,) = initPoolAndAddLiquidity( - currency0, currency1, IHooks(address(blankSwapHooks)), 3000, SQRT_PRICE_1_1, ZERO_BYTES + PoolId id; + (key, id) = initPoolAndAddLiquidity( + currency0, currency1, IHooks(address(blankSwapHooks)), 500, SQRT_PRICE_1_1, ZERO_BYTES ); swap(key, true, 0.0001 ether, ZERO_BYTES); + swap(key, true, 0.0001 ether, ZERO_BYTES); snapLastCall("MIDDLEWARE_PROTECT-vanilla"); uint160 maxFeeBips = 0; (, bytes32 salt) = HookMiner.find( address(factory), flags, type(MiddlewareProtect).creationCode, - abi.encode(address(manager), address(viewQuoter), address(blankSwapHooks)) + abi.encode(address(manager), address(cheapQuoter), address(blankSwapHooks)) ); address hookAddress = factory.createMiddleware(address(blankSwapHooks), salt); (PoolKey memory protectedKey,) = - initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddress), 3000, SQRT_PRICE_1_1, ZERO_BYTES); + initPoolAndAddLiquidity(currency0, currency1, IHooks(hookAddress), 500, SQRT_PRICE_1_1, ZERO_BYTES); + swap(protectedKey, true, 0.0001 ether, ZERO_BYTES); swap(protectedKey, true, 0.0001 ether, ZERO_BYTES); snapLastCall("MIDDLEWARE_PROTECT-protected"); - swap(key, true, 0.01 ether, ZERO_BYTES); + (, int24 tick,,) = StateLibrary.getSlot0(manager, id); + + IPoolManager.ModifyLiquidityParams memory params = IPoolManager.ModifyLiquidityParams({ + tickLower: tick * 100, + tickUpper: tick * 2, + liquidityDelta: 100e18, + salt: 0 + }); + modifyLiquidityRouter.modifyLiquidity(key, params, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity(protectedKey, params, ZERO_BYTES); + + swap(key, true, 0.1 ether, ZERO_BYTES); snapLastCall("MIDDLEWARE_PROTECT-multi-vanilla"); - swap(protectedKey, true, 0.01 ether, ZERO_BYTES); + swap(protectedKey, true, 0.1 ether, ZERO_BYTES); snapLastCall("MIDDLEWARE_PROTECT-multi-protected"); } } From 6632806f705ca21d12d7845a5070de87d353bc29 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:25:21 -0400 Subject: [PATCH 50/52] update docs --- src/middleware/ABOUT_MIDDLEWARE.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/middleware/ABOUT_MIDDLEWARE.md b/src/middleware/ABOUT_MIDDLEWARE.md index 5fce0645..cd4e83ac 100644 --- a/src/middleware/ABOUT_MIDDLEWARE.md +++ b/src/middleware/ABOUT_MIDDLEWARE.md @@ -69,13 +69,32 @@ address hookAddress = factory.createMiddleware(implementation, maxFeeBips, salt) ### Gas Snapshots | | Unprotected | Protected | Diff | | --- | --- | --- | --- | -| Before + After remove (only proxy) | 124822 | 128379 | 3557 | -| Before + After remove (OVERRIDE) | 124822 | 133820 | 8998 | -| Before + After remove | 124822 | 135757 | 10935 | -| Before + After remove + returns deltas | 124851 | 138303 | 13452 | -| Before + After remove + takes fee | 181009 | 197499 | 16490 | +| Before + After remove (only proxy) | 124,822 | 128,379 | 3,557 | +| Before + After remove (OVERRIDE) | 124,822 | 133,820 | 8,998 | +| Before + After remove | 124,822 | 135,757 | 10,935 | +| Before + After remove + returns deltas | 124,851 | 138,303 | 13,452 | +| Before + After remove + takes fee | 181,009 | 197,499 | 16,490 | ### Override There is a small gas overhead when using the middleware. An advanced caller who is confident that the checks will pass can skip them by passing a hookData starting with OVERRIDE_BYTES. The remaining bytes will then be used to do a standard hook call. + +# Middleware Protect +A malicious hook could frontrun a user in the beforeSwap hook, extracting value at the cost of the user. + +MiddlewareProtect is one possible middleware, designed to revert if this happens. + +Before any hooks are called, it quotes the output amount. Then, in the afterSwap hook, it compares the output amount to the quote. If they differ, the swap reverts. + +> [!IMPORTANT] +> You must mine the implementation hook address. + +> [!NOTE] +> If your middleware uses the beforeSwap flag, it must also use the afterSwap flag, even if the implementation does not use afterSwap. + +### Gas Snapshots +| | Unprotected | Protected | Diff | +| --- | --- | --- | --- | +| Single-tick swap | 124,869 | 151,501 | 26,632 | +| Multi-tick swap | 143,854 | 184,020 | 40,166 | \ No newline at end of file From cea56de9f8ed6796798ed30b47890dcc6b6a78a3 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:48:02 -0400 Subject: [PATCH 51/52] use tstore --- .../MIDDLEWARE_PROTECT-multi-protected.snap | 2 +- .../MIDDLEWARE_PROTECT-protected.snap | 2 +- src/libraries/Quote.sol | 28 +++++++++++++++++++ src/middleware/CheapQuoter.sol | 5 +--- src/middleware/MiddlewareProtect.sol | 15 ++++------ 5 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 src/libraries/Quote.sol diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap index 8be44c61..08051b19 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap @@ -1 +1 @@ -184020 \ No newline at end of file +178969 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap index afb522c2..d5707a7d 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap @@ -1 +1 @@ -151513 \ No newline at end of file +149436 \ No newline at end of file diff --git a/src/libraries/Quote.sol b/src/libraries/Quote.sol new file mode 100644 index 00000000..2d35352b --- /dev/null +++ b/src/libraries/Quote.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +/// @notice This is a temporary library that allows us to use transient storage (tstore/tload) +/// for the quote. +/// TODO: This library can be deleted when we have the transient keyword support in solidity. +library Quote { + // The slot holding the quote. bytes32(uint256(keccak256("Quote")) - 1) + bytes32 internal constant QUOTE_SLOT = 0xbbd426867243227198e50d68cdb6f9a2a3a1c5ef433a2b6e7fcf3f462364310a; + + function read() internal view returns (int256 quote) { + assembly ("memory-safe") { + quote := tload(QUOTE_SLOT) + } + } + + function set(int256 quote) internal { + assembly ("memory-safe") { + tstore(QUOTE_SLOT, quote) + } + } + + function reset() internal { + assembly ("memory-safe") { + tstore(QUOTE_SLOT, 0) + } + } +} diff --git a/src/middleware/CheapQuoter.sol b/src/middleware/CheapQuoter.sol index caf6bd68..8a9d016c 100644 --- a/src/middleware/CheapQuoter.sol +++ b/src/middleware/CheapQuoter.sol @@ -181,10 +181,7 @@ contract CheapQuoter { // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved state.tick = TickMath.getTickAtSqrtPrice(state.sqrtPriceX96); } - - quote = quoteParams.exactInput - ? state.amountCalculated - : state.amountSpecifiedRemaining + swapParams.amountSpecified; } + quote = state.amountCalculated; } } diff --git a/src/middleware/MiddlewareProtect.sol b/src/middleware/MiddlewareProtect.sol index 2d5f8b53..1e16f60f 100644 --- a/src/middleware/MiddlewareProtect.sol +++ b/src/middleware/MiddlewareProtect.sol @@ -10,17 +10,14 @@ import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; -import {NonZeroDeltaCount} from "@uniswap/v4-core/src/libraries/NonZeroDeltaCount.sol"; -import {IExttload} from "@uniswap/v4-core/src/interfaces/IExttload.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {BaseMiddleware} from "./BaseMiddleware.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; -import {console} from "forge-std/console.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {CheapQuoter} from "./CheapQuoter.sol"; +import {Quote} from "../libraries/Quote.sol"; contract MiddlewareProtect is BaseMiddleware { using CustomRevert for bytes4; @@ -46,9 +43,6 @@ contract MiddlewareProtect is BaseMiddleware { CheapQuoter public immutable cheapQuoter; - // todo: use tstore - int256 private quote; - constructor(IPoolManager _poolManager, CheapQuoter _cheapQuoter, address _impl) BaseMiddleware(_poolManager, _impl) { @@ -60,7 +54,7 @@ contract MiddlewareProtect is BaseMiddleware { external returns (bytes4, BeforeSwapDelta, uint24) { - quote = cheapQuoter.quote(key, params); + Quote.set(cheapQuoter.quote(key, params)); (bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data); if (!success) { _handleRevert(returnData); @@ -77,8 +71,9 @@ contract MiddlewareProtect is BaseMiddleware { ) external returns (bytes4, int128) { IHooks implementation = IHooks(address(implementation)); if (implementation.hasPermission(Hooks.BEFORE_SWAP_FLAG)) { - int256 amountOut = params.zeroForOne ? delta.amount1() : delta.amount0(); - if (amountOut != quote) revert HookModifiedOutput(); + int256 amountActual = params.zeroForOne == params.amountSpecified < 0 ? delta.amount1() : delta.amount0(); + if (amountActual != Quote.read()) revert HookModifiedOutput(); + Quote.reset(); if (!implementation.hasPermission(Hooks.AFTER_SWAP_FLAG)) { return (BaseHook.afterSwap.selector, 0); } From 5eec68a4c05f2dcaa5705eae13d7a1dbb5f23e0f Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:01:25 -0400 Subject: [PATCH 52/52] update gas snapshots --- .forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap | 2 +- .forge-snapshots/MIDDLEWARE_PROTECT-protected.snap | 2 +- src/middleware/ABOUT_MIDDLEWARE.md | 4 ++-- src/middleware/CheapQuoter.sol | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap index 08051b19..8aa34890 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap @@ -1 +1 @@ -178969 \ No newline at end of file +178303 \ No newline at end of file diff --git a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap index d5707a7d..dc388f5d 100644 --- a/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap +++ b/.forge-snapshots/MIDDLEWARE_PROTECT-protected.snap @@ -1 +1 @@ -149436 \ No newline at end of file +149751 \ No newline at end of file diff --git a/src/middleware/ABOUT_MIDDLEWARE.md b/src/middleware/ABOUT_MIDDLEWARE.md index cd4e83ac..bbb4fbc5 100644 --- a/src/middleware/ABOUT_MIDDLEWARE.md +++ b/src/middleware/ABOUT_MIDDLEWARE.md @@ -96,5 +96,5 @@ Before any hooks are called, it quotes the output amount. Then, in the afterSwap ### Gas Snapshots | | Unprotected | Protected | Diff | | --- | --- | --- | --- | -| Single-tick swap | 124,869 | 151,501 | 26,632 | -| Multi-tick swap | 143,854 | 184,020 | 40,166 | \ No newline at end of file +| Single-tick swap | 124,869 | 149,751 | 24,882 | +| Multi-tick swap | 143,854 | 178,303 | 34,449 | \ No newline at end of file diff --git a/src/middleware/CheapQuoter.sol b/src/middleware/CheapQuoter.sol index 8a9d016c..a77e695c 100644 --- a/src/middleware/CheapQuoter.sol +++ b/src/middleware/CheapQuoter.sol @@ -5,7 +5,7 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {SwapMath} from "@uniswap/v4-core/src/libraries/SwapMath.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {LiquidityMath} from "@uniswap/v4-core/src/libraries/LiquidityMath.sol"; import {PoolTickBitmap} from "../libraries/PoolTickBitmap.sol"; import {Slot0, Slot0Library} from "@uniswap/v4-core/src/types/Slot0.sol"; @@ -167,7 +167,7 @@ contract CheapQuoter { if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { // if the tick is initialized, run the tick transition if (step.initialized) { - (, int128 liquidityNet,,) = poolManager.getTickInfo(poolKey.toId(), step.tickNext); + (, int128 liquidityNet) = poolManager.getTickLiquidity(poolKey.toId(), step.tickNext); // if we're moving leftward, we interpret liquidityNet as the opposite sign // safe because liquidityNet cannot be type(int128).min