From c08fdcf98c19cf05e74cf9999c4f15089a1608c2 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Thu, 17 Oct 2024 11:15:22 -0400 Subject: [PATCH 01/14] add wrap unwrap --- script/DeployPosm.s.sol | 4 +++- src/PositionManager.sol | 9 +++++++-- src/base/NativeWrapper.sol | 26 ++++++++++++++++++++++++++ src/interfaces/external/IWETH9.sol | 13 +++++++++++++ test/shared/PosmTestSetup.sol | 5 ++++- 5 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 src/base/NativeWrapper.sol create mode 100644 src/interfaces/external/IWETH9.sol diff --git a/script/DeployPosm.s.sol b/script/DeployPosm.s.sol index 5bbb6184..e3a20758 100644 --- a/script/DeployPosm.s.sol +++ b/script/DeployPosm.s.sol @@ -10,6 +10,7 @@ import {PositionManager} from "../src/PositionManager.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; import {IPositionDescriptor} from "../src/interfaces/IPositionDescriptor.sol"; import {PositionDescriptor} from "../src/PositionDescriptor.sol"; +import {IWETH9} from "../src/interfaces/external/IWETH9.sol"; contract DeployPosmTest is Script { function setUp() public {} @@ -30,7 +31,8 @@ contract DeployPosmTest is Script { IPoolManager(poolManager), IAllowanceTransfer(permit2), unsubscribeGasLimit, - IPositionDescriptor(address(positionDescriptor)) + IPositionDescriptor(address(positionDescriptor)), + IWETH9(wrappedNative) ); console2.log("PositionManager", address(posm)); diff --git a/src/PositionManager.sol b/src/PositionManager.sol index d5721e34..1eab14ab 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -26,6 +26,8 @@ import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; import {Permit2Forwarder} from "./base/Permit2Forwarder.sol"; import {SlippageCheck} from "./libraries/SlippageCheck.sol"; import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol"; +import {NativeWrapper} from "./base/NativeWrapper.sol"; +import {IWETH9} from "./interfaces/external/IWETH9.sol"; // 444444444 // 444444444444 444444 @@ -102,7 +104,8 @@ contract PositionManager is ReentrancyLock, BaseActionsRouter, Notifier, - Permit2Forwarder + Permit2Forwarder, + NativeWrapper { using PoolIdLibrary for PoolKey; using StateLibrary for IPoolManager; @@ -126,12 +129,14 @@ contract PositionManager is IPoolManager _poolManager, IAllowanceTransfer _permit2, uint256 _unsubscribeGasLimit, - IPositionDescriptor _tokenDescriptor + IPositionDescriptor _tokenDescriptor, + IWETH9 _weth9 ) BaseActionsRouter(_poolManager) Permit2Forwarder(_permit2) ERC721Permit_v4("Uniswap v4 Positions NFT", "UNI-V4-POSM") Notifier(_unsubscribeGasLimit) + NativeWrapper(_weth9) { tokenDescriptor = _tokenDescriptor; } diff --git a/src/base/NativeWrapper.sol b/src/base/NativeWrapper.sol new file mode 100644 index 00000000..0862bd61 --- /dev/null +++ b/src/base/NativeWrapper.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {IWETH9} from "../interfaces/external/IWETH9.sol"; +import {ActionConstants} from "../libraries/ActionConstants.sol"; + +/// @title Native Wrapper +/// @notice Allows wrapping and unwrapping of native tokens before or after adding/removing liquidity +contract NativeWrapper { + IWETH9 public immutable WETH9; + + constructor(IWETH9 _weth9) { + WETH9 = _weth9; + } + + function wrap(uint256 _amount) external payable { + uint256 amount = _amount == ActionConstants.CONTRACT_BALANCE ? address(this).balance : _amount; + WETH9.deposit{value: amount}(); + } + + /// @dev payable so it can be multicalled + function unwrap(uint256 _amount) external payable { + uint256 amount = _amount == ActionConstants.CONTRACT_BALANCE ? WETH9.balanceOf(address(this)) : _amount; + WETH9.withdraw(amount); + } +} diff --git a/src/interfaces/external/IWETH9.sol b/src/interfaces/external/IWETH9.sol new file mode 100644 index 00000000..37c3be8e --- /dev/null +++ b/src/interfaces/external/IWETH9.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title Interface for WETH9 +interface IWETH9 is IERC20 { + /// @notice Deposit ether to get wrapped ether + function deposit() external payable; + + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external; +} diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol index 0a79edd1..e1a6aa77 100644 --- a/test/shared/PosmTestSetup.sol +++ b/test/shared/PosmTestSetup.sol @@ -17,6 +17,8 @@ import {HookSavesDelta} from "./HookSavesDelta.sol"; import {HookModifyLiquidities} from "./HookModifyLiquidities.sol"; import {PositionDescriptor} from "../../src/PositionDescriptor.sol"; import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; +import {IWETH9} from "../../src/interfaces/external/IWETH9.sol"; +import {WETH} from "solmate/src/tokens/WETH.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { @@ -26,6 +28,7 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { PositionDescriptor public positionDescriptor; HookSavesDelta hook; address hookAddr = address(uint160(Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.AFTER_REMOVE_LIQUIDITY_FLAG)); + IWETH9 public _WETH9 = IWETH9(address(new WETH())); HookModifyLiquidities hookModifyLiquidities; address hookModifyLiquiditiesAddr = address( @@ -60,7 +63,7 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { // We use deployPermit2() to prevent having to use via-ir in this repository. permit2 = IAllowanceTransfer(deployPermit2()); positionDescriptor = new PositionDescriptor(poolManager, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, "ETH"); - lpm = new PositionManager(poolManager, permit2, 100_000, positionDescriptor); + lpm = new PositionManager(poolManager, permit2, 100_000, positionDescriptor, _WETH9); } function seedBalance(address to) internal { From 4c048cefb43d5a46ced3df3ccbfe14cfd8fb9543 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Fri, 18 Oct 2024 09:52:29 -0400 Subject: [PATCH 02/14] checkpoint --- .../PositionManager_burn_empty.snap | 2 +- .../PositionManager_burn_empty_native.snap | 2 +- ...anager_burn_nonEmpty_native_withClose.snap | 2 +- ...ger_burn_nonEmpty_native_withTakePair.snap | 2 +- ...sitionManager_burn_nonEmpty_withClose.snap | 2 +- ...ionManager_burn_nonEmpty_withTakePair.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_collect_withClose.snap | 2 +- .../PositionManager_collect_withTakePair.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...onManager_decreaseLiquidity_withClose.snap | 2 +- ...anager_decreaseLiquidity_withTakePair.snap | 2 +- .../PositionManager_decrease_burnEmpty.snap | 2 +- ...tionManager_decrease_burnEmpty_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- .../PositionManager_decrease_take_take.snap | 2 +- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- ...ger_increase_autocompound_clearExcess.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .../PositionManager_permit_twice.snap | 2 +- .../PositionManager_unsubscribe.snap | 2 +- src/PositionManager.sol | 18 ++ src/base/NativeWrapper.sol | 17 +- src/libraries/Actions.sol | 6 +- src/libraries/CalldataDecoder.sol | 7 + .../PositionManager.modifyLiquidities.t.sol | 202 ++++++++++++++++++ test/shared/PosmTestSetup.sol | 20 ++ 42 files changed, 295 insertions(+), 47 deletions(-) diff --git a/.forge-snapshots/PositionManager_burn_empty.snap b/.forge-snapshots/PositionManager_burn_empty.snap index 0e85ca4a..949dd08a 100644 --- a/.forge-snapshots/PositionManager_burn_empty.snap +++ b/.forge-snapshots/PositionManager_burn_empty.snap @@ -1 +1 @@ -50446 \ No newline at end of file +50481 \ 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 0e85ca4a..949dd08a 100644 --- a/.forge-snapshots/PositionManager_burn_empty_native.snap +++ b/.forge-snapshots/PositionManager_burn_empty_native.snap @@ -1 +1 @@ -50446 \ No newline at end of file +50481 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap index 9e62ab4d..2c42da0a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withClose.snap @@ -1 +1 @@ -125584 \ No newline at end of file +125619 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap index 33aa7739..fc3b2308 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_native_withTakePair.snap @@ -1 +1 @@ -125031 \ No newline at end of file +125066 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap index 6a3bb39f..c8ab0679 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withClose.snap @@ -1 +1 @@ -132446 \ No newline at end of file +132481 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap index 62abe7a1..9756e81a 100644 --- a/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap +++ b/.forge-snapshots/PositionManager_burn_nonEmpty_withTakePair.snap @@ -1 +1 @@ -131893 \ No newline at end of file +131928 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 83e0e3d6..88dbe749 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -146294 \ No newline at end of file +146338 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index ab27c01b..74123290 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -154872 \ No newline at end of file +154916 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withClose.snap b/.forge-snapshots/PositionManager_collect_withClose.snap index ab27c01b..74123290 100644 --- a/.forge-snapshots/PositionManager_collect_withClose.snap +++ b/.forge-snapshots/PositionManager_collect_withClose.snap @@ -1 +1 @@ -154872 \ No newline at end of file +154916 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_withTakePair.snap b/.forge-snapshots/PositionManager_collect_withTakePair.snap index 47b66e44..05a24ac1 100644 --- a/.forge-snapshots/PositionManager_collect_withTakePair.snap +++ b/.forge-snapshots/PositionManager_collect_withTakePair.snap @@ -1 +1 @@ -154193 \ No newline at end of file +154237 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index e13e87a3..e8aef4e1 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -111980 \ No newline at end of file +112016 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap index f5f1f8b2..b2689dfb 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withClose.snap @@ -1 +1 @@ -119753 \ No newline at end of file +119797 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap index e4b3cfde..35d78118 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_withTakePair.snap @@ -1 +1 @@ -119074 \ No newline at end of file +119118 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap index 31c3a99c..b9be72d2 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty.snap @@ -1 +1 @@ -135243 \ No newline at end of file +135278 \ 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 5cbd7ccc..2e25cfd7 100644 --- a/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap +++ b/.forge-snapshots/PositionManager_decrease_burnEmpty_native.snap @@ -1 +1 @@ -128380 \ No newline at end of file +128416 \ 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 af29b36e..2ed3a7de 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -132440 \ No newline at end of file +132484 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_take_take.snap b/.forge-snapshots/PositionManager_decrease_take_take.snap index 9497d424..f6c25681 100644 --- a/.forge-snapshots/PositionManager_decrease_take_take.snap +++ b/.forge-snapshots/PositionManager_decrease_take_take.snap @@ -1 +1 @@ -120329 \ No newline at end of file +120373 \ 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 df7539ba..38fb0a72 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -159057 \ No newline at end of file +159137 \ 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 d62921d3..80ee22f1 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -157997 \ No newline at end of file +158077 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 5224273f..9fce9410 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -140872 \ No newline at end of file +140934 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 770899b4..b6d9ed62 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -136359 \ No newline at end of file +136403 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index d9726ba2..11569b61 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -177364 \ No newline at end of file +177408 \ 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 21e55c88..7becdebb 100644 --- a/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap +++ b/.forge-snapshots/PositionManager_increase_autocompound_clearExcess.snap @@ -1 +1 @@ -148040 \ No newline at end of file +148084 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 742cc68e..c5779ebc 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -364745 \ No newline at end of file +364807 \ 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 7c56236a..97a3a003 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -373268 \ No newline at end of file +373326 \ 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 822d688b..4cb98c64 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -372491 \ No newline at end of file +372549 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index ccfdb51f..884380c2 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -317617 \ No newline at end of file +317697 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 9f9a6b77..a24bf2ba 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -318287 \ No newline at end of file +318367 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index ccc454dd..60b8b165 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -243856 \ No newline at end of file +243936 \ 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 23648cc9..41e4b5f7 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -419060 \ No newline at end of file +419096 \ 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 cedd0614..cfcf7086 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -323648 \ No newline at end of file +323728 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index cf0292c9..3684ccf8 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -420170 \ No newline at end of file +420250 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 549b6841..6027fb7c 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -419228 \ No newline at end of file +419308 \ 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 b51b3f66..07c741d0 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -455975 \ No newline at end of file +456098 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_permit_twice.snap b/.forge-snapshots/PositionManager_permit_twice.snap index 379f9611..d650ccbd 100644 --- a/.forge-snapshots/PositionManager_permit_twice.snap +++ b/.forge-snapshots/PositionManager_permit_twice.snap @@ -1 +1 @@ -44852 \ No newline at end of file +44876 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_unsubscribe.snap b/.forge-snapshots/PositionManager_unsubscribe.snap index 0151c604..c0f309cf 100644 --- a/.forge-snapshots/PositionManager_unsubscribe.snap +++ b/.forge-snapshots/PositionManager_unsubscribe.snap @@ -1 +1 @@ -59238 \ No newline at end of file +59260 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 1eab14ab..c93fc273 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -247,6 +247,14 @@ contract PositionManager is (Currency currency, address to) = params.decodeCurrencyAndAddress(); _sweep(currency, _mapRecipient(to)); return; + } else if (action == Actions.WRAP) { + (uint256 amount) = params.decodeUint256(); + _wrap(amount); + return; + } else if (action == Actions.UNWRAP) { + (uint256 amount) = params.decodeUint256(); + _unwrap(amount); + return; } } revert UnsupportedAction(action); @@ -392,6 +400,16 @@ contract PositionManager is if (balance > 0) currency.transfer(to, balance); } + function _wrap(uint256 _amount) internal { + uint256 amount = _map(_amount, address(this).balance); + if (amount > 0) WETH9.deposit{value: amount}(); + } + + function _unwrap(uint256 _amount) internal { + uint256 amount = _map(_amount, WETH9.balanceOf(address(this))); + if (amount > 0) WETH9.withdraw(amount); + } + function _modifyLiquidity( PositionInfo info, PoolKey memory poolKey, diff --git a/src/base/NativeWrapper.sol b/src/base/NativeWrapper.sol index 0862bd61..2e5bab69 100644 --- a/src/base/NativeWrapper.sol +++ b/src/base/NativeWrapper.sol @@ -5,22 +5,21 @@ import {IWETH9} from "../interfaces/external/IWETH9.sol"; import {ActionConstants} from "../libraries/ActionConstants.sol"; /// @title Native Wrapper -/// @notice Allows wrapping and unwrapping of native tokens before or after adding/removing liquidity +/// @notice Immutables and helpers for wrapping and unwrapping native contract NativeWrapper { IWETH9 public immutable WETH9; + error InsufficientBalance(); + constructor(IWETH9 _weth9) { WETH9 = _weth9; } - function wrap(uint256 _amount) external payable { - uint256 amount = _amount == ActionConstants.CONTRACT_BALANCE ? address(this).balance : _amount; - WETH9.deposit{value: amount}(); + function _map(uint256 amount, uint256 balance) internal pure returns (uint256) { + if (amount == ActionConstants.CONTRACT_BALANCE) return balance; + if (amount > balance) revert InsufficientBalance(); + return amount; } - /// @dev payable so it can be multicalled - function unwrap(uint256 _amount) external payable { - uint256 amount = _amount == ActionConstants.CONTRACT_BALANCE ? WETH9.balanceOf(address(this)) : _amount; - WETH9.withdraw(amount); - } + receive() external payable {} } diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 49d3e04f..16a8ce8c 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -33,8 +33,10 @@ library Actions { uint256 constant CLOSE_CURRENCY = 0x17; uint256 constant CLEAR_OR_TAKE = 0x18; uint256 constant SWEEP = 0x19; + uint256 constant WRAP = 0x20; + uint256 constant UNWRAP = 0x21; // minting/burning 6909s to close deltas - uint256 constant MINT_6909 = 0x20; - uint256 constant BURN_6909 = 0x21; + uint256 constant MINT_6909 = 0x22; + uint256 constant BURN_6909 = 0x23; } diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index 00cf6e33..a14f3a34 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -241,6 +241,13 @@ library CalldataDecoder { } } + /// @dev equivalent to: abi.decode(params, (uint256)) in calldata + function decodeUint256(bytes calldata params) internal pure returns (uint256 amount) { + assembly ("memory-safe") { + amount := calldataload(params.offset) + } + } + /// @dev equivalent to: abi.decode(params, (Currency, uint256, bool)) in calldata function decodeCurrencyUint256AndBool(bytes calldata params) internal diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index e71c121f..b9da60a7 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; @@ -13,8 +15,11 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {ReentrancyLock} from "../../src/base/ReentrancyLock.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {PositionManager} from "../../src/PositionManager.sol"; @@ -23,10 +28,15 @@ import {PositionConfig} from "../shared/PositionConfig.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; +import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; +import {Planner, Plan} from "../shared/Planner.sol"; + +import "forge-std/console2.sol"; contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityFuzzers { using StateLibrary for IPoolManager; using PoolIdLibrary for PoolKey; + using Planner for Plan; PoolId poolId; address alice; @@ -54,8 +64,19 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF seedBalance(address(hookModifyLiquidities)); (key, poolId) = initPool(currency0, currency1, IHooks(hookModifyLiquidities), 3000, SQRT_PRICE_1_1); + initWethPool(currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); + + seedWeth(address(this)); + approvePosmCurrency(Currency.wrap(address(_WETH9))); + wethConfig = PositionConfig({ + poolKey: wethKey, + tickLower: TickMath.minUsableTick(wethKey.tickSpacing), + tickUpper: TickMath.maxUsableTick(wethKey.tickSpacing) + }); config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); + + vm.deal(address(this), 1000 ether); } /// @dev minting liquidity without approval is allowable @@ -308,4 +329,185 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF ); lpm.modifyLiquidities(calls, _deadline); } + + function test_weth_wrap_increaseLiquidity() public { + // weth-currency1 pool initialized as wethKey + // input: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _wrap + // 2 _mint + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + // Note there is no sweep encoding for weth, but that would be the safest action. + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + Plan memory planner = Planner.init(); + // potential to move below mint call and use open delta for amount + planner.add(Actions.WRAP, abi.encode(100 ether)); + planner.add( + Actions.MINT_POSITION, + abi.encode( + wethConfig.poolKey, + wethConfig.tickLower, + wethConfig.tickUpper, + liquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + // weth9 payer is the contract + planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); + // other currency can close normally + planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + bytes memory actions = planner.encode(); + + lpm.modifyLiquidities{value: 100 ether}(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + // Eth was spent. + assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 100 ether, 1 wei); + assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + } + + function test_weth_burn_unwrap() public { + // weth-currency1 pool + // output: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _burn + // 2 _take where the weth is sent to the lpm contract + // 3 _take where currency1 is sent to the msg sender + // 4 _unwrap + // 5 _sweep where eth is sent to msg sender + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + bytes memory actions = getMintEncoded(wethConfig, liquidityAmount, address(this), ZERO_BYTES); + lpm.modifyLiquidities(actions, _deadline); + + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + console2.log(balance1Before); + Plan memory planner = Planner.init(); + planner.add( + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + // take the weth to the position manager to be unwrapped + planner.add(Actions.TAKE, abi.encode(address(_WETH9), ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); + planner.add( + Actions.TAKE, + abi.encode(address(Currency.unwrap(currency1)), ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA) + ); + planner.add(Actions.UNWRAP, abi.encode(ActionConstants.CONTRACT_BALANCE)); + planner.add(Actions.SWEEP, abi.encode(address(0), ActionConstants.MSG_SENDER)); + + actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + assertApproxEqAbs(balanceEthAfter - balanceEthBefore, 100 ether, 1 wei); + assertApproxEqAbs(balance1After - balance1Before, 100 ether, 1 wei); + assertEq(lpm.getPositionLiquidity(tokenId), 0); + } + + // ETH-USDC pool with WETH input and output + // TODO: Pull Weth in with transferFrom call to permit2 + function test_weth_unwrap_add() public { + // eth-currency1 pool + // input: weth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _wrap + // 2 _mint + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + + uint256 tokenId = lpm.nextTokenId(); + + uint256 balanceWethBefore = IERC20(address(_WETH9)).balanceOf(address(this)); + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + PoolKey memory nativeKey = PoolKey({ + currency0: CurrencyLibrary.ADDRESS_ZERO, + currency1: currency1, + fee: 3000, + tickSpacing: 10, + hooks: IHooks(address(0)) + }); + + lpm.initializePool(nativeKey, SQRT_PRICE_1_1); + + PositionConfig memory ethConfig = PositionConfig({poolKey: nativeKey, tickLower: -60, tickUpper: 60}); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(ethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode( + ethConfig.poolKey, + ethConfig.tickLower, + ethConfig.tickUpper, + liquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + // potential to actually map the amount to the open delta as well? + planner.add(Actions.UNWRAP, abi.encode(100 ether)); + + // weth9 payer is the contract + planner.add(Actions.SETTLE, abi.encode(address(0), ActionConstants.OPEN_DELTA, false)); + // other currency can close normally + planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + planner.add(Actions.SWEEP, abi.encode(address(0), ActionConstants.CONTRACT_BALANCE)); + bytes memory actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + uint256 balanceWethAfter = IERC20(address(_WETH9)).balanceOf(address(this)); + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + assertApproxEqAbs(balanceWethBefore - balanceWethAfter, 100 ether, 1 wei); + assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + } } diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol index e1a6aa77..14ea4528 100644 --- a/test/shared/PosmTestSetup.sol +++ b/test/shared/PosmTestSetup.sol @@ -19,6 +19,10 @@ import {PositionDescriptor} from "../../src/PositionDescriptor.sol"; import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; import {IWETH9} from "../../src/interfaces/external/IWETH9.sol"; import {WETH} from "solmate/src/tokens/WETH.sol"; +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; +import {SortTokens} from "@uniswap/v4-core/test/utils/SortTokens.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PositionConfig} from "../shared/PositionConfig.sol"; /// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { @@ -38,6 +42,9 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { ) ); + PoolKey wethKey; + PositionConfig wethConfig; + function deployPosmHookSavesDelta() public { HookSavesDelta impl = new HookSavesDelta(); vm.etch(hookAddr, address(impl).code); @@ -91,6 +98,19 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { vm.stopPrank(); } + function seedWeth(address to) internal { + vm.deal(address(this), STARTING_USER_BALANCE); + _WETH9.deposit{value: STARTING_USER_BALANCE}(); + _WETH9.transfer(to, STARTING_USER_BALANCE); + } + + function initWethPool(Currency currencyB, IHooks hooks, uint24 fee, uint160 sqrtPriceX96) internal { + (Currency _currency0, Currency _currency1) = + SortTokens.sort(MockERC20(address(_WETH9)), MockERC20(Currency.unwrap(currencyB))); + + (wethKey,) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96); + } + function permit(uint256 privateKey, uint256 tokenId, address operator, uint256 nonce) internal { bytes32 digest = getDigest(operator, tokenId, 1, block.timestamp + 1); From 4bbf7a0e131831c56fb9eb73d2847a7a62948b5d Mon Sep 17 00:00:00 2001 From: dianakocsis Date: Fri, 18 Oct 2024 10:21:31 -0400 Subject: [PATCH 03/14] fix addresses --- test/PositionDescriptor.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PositionDescriptor.t.sol b/test/PositionDescriptor.t.sol index dabe8393..b318ce14 100644 --- a/test/PositionDescriptor.t.sol +++ b/test/PositionDescriptor.t.sol @@ -122,7 +122,7 @@ contract PositionDescriptorTest is Test, PosmTestSetup, GasSnapshot { assertEq(token.name, "Uniswap - 0.3% - TEST/TEST - 1.0060<>1.0121"); assertEq( token.description, - unicode"This NFT represents a liquidity position in a Uniswap v4 TEST-TEST pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: 0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\nTEST Address: 0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\nTEST Address: 0x2e234dae75c793f67a35089c9d99245e1c58470b\nHook Address: 0x0000000000000000000000000000000000000000\nFee Tier: 0.3%\nToken ID: 1\n\n⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + unicode"This NFT represents a liquidity position in a Uniswap v4 TEST-TEST pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: 0x2e234dae75c793f67a35089c9d99245e1c58470b\nTEST Address: 0xf62849f9a0b5bf2913b396098f7c7019b51a820a\nTEST Address: 0xc7183455a4c133ae270771860664b6b7ec320bb1\nHook Address: 0x0000000000000000000000000000000000000000\nFee Tier: 0.3%\nToken ID: 1\n\n⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." ); } From ab872a370cf5c1e846aa816a2cf004220d25ecb7 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Fri, 18 Oct 2024 14:26:10 -0400 Subject: [PATCH 04/14] remove test --- .../PositionManager.modifyLiquidities.t.sol | 71 ------------------- 1 file changed, 71 deletions(-) diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index b9da60a7..5a4d2174 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -439,75 +439,4 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertApproxEqAbs(balance1After - balance1Before, 100 ether, 1 wei); assertEq(lpm.getPositionLiquidity(tokenId), 0); } - - // ETH-USDC pool with WETH input and output - // TODO: Pull Weth in with transferFrom call to permit2 - function test_weth_unwrap_add() public { - // eth-currency1 pool - // input: weth, currency1 - // modifyLiquidities call to mint liquidity weth and currency1 - // 1 _wrap - // 2 _mint - // 3 _settle weth where the payer is the contract - // 4 _close currency1, payer is caller - - uint256 tokenId = lpm.nextTokenId(); - - uint256 balanceWethBefore = IERC20(address(_WETH9)).balanceOf(address(this)); - uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); - - PoolKey memory nativeKey = PoolKey({ - currency0: CurrencyLibrary.ADDRESS_ZERO, - currency1: currency1, - fee: 3000, - tickSpacing: 10, - hooks: IHooks(address(0)) - }); - - lpm.initializePool(nativeKey, SQRT_PRICE_1_1); - - PositionConfig memory ethConfig = PositionConfig({poolKey: nativeKey, tickLower: -60, tickUpper: 60}); - - uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( - SQRT_PRICE_1_1, - TickMath.getSqrtPriceAtTick(ethConfig.tickLower), - TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), - 100 ether, - 100 ether - ); - - Plan memory planner = Planner.init(); - planner.add( - Actions.MINT_POSITION, - abi.encode( - ethConfig.poolKey, - ethConfig.tickLower, - ethConfig.tickUpper, - liquidityAmount, - MAX_SLIPPAGE_INCREASE, - MAX_SLIPPAGE_INCREASE, - ActionConstants.MSG_SENDER, - ZERO_BYTES - ) - ); - - // potential to actually map the amount to the open delta as well? - planner.add(Actions.UNWRAP, abi.encode(100 ether)); - - // weth9 payer is the contract - planner.add(Actions.SETTLE, abi.encode(address(0), ActionConstants.OPEN_DELTA, false)); - // other currency can close normally - planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); - planner.add(Actions.SWEEP, abi.encode(address(0), ActionConstants.CONTRACT_BALANCE)); - bytes memory actions = planner.encode(); - - lpm.modifyLiquidities(actions, _deadline); - - uint256 balanceWethAfter = IERC20(address(_WETH9)).balanceOf(address(this)); - uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); - - assertApproxEqAbs(balanceWethBefore - balanceWethAfter, 100 ether, 1 wei); - assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); - assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); - } } From 94d8f074b6589fd0ed204bcebb62d20528ca0569 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Fri, 18 Oct 2024 16:45:40 -0400 Subject: [PATCH 05/14] eth-weth update --- ...ger_increaseLiquidity_erc20_withClose.snap | 2 +- ...ncreaseLiquidity_erc20_withSettlePair.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- ...anager_mint_nativeWithSweep_withClose.snap | 2 +- ...r_mint_nativeWithSweep_withSettlePair.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- .../PositionManager_mint_withClose.snap | 2 +- .../PositionManager_mint_withSettlePair.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- src/PositionManager.sol | 20 ++++---------- src/base/DeltaResolver.sol | 27 +++++++++++++++++++ src/base/NativeWrapper.sol | 24 +++++++++++------ 16 files changed, 61 insertions(+), 36 deletions(-) diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap index 38fb0a72..33487c1d 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap @@ -1 +1 @@ -159137 \ No newline at end of file +159101 \ 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 80ee22f1..0183a157 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -1 +1 @@ -158077 \ No newline at end of file +158041 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 9fce9410..1ab92e5e 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -140934 \ No newline at end of file +140916 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index c5779ebc..880c3c39 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -364807 \ No newline at end of file +364789 \ 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 97a3a003..d7077771 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -1 +1 @@ -373326 \ No newline at end of file +373308 \ 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 4cb98c64..9c4273e3 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -1 +1 @@ -372549 \ No newline at end of file +372531 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 884380c2..94bb62b1 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -317697 \ No newline at end of file +317661 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index a24bf2ba..9167df6a 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -318367 \ No newline at end of file +318331 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index 60b8b165..cfcb3798 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -243936 \ No newline at end of file +243900 \ 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 cfcf7086..803f6d44 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -323728 \ No newline at end of file +323692 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withClose.snap b/.forge-snapshots/PositionManager_mint_withClose.snap index 3684ccf8..4d77ff7e 100644 --- a/.forge-snapshots/PositionManager_mint_withClose.snap +++ b/.forge-snapshots/PositionManager_mint_withClose.snap @@ -1 +1 @@ -420250 \ No newline at end of file +420214 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap index 6027fb7c..65a2792b 100644 --- a/.forge-snapshots/PositionManager_mint_withSettlePair.snap +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -1 +1 @@ -419308 \ No newline at end of file +419272 \ 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 07c741d0..410f01f2 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -456098 \ No newline at end of file +456062 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index c93fc273..008e25ca 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.26; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; @@ -248,12 +248,12 @@ contract PositionManager is _sweep(currency, _mapRecipient(to)); return; } else if (action == Actions.WRAP) { - (uint256 amount) = params.decodeUint256(); - _wrap(amount); + uint256 amount = params.decodeUint256(); + _wrap(_mapWrapUnwrapAmount(CurrencyLibrary.ADDRESS_ZERO, amount, Currency.wrap(address(WETH9)))); return; } else if (action == Actions.UNWRAP) { - (uint256 amount) = params.decodeUint256(); - _unwrap(amount); + uint256 amount = params.decodeUint256(); + _unwrap(_mapWrapUnwrapAmount(Currency.wrap(address(WETH9)), amount, CurrencyLibrary.ADDRESS_ZERO)); return; } } @@ -400,16 +400,6 @@ contract PositionManager is if (balance > 0) currency.transfer(to, balance); } - function _wrap(uint256 _amount) internal { - uint256 amount = _map(_amount, address(this).balance); - if (amount > 0) WETH9.deposit{value: amount}(); - } - - function _unwrap(uint256 _amount) internal { - uint256 amount = _map(_amount, WETH9.balanceOf(address(this))); - if (amount > 0) WETH9.withdraw(amount); - } - function _modifyLiquidity( PositionInfo info, PoolKey memory poolKey, diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index ccde3d4d..6856202d 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -16,6 +16,8 @@ abstract contract DeltaResolver is ImmutableState { error DeltaNotPositive(Currency currency); /// @notice Emitted trying to take a negative delta. error DeltaNotNegative(Currency currency); + /// @notice Emitted when the contract does not have enough balance to wrap or unwrap. + error InsufficientBalance(); /// @notice Take an amount of currency out of the PoolManager /// @param currency Currency to take @@ -87,4 +89,29 @@ abstract contract DeltaResolver is ImmutableState { return amount; } } + + /// @notice Calculates the sanitized amount before wrapping/unwrapping. + /// @param inputCurrency The currency, either native or wrapped native, that the contract holds + /// @param amount The amount to wrap or unwrap. Can be CONTRACT_BALANCE or OPEN_DELTA or a specific amount + /// @param outputCurrency The currency after the wrap/unwrap that the user may owe a balance in on the poolManager + function _mapWrapUnwrapAmount(Currency inputCurrency, uint256 amount, Currency outputCurrency) + internal + view + returns (uint256 _amount) + { + // if wrapping, the balance in this contract should be in ETH + // if unwrapping, the balance in this contract should be in WETH + uint256 balance = inputCurrency.balanceOf(address(this)); + if (amount == ActionConstants.CONTRACT_BALANCE) { + return balance; + } + if (amount == ActionConstants.OPEN_DELTA) { + // if wrapping, the open currency on the contract is WETH. + // if unwrapping, the open currency on the contract is ETH. + _amount = _getFullDebt(outputCurrency); + } else { + _amount = amount; + } + if (_amount > balance) revert InsufficientBalance(); + } } diff --git a/src/base/NativeWrapper.sol b/src/base/NativeWrapper.sol index 2e5bab69..93252c6f 100644 --- a/src/base/NativeWrapper.sol +++ b/src/base/NativeWrapper.sol @@ -3,23 +3,31 @@ pragma solidity ^0.8.0; import {IWETH9} from "../interfaces/external/IWETH9.sol"; import {ActionConstants} from "../libraries/ActionConstants.sol"; +import {ImmutableState} from "./ImmutableState.sol"; /// @title Native Wrapper -/// @notice Immutables and helpers for wrapping and unwrapping native -contract NativeWrapper { +/// @notice Used for wrapping and unwrapping native +abstract contract NativeWrapper is ImmutableState { IWETH9 public immutable WETH9; - error InsufficientBalance(); + /// @notice Thrown when a non-expected address sends ETH to this contract + error InvalidEthSender(); constructor(IWETH9 _weth9) { WETH9 = _weth9; } - function _map(uint256 amount, uint256 balance) internal pure returns (uint256) { - if (amount == ActionConstants.CONTRACT_BALANCE) return balance; - if (amount > balance) revert InsufficientBalance(); - return amount; + /// @dev The amount should already be <= the current balance in this contract. + function _wrap(uint256 amount) internal { + if (amount > 0) WETH9.deposit{value: amount}(); } - receive() external payable {} + /// @dev The amount should already be <= the current balance in this contract. + function _unwrap(uint256 amount) internal { + if (amount > 0) WETH9.withdraw(amount); + } + + receive() external payable { + if (msg.sender != address(WETH9) && msg.sender != address(poolManager)) revert InvalidEthSender(); + } } From 0070fcd2d1176e6e20720bd91cb060473d12e3dd Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Sun, 20 Oct 2024 17:53:21 -0400 Subject: [PATCH 06/14] comments --- src/base/DeltaResolver.sol | 11 ++++++----- src/base/NativeWrapper.sol | 3 ++- .../PositionManager.modifyLiquidities.t.sol | 3 --- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 6856202d..c9bfa739 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -91,7 +91,7 @@ abstract contract DeltaResolver is ImmutableState { } /// @notice Calculates the sanitized amount before wrapping/unwrapping. - /// @param inputCurrency The currency, either native or wrapped native, that the contract holds + /// @param inputCurrency The currency, either native or wrapped native, that this contract holds /// @param amount The amount to wrap or unwrap. Can be CONTRACT_BALANCE or OPEN_DELTA or a specific amount /// @param outputCurrency The currency after the wrap/unwrap that the user may owe a balance in on the poolManager function _mapWrapUnwrapAmount(Currency inputCurrency, uint256 amount, Currency outputCurrency) @@ -99,15 +99,16 @@ abstract contract DeltaResolver is ImmutableState { view returns (uint256 _amount) { - // if wrapping, the balance in this contract should be in ETH - // if unwrapping, the balance in this contract should be in WETH + // if wrapping, the balance in this is in ETH + // if unwrapping, the balance in this contract is in WETH uint256 balance = inputCurrency.balanceOf(address(this)); if (amount == ActionConstants.CONTRACT_BALANCE) { + // return early to avoid unnecessary balance check return balance; } if (amount == ActionConstants.OPEN_DELTA) { - // if wrapping, the open currency on the contract is WETH. - // if unwrapping, the open currency on the contract is ETH. + // if wrapping, the open currency on the PoolManager is WETH. + // if unwrapping, the open currency on the PoolManager is ETH. _amount = _getFullDebt(outputCurrency); } else { _amount = amount; diff --git a/src/base/NativeWrapper.sol b/src/base/NativeWrapper.sol index 93252c6f..ef6a3f2a 100644 --- a/src/base/NativeWrapper.sol +++ b/src/base/NativeWrapper.sol @@ -8,9 +8,10 @@ import {ImmutableState} from "./ImmutableState.sol"; /// @title Native Wrapper /// @notice Used for wrapping and unwrapping native abstract contract NativeWrapper is ImmutableState { + /// @notice The address for WETH9 IWETH9 public immutable WETH9; - /// @notice Thrown when a non-expected address sends ETH to this contract + /// @notice Thrown when an unexpected address sends ETH to this contract error InvalidEthSender(); constructor(IWETH9 _weth9) { diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index 5a4d2174..d3f4adbd 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -31,8 +31,6 @@ import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {Planner, Plan} from "../shared/Planner.sol"; -import "forge-std/console2.sol"; - contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityFuzzers { using StateLibrary for IPoolManager; using PoolIdLibrary for PoolKey; @@ -414,7 +412,6 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF uint256 balanceEthBefore = address(this).balance; uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); - console2.log(balance1Before); Plan memory planner = Planner.init(); planner.add( Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) From d643d657dfe4a11716e31cbda184b43917a83a32 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Sun, 20 Oct 2024 17:58:20 -0400 Subject: [PATCH 07/14] add decode fuzz test --- test/libraries/CalldataDecoder.t.sol | 7 +++++++ test/mocks/MockCalldataDecoder.sol | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index c41122eb..dd47a949 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -232,6 +232,13 @@ contract CalldataDecoderTest is Test { assertEq(amount, _amount); } + function test_fuzz_decodeUint256(uint256 _amount) public { + bytes memory params = abi.encode(_amount); + (uint256 amount) = decoder.decodeUint256(params); + + assertEq(amount, _amount); + } + function _assertEq(PathKey[] memory path1, PathKey[] memory path2) internal pure { assertEq(path1.length, path2.length); for (uint256 i = 0; i < path1.length; i++) { diff --git a/test/mocks/MockCalldataDecoder.sol b/test/mocks/MockCalldataDecoder.sol index e25d8ec7..c61db4af 100644 --- a/test/mocks/MockCalldataDecoder.sol +++ b/test/mocks/MockCalldataDecoder.sol @@ -136,4 +136,8 @@ contract MockCalldataDecoder { { return params.decodeCurrencyAddressAndUint256(); } + + function decodeUint256(bytes calldata params) external pure returns (uint256) { + return params.decodeUint256(); + } } From 204f515bd3db66cd8e2f7acdea9f848b3e76d344 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Sun, 20 Oct 2024 18:04:41 -0400 Subject: [PATCH 08/14] fmt --- test/libraries/CalldataDecoder.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index dd47a949..de100667 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -234,7 +234,7 @@ contract CalldataDecoderTest is Test { function test_fuzz_decodeUint256(uint256 _amount) public { bytes memory params = abi.encode(_amount); - (uint256 amount) = decoder.decodeUint256(params); + uint256 amount = decoder.decodeUint256(params); assertEq(amount, _amount); } From d62e3a8b34de07b286e2481664d96934b2e3df2f Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Sun, 20 Oct 2024 21:13:28 -0400 Subject: [PATCH 09/14] wrap tests --- src/base/DeltaResolver.sol | 2 +- .../PositionManager.modifyLiquidities.t.sol | 171 +++++++++++++++++- 2 files changed, 167 insertions(+), 6 deletions(-) diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index c9bfa739..4a20dae0 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -99,7 +99,7 @@ abstract contract DeltaResolver is ImmutableState { view returns (uint256 _amount) { - // if wrapping, the balance in this is in ETH + // if wrapping, the balance in this contract is in ETH // if unwrapping, the balance in this contract is in WETH uint256 balance = inputCurrency.balanceOf(address(this)); if (amount == ActionConstants.CONTRACT_BALANCE) { diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index d3f4adbd..46496db2 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -30,6 +30,9 @@ import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {Planner, Plan} from "../shared/Planner.sol"; +import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; + +import "forge-std/console2.sol"; contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityFuzzers { using StateLibrary for IPoolManager; @@ -328,15 +331,138 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF lpm.modifyLiquidities(calls, _deadline); } - function test_weth_wrap_increaseLiquidity() public { + function test_wrap_increaseLiquidity_usingContractBalance() public { // weth-currency1 pool initialized as wethKey // input: eth, currency1 // modifyLiquidities call to mint liquidity weth and currency1 - // 1 _wrap + // 1 _wrap with contract balance // 2 _mint // 3 _settle weth where the payer is the contract // 4 _close currency1, payer is caller - // Note there is no sweep encoding for weth, but that would be the safest action. + // 5 _sweep weth since eth was entirely wrapped + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + Plan memory planner = Planner.init(); + planner.add(Actions.WRAP, abi.encode(ActionConstants.CONTRACT_BALANCE)); + planner.add( + Actions.MINT_POSITION, + abi.encode( + wethConfig.poolKey, + wethConfig.tickLower, + wethConfig.tickUpper, + liquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + // weth9 payer is the contract + planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); + // other currency can close normally + planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + // we wrapped the full contract balance so we sweep back in the wrapped currency + planner.add(Actions.SWEEP, abi.encode(address(_WETH9), ActionConstants.MSG_SENDER)); + bytes memory actions = planner.encode(); + + // Overestimate eth amount. + lpm.modifyLiquidities{value: 102 ether}(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + // The full eth amount was "spent" because some was wrapped into weth and refunded. + assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 102 ether, 1 wei); + assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_wrap_increaseLiquidity_openDelta() public { + // weth-currency1 pool initialized as wethKey + // input: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _mint + // 2 _wrap with open delta + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + // 5 _sweep eth since only the open delta amount was wrapped + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + Plan memory planner = Planner.init(); + + planner.add( + Actions.MINT_POSITION, + abi.encode( + wethConfig.poolKey, + wethConfig.tickLower, + wethConfig.tickUpper, + liquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + + planner.add(Actions.WRAP, abi.encode(ActionConstants.OPEN_DELTA)); + + // weth9 payer is the contract + planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); + // other currency can close normally + planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + // we wrapped the full contract balance so we sweep back in the wrapped currency + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER)); + bytes memory actions = planner.encode(); + + lpm.modifyLiquidities{value: 102 ether}(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + + // Approx 100 eth was spent because the extra 2 were refunded. + assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 100 ether, 1 wei); + assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); + assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_wrap_increaseLiquidity_usingExactAmount() public { + // weth-currency1 pool initialized as wethKey + // input: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _wrap with an amount + // 2 _mint + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + // 5 _sweep weth since eth was entirely wrapped uint256 balanceEthBefore = address(this).balance; uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); @@ -351,7 +477,6 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF ); Plan memory planner = Planner.init(); - // potential to move below mint call and use open delta for amount planner.add(Actions.WRAP, abi.encode(100 ether)); planner.add( Actions.MINT_POSITION, @@ -371,6 +496,8 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); // other currency can close normally planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); + // we wrapped the full contract balance so we sweep back in the wrapped currency for safety measure + planner.add(Actions.SWEEP, abi.encode(address(_WETH9), ActionConstants.MSG_SENDER)); bytes memory actions = planner.encode(); lpm.modifyLiquidities{value: 100 ether}(actions, _deadline); @@ -378,11 +505,26 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF uint256 balanceEthAfter = address(this).balance; uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); - // Eth was spent. + // The full eth amount was "spent" because some was wrapped into weth and refunded. assertApproxEqAbs(balanceEthBefore - balanceEthAfter, 100 ether, 1 wei); assertApproxEqAbs(balance1Before - balance1After, 100 ether, 1 wei); assertEq(lpm.ownerOf(tokenId), address(this)); assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); + } + + function test_wrap_increaseLiquidity_revertsInsufficientBalance() public { + // 1 _wrap with more eth than is sent in + + Plan memory planner = Planner.init(); + // Wrap more eth than what is sent in. + planner.add(Actions.WRAP, abi.encode(101 ether)); + + bytes memory actions = planner.encode(); + + vm.expectRevert(DeltaResolver.InsufficientBalance.selector); + lpm.modifyLiquidities{value: 100 ether}(actions, _deadline); } function test_weth_burn_unwrap() public { @@ -436,4 +578,23 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertApproxEqAbs(balance1After - balance1Before, 100 ether, 1 wei); assertEq(lpm.getPositionLiquidity(tokenId), 0); } + + function test_weth_increase_wrap_openDelta() public { + // weth-currency1 pool initialized as wethKey + // input: eth, currency1 + // modifyLiquidities call to mint liquidity weth and currency1 + // 1 _wrap + // 2 _mint + // 3 _settle weth where the payer is the contract + // 4 _close currency1, payer is caller + // Note there is no sweep encoding for weth, but that would be the safest action. + } + + // Scenario 1 - WRAP + // Eth in + // Weth pool + + // 1.1 - Use contract balance + // 1.2 - Use open delta + // 1.3 - Amount > balance & Expect Revert } From f8bde43bb01ed3d9a07e4b5e9aafa8ff37ebdead Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Sun, 20 Oct 2024 22:12:29 -0400 Subject: [PATCH 10/14] unwrap tests --- src/base/DeltaResolver.sol | 1 + .../PositionManager.modifyLiquidities.t.sol | 126 +++++++++++++++--- test/shared/PosmTestSetup.sol | 1 - 3 files changed, 108 insertions(+), 20 deletions(-) diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 4a20dae0..49ec2a2a 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -109,6 +109,7 @@ abstract contract DeltaResolver is ImmutableState { if (amount == ActionConstants.OPEN_DELTA) { // if wrapping, the open currency on the PoolManager is WETH. // if unwrapping, the open currency on the PoolManager is ETH. + // note that we use the DEBT amount. positive deltas can be taken and then wrapped. _amount = _getFullDebt(outputCurrency); } else { _amount = amount; diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index 46496db2..c6e9eaf2 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -45,6 +45,8 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF address bob; PositionConfig config; + PositionConfig wethConfig; + PositionConfig nativeConfig; function setUp() public { (alice, alicePK) = makeAddrAndKey("ALICE"); @@ -69,13 +71,17 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF seedWeth(address(this)); approvePosmCurrency(Currency.wrap(address(_WETH9))); + + nativeKey = PoolKey(CurrencyLibrary.ADDRESS_ZERO, currency1, 3000, 60, IHooks(address(0))); + manager.initialize(nativeKey, SQRT_PRICE_1_1); + + config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); wethConfig = PositionConfig({ poolKey: wethKey, tickLower: TickMath.minUsableTick(wethKey.tickSpacing), tickUpper: TickMath.maxUsableTick(wethKey.tickSpacing) }); - - config = PositionConfig({poolKey: key, tickLower: -60, tickUpper: 60}); + nativeConfig = PositionConfig({poolKey: nativeKey, tickLower: -120, tickUpper: 120}); vm.deal(address(this), 1000 ether); } @@ -527,14 +533,14 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF lpm.modifyLiquidities{value: 100 ether}(actions, _deadline); } - function test_weth_burn_unwrap() public { + function test_unwrap_usingContractBalance() public { // weth-currency1 pool // output: eth, currency1 // modifyLiquidities call to mint liquidity weth and currency1 // 1 _burn // 2 _take where the weth is sent to the lpm contract // 3 _take where currency1 is sent to the msg sender - // 4 _unwrap + // 4 _unwrap using contract balance // 5 _sweep where eth is sent to msg sender uint256 tokenId = lpm.nextTokenId(); @@ -565,7 +571,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF abi.encode(address(Currency.unwrap(currency1)), ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA) ); planner.add(Actions.UNWRAP, abi.encode(ActionConstants.CONTRACT_BALANCE)); - planner.add(Actions.SWEEP, abi.encode(address(0), ActionConstants.MSG_SENDER)); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER)); actions = planner.encode(); @@ -577,24 +583,106 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertApproxEqAbs(balanceEthAfter - balanceEthBefore, 100 ether, 1 wei); assertApproxEqAbs(balance1After - balance1Before, 100 ether, 1 wei); assertEq(lpm.getPositionLiquidity(tokenId), 0); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); } - function test_weth_increase_wrap_openDelta() public { - // weth-currency1 pool initialized as wethKey - // input: eth, currency1 + function test_unwrap_openDelta_reinvest() public { + // weth-currency1 pool rolls half to eth-currency1 pool + // output: eth, currency1 // modifyLiquidities call to mint liquidity weth and currency1 - // 1 _wrap - // 2 _mint - // 3 _settle weth where the payer is the contract - // 4 _close currency1, payer is caller - // Note there is no sweep encoding for weth, but that would be the safest action. + // 1 _burn (weth-currency1) + // 2 _take where the weth is sent to the lpm contract + // 4 _mint to an eth pool + // 4 _unwrap using open delta (pool managers ETH balance) + // 3 _take where leftover currency1 is sent to the msg sender + // 5 _settle eth oprn delta + // 5 _sweep leftover weth + + uint256 tokenId = lpm.nextTokenId(); + + uint128 liquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(wethConfig.tickLower), + TickMath.getSqrtPriceAtTick(wethConfig.tickUpper), + 100 ether, + 100 ether + ); + + bytes memory actions = getMintEncoded(wethConfig, liquidityAmount, address(this), ZERO_BYTES); + lpm.modifyLiquidities(actions, _deadline); + + assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); + + uint256 balanceEthBefore = address(this).balance; + uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 balanceWethBefore = _WETH9.balanceOf(address(this)); + + uint128 newLiquidityAmount = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(nativeConfig.tickLower), + TickMath.getSqrtPriceAtTick(nativeConfig.tickUpper), + 50 ether, + 50 ether + ); + + Plan memory planner = Planner.init(); + planner.add( + Actions.BURN_POSITION, abi.encode(tokenId, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) + ); + // take the weth to the position manager to be unwrapped + planner.add(Actions.TAKE, abi.encode(address(_WETH9), ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)); + planner.add( + Actions.MINT_POSITION, + abi.encode( + nativeConfig.poolKey, + nativeConfig.tickLower, + nativeConfig.tickUpper, + newLiquidityAmount, + MAX_SLIPPAGE_INCREASE, + MAX_SLIPPAGE_INCREASE, + ActionConstants.MSG_SENDER, + ZERO_BYTES + ) + ); + planner.add(Actions.UNWRAP, abi.encode(ActionConstants.OPEN_DELTA)); + // pay the eth + planner.add(Actions.SETTLE, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.OPEN_DELTA, false)); + // take the leftover currency1 + planner.add( + Actions.TAKE, + abi.encode(address(Currency.unwrap(currency1)), ActionConstants.MSG_SENDER, ActionConstants.OPEN_DELTA) + ); + planner.add(Actions.SWEEP, abi.encode(address(_WETH9), ActionConstants.MSG_SENDER)); + + actions = planner.encode(); + + lpm.modifyLiquidities(actions, _deadline); + + uint256 balanceEthAfter = address(this).balance; + uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this)); + uint256 balanceWethAfter = _WETH9.balanceOf(address(this)); + + // Eth balance should not change. + assertEq(balanceEthAfter, balanceEthBefore); + // Only half of the original liquidity was reinvested. + assertApproxEqAbs(balance1After - balance1Before, 50 ether, 1 wei); + assertApproxEqAbs(balanceWethAfter - balanceWethBefore, 50 ether, 1 wei); + assertEq(lpm.getPositionLiquidity(tokenId), 0); + assertEq(_WETH9.balanceOf(address(lpm)), 0); + assertEq(address(lpm).balance, 0); } - // Scenario 1 - WRAP - // Eth in - // Weth pool + function test_unwrap_revertsInsufficientBalance() public { + // 1 _unwrap with more than is in the contract - // 1.1 - Use contract balance - // 1.2 - Use open delta - // 1.3 - Amount > balance & Expect Revert + Plan memory planner = Planner.init(); + // unwraps more eth than what is in the contract + planner.add(Actions.UNWRAP, abi.encode(101 ether)); + + bytes memory actions = planner.encode(); + + vm.expectRevert(DeltaResolver.InsufficientBalance.selector); + lpm.modifyLiquidities(actions, _deadline); + } } diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol index 14ea4528..22e09ed9 100644 --- a/test/shared/PosmTestSetup.sol +++ b/test/shared/PosmTestSetup.sol @@ -43,7 +43,6 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { ); PoolKey wethKey; - PositionConfig wethConfig; function deployPosmHookSavesDelta() public { HookSavesDelta impl = new HookSavesDelta(); From 30267f6641ed3ef4475d4f184f673996728b66f4 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Sun, 20 Oct 2024 22:48:17 -0400 Subject: [PATCH 11/14] use amount --- src/base/DeltaResolver.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 49ec2a2a..5c6bc219 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -97,7 +97,7 @@ abstract contract DeltaResolver is ImmutableState { function _mapWrapUnwrapAmount(Currency inputCurrency, uint256 amount, Currency outputCurrency) internal view - returns (uint256 _amount) + returns (uint256) { // if wrapping, the balance in this contract is in ETH // if unwrapping, the balance in this contract is in WETH @@ -110,10 +110,9 @@ abstract contract DeltaResolver is ImmutableState { // if wrapping, the open currency on the PoolManager is WETH. // if unwrapping, the open currency on the PoolManager is ETH. // note that we use the DEBT amount. positive deltas can be taken and then wrapped. - _amount = _getFullDebt(outputCurrency); - } else { - _amount = amount; + amount = _getFullDebt(outputCurrency); } - if (_amount > balance) revert InsufficientBalance(); + if (amount > balance) revert InsufficientBalance(); + return amount; } } From 12e07a8281a4debb101179046a2cf8d466fd5574 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Mon, 21 Oct 2024 16:48:43 -0400 Subject: [PATCH 12/14] final comments --- src/base/DeltaResolver.sol | 4 ++-- .../position-managers/PositionManager.modifyLiquidities.t.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 5c6bc219..718995d9 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -92,7 +92,7 @@ abstract contract DeltaResolver is ImmutableState { /// @notice Calculates the sanitized amount before wrapping/unwrapping. /// @param inputCurrency The currency, either native or wrapped native, that this contract holds - /// @param amount The amount to wrap or unwrap. Can be CONTRACT_BALANCE or OPEN_DELTA or a specific amount + /// @param amount The amount to wrap or unwrap. Can be CONTRACT_BALANCE, OPEN_DELTA or a specific amount /// @param outputCurrency The currency after the wrap/unwrap that the user may owe a balance in on the poolManager function _mapWrapUnwrapAmount(Currency inputCurrency, uint256 amount, Currency outputCurrency) internal @@ -109,7 +109,7 @@ abstract contract DeltaResolver is ImmutableState { if (amount == ActionConstants.OPEN_DELTA) { // if wrapping, the open currency on the PoolManager is WETH. // if unwrapping, the open currency on the PoolManager is ETH. - // note that we use the DEBT amount. positive deltas can be taken and then wrapped. + // note that we use the DEBT amount. Positive deltas can be taken and then wrapped. amount = _getFullDebt(outputCurrency); } if (amount > balance) revert InsufficientBalance(); diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index c6e9eaf2..a8f89738 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -596,7 +596,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // 4 _mint to an eth pool // 4 _unwrap using open delta (pool managers ETH balance) // 3 _take where leftover currency1 is sent to the msg sender - // 5 _settle eth oprn delta + // 5 _settle eth open delta // 5 _sweep leftover weth uint256 tokenId = lpm.nextTokenId(); From 6261e17929c57612a7f3c2019861f13223d1ea7f Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Thu, 24 Oct 2024 15:33:08 -0400 Subject: [PATCH 13/14] update comment --- .../PositionManager.modifyLiquidities.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index a8f89738..a6d0282d 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -337,7 +337,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF lpm.modifyLiquidities(calls, _deadline); } - function test_wrap_increaseLiquidity_usingContractBalance() public { + function test_wrap_mint_usingContractBalance() public { // weth-currency1 pool initialized as wethKey // input: eth, currency1 // modifyLiquidities call to mint liquidity weth and currency1 @@ -442,7 +442,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); // other currency can close normally planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); - // we wrapped the full contract balance so we sweep back in the wrapped currency + // we wrapped the open delta balance so we sweep back in the native currency planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.ADDRESS_ZERO, ActionConstants.MSG_SENDER)); bytes memory actions = planner.encode(); @@ -502,7 +502,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF planner.add(Actions.SETTLE, abi.encode(address(_WETH9), ActionConstants.OPEN_DELTA, false)); // other currency can close normally planner.add(Actions.CLOSE_CURRENCY, abi.encode(currency1)); - // we wrapped the full contract balance so we sweep back in the wrapped currency for safety measure + // we wrapped all 100 eth so we sweep back in the wrapped currency for safety measure planner.add(Actions.SWEEP, abi.encode(address(_WETH9), ActionConstants.MSG_SENDER)); bytes memory actions = planner.encode(); From a63a74eb19444e1e84a684275d3c118390addab1 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Thu, 24 Oct 2024 16:15:24 -0400 Subject: [PATCH 14/14] test naming update --- .../PositionManager.modifyLiquidities.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index a6d0282d..c805dd27 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -398,7 +398,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertEq(address(lpm).balance, 0); } - function test_wrap_increaseLiquidity_openDelta() public { + function test_wrap_mint_openDelta() public { // weth-currency1 pool initialized as wethKey // input: eth, currency1 // modifyLiquidities call to mint liquidity weth and currency1 @@ -460,7 +460,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertEq(address(lpm).balance, 0); } - function test_wrap_increaseLiquidity_usingExactAmount() public { + function test_wrap_mint_usingExactAmount() public { // weth-currency1 pool initialized as wethKey // input: eth, currency1 // modifyLiquidities call to mint liquidity weth and currency1 @@ -520,7 +520,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertEq(address(lpm).balance, 0); } - function test_wrap_increaseLiquidity_revertsInsufficientBalance() public { + function test_wrap_mint_revertsInsufficientBalance() public { // 1 _wrap with more eth than is sent in Plan memory planner = Planner.init();