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 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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(); }