diff --git a/foundry.toml b/foundry.toml index 30dff3ef..a77b97d5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,15 +1,27 @@ [profile.default] out = 'foundry-out' solc_version = '0.8.26' -optimizer_runs = 1 +optimizer_runs = 44444444 via_ir = true ffi = true -fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] +fs_permissions = [{ access = "read-write", path = ".forge-snapshots/" }, { access = "read", path = "foundry-out/" }] evm_version = "cancun" gas_limit = "3000000000" fuzz_runs = 10_000 bytecode_hash = "none" +additional_compiler_profiles = [ + { name = "posm", via_ir = true, optimizer_runs = 30000 }, + { name = "descriptor", via_ir = true, optimizer_runs = 1 }, + { name = "test", via_ir = false } +] + +compilation_restrictions = [ + { paths = "src/PositionManager.sol", optimizer_runs = 30000 }, + { paths = "src/PositionDescriptor.sol", optimizer_runs = 1 }, + { paths = "test/**", via_ir = false } +] + [profile.debug] via_ir = false optimizer_runs = 200 diff --git a/script/DeployPosm.s.sol b/script/DeployPosm.s.sol index b87fbea0..c51c2ca4 100644 --- a/script/DeployPosm.s.sol +++ b/script/DeployPosm.s.sol @@ -5,11 +5,8 @@ import "forge-std/console2.sol"; import "forge-std/Script.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {StateView} from "../src/lens/StateView.sol"; -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 {Deploy, IPositionDescriptor, IPositionManager} from "../test/shared/Deploy.sol"; import {IWETH9} from "../src/interfaces/external/IWETH9.sol"; contract DeployPosmTest is Script { @@ -21,18 +18,14 @@ contract DeployPosmTest is Script { uint256 unsubscribeGasLimit, address wrappedNative, string memory nativeCurrencyLabel - ) public returns (PositionDescriptor positionDescriptor, PositionManager posm) { + ) public returns (IPositionDescriptor positionDescriptor, IPositionManager posm) { vm.startBroadcast(); - positionDescriptor = new PositionDescriptor(IPoolManager(poolManager), wrappedNative, nativeCurrencyLabel); + positionDescriptor = Deploy.positionDescriptor(poolManager, wrappedNative, nativeCurrencyLabel, hex"00"); console2.log("PositionDescriptor", address(positionDescriptor)); - posm = new PositionManager{salt: hex"03"}( - IPoolManager(poolManager), - IAllowanceTransfer(permit2), - unsubscribeGasLimit, - IPositionDescriptor(address(positionDescriptor)), - IWETH9(wrappedNative) + posm = Deploy.positionManager( + poolManager, permit2, unsubscribeGasLimit, address(positionDescriptor), wrappedNative, hex"03" ); console2.log("PositionManager", address(posm)); diff --git a/script/DeployStateView.s.sol b/script/DeployStateView.s.sol index 9584099d..230bd819 100644 --- a/script/DeployStateView.s.sol +++ b/script/DeployStateView.s.sol @@ -4,17 +4,16 @@ pragma solidity ^0.8.20; import "forge-std/console2.sol"; import "forge-std/Script.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {StateView} from "../src/lens/StateView.sol"; +import {Deploy, IStateView} from "../test/shared/Deploy.sol"; contract DeployStateView is Script { function setUp() public {} - function run(address poolManager) public returns (StateView state) { + function run(address poolManager) public returns (IStateView state) { vm.startBroadcast(); // forge script --broadcast --sig 'run(address)' --rpc-url --private-key --verify script/DeployStateView.s.sol:DeployStateView - state = new StateView(IPoolManager(poolManager)); + state = Deploy.stateView(poolManager, hex"00"); console2.log("StateView", address(state)); console2.log("PoolManager", address(state.poolManager())); diff --git a/script/DeployV4Quoter.s.sol b/script/DeployV4Quoter.s.sol index 7cc61d5f..72361df6 100644 --- a/script/DeployV4Quoter.s.sol +++ b/script/DeployV4Quoter.s.sol @@ -4,17 +4,16 @@ pragma solidity ^0.8.20; import "forge-std/console2.sol"; import "forge-std/Script.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {V4Quoter} from "../src/lens/V4Quoter.sol"; +import {Deploy, IV4Quoter} from "../test/shared/Deploy.sol"; contract DeployV4Quoter is Script { function setUp() public {} - function run(address poolManager) public returns (V4Quoter state) { + function run(address poolManager) public returns (IV4Quoter state) { vm.startBroadcast(); // forge script --broadcast --sig 'run(address)' --rpc-url --private-key --verify script/DeployV4Quoter.s.sol:DeployV4Quoter - state = new V4Quoter(IPoolManager(poolManager)); + state = Deploy.v4Quoter(poolManager, hex"00"); console2.log("V4Quoter", address(state)); console2.log("PoolManager", address(state.poolManager())); diff --git a/snapshots/BaseActionsRouterTest.json b/snapshots/BaseActionsRouterTest.json index 7290bb57..d7241dbe 100644 --- a/snapshots/BaseActionsRouterTest.json +++ b/snapshots/BaseActionsRouterTest.json @@ -1,3 +1,3 @@ { - "BaseActionsRouter_mock10commands": "61332" + "BaseActionsRouter_mock10commands": "63076" } \ No newline at end of file diff --git a/snapshots/PaymentsTests.json b/snapshots/PaymentsTests.json index f2523f09..c3e810d1 100644 --- a/snapshots/PaymentsTests.json +++ b/snapshots/PaymentsTests.json @@ -1,6 +1,6 @@ { - "Payments_swap_settleFromCaller_takeAllToMsgSender": "132997", - "Payments_swap_settleFromCaller_takeAllToSpecifiedAddress": "134973", - "Payments_swap_settleWithBalance_takeAllToMsgSender": "127102", - "Payments_swap_settleWithBalance_takeAllToSpecifiedAddress": "127212" + "Payments_swap_settleFromCaller_takeAllToMsgSender": "133006", + "Payments_swap_settleFromCaller_takeAllToSpecifiedAddress": "134475", + "Payments_swap_settleWithBalance_takeAllToMsgSender": "126957", + "Payments_swap_settleWithBalance_takeAllToSpecifiedAddress": "127095" } \ No newline at end of file diff --git a/snapshots/PosMGasTest.json b/snapshots/PosMGasTest.json index 10bfc9b2..195b2104 100644 --- a/snapshots/PosMGasTest.json +++ b/snapshots/PosMGasTest.json @@ -1,42 +1,42 @@ { - "PositionManager_burn_empty": "51576", - "PositionManager_burn_empty_native": "51576", - "PositionManager_burn_nonEmpty_native_withClose": "128316", - "PositionManager_burn_nonEmpty_native_withTakePair": "127696", - "PositionManager_burn_nonEmpty_withClose": "135236", - "PositionManager_burn_nonEmpty_withTakePair": "134615", - "PositionManager_collect_native": "148571", - "PositionManager_collect_sameRange": "157220", - "PositionManager_collect_withClose": "157220", - "PositionManager_collect_withTakePair": "156456", - "PositionManager_decreaseLiquidity_native": "114165", - "PositionManager_decreaseLiquidity_withClose": "122555", - "PositionManager_decreaseLiquidity_withTakePair": "121791", - "PositionManager_decrease_burnEmpty": "138177", - "PositionManager_decrease_burnEmpty_native": "131258", - "PositionManager_decrease_sameRange_allLiquidity": "135218", - "PositionManager_decrease_take_take": "123169", - "PositionManager_increaseLiquidity_erc20_withClose": "162419", - "PositionManager_increaseLiquidity_erc20_withSettlePair": "161283", - "PositionManager_increaseLiquidity_native": "145296", - "PositionManager_increase_autocompoundExactUnclaimedFees": "138010", - "PositionManager_increase_autocompoundExcessFeesCredit": "180194", - "PositionManager_increase_autocompound_clearExcess": "150782", - "PositionManager_mint_native": "369623", - "PositionManager_mint_nativeWithSweep_withClose": "378280", - "PositionManager_mint_nativeWithSweep_withSettlePair": "377364", - "PositionManager_mint_onSameTickLower": "321205", - "PositionManager_mint_onSameTickUpper": "321875", - "PositionManager_mint_sameRange": "247444", - "PositionManager_mint_settleWithBalance_sweep": "423280", - "PositionManager_mint_warmedPool_differentRange": "327236", - "PositionManager_mint_withClose": "423986", - "PositionManager_mint_withSettlePair": "422936", - "PositionManager_multicall_initialize_mint": "460558", - "PositionManager_permit": "79259", - "PositionManager_permit_secondPosition": "62159", - "PositionManager_permit_twice": "45035", - "PositionManager_subscribe": "88475", - "PositionManager_unsubscribe": "63253", - "positionManager bytecode size": "19060" + "PositionManager_burn_empty": "51102", + "PositionManager_burn_empty_native": "51102", + "PositionManager_burn_nonEmpty_native_withClose": "127695", + "PositionManager_burn_nonEmpty_native_withTakePair": "127137", + "PositionManager_burn_nonEmpty_withClose": "134600", + "PositionManager_burn_nonEmpty_withTakePair": "134043", + "PositionManager_collect_native": "147732", + "PositionManager_collect_sameRange": "156364", + "PositionManager_collect_withClose": "156364", + "PositionManager_collect_withTakePair": "155679", + "PositionManager_decreaseLiquidity_native": "113681", + "PositionManager_decreaseLiquidity_withClose": "121933", + "PositionManager_decreaseLiquidity_withTakePair": "121248", + "PositionManager_decrease_burnEmpty": "137500", + "PositionManager_decrease_burnEmpty_native": "130595", + "PositionManager_decrease_sameRange_allLiquidity": "134649", + "PositionManager_decrease_take_take": "122490", + "PositionManager_increaseLiquidity_erc20_withClose": "160652", + "PositionManager_increaseLiquidity_erc20_withSettlePair": "159563", + "PositionManager_increaseLiquidity_native": "143532", + "PositionManager_increase_autocompoundExactUnclaimedFees": "138231", + "PositionManager_increase_autocompoundExcessFeesCredit": "179593", + "PositionManager_increase_autocompound_clearExcess": "149511", + "PositionManager_mint_native": "367550", + "PositionManager_mint_nativeWithSweep_withClose": "376153", + "PositionManager_mint_nativeWithSweep_withSettlePair": "375348", + "PositionManager_mint_onSameTickLower": "319298", + "PositionManager_mint_onSameTickUpper": "319940", + "PositionManager_mint_sameRange": "245522", + "PositionManager_mint_settleWithBalance_sweep": "420760", + "PositionManager_mint_warmedPool_differentRange": "325316", + "PositionManager_mint_withClose": "421910", + "PositionManager_mint_withSettlePair": "420939", + "PositionManager_multicall_initialize_mint": "458237", + "PositionManager_permit": "79175", + "PositionManager_permit_secondPosition": "62063", + "PositionManager_permit_twice": "44975", + "PositionManager_subscribe": "87968", + "PositionManager_unsubscribe": "62697", + "positionManager bytecode size": "23877" } \ No newline at end of file diff --git a/snapshots/PositionDescriptorTest.json b/snapshots/PositionDescriptorTest.json index 32cd6922..5be19f6a 100644 --- a/snapshots/PositionDescriptorTest.json +++ b/snapshots/PositionDescriptorTest.json @@ -1,3 +1,3 @@ { - "positionDescriptor bytecode size": "24179" + "positionDescriptor bytecode size": "24110" } \ No newline at end of file diff --git a/snapshots/QuoterTest.json b/snapshots/QuoterTest.json index 34049732..a3c80a4f 100644 --- a/snapshots/QuoterTest.json +++ b/snapshots/QuoterTest.json @@ -1,15 +1,15 @@ { - "Quoter_exactInputSingle_oneForZero_multiplePositions": "146317", - "Quoter_exactInputSingle_zeroForOne_multiplePositions": "151973", - "Quoter_exactOutputSingle_oneForZero": "80048", - "Quoter_exactOutputSingle_zeroForOne": "84626", - "Quoter_quoteExactInput_oneHop_1TickLoaded": "122994", - "Quoter_quoteExactInput_oneHop_initializedAfter": "147949", - "Quoter_quoteExactInput_oneHop_startingInitialized": "81420", - "Quoter_quoteExactInput_twoHops": "205421", - "Quoter_quoteExactOutput_oneHop_1TickLoaded": "122296", - "Quoter_quoteExactOutput_oneHop_2TicksLoaded": "152648", - "Quoter_quoteExactOutput_oneHop_initializedAfter": "122364", - "Quoter_quoteExactOutput_oneHop_startingInitialized": "98875", - "Quoter_quoteExactOutput_twoHops": "204897" + "Quoter_exactInputSingle_oneForZero_multiplePositions": "145902", + "Quoter_exactInputSingle_zeroForOne_multiplePositions": "152117", + "Quoter_exactOutputSingle_oneForZero": "79267", + "Quoter_exactOutputSingle_zeroForOne": "84512", + "Quoter_quoteExactInput_oneHop_1TickLoaded": "122728", + "Quoter_quoteExactInput_oneHop_initializedAfter": "147363", + "Quoter_quoteExactInput_oneHop_startingInitialized": "80638", + "Quoter_quoteExactInput_twoHops": "204942", + "Quoter_quoteExactOutput_oneHop_1TickLoaded": "122224", + "Quoter_quoteExactOutput_oneHop_2TicksLoaded": "152879", + "Quoter_quoteExactOutput_oneHop_initializedAfter": "122251", + "Quoter_quoteExactOutput_oneHop_startingInitialized": "98545", + "Quoter_quoteExactOutput_twoHops": "204670" } \ No newline at end of file diff --git a/snapshots/StateViewTest.json b/snapshots/StateViewTest.json index 8da85637..dc141e39 100644 --- a/snapshots/StateViewTest.json +++ b/snapshots/StateViewTest.json @@ -1,12 +1,12 @@ { - "StateView_extsload_getFeeGrowthGlobals": "2367", - "StateView_extsload_getFeeGrowthInside": "8444", - "StateView_extsload_getLiquidity": "1480", - "StateView_extsload_getPositionInfo": "2973", - "StateView_extsload_getPositionLiquidity": "1750", - "StateView_extsload_getSlot0": "1548", - "StateView_extsload_getTickBitmap": "1476", - "StateView_extsload_getTickFeeGrowthOutside": "2672", - "StateView_extsload_getTickInfo": "2896", - "StateView_extsload_getTickLiquidity": "1748" + "StateView_extsload_getFeeGrowthGlobals": "2203", + "StateView_extsload_getFeeGrowthInside": "7875", + "StateView_extsload_getLiquidity": "1439", + "StateView_extsload_getPositionInfo": "2761", + "StateView_extsload_getPositionLiquidity": "1691", + "StateView_extsload_getSlot0": "1486", + "StateView_extsload_getTickBitmap": "1432", + "StateView_extsload_getTickFeeGrowthOutside": "2490", + "StateView_extsload_getTickInfo": "2693", + "StateView_extsload_getTickLiquidity": "1686" } \ No newline at end of file diff --git a/snapshots/V4RouterTest.json b/snapshots/V4RouterTest.json index 0b39471b..ff5612e8 100644 --- a/snapshots/V4RouterTest.json +++ b/snapshots/V4RouterTest.json @@ -1,26 +1,26 @@ { - "V4Router_Bytecode": "5137", - "V4Router_ExactIn1Hop_nativeIn": "121819", - "V4Router_ExactIn1Hop_nativeOut": "120826", - "V4Router_ExactIn1Hop_oneForZero": "129715", - "V4Router_ExactIn1Hop_zeroForOne": "135623", - "V4Router_ExactIn2Hops": "191979", - "V4Router_ExactIn2Hops_nativeIn": "178175", - "V4Router_ExactIn3Hops": "248385", - "V4Router_ExactIn3Hops_nativeIn": "234581", - "V4Router_ExactInputSingle": "134546", - "V4Router_ExactInputSingle_nativeIn": "120742", - "V4Router_ExactInputSingle_nativeOut": "119714", - "V4Router_ExactOut1Hop_nativeIn_sweepETH": "128078", - "V4Router_ExactOut1Hop_nativeOut": "121895", - "V4Router_ExactOut1Hop_oneForZero": "130784", - "V4Router_ExactOut1Hop_zeroForOne": "134905", - "V4Router_ExactOut2Hops": "190319", - "V4Router_ExactOut2Hops_nativeIn": "183492", - "V4Router_ExactOut3Hops": "245811", - "V4Router_ExactOut3Hops_nativeIn": "238984", - "V4Router_ExactOut3Hops_nativeOut": "224567", - "V4Router_ExactOutputSingle": "133809", - "V4Router_ExactOutputSingle_nativeIn_sweepETH": "126982", - "V4Router_ExactOutputSingle_nativeOut": "120876" + "V4Router_Bytecode": "8975", + "V4Router_ExactIn1Hop_nativeIn": "121653", + "V4Router_ExactIn1Hop_nativeOut": "120119", + "V4Router_ExactIn1Hop_oneForZero": "128991", + "V4Router_ExactIn1Hop_zeroForOne": "135517", + "V4Router_ExactIn2Hops": "192843", + "V4Router_ExactIn2Hops_nativeIn": "178979", + "V4Router_ExactIn3Hops": "250218", + "V4Router_ExactIn3Hops_nativeIn": "236354", + "V4Router_ExactInputSingle": "134001", + "V4Router_ExactInputSingle_nativeIn": "120137", + "V4Router_ExactInputSingle_nativeOut": "118581", + "V4Router_ExactOut1Hop_nativeIn_sweepETH": "127940", + "V4Router_ExactOut1Hop_nativeOut": "121329", + "V4Router_ExactOut1Hop_oneForZero": "130201", + "V4Router_ExactOut1Hop_zeroForOne": "134858", + "V4Router_ExactOut2Hops": "191291", + "V4Router_ExactOut2Hops_nativeIn": "184373", + "V4Router_ExactOut3Hops": "247788", + "V4Router_ExactOut3Hops_nativeIn": "240870", + "V4Router_ExactOut3Hops_nativeOut": "224943", + "V4Router_ExactOutputSingle": "133337", + "V4Router_ExactOutputSingle_nativeIn_sweepETH": "126419", + "V4Router_ExactOutputSingle_nativeOut": "119821" } \ No newline at end of file diff --git a/src/PositionDescriptor.sol b/src/PositionDescriptor.sol index 021cbd5e..5dbbe151 100644 --- a/src/PositionDescriptor.sol +++ b/src/PositionDescriptor.sol @@ -21,8 +21,6 @@ contract PositionDescriptor is IPositionDescriptor { using CurrencyLibrary for Currency; using PositionInfoLibrary for PositionInfo; - error InvalidTokenId(uint256 tokenId); - // mainnet addresses address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; @@ -87,18 +85,12 @@ contract PositionDescriptor is IPositionDescriptor { ); } - /// @notice Returns true if currency0 has higher priority than currency1 - /// @param currency0 The first currency address - /// @param currency1 The second currency address - /// @return flipRatio True if currency0 has higher priority than currency1 + /// @inheritdoc IPositionDescriptor function flipRatio(address currency0, address currency1) public view returns (bool) { return currencyRatioPriority(currency0) > currencyRatioPriority(currency1); } - /// @notice Returns the priority of a currency. - /// For certain currencies on mainnet, the smaller the currency, the higher the priority - /// @param currency The currency address - /// @return priority The priority of the currency + /// @inheritdoc IPositionDescriptor function currencyRatioPriority(address currency) public view returns (int256) { // Currencies in order of priority on mainnet: USDC, USDT, DAI, (ETH, WETH), TBTC, WBTC // wrapped native is different address on different chains. passed in constructor diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol index 52bacdb3..55e57742 100644 --- a/src/base/BaseV4Quoter.sol +++ b/src/base/BaseV4Quoter.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; diff --git a/src/base/Permit2Forwarder.sol b/src/base/Permit2Forwarder.sol index d5a749a8..43cdc1ce 100644 --- a/src/base/Permit2Forwarder.sol +++ b/src/base/Permit2Forwarder.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {IPermit2Forwarder, IAllowanceTransfer} from "../interfaces/IPermit2Forwarder.sol"; -/// @notice PermitForwarder allows permitting this contract as a spender on permit2 +/// @notice Permit2Forwarder 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 { +contract Permit2Forwarder is IPermit2Forwarder { /// @notice the Permit2 contract to forward approvals IAllowanceTransfer public immutable permit2; @@ -13,11 +13,7 @@ contract Permit2Forwarder { permit2 = _permit2; } - /// @notice allows forwarding a single permit to permit2 - /// @dev this function is payable to allow multicall with NATIVE based actions - /// @param owner the owner of the tokens - /// @param permitSingle the permit data - /// @param signature the signature of the permit; abi.encodePacked(r, s, v) + /// @inheritdoc IPermit2Forwarder function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature) external payable @@ -30,11 +26,7 @@ contract Permit2Forwarder { } } - /// @notice allows forwarding batch permits to permit2 - /// @dev this function is payable to allow multicall with NATIVE based actions - /// @param owner the owner of the tokens - /// @param _permitBatch a batch of approvals - /// @param signature the signature of the permit; abi.encodePacked(r, s, v) + /// @inheritdoc IPermit2Forwarder function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature) external payable diff --git a/src/base/PoolInitializer.sol b/src/base/PoolInitializer.sol index fea60340..5212afc6 100644 --- a/src/base/PoolInitializer.sol +++ b/src/base/PoolInitializer.sol @@ -2,18 +2,14 @@ pragma solidity ^0.8.0; import {ImmutableState} from "./ImmutableState.sol"; - import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IPoolInitializer} from "../interfaces/IPoolInitializer.sol"; /// @title Pool Initializer /// @notice Initializes a Uniswap v4 Pool /// @dev Enables create pool + mint liquidity in a single transaction with multicall -abstract contract PoolInitializer is ImmutableState { - /// @notice Initialize a Uniswap v4 Pool - /// @dev If the pool is already initialized, this function will not revert and just return type(int24).max - /// @param key the PoolKey of the pool to initialize - /// @param sqrtPriceX96 the initial sqrtPriceX96 of the pool - /// @return tick The current tick of the pool +abstract contract PoolInitializer is ImmutableState, IPoolInitializer { + /// @inheritdoc IPoolInitializer function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24) { try poolManager.initialize(key, sqrtPriceX96) returns (int24 tick) { return tick; diff --git a/src/base/UnorderedNonce.sol b/src/base/UnorderedNonce.sol index b08b5d92..53b93b96 100644 --- a/src/base/UnorderedNonce.sol +++ b/src/base/UnorderedNonce.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {IUnorderedNonce} from "../interfaces/IUnorderedNonce.sol"; + /// @title Unordered Nonce /// @notice Contract state and methods for using unordered nonces in signatures -contract UnorderedNonce { - error NonceAlreadyUsed(); - - /// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap - /// @dev word is at most type(uint248).max +contract UnorderedNonce is IUnorderedNonce { + /// @inheritdoc IUnorderedNonce mapping(address owner => mapping(uint256 word => uint256 bitmap)) public nonces; /// @notice Consume a nonce, reverting if it has already been used @@ -22,9 +21,7 @@ contract UnorderedNonce { if (flipped & bit == 0) revert NonceAlreadyUsed(); } - /// @notice Revoke a nonce by spending it, preventing it from being used again - /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce - /// @dev payable so it can be multicalled with native-token related actions + /// @inheritdoc IUnorderedNonce function revokeNonce(uint256 nonce) external payable { _useUnorderedNonce(msg.sender, nonce); } diff --git a/src/interfaces/IPermit2Forwarder.sol b/src/interfaces/IPermit2Forwarder.sol new file mode 100644 index 00000000..98403ac8 --- /dev/null +++ b/src/interfaces/IPermit2Forwarder.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +/// @title IPermit2Forwarder +/// @notice Interface for the Permit2Forwarder contract +interface IPermit2Forwarder { + /// @notice allows forwarding a single permit to permit2 + /// @dev this function is payable to allow multicall with NATIVE based actions + /// @param owner the owner of the tokens + /// @param permitSingle the permit data + /// @param signature the signature of the permit; abi.encodePacked(r, s, v) + /// @return err the error returned by a reverting permit call, empty if successful + function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature) + external + payable + returns (bytes memory err); + + /// @notice allows forwarding batch permits to permit2 + /// @dev this function is payable to allow multicall with NATIVE based actions + /// @param owner the owner of the tokens + /// @param _permitBatch a batch of approvals + /// @param signature the signature of the permit; abi.encodePacked(r, s, v) + /// @return err the error returned by a reverting permit call, empty if successful + function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature) + external + payable + returns (bytes memory err); +} diff --git a/src/interfaces/IPoolInitializer.sol b/src/interfaces/IPoolInitializer.sol new file mode 100644 index 00000000..b6168d4b --- /dev/null +++ b/src/interfaces/IPoolInitializer.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +interface IPoolInitializer { + /// @notice Initialize a Uniswap v4 Pool + /// @dev If the pool is already initialized, this function will not revert and just return type(int24).max + /// @param key The PoolKey of the pool to initialize + /// @param sqrtPriceX96 The initial starting price of the pool, expressed as a sqrtPriceX96 + /// @return The current tick of the pool, or type(int24).max if the pool creation failed, or the pool already existed + function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24); +} diff --git a/src/interfaces/IPositionDescriptor.sol b/src/interfaces/IPositionDescriptor.sol index a1736cea..70f8d3eb 100644 --- a/src/interfaces/IPositionDescriptor.sol +++ b/src/interfaces/IPositionDescriptor.sol @@ -2,13 +2,38 @@ pragma solidity ^0.8.24; import "./IPositionManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; /// @title Describes position NFT tokens via URI interface IPositionDescriptor { + error InvalidTokenId(uint256 tokenId); + /// @notice Produces the URI describing a particular token ID /// @dev Note this URI may be a data: URI with the JSON contents directly inlined /// @param positionManager The position manager for which to describe the token /// @param tokenId The ID of the token for which to produce a description, which may not be valid /// @return The URI of the ERC721-compliant metadata function tokenURI(IPositionManager positionManager, uint256 tokenId) external view returns (string memory); + + /// @notice Returns true if currency0 has higher priority than currency1 + /// @param currency0 The first currency address + /// @param currency1 The second currency address + /// @return True if currency0 has higher priority than currency1 + function flipRatio(address currency0, address currency1) external view returns (bool); + + /// @notice Returns the priority of a currency. + /// For certain currencies on mainnet, the smaller the currency, the higher the priority + /// And those with the higher priority values (more positive values) will be in the numerator of the price ratio + /// @param currency The currency address + /// @return The priority of the currency + function currencyRatioPriority(address currency) external view returns (int256); + + /// @return The wrapped native token for this descriptor + function wrappedNative() external view returns (address); + + /// @return The native currency label for this descriptor + function nativeCurrencyLabel() external view returns (string memory); + + /// @return The pool manager for this descriptor + function poolManager() external view returns (IPoolManager); } diff --git a/src/interfaces/IPositionManager.sol b/src/interfaces/IPositionManager.sol index 86df7bbf..e9746fbf 100644 --- a/src/interfaces/IPositionManager.sol +++ b/src/interfaces/IPositionManager.sol @@ -6,10 +6,25 @@ import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; import {INotifier} from "./INotifier.sol"; import {IImmutableState} from "./IImmutableState.sol"; +import {IERC721Permit_v4} from "./IERC721Permit_v4.sol"; +import {IEIP712_v4} from "./IEIP712_v4.sol"; +import {IMulticall_v4} from "./IMulticall_v4.sol"; +import {IPoolInitializer} from "./IPoolInitializer.sol"; +import {IUnorderedNonce} from "./IUnorderedNonce.sol"; +import {IPermit2Forwarder} from "./IPermit2Forwarder.sol"; /// @title IPositionManager /// @notice Interface for the PositionManager contract -interface IPositionManager is INotifier, IImmutableState { +interface IPositionManager is + INotifier, + IImmutableState, + IERC721Permit_v4, + IEIP712_v4, + IMulticall_v4, + IPoolInitializer, + IUnorderedNonce, + IPermit2Forwarder +{ /// @notice Thrown when the caller is not approved to modify a position error NotApproved(address caller); /// @notice Thrown when the block.timestamp exceeds the user-provided deadline @@ -34,13 +49,20 @@ interface IPositionManager is INotifier, IImmutableState { /// @return uint256 The next token ID function nextTokenId() external view returns (uint256); + /// @notice Returns the liquidity of a position /// @param tokenId the ERC721 tokenId /// @return liquidity the position's liquidity, as a liquidityAmount /// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity); + /// @notice Returns the pool key and position info of a position /// @param tokenId the ERC721 tokenId - /// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper) /// @return poolKey the pool key of the position + /// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper) function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo); + + /// @notice Returns the position info of a position + /// @param tokenId the ERC721 tokenId + /// @return a uint256 packed value holding information about the position including the range (tickLower, tickUpper) + function positionInfo(uint256 tokenId) external view returns (PositionInfo); } diff --git a/src/interfaces/IStateView.sol b/src/interfaces/IStateView.sol new file mode 100644 index 00000000..31f62b3a --- /dev/null +++ b/src/interfaces/IStateView.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {IImmutableState} from "../interfaces/IImmutableState.sol"; + +/// @title StateView Interface +/// @notice A view only contract wrapping the StateLibrary.sol library for reading storage in v4-core. +/// @dev The contract is intended for offchain clients. Use StateLibrary.sol directly if reading state onchain. +interface IStateView is IImmutableState { + /// @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee + /// @dev Corresponds to pools[poolId].slot0 + /// @param poolId The ID of the pool. + /// @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision. + /// @return tick The current tick of the pool. + /// @return protocolFee The protocol fee of the pool. + /// @return lpFee The swap fee of the pool. + function getSlot0(PoolId poolId) + external + view + returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee); + + /// @notice Retrieves the tick information of a pool at a specific tick. + /// @dev Corresponds to pools[poolId].ticks[tick] + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve information for. + /// @return liquidityGross The total position liquidity that references this tick + /// @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) + /// @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + /// @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + function getTickInfo(PoolId poolId, int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128 + ); + + /// @notice Retrieves the liquidity information of a pool at a specific tick. + /// @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve liquidity for. + /// @return liquidityGross The total position liquidity that references this tick + /// @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) + function getTickLiquidity(PoolId poolId, int24 tick) + external + view + returns (uint128 liquidityGross, int128 liquidityNet); + + /// @notice Retrieves the fee growth outside a tick range of a pool + /// @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve fee growth for. + /// @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + /// @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + function getTickFeeGrowthOutside(PoolId poolId, int24 tick) + external + view + returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128); + + /// @notice Retrieves the global fee growth of a pool. + /// @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128 + /// @param poolId The ID of the pool. + /// @return feeGrowthGlobal0 The global fee growth for token0. + /// @return feeGrowthGlobal1 The global fee growth for token1. + function getFeeGrowthGlobals(PoolId poolId) + external + view + returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1); + + /// @notice Retrieves the total liquidity of a pool. + /// @dev Corresponds to pools[poolId].liquidity + /// @param poolId The ID of the pool. + /// @return liquidity The liquidity of the pool. + function getLiquidity(PoolId poolId) external view returns (uint128 liquidity); + + /// @notice Retrieves the tick bitmap of a pool at a specific tick. + /// @dev Corresponds to pools[poolId].tickBitmap[tick] + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve the bitmap for. + /// @return tickBitmap The bitmap of the tick. + function getTickBitmap(PoolId poolId, int16 tick) external view returns (uint256 tickBitmap); + + /// @notice Retrieves the position info without needing to calculate the `positionId`. + /// @dev Corresponds to pools[poolId].positions[positionId] + /// @param poolId The ID of the pool. + /// @param owner The owner of the liquidity position. + /// @param tickLower The lower tick of the liquidity range. + /// @param tickUpper The upper tick of the liquidity range. + /// @param salt The bytes32 randomness to further distinguish position state. + /// @return liquidity The liquidity of the position. + /// @return feeGrowthInside0LastX128 The fee growth inside the position for token0. + /// @return feeGrowthInside1LastX128 The fee growth inside the position for token1. + function getPositionInfo(PoolId poolId, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) + external + view + returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128); + + /// @notice Retrieves the position information of a pool at a specific position ID. + /// @dev Corresponds to pools[poolId].positions[positionId] + /// @param poolId The ID of the pool. + /// @param positionId The ID of the position. + /// @return liquidity The liquidity of the position. + /// @return feeGrowthInside0LastX128 The fee growth inside the position for token0. + /// @return feeGrowthInside1LastX128 The fee growth inside the position for token1. + function getPositionInfo(PoolId poolId, bytes32 positionId) + external + view + returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128); + + /// @notice Retrieves the liquidity of a position. + /// @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieving liquidity as compared to getPositionInfo + /// @param poolId The ID of the pool. + /// @param positionId The ID of the position. + /// @return liquidity The liquidity of the position. + function getPositionLiquidity(PoolId poolId, bytes32 positionId) external view returns (uint128 liquidity); + + /// @notice Calculate the fee growth inside a tick range of a pool + /// @dev pools[poolId].feeGrowthInside0LastX128 in Position.Info is cached and can become stale. This function will calculate the up to date feeGrowthInside + /// @param poolId The ID of the pool. + /// @param tickLower The lower tick of the range. + /// @param tickUpper The upper tick of the range. + /// @return feeGrowthInside0X128 The fee growth inside the tick range for token0. + /// @return feeGrowthInside1X128 The fee growth inside the tick range for token1. + function getFeeGrowthInside(PoolId poolId, int24 tickLower, int24 tickUpper) + external + view + returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128); +} diff --git a/src/interfaces/IUnorderedNonce.sol b/src/interfaces/IUnorderedNonce.sol new file mode 100644 index 00000000..4e1866a6 --- /dev/null +++ b/src/interfaces/IUnorderedNonce.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title UnorderedNonce Interface +/// @notice Interface for the UnorderedNonce contract +interface IUnorderedNonce { + error NonceAlreadyUsed(); + + /// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap + /// @dev word is at most type(uint248).max + function nonces(address owner, uint256 word) external view returns (uint256); + + /// @notice Revoke a nonce by spending it, preventing it from being used again + /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce + /// @dev payable so it can be multicalled with native-token related actions + function revokeNonce(uint256 nonce) external payable; +} diff --git a/src/interfaces/IV4Quoter.sol b/src/interfaces/IV4Quoter.sol index f502a4f4..d51ddb3c 100644 --- a/src/interfaces/IV4Quoter.sol +++ b/src/interfaces/IV4Quoter.sol @@ -4,13 +4,14 @@ pragma solidity ^0.8.0; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PathKey} from "../libraries/PathKey.sol"; +import {IImmutableState} from "./IImmutableState.sol"; /// @title V4 Quoter Interface /// @notice Supports quoting the delta amounts for exact input or exact output swaps. /// @notice For each pool also tells you the sqrt price of the pool after the swap. /// @dev These functions are not marked view because they rely on calling non-view functions and reverting /// to compute the result. They are also not gas efficient and should not be called on-chain. -interface IV4Quoter { +interface IV4Quoter is IImmutableState { struct QuoteExactSingleParams { PoolKey poolKey; bool zeroForOne; diff --git a/src/lens/StateView.sol b/src/lens/StateView.sol index 6527d2f7..e3894c19 100644 --- a/src/lens/StateView.sol +++ b/src/lens/StateView.sol @@ -5,24 +5,16 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; - import {ImmutableState} from "../base/ImmutableState.sol"; +import {IStateView} from "../interfaces/IStateView.sol"; /// @notice A view only contract wrapping the StateLibrary.sol library for reading storage in v4-core. -contract StateView is ImmutableState { +contract StateView is ImmutableState, IStateView { using StateLibrary for IPoolManager; constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {} - /** - * @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee - * @dev Corresponds to pools[poolId].slot0 - * @param poolId The ID of the pool. - * @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision. - * @return tick The current tick of the pool. - * @return protocolFee The protocol fee of the pool. - * @return lpFee The swap fee of the pool. - */ + /// @inheritdoc IStateView function getSlot0(PoolId poolId) external view @@ -31,16 +23,7 @@ contract StateView is ImmutableState { return poolManager.getSlot0(poolId); } - /** - * @notice Retrieves the tick information of a pool at a specific tick. - * @dev Corresponds to pools[poolId].ticks[tick] - * @param poolId The ID of the pool. - * @param tick The tick to retrieve information for. - * @return liquidityGross The total position liquidity that references this tick - * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) - * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) - * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) - */ + /// @inheritdoc IStateView function getTickInfo(PoolId poolId, int24 tick) external view @@ -54,14 +37,7 @@ contract StateView is ImmutableState { return poolManager.getTickInfo(poolId, tick); } - /** - * @notice Retrieves the liquidity information of a pool at a specific tick. - * @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo - * @param poolId The ID of the pool. - * @param tick The tick to retrieve liquidity for. - * @return liquidityGross The total position liquidity that references this tick - * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) - */ + /// @inheritdoc IStateView function getTickLiquidity(PoolId poolId, int24 tick) external view @@ -70,14 +46,7 @@ contract StateView is ImmutableState { return poolManager.getTickLiquidity(poolId, tick); } - /** - * @notice Retrieves the fee growth outside a tick range of a pool - * @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo - * @param poolId The ID of the pool. - * @param tick The tick to retrieve fee growth for. - * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) - * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) - */ + /// @inheritdoc IStateView function getTickFeeGrowthOutside(PoolId poolId, int24 tick) external view @@ -86,13 +55,7 @@ contract StateView is ImmutableState { return poolManager.getTickFeeGrowthOutside(poolId, tick); } - /** - * @notice Retrieves the global fee growth of a pool. - * @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128 - * @param poolId The ID of the pool. - * @return feeGrowthGlobal0 The global fee growth for token0. - * @return feeGrowthGlobal1 The global fee growth for token1. - */ + /// @inheritdoc IStateView function getFeeGrowthGlobals(PoolId poolId) external view @@ -101,39 +64,17 @@ contract StateView is ImmutableState { return poolManager.getFeeGrowthGlobals(poolId); } - /** - * @notice Retrieves the total liquidity of a pool. - * @dev Corresponds to pools[poolId].liquidity - * @param poolId The ID of the pool. - * @return liquidity The liquidity of the pool. - */ + /// @inheritdoc IStateView function getLiquidity(PoolId poolId) external view returns (uint128 liquidity) { return poolManager.getLiquidity(poolId); } - /** - * @notice Retrieves the tick bitmap of a pool at a specific tick. - * @dev Corresponds to pools[poolId].tickBitmap[tick] - * @param poolId The ID of the pool. - * @param tick The tick to retrieve the bitmap for. - * @return tickBitmap The bitmap of the tick. - */ + /// @inheritdoc IStateView function getTickBitmap(PoolId poolId, int16 tick) external view returns (uint256 tickBitmap) { return poolManager.getTickBitmap(poolId, tick); } - /** - * @notice Retrieves the position info without needing to calculate the `positionId`. - * @dev Corresponds to pools[poolId].positions[positionId] - * @param poolId The ID of the pool. - * @param owner The owner of the liquidity position. - * @param tickLower The lower tick of the liquidity range. - * @param tickUpper The upper tick of the liquidity range. - * @param salt The bytes32 randomness to further distinguish position state. - * @return liquidity The liquidity of the position. - * @return feeGrowthInside0LastX128 The fee growth inside the position for token0. - * @return feeGrowthInside1LastX128 The fee growth inside the position for token1. - */ + /// @inheritdoc IStateView function getPositionInfo(PoolId poolId, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view @@ -142,15 +83,7 @@ contract StateView is ImmutableState { return poolManager.getPositionInfo(poolId, owner, tickLower, tickUpper, salt); } - /** - * @notice Retrieves the position information of a pool at a specific position ID. - * @dev Corresponds to pools[poolId].positions[positionId] - * @param poolId The ID of the pool. - * @param positionId The ID of the position. - * @return liquidity The liquidity of the position. - * @return feeGrowthInside0LastX128 The fee growth inside the position for token0. - * @return feeGrowthInside1LastX128 The fee growth inside the position for token1. - */ + /// @inheritdoc IStateView function getPositionInfo(PoolId poolId, bytes32 positionId) external view @@ -159,26 +92,12 @@ contract StateView is ImmutableState { return poolManager.getPositionInfo(poolId, positionId); } - /** - * @notice Retrieves the liquidity of a position. - * @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieving liquidity as compared to getPositionInfo - * @param poolId The ID of the pool. - * @param positionId The ID of the position. - * @return liquidity The liquidity of the position. - */ + /// @inheritdoc IStateView function getPositionLiquidity(PoolId poolId, bytes32 positionId) external view returns (uint128 liquidity) { return poolManager.getPositionLiquidity(poolId, positionId); } - /** - * @notice Calculate the fee growth inside a tick range of a pool - * @dev pools[poolId].feeGrowthInside0LastX128 in Position.Info is cached and can become stale. This function will calculate the up to date feeGrowthInside - * @param poolId The ID of the pool. - * @param tickLower The lower tick of the range. - * @param tickUpper The upper tick of the range. - * @return feeGrowthInside0X128 The fee growth inside the tick range for token0. - * @return feeGrowthInside1X128 The fee growth inside the tick range for token1. - */ + /// @inheritdoc IStateView function getFeeGrowthInside(PoolId poolId, int24 tickLower, int24 tickUpper) external view diff --git a/test/PositionDescriptor.t.sol b/test/PositionDescriptor.t.sol index c20465f0..57b1a641 100644 --- a/test/PositionDescriptor.t.sol +++ b/test/PositionDescriptor.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; -import {PositionDescriptor} from "../src/PositionDescriptor.sol"; +import {IPositionDescriptor} from "../src/interfaces/IPositionDescriptor.sol"; import {CurrencyRatioSortOrder} from "../src/libraries/CurrencyRatioSortOrder.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; @@ -87,149 +87,164 @@ contract PositionDescriptorTest is Test, PosmTestSetup { function test_tokenURI_succeeds() public { int24 tickLower = int24(key.tickSpacing); int24 tickUpper = int24(key.tickSpacing * 2); - uint256 amount0Desired = 100e18; - uint256 amount1Desired = 100e18; - uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( - SQRT_PRICE_1_1, - TickMath.getSqrtPriceAtTick(tickLower), - TickMath.getSqrtPriceAtTick(tickUpper), - amount0Desired, - amount1Desired - ); - - PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper}); uint256 tokenId = lpm.nextTokenId(); - mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - - // The prefix length is calculated by converting the string to bytes and finding its length - uint256 prefixLength = bytes("data:application/json;base64,").length; - - string memory uri = positionDescriptor.tokenURI(lpm, tokenId); - // Convert the uri to bytes - bytes memory uriBytes = bytes(uri); - - // Slice the uri to get only the base64-encoded part - bytes memory base64Part = new bytes(uriBytes.length - prefixLength); - - for (uint256 i = 0; i < base64Part.length; i++) { - base64Part[i] = uriBytes[i + prefixLength]; + Token memory token; + { + uint256 amount0Desired = 100e18; + uint256 amount1Desired = 100e18; + uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + amount0Desired, + amount1Desired + ); + + PositionConfig memory config = PositionConfig({poolKey: key, tickLower: tickLower, tickUpper: tickUpper}); + mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); + + // The prefix length is calculated by converting the string to bytes and finding its length + uint256 prefixLength = bytes("data:application/json;base64,").length; + + string memory uri = positionDescriptor.tokenURI(lpm, tokenId); + // Convert the uri to bytes + bytes memory uriBytes = bytes(uri); + + // Slice the uri to get only the base64-encoded part + bytes memory base64Part = new bytes(uriBytes.length - prefixLength); + + for (uint256 i = 0; i < base64Part.length; i++) { + base64Part[i] = uriBytes[i + prefixLength]; + } + + // Decode the base64-encoded part + bytes memory decoded = Base64.decode(string(base64Part)); + string memory json = string(decoded); + + // decode json + bytes memory data = vm.parseJson(json); + token = abi.decode(data, (Token)); } - // Decode the base64-encoded part - bytes memory decoded = Base64.decode(string(base64Part)); - string memory json = string(decoded); - - // decode json - bytes memory data = vm.parseJson(json); - Token memory token = abi.decode(data, (Token)); - // quote is currency1, base is currency0 assertFalse(positionDescriptor.flipRatio(Currency.unwrap(key.currency0), Currency.unwrap(key.currency1))); string memory symbol0 = SafeCurrencyMetadata.currencySymbol(Currency.unwrap(currency0), nativeCurrencyLabel); string memory symbol1 = SafeCurrencyMetadata.currencySymbol(Currency.unwrap(currency1), nativeCurrencyLabel); - string memory managerAddress = toHexString(address(manager)); - string memory currency0Address = toHexString(Currency.unwrap(currency0)); - string memory currency1Address = toHexString(Currency.unwrap(currency1)); - string memory id = uintToString(tokenId); - string memory hookAddress = address(key.hooks) == address(0) - ? "No Hook" - : string(abi.encodePacked("0x", toHexString(address(key.hooks)))); string memory fee = Descriptor.feeToPercentString(key.fee); - string memory tickToDecimal0 = Descriptor.tickToDecimalString( - tickLower, - key.tickSpacing, - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), - false - ); - string memory tickToDecimal1 = Descriptor.tickToDecimalString( - tickUpper, - key.tickSpacing, - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), - false - ); - - assertEq( - token.name, - string( - abi.encodePacked( - "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1 + { + string memory tickToDecimal0 = Descriptor.tickToDecimalString( + tickLower, + key.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + string memory tickToDecimal1 = Descriptor.tickToDecimalString( + tickUpper, + key.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + + assertEq( + token.name, + string( + abi.encodePacked( + "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1 + ) ) - ) - ); - assertEq( - token.description, - string( - abi.encodePacked( - unicode"This NFT represents a liquidity position in a Uniswap v4 ", - symbol1, - "-", - symbol0, - " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ", - managerAddress, - "\n", - symbol1, - " Address: ", - currency1Address, - "\n", - symbol0, - " Address: ", - currency0Address, - "\nHook Address: ", - hookAddress, - "\nFee Tier: ", - fee, - "\nToken ID: ", - id, - "\n\n", - unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ); + } + { + string memory managerAddress = toHexString(address(manager)); + string memory currency0Address = toHexString(Currency.unwrap(currency0)); + string memory currency1Address = toHexString(Currency.unwrap(currency1)); + string memory id = uintToString(tokenId); + string memory hookAddress = address(key.hooks) == address(0) + ? "No Hook" + : string(abi.encodePacked("0x", toHexString(address(key.hooks)))); + + assertEq( + token.description, + string( + abi.encodePacked( + abi.encodePacked( + unicode"This NFT represents a liquidity position in a Uniswap v4 ", + symbol1, + "-", + symbol0, + " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ", + managerAddress, + "\n", + symbol1, + " Address: ", + currency1Address + ), + abi.encodePacked( + "\n", + symbol0, + " Address: ", + currency0Address, + "\nHook Address: ", + hookAddress, + "\nFee Tier: ", + fee, + "\nToken ID: ", + id, + "\n\n", + unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ) + ) ) - ) - ); + ); + } } function test_native_tokenURI_succeeds() public { (nativeKey,) = initPool(CurrencyLibrary.ADDRESS_ZERO, currency1, IHooks(address(0)), 3000, SQRT_PRICE_1_1); int24 tickLower = int24(nativeKey.tickSpacing); int24 tickUpper = int24(nativeKey.tickSpacing * 2); - uint256 amount0Desired = 100e18; - uint256 amount1Desired = 100e18; - uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( - SQRT_PRICE_1_1, - TickMath.getSqrtPriceAtTick(tickLower), - TickMath.getSqrtPriceAtTick(tickUpper), - amount0Desired, - amount1Desired - ); - - PositionConfig memory config = PositionConfig({poolKey: nativeKey, tickLower: tickLower, tickUpper: tickUpper}); + Token memory token; uint256 tokenId = lpm.nextTokenId(); - mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - - // The prefix length is calculated by converting the string to bytes and finding its length - uint256 prefixLength = bytes("data:application/json;base64,").length; - - string memory uri = positionDescriptor.tokenURI(lpm, tokenId); - // Convert the uri to bytes - bytes memory uriBytes = bytes(uri); - - // Slice the uri to get only the base64-encoded part - bytes memory base64Part = new bytes(uriBytes.length - prefixLength); - - for (uint256 i = 0; i < base64Part.length; i++) { - base64Part[i] = uriBytes[i + prefixLength]; + { + uint256 amount0Desired = 100e18; + uint256 amount1Desired = 100e18; + uint256 liquidityToAdd = LiquidityAmounts.getLiquidityForAmounts( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + amount0Desired, + amount1Desired + ); + + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: tickLower, tickUpper: tickUpper}); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); + // The prefix length is calculated by converting the string to bytes and finding its length + uint256 prefixLength = bytes("data:application/json;base64,").length; + + string memory uri = positionDescriptor.tokenURI(lpm, tokenId); + // Convert the uri to bytes + bytes memory uriBytes = bytes(uri); + + // Slice the uri to get only the base64-encoded part + bytes memory base64Part = new bytes(uriBytes.length - prefixLength); + + for (uint256 i = 0; i < base64Part.length; i++) { + base64Part[i] = uriBytes[i + prefixLength]; + } + + // Decode the base64-encoded part + bytes memory decoded = Base64.decode(string(base64Part)); + string memory json = string(decoded); + + // decode json + bytes memory data = vm.parseJson(json); + token = abi.decode(data, (Token)); } - // Decode the base64-encoded part - bytes memory decoded = Base64.decode(string(base64Part)); - string memory json = string(decoded); - - // decode json - bytes memory data = vm.parseJson(json); - Token memory token = abi.decode(data, (Token)); - // quote is currency1, base is currency0 assertFalse( positionDescriptor.flipRatio(Currency.unwrap(nativeKey.currency0), Currency.unwrap(nativeKey.currency1)) @@ -239,70 +254,79 @@ contract PositionDescriptorTest is Test, PosmTestSetup { SafeCurrencyMetadata.currencySymbol(Currency.unwrap(nativeKey.currency0), nativeCurrencyLabel); string memory symbol1 = SafeCurrencyMetadata.currencySymbol(Currency.unwrap(nativeKey.currency1), nativeCurrencyLabel); - string memory managerAddress = toHexString(address(manager)); - string memory currency0Address = Currency.unwrap(nativeKey.currency0) == address(0) - ? "Native" - : toHexString(Currency.unwrap(nativeKey.currency0)); - string memory currency1Address = Currency.unwrap(nativeKey.currency1) == address(0) - ? "Native" - : toHexString(Currency.unwrap(nativeKey.currency1)); - string memory id = uintToString(tokenId); - string memory hookAddress = address(nativeKey.hooks) == address(0) - ? "No Hook" - : string(abi.encodePacked("0x", toHexString(address(nativeKey.hooks)))); string memory fee = Descriptor.feeToPercentString(nativeKey.fee); - string memory tickToDecimal0 = Descriptor.tickToDecimalString( - tickLower, - nativeKey.tickSpacing, - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), - false - ); - string memory tickToDecimal1 = Descriptor.tickToDecimalString( - tickUpper, - nativeKey.tickSpacing, - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), - SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), - false - ); - - assertEq( - token.name, - string( - abi.encodePacked( - "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1 + { + string memory tickToDecimal0 = Descriptor.tickToDecimalString( + tickLower, + nativeKey.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + string memory tickToDecimal1 = Descriptor.tickToDecimalString( + tickUpper, + nativeKey.tickSpacing, + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency0)), + SafeCurrencyMetadata.currencyDecimals(Currency.unwrap(currency1)), + false + ); + + assertEq( + token.name, + string( + abi.encodePacked( + "Uniswap - ", fee, " - ", symbol1, "/", symbol0, " - ", tickToDecimal0, "<>", tickToDecimal1 + ) ) - ) - ); - assertEq( - token.description, - string( - abi.encodePacked( - unicode"This NFT represents a liquidity position in a Uniswap v4 ", - symbol1, - "-", - symbol0, - " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ", - managerAddress, - "\n", - symbol1, - " Address: ", - currency1Address, - "\n", - symbol0, - " Address: ", - currency0Address, - "\nHook Address: ", - hookAddress, - "\nFee Tier: ", - fee, - "\nToken ID: ", - id, - "\n\n", - unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ); + } + { + string memory managerAddress = toHexString(address(manager)); + string memory currency0Address = Currency.unwrap(nativeKey.currency0) == address(0) + ? "Native" + : toHexString(Currency.unwrap(nativeKey.currency0)); + string memory currency1Address = Currency.unwrap(nativeKey.currency1) == address(0) + ? "Native" + : toHexString(Currency.unwrap(nativeKey.currency1)); + string memory id = uintToString(tokenId); + string memory hookAddress = address(nativeKey.hooks) == address(0) + ? "No Hook" + : string(abi.encodePacked("0x", toHexString(address(nativeKey.hooks)))); + + assertEq( + token.description, + string( + abi.encodePacked( + abi.encodePacked( + unicode"This NFT represents a liquidity position in a Uniswap v4 ", + symbol1, + "-", + symbol0, + " pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: ", + managerAddress, + "\n", + symbol1, + " Address: ", + currency1Address, + "\n", + symbol0 + ), + abi.encodePacked( + " Address: ", + currency0Address, + "\nHook Address: ", + hookAddress, + "\nFee Tier: ", + fee, + "\nToken ID: ", + id, + "\n\n", + unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ) + ) ) - ) - ); + ); + } } function test_tokenURI_revertsWithInvalidTokenId() public { @@ -322,7 +346,7 @@ contract PositionDescriptorTest is Test, PosmTestSetup { uint256 tokenId = lpm.nextTokenId(); mint(config, liquidityToAdd, ActionConstants.MSG_SENDER, ZERO_BYTES); - vm.expectRevert(abi.encodeWithSelector(PositionDescriptor.InvalidTokenId.selector, tokenId + 1)); + vm.expectRevert(abi.encodeWithSelector(IPositionDescriptor.InvalidTokenId.selector, tokenId + 1)); positionDescriptor.tokenURI(lpm, tokenId + 1); } diff --git a/test/StateViewTest.t.sol b/test/StateViewTest.t.sol index 64a6e750..41f5e79f 100644 --- a/test/StateViewTest.t.sol +++ b/test/StateViewTest.t.sol @@ -18,8 +18,7 @@ import {FixedPoint128} from "@uniswap/v4-core/src/libraries/FixedPoint128.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {Fuzzers} from "@uniswap/v4-core/src/test/Fuzzers.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; - -import {StateView} from "../src/lens/StateView.sol"; +import {Deploy, IStateView} from "./shared/Deploy.sol"; /// This test was taken from StateLibrary.t.sol in v4-core and adapted to use the StateView contract instead. contract StateViewTest is Test, Deployers, Fuzzers { @@ -28,7 +27,7 @@ contract StateViewTest is Test, Deployers, Fuzzers { PoolId poolId; - StateView state; + IStateView state; function setUp() public { deployFreshManagerAndRouters(); @@ -39,7 +38,7 @@ contract StateViewTest is Test, Deployers, Fuzzers { poolId = key.toId(); manager.initialize(key, SQRT_PRICE_1_1); - state = new StateView(manager); + state = Deploy.stateView(address(manager), hex"00"); } function test_getSlot0() public { diff --git a/test/UnorderedNonce.t.sol b/test/UnorderedNonce.t.sol index f7b46b2d..0983876d 100644 --- a/test/UnorderedNonce.t.sol +++ b/test/UnorderedNonce.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; -import {UnorderedNonce} from "../src/base/UnorderedNonce.sol"; +import {UnorderedNonce, IUnorderedNonce} from "../src/base/UnorderedNonce.sol"; import {MockUnorderedNonce} from "./mocks/MockUnorderedNonce.sol"; contract UnorderedNonceTest is Test { @@ -17,11 +17,11 @@ contract UnorderedNonceTest is Test { unorderedNonce.spendNonce(address(this), 0); unorderedNonce.spendNonce(address(this), 1); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 1); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 5); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 0); unorderedNonce.spendNonce(address(this), 4); } @@ -30,9 +30,9 @@ contract UnorderedNonceTest is Test { unorderedNonce.spendNonce(address(this), 255); unorderedNonce.spendNonce(address(this), 256); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 255); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 256); } @@ -40,9 +40,9 @@ contract UnorderedNonceTest is Test { unorderedNonce.spendNonce(address(this), 2 ** 240); unorderedNonce.spendNonce(address(this), 2 ** 240 + 1); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 2 ** 240); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 2 ** 240 + 1); unorderedNonce.spendNonce(address(this), 2 ** 240 + 2); @@ -51,13 +51,13 @@ contract UnorderedNonceTest is Test { function testInvalidateFullWord() public { unorderedNonce.batchSpendNonces(0, 2 ** 256 - 1); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 0); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 1); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 254); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 255); unorderedNonce.spendNonce(address(this), 256); } @@ -68,9 +68,9 @@ contract UnorderedNonceTest is Test { unorderedNonce.spendNonce(address(this), 0); unorderedNonce.spendNonce(address(this), 254); unorderedNonce.spendNonce(address(this), 255); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 256); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), 511); unorderedNonce.spendNonce(address(this), 512); } @@ -87,21 +87,21 @@ contract UnorderedNonceTest is Test { nonce = bound(nonce, 0, (word + 2) * 256); if ((word * 256) <= nonce && nonce < ((word + 1) * 256)) { - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); } unorderedNonce.spendNonce(address(this), nonce); } function test_fuzz_UsingNonceTwiceFails(uint256 nonce) public { unorderedNonce.spendNonce(address(this), nonce); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), nonce); } function test_fuzz_UseTwoRandomNonces(uint256 first, uint256 second) public { unorderedNonce.spendNonce(address(this), first); if (first == second) { - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.spendNonce(address(this), second); } else { unorderedNonce.spendNonce(address(this), second); @@ -110,13 +110,13 @@ contract UnorderedNonceTest is Test { function test_fuzz_revokeNonce(uint256 nonce) public { unorderedNonce.revokeNonce(nonce); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.revokeNonce(nonce); } function test_fuzz_revokeNonce_twoNonces(uint256 first, uint256 second) public { unorderedNonce.revokeNonce(first); - if (first == second) vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + if (first == second) vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); unorderedNonce.revokeNonce(second); } } diff --git a/test/V4Quoter.t.sol b/test/V4Quoter.t.sol index 3b8b03f8..1c0fd5d1 100644 --- a/test/V4Quoter.t.sol +++ b/test/V4Quoter.t.sol @@ -4,8 +4,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {PathKey} from "../src/libraries/PathKey.sol"; -import {IV4Quoter} from "../src/interfaces/IV4Quoter.sol"; -import {V4Quoter} from "../src/lens/V4Quoter.sol"; +import {Deploy, IV4Quoter} from "../test/shared/Deploy.sol"; import {BaseV4Quoter} from "../src/base/BaseV4Quoter.sol"; // v4-core @@ -39,7 +38,7 @@ contract QuoterTest is Test, Deployers { uint256 internal constant CONTROLLER_GAS_LIMIT = 500000; - V4Quoter quoter; + IV4Quoter quoter; PoolModifyLiquidityTest positionManager; @@ -55,7 +54,7 @@ contract QuoterTest is Test, Deployers { function setUp() public { deployFreshManagerAndRouters(); - quoter = new V4Quoter(IPoolManager(manager)); + quoter = Deploy.v4Quoter(address(manager), hex"00"); positionManager = new PoolModifyLiquidityTest(manager); // salts are chosen so that address(token0) < address(token1) && address(token1) < address(token2) diff --git a/test/erc721Permit/ERC721Permit.permit.t.sol b/test/erc721Permit/ERC721Permit.permit.t.sol index 0a741381..00a50e80 100644 --- a/test/erc721Permit/ERC721Permit.permit.t.sol +++ b/test/erc721Permit/ERC721Permit.permit.t.sol @@ -8,7 +8,7 @@ import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; import {MockERC721Permit} from "../mocks/MockERC721Permit.sol"; import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; import {IERC721} from "forge-std/interfaces/IERC721.sol"; -import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; +import {IUnorderedNonce} from "../../src/interfaces/IUnorderedNonce.sol"; contract ERC721PermitTest is Test { MockERC721Permit erc721Permit; @@ -169,7 +169,7 @@ contract ERC721PermitTest is Test { bytes memory signature = abi.encodePacked(r, s, v); vm.startPrank(alice); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); erc721Permit.permit(bob, tokenIdAlice, block.timestamp, nonce, signature); vm.stopPrank(); } @@ -192,7 +192,7 @@ contract ERC721PermitTest is Test { bytes memory signature = abi.encodePacked(r, s, v); vm.startPrank(alice); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); erc721Permit.permit(bob, tokenIdAlice2, block.timestamp, nonce, signature); vm.stopPrank(); } diff --git a/test/erc721Permit/ERC721Permit.permitForAll.t.sol b/test/erc721Permit/ERC721Permit.permitForAll.t.sol index c8150895..4cfa43a8 100644 --- a/test/erc721Permit/ERC721Permit.permitForAll.t.sol +++ b/test/erc721Permit/ERC721Permit.permitForAll.t.sol @@ -8,7 +8,7 @@ import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; import {MockERC721Permit} from "../mocks/MockERC721Permit.sol"; import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; import {IERC721} from "forge-std/interfaces/IERC721.sol"; -import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; +import {IUnorderedNonce} from "../../src/interfaces/IUnorderedNonce.sol"; contract ERC721PermitForAllTest is Test { MockERC721Permit erc721Permit; @@ -141,7 +141,7 @@ contract ERC721PermitForAllTest is Test { bytes memory signature = abi.encodePacked(r, s, v); vm.startPrank(alice); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); erc721Permit.permitForAll(alice, bob, true, block.timestamp, nonce, signature); vm.stopPrank(); } @@ -292,7 +292,7 @@ contract ERC721PermitForAllTest is Test { // Nonce does not work with permitForAll vm.startPrank(bob); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); vm.stopPrank(); } @@ -311,7 +311,7 @@ contract ERC721PermitForAllTest is Test { // Nonce does not work with permitForAll vm.startPrank(bob); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); erc721Permit.permitForAll(alice, bob, true, deadline, nonce, signature); vm.stopPrank(); } diff --git a/test/mocks/MockBadSubscribers.sol b/test/mocks/MockBadSubscribers.sol index d9c99cbb..a430185d 100644 --- a/test/mocks/MockBadSubscribers.sol +++ b/test/mocks/MockBadSubscribers.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.20; import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol"; /// @notice A subscriber contract that returns values from the subscriber entrypoints contract MockReturnDataSubscriber is ISubscriber { - PositionManager posm; + IPositionManager posm; uint256 public notifySubscribeCount; uint256 public notifyUnsubscribeCount; @@ -20,7 +20,7 @@ contract MockReturnDataSubscriber is ISubscriber { uint256 memPtr; - constructor(PositionManager _posm) { + constructor(IPositionManager _posm) { posm = _posm; } @@ -48,9 +48,7 @@ contract MockReturnDataSubscriber is ISubscriber { notifyModifyLiquidityCount++; } - function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) - external - { + function notifyBurn(uint256, address, PositionInfo, uint256, BalanceDelta) external pure { return; } @@ -61,13 +59,13 @@ contract MockReturnDataSubscriber is ISubscriber { /// @notice A subscriber contract that returns values from the subscriber entrypoints contract MockRevertSubscriber is ISubscriber { - PositionManager posm; + IPositionManager posm; error NotAuthorizedNotifer(address sender); error TestRevert(string); - constructor(PositionManager _posm) { + constructor(IPositionManager _posm) { posm = _posm; } @@ -92,9 +90,7 @@ contract MockRevertSubscriber is ISubscriber { revert TestRevert("notifyModifyLiquidity"); } - function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) - external - { + function notifyBurn(uint256, address, PositionInfo, uint256, BalanceDelta) external pure { return; } diff --git a/test/mocks/MockReenterHook.sol b/test/mocks/MockReenterHook.sol index e6d6f2db..527475c9 100644 --- a/test/mocks/MockReenterHook.sol +++ b/test/mocks/MockReenterHook.sol @@ -5,10 +5,11 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {BaseTestHooks} from "@uniswap/v4-core/src/test/BaseTestHooks.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; contract MockReenterHook is BaseTestHooks { - PositionManager posm; + IPositionManager posm; function beforeAddLiquidity( address, @@ -21,8 +22,8 @@ contract MockReenterHook is BaseTestHooks { } (bytes4 selector, address owner, uint256 tokenId) = abi.decode(functionSelector, (bytes4, address, uint256)); - if (selector == posm.transferFrom.selector) { - posm.transferFrom(owner, address(this), tokenId); + if (selector == IERC721(address(posm)).transferFrom.selector) { + IERC721(address(posm)).transferFrom(owner, address(this), tokenId); } else if (selector == posm.subscribe.selector) { posm.subscribe(tokenId, address(this), ""); } else if (selector == posm.unsubscribe.selector) { @@ -31,7 +32,7 @@ contract MockReenterHook is BaseTestHooks { return this.beforeAddLiquidity.selector; } - function setPosm(PositionManager _posm) external { + function setPosm(IPositionManager _posm) external { posm = _posm; } } diff --git a/test/mocks/MockSubscriber.sol b/test/mocks/MockSubscriber.sol index 64a167ab..fb126927 100644 --- a/test/mocks/MockSubscriber.sol +++ b/test/mocks/MockSubscriber.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.20; import {ISubscriber} from "../../src/interfaces/ISubscriber.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol"; /// @notice A subscriber contract that ingests updates from the v4 position manager contract MockSubscriber is ISubscriber { - PositionManager posm; + IPositionManager posm; uint256 public notifySubscribeCount; uint256 public notifyUnsubscribeCount; @@ -23,7 +23,7 @@ contract MockSubscriber is ISubscriber { error NotImplemented(); - constructor(PositionManager _posm) { + constructor(IPositionManager _posm) { posm = _posm; } @@ -47,10 +47,7 @@ contract MockSubscriber is ISubscriber { feesAccrued = _feesAccrued; } - function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) - external - onlyByPosm - { + function notifyBurn(uint256, address, PositionInfo, uint256, BalanceDelta) external onlyByPosm { notifyBurnCount++; } } diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index dd99d86b..f9eea6a2 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -16,9 +16,9 @@ import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {Actions} from "../../src/libraries/Actions.sol"; @@ -232,7 +232,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { // old position was burned vm.expectRevert(); - lpm.ownerOf(tokenId); + IERC721(address(lpm)).ownerOf(tokenId); { // old position has no liquidity @@ -241,7 +241,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { // new token was minted uint256 newTokenId = lpm.nextTokenId() - 1; - assertEq(lpm.ownerOf(newTokenId), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(newTokenId), address(this)); // new token has expected liquidity diff --git a/test/position-managers/FeeCollection.t.sol b/test/position-managers/FeeCollection.t.sol index c2508536..16976301 100644 --- a/test/position-managers/FeeCollection.t.sol +++ b/test/position-managers/FeeCollection.t.sol @@ -13,7 +13,6 @@ import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index a0264d2b..bfda58e7 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -19,7 +19,6 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {SlippageCheck} from "../../src/libraries/SlippageCheck.sol"; diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index c79f74de..2d7ef94e 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -20,12 +20,11 @@ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -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 {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {MockSubscriber} from "../mocks/MockSubscriber.sol"; @@ -250,7 +249,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // OZ 721 will revert if the token does not exist vm.expectRevert(); - lpm.ownerOf(1); + IERC721(address(lpm)).ownerOf(1); // no tokens were lost, TODO: fuzzer showing off by 1 sometimes assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei); @@ -308,7 +307,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // OZ 721 will revert if the token does not exist vm.expectRevert(); - lpm.ownerOf(1); + IERC721(address(lpm)).ownerOf(1); // no tokens were lost, TODO: fuzzer showing off by 1 sometimes assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei); @@ -356,7 +355,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // OZ 721 will revert if the token does not exist vm.expectRevert(); - lpm.ownerOf(1); + IERC721(address(lpm)).ownerOf(1); // no tokens were lost, TODO: fuzzer showing off by 1 sometimes assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei); @@ -409,7 +408,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // OZ 721 will revert if the token does not exist vm.expectRevert(); - lpm.ownerOf(1); + IERC721(address(lpm)).ownerOf(1); // no tokens were lost, TODO: fuzzer showing off by 1 sometimes assertApproxEqAbs(currency0.balanceOfSelf(), balance0Start, 1 wei); diff --git a/test/position-managers/Permit.t.sol b/test/position-managers/Permit.t.sol index 0ce9a5b9..e60c1d27 100644 --- a/test/position-managers/Permit.t.sol +++ b/test/position-managers/Permit.t.sol @@ -15,7 +15,7 @@ import {SignatureVerification} from "permit2/src/libraries/SignatureVerification import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; import {ERC721Permit_v4} from "../../src/base/ERC721Permit_v4.sol"; -import {UnorderedNonce} from "../../src/base/UnorderedNonce.sol"; +import {IUnorderedNonce} from "../../src/interfaces/IUnorderedNonce.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; @@ -234,7 +234,7 @@ contract PermitTest is Test, PosmTestSetup { (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePK, digest); bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert(UnorderedNonce.NonceAlreadyUsed.selector); + vm.expectRevert(IUnorderedNonce.NonceAlreadyUsed.selector); lpm.permit(bob, tokenIdAlice, block.timestamp + 1, nonce, signature); } diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index fec63940..a88d2a2d 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -15,9 +15,8 @@ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {IPositionManager, IPoolInitializer} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {Planner, Plan} from "../shared/Planner.sol"; @@ -396,7 +395,7 @@ contract PosMGasTest is Test, PosmTestSetup { // Use multicall to initialize a pool and mint liquidity bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); + calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1); config = PositionConfig({ poolKey: key, diff --git a/test/position-managers/PositionManager.modifyLiquidities.t.sol b/test/position-managers/PositionManager.modifyLiquidities.t.sol index 66475861..f2e5674e 100644 --- a/test/position-managers/PositionManager.modifyLiquidities.t.sol +++ b/test/position-managers/PositionManager.modifyLiquidities.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; @@ -26,7 +27,6 @@ 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"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {BipsLibrary} from "../../src/libraries/BipsLibrary.sol"; @@ -123,8 +123,8 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF liquidity = lpm.getPositionLiquidity(hookTokenId); assertEq(liquidity, newLiquidity); - assertEq(lpm.ownerOf(tokenId), address(this)); // original position owned by this contract - assertEq(lpm.ownerOf(hookTokenId), address(hookModifyLiquidities)); // hook position owned by hook + assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this)); // original position owned by this contract + assertEq(IERC721(address(lpm)).ownerOf(hookTokenId), address(hookModifyLiquidities)); // hook position owned by hook } /// @dev hook must be approved to increase liquidity @@ -134,7 +134,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF mint(config, initialLiquidity, address(this), ZERO_BYTES); // approve the hook for increasing liquidity - lpm.approve(address(hookModifyLiquidities), tokenId); + IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId); // hook increases liquidity in beforeSwap via hookData uint256 newLiquidity = 10e18; @@ -154,7 +154,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF mint(config, initialLiquidity, address(this), ZERO_BYTES); // approve the hook for decreasing liquidity - lpm.approve(address(hookModifyLiquidities), tokenId); + IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId); // hook decreases liquidity in beforeSwap via hookData uint256 liquidityToDecrease = 10e18; @@ -174,7 +174,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF mint(config, initialLiquidity, address(this), ZERO_BYTES); // approve the hook for collecting liquidity - lpm.approve(address(hookModifyLiquidities), tokenId); + IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId); // donate to generate revenue uint256 feeRevenue0 = 1e18; @@ -212,7 +212,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF BalanceDelta mintDelta = hookModifyLiquidities.deltas(hookModifyLiquidities.numberDeltasReturned() - 1); // approve the hook for burning liquidity - lpm.approve(address(hookModifyLiquidities), tokenId); + IERC721(address(lpm)).approve(address(hookModifyLiquidities), tokenId); uint256 balance0HookBefore = currency0.balanceOf(address(hookModifyLiquidities)); uint256 balance1HookBefore = currency1.balanceOf(address(hookModifyLiquidities)); @@ -227,7 +227,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertEq(liquidity, 0); // 721 will revert if the token does not exist vm.expectRevert(); - lpm.ownerOf(tokenId); + IERC721(address(lpm)).ownerOf(tokenId); // hook claimed the burned liquidity assertEq( @@ -417,7 +417,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // 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(IERC721(address(lpm)).ownerOf(tokenId), address(this)); assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); assertEq(_WETH9.balanceOf(address(lpm)), 0); assertEq(address(lpm).balance, 0); @@ -479,7 +479,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // 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(IERC721(address(lpm)).ownerOf(tokenId), address(this)); assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); assertEq(_WETH9.balanceOf(address(lpm)), 0); assertEq(address(lpm).balance, 0); @@ -539,7 +539,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // 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(IERC721(address(lpm)).ownerOf(tokenId), address(this)); assertEq(lpm.getPositionLiquidity(tokenId), liquidityAmount); assertEq(_WETH9.balanceOf(address(lpm)), 0); assertEq(address(lpm).balance, 0); @@ -756,7 +756,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF uint256 fotBalanceAfter = Currency.wrap(address(fotToken)).balanceOf(address(this)); - assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this)); assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity); assertEq(fotBalanceBefore - fotBalanceAfter, 1000e18); } @@ -768,7 +768,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF mint(fotConfig, initialLiquidity, address(this), ZERO_BYTES); - assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this)); assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity); Plan memory planner = Planner.init(); @@ -801,7 +801,7 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF mint(fotConfig, initialLiquidity, address(this), ZERO_BYTES); - assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this)); assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity); // Use a 1% fee. @@ -837,6 +837,16 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF assertEq(lpm.getPositionLiquidity(tokenId), initialLiquidity + newLiquidity); } + struct BalanceDiff { + uint256 _before; + uint256 _after; + } + + struct Balance { + uint256 _0; + uint256 _1; + } + function test_fuzz_mintFromDeltas_burn_fot( uint256 bips, uint256 amount0, @@ -846,7 +856,6 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF ) public { bips = bound(bips, 1, 10_000); MockFOT(address(fotToken)).setFee(bips); - tickLower = int24( bound( tickLower, @@ -867,28 +876,30 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF vm.assume(tickUpper > tickLower); (uint160 sqrtPriceX96,,,) = manager.getSlot0(fotKey.toId()); - uint128 maxLiquidityPerTick = Pool.tickSpacingToMaxLiquidityPerTick(fotKey.tickSpacing); - - (uint256 maxAmount0, uint256 maxAmount1) = LiquidityAmounts.getAmountsForLiquidity( - sqrtPriceX96, - TickMath.getSqrtPriceAtTick(tickLower), - TickMath.getSqrtPriceAtTick(tickUpper), - maxLiquidityPerTick - ); + { + uint128 maxLiquidityPerTick = Pool.tickSpacingToMaxLiquidityPerTick(fotKey.tickSpacing); + (uint256 maxAmount0, uint256 maxAmount1) = LiquidityAmounts.getAmountsForLiquidity( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + maxLiquidityPerTick + ); - maxAmount0 = maxAmount0 == 0 ? 1 : maxAmount0 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount0; - maxAmount1 = maxAmount1 == 0 ? 1 : maxAmount1 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount1; - amount0 = bound(amount0, 1, maxAmount0); - amount1 = bound(amount1, 1, maxAmount1); + maxAmount0 = maxAmount0 == 0 ? 1 : maxAmount0 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount0; + maxAmount1 = maxAmount1 == 0 ? 1 : maxAmount1 > STARTING_USER_BALANCE ? STARTING_USER_BALANCE : maxAmount1; + amount0 = bound(amount0, 1, maxAmount0); + amount1 = bound(amount1, 1, maxAmount1); + } uint256 tokenId = lpm.nextTokenId(); - uint256 balance0 = fotKey.currency0.balanceOf(address(this)); - uint256 balance1 = fotKey.currency1.balanceOf(address(this)); - uint256 balance0PM = fotKey.currency0.balanceOf(address(manager)); - uint256 balance1PM = fotKey.currency1.balanceOf(address(manager)); + BalanceDiff memory balance0 = BalanceDiff(fotKey.currency0.balanceOf(address(this)), 0); + BalanceDiff memory balance1 = BalanceDiff(fotKey.currency1.balanceOf(address(this)), 0); + BalanceDiff memory balance0PM = BalanceDiff(fotKey.currency0.balanceOf(address(manager)), 0); + BalanceDiff memory balance1PM = BalanceDiff(fotKey.currency1.balanceOf(address(manager)), 0); Plan memory planner = Planner.init(); + planner.add(Actions.SETTLE, abi.encode(fotKey.currency0, amount0, true)); planner.add(Actions.SETTLE, abi.encode(fotKey.currency1, amount1, true)); planner.add( @@ -906,60 +917,69 @@ contract PositionManagerModifyLiquiditiesTest is Test, PosmTestSetup, LiquidityF // take the excess of each currency planner.add(Actions.TAKE_PAIR, abi.encode(fotKey.currency0, fotKey.currency1, ActionConstants.MSG_SENDER)); - bytes memory actions = planner.encode(); - - bool currency0IsFOT = fotKey.currency0 == Currency.wrap(address(fotToken)); - bool positionIsEntirelyInOtherToken = currency0IsFOT - ? tickUpper <= TickMath.getTickAtSqrtPrice(sqrtPriceX96) - : tickLower > TickMath.getTickAtSqrtPrice(sqrtPriceX96); - - if (bips == 10000 && !positionIsEntirelyInOtherToken) { + // needed to remove variables because of stack too deep + // Read below as: + // bool currency0IsFOT = fotKey.currency0 == Currency.wrap(address(fotToken)); + // bool positionIsEntirelyInOtherToken = currency0IsFOT + // ? tickUpper <= TickMath.getTickAtSqrtPrice(sqrtPriceX96) + // : tickLower > TickMath.getTickAtSqrtPrice(sqrtPriceX96); + // if (bips == 10000 && !positionIsEntirelyInOtherToken) { + if ( + bips == 10000 + && !( + fotKey.currency0 == Currency.wrap(address(fotToken)) + ? tickUpper <= TickMath.getTickAtSqrtPrice(sqrtPriceX96) + : tickLower > TickMath.getTickAtSqrtPrice(sqrtPriceX96) + ) + ) { vm.expectRevert(Position.CannotUpdateEmptyPosition.selector); - lpm.modifyLiquidities(actions, _deadline); + lpm.modifyLiquidities(planner.encode(), _deadline); } else { // MINT FROM DELTAS. - lpm.modifyLiquidities(actions, _deadline); + lpm.modifyLiquidities(planner.encode(), _deadline); - uint256 balance0After = fotKey.currency0.balanceOf(address(this)); - uint256 balance1After = fotKey.currency1.balanceOf(address(this)); - uint256 balance0PMAfter = fotKey.currency0.balanceOf(address(manager)); - uint256 balance1PMAfter = fotKey.currency1.balanceOf(address(manager)); + balance0._after = fotKey.currency0.balanceOf(address(this)); + balance1._after = fotKey.currency1.balanceOf(address(this)); + balance0PM._after = fotKey.currency0.balanceOf(address(manager)); + balance1PM._after = fotKey.currency1.balanceOf(address(manager)); // Calculate the expected resulting balances used to create liquidity after the fee is applied. - uint256 amountInFOT = currency0IsFOT ? amount0 : amount1; - uint256 expectedFee = amountInFOT.calculatePortion(bips); - (uint256 expected0, uint256 expected1) = currency0IsFOT - ? (balance0 - balance0After - expectedFee, balance1 - balance1After) - : (balance0 - balance0After, balance1 - balance1After - expectedFee); - - assertEq(expected0, balance0PMAfter - balance0PM); - assertEq(expected1, balance1PMAfter - balance1PM); - - // the liquidity that was created is a diff of the balance change - uint128 expectedLiquidity = LiquidityAmounts.getLiquidityForAmounts( - sqrtPriceX96, - TickMath.getSqrtPriceAtTick(tickLower), - TickMath.getSqrtPriceAtTick(tickUpper), - expected0, - expected1 - ); - - assertEq(lpm.ownerOf(tokenId), address(this)); - assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity); - + Balance memory expected; + { + bool currency0IsFOT = fotKey.currency0 == Currency.wrap(address(fotToken)); + uint256 expectedFee = (currency0IsFOT ? amount0 : amount1).calculatePortion(bips); + (expected._0, expected._1) = currency0IsFOT + ? (balance0._before - balance0._after - expectedFee, balance1._before - balance1._after) + : (balance0._before - balance0._after, balance1._before - balance1._after - expectedFee); + } + assertEq(expected._0, balance0PM._after - balance0PM._before); + assertEq(expected._1, balance1PM._after - balance1PM._before); + { + // the liquidity that was created is a diff of the balance change + uint128 expectedLiquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + expected._0, + expected._1 + ); + + assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this)); + assertEq(lpm.getPositionLiquidity(tokenId), expectedLiquidity); + } // BURN. planner = Planner.init(); // Note that the slippage does not include the fee from the transfer. planner.add( Actions.BURN_POSITION, - abi.encode(tokenId, expected0 == 0 ? 0 : expected0 - 1, expected1 == 0 ? 0 : expected1 - 1, ZERO_BYTES) + abi.encode( + tokenId, expected._0 == 0 ? 0 : expected._0 - 1, expected._1 == 0 ? 0 : expected._1 - 1, ZERO_BYTES + ) ); planner.add(Actions.TAKE_PAIR, abi.encode(fotKey.currency0, fotKey.currency1, ActionConstants.MSG_SENDER)); - actions = planner.encode(); - - lpm.modifyLiquidities(actions, _deadline); + lpm.modifyLiquidities(planner.encode(), _deadline); assertEq(lpm.getPositionLiquidity(tokenId), 0); } diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index 087c2102..18efd831 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -16,18 +16,17 @@ 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"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; -import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; -import {PoolInitializer} from "../../src/base/PoolInitializer.sol"; +import {IPositionManager, IPoolInitializer} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {IMulticall_v4} from "../../src/interfaces/IMulticall_v4.sol"; import {LiquidityFuzzers} from "../shared/fuzz/LiquidityFuzzers.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; import {Permit2SignatureHelpers} from "../shared/Permit2SignatureHelpers.sol"; -import {Permit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; +import {Permit2Forwarder, IPermit2Forwarder} from "../../src/base/Permit2Forwarder.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; import {IERC721Permit_v4} from "../../src/interfaces/IERC721Permit_v4.sol"; @@ -98,7 +97,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest // Use multicall to initialize a pool and mint liquidity bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); + calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1); config = PositionConfig({ poolKey: key, @@ -139,7 +138,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest // Use multicall to initialize the pool again. bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); + calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1); config = PositionConfig({ poolKey: key, @@ -185,7 +184,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest // Use multicall to initialize a pool and mint liquidity bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeWithSelector(lpm.initializePool.selector, key, SQRT_PRICE_1_1); + calls[0] = abi.encodeWithSelector(IPoolInitializer.initializePool.selector, key, SQRT_PRICE_1_1); config = PositionConfig({ poolKey: key, @@ -341,7 +340,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest assertEq(_amount, permitAmount); assertEq(liquidity, 10e18); - assertEq(lpm.ownerOf(tokenId), bob); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), bob); } function test_multicall_permit_batch_mint() public { @@ -398,7 +397,7 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest assertEq(_amount0, permitAmount); assertEq(_amount1, permitAmount); assertEq(liquidity, 10e18); - assertEq(lpm.ownerOf(tokenId), bob); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), bob); } /// @notice test that a front-ran permit does not fail a multicall with permit @@ -440,20 +439,20 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest // charlie tries to mint an LP token with multicall(permit, permit, mint) bytes[] memory calls = new bytes[](3); - calls[0] = abi.encodeWithSelector(Permit2Forwarder(lpm).permit.selector, charlie, permit0, sig0); - calls[1] = abi.encodeWithSelector(Permit2Forwarder(lpm).permit.selector, charlie, permit1, sig1); + calls[0] = abi.encodeWithSelector(IPermit2Forwarder.permit.selector, charlie, permit0, sig0); + calls[1] = abi.encodeWithSelector(IPermit2Forwarder.permit.selector, charlie, permit1, sig1); bytes memory mintCall = getMintEncoded(config, 10e18, charlie, ZERO_BYTES); calls[2] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, mintCall, _deadline); uint256 tokenId = lpm.nextTokenId(); vm.expectRevert(); - lpm.ownerOf(tokenId); // token does not exist + IERC721(address(lpm)).ownerOf(tokenId); // token does not exist bytes[] memory results = lpm.multicall(calls); assertEq(results[0], abi.encode(abi.encodeWithSelector(InvalidNonce.selector))); assertEq(results[1], abi.encode(abi.encodeWithSelector(InvalidNonce.selector))); - assertEq(lpm.ownerOf(tokenId), charlie); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), charlie); } /// @notice test that a front-ran permitBatch does not fail a multicall with permitBatch @@ -492,17 +491,17 @@ contract PositionManagerMulticallTest is Test, Permit2SignatureHelpers, PosmTest // charlie tries to mint an LP token with multicall(permitBatch, mint) bytes[] memory calls = new bytes[](2); - calls[0] = abi.encodeWithSelector(Permit2Forwarder(lpm).permitBatch.selector, charlie, permit, sig); + calls[0] = abi.encodeWithSelector(lpm.permitBatch.selector, charlie, permit, sig); bytes memory mintCall = getMintEncoded(config, 10e18, charlie, ZERO_BYTES); calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, mintCall, _deadline); uint256 tokenId = lpm.nextTokenId(); vm.expectRevert(); - lpm.ownerOf(tokenId); // token does not exist + IERC721(address(lpm)).ownerOf(tokenId); // token does not exist bytes[] memory results = lpm.multicall(calls); assertEq(results[0], abi.encode(abi.encodeWithSelector(InvalidNonce.selector))); - assertEq(lpm.ownerOf(tokenId), charlie); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), charlie); } } diff --git a/test/position-managers/PositionManager.notifier.t.sol b/test/position-managers/PositionManager.notifier.t.sol index 5dd2aea8..b69d0a86 100644 --- a/test/position-managers/PositionManager.notifier.t.sol +++ b/test/position-managers/PositionManager.notifier.t.sol @@ -22,6 +22,7 @@ import {INotifier} from "../../src/interfaces/INotifier.sol"; import {MockReturnDataSubscriber, MockRevertSubscriber} from "../mocks/MockBadSubscribers.sol"; import {PositionInfoLibrary, PositionInfo} from "../../src/libraries/PositionInfoLibrary.sol"; import {MockReenterHook} from "../mocks/MockReenterHook.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; contract PositionManagerNotifierTest is Test, PosmTestSetup { using PoolIdLibrary for PoolKey; @@ -90,7 +91,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -109,7 +110,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); vm.expectRevert(INotifier.NoCodeSubscriber.selector); @@ -122,7 +123,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); // successfully subscribe @@ -141,7 +142,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -170,7 +171,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -197,7 +198,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -220,7 +221,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -228,7 +229,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); - lpm.transferFrom(alice, bob, tokenId); + IERC721(address(lpm)).transferFrom(alice, bob, tokenId); assertEq(sub.notifyUnsubscribeCount(), 1); assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); @@ -241,7 +242,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -252,7 +253,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { vm.etch(address(sub), ZERO_BYTES); // unsubscribe happens anyway - lpm.transferFrom(alice, bob, tokenId); + IERC721(address(lpm)).transferFrom(alice, bob, tokenId); assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); assertEq(address(lpm.subscriber(tokenId)), address(0)); @@ -264,7 +265,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -272,7 +273,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); - lpm.safeTransferFrom(alice, bob, tokenId); + IERC721(address(lpm)).safeTransferFrom(alice, bob, tokenId); assertEq(sub.notifyUnsubscribeCount(), 1); assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); @@ -285,7 +286,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -296,7 +297,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { vm.etch(address(sub), ZERO_BYTES); // unsubscribe happens anyway - lpm.safeTransferFrom(alice, bob, tokenId); + IERC721(address(lpm)).safeTransferFrom(alice, bob, tokenId); assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); assertEq(address(lpm.subscriber(tokenId)), address(0)); @@ -308,7 +309,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -316,7 +317,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { assertEq(lpm.positionInfo(tokenId).hasSubscriber(), true); assertEq(address(lpm.subscriber(tokenId)), address(sub)); - lpm.safeTransferFrom(alice, bob, tokenId, ""); + IERC721(address(lpm)).safeTransferFrom(alice, bob, tokenId, ""); assertEq(sub.notifyUnsubscribeCount(), 1); assertEq(lpm.positionInfo(tokenId).hasSubscriber(), false); @@ -329,7 +330,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -347,7 +348,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(badSubscriber), ZERO_BYTES); @@ -367,7 +368,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -467,7 +468,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); vm.expectRevert(INotifier.NotSubscribed.selector); @@ -480,7 +481,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -499,7 +500,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), subData); @@ -516,7 +517,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); revertSubscriber.setRevert(true); @@ -539,7 +540,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(revertSubscriber), ZERO_BYTES); @@ -574,7 +575,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), subData); @@ -601,7 +602,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { // approve this contract to operate on alices liq vm.startPrank(alice); - lpm.approve(address(this), tokenId); + IERC721(address(lpm)).approve(address(this), tokenId); vm.stopPrank(); lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -626,7 +627,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData); // approve hook as it should not revert because it does not have permissions - lpm.approve(address(reenterHook), tokenId); + IERC721(address(lpm)).approve(address(reenterHook), tokenId); // subscribe as it should not revert because there is no subscriber lpm.subscribe(tokenId, address(sub), ZERO_BYTES); @@ -651,7 +652,7 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData); // approve hook as it should not revert because it does not have permissions - lpm.approve(address(reenterHook), tokenId); + IERC721(address(lpm)).approve(address(reenterHook), tokenId); // should revert since the pool manager is unlocked vm.expectRevert( @@ -670,11 +671,11 @@ contract PositionManagerNotifierTest is Test, PosmTestSetup { uint256 tokenId = lpm.nextTokenId(); mint(reenterConfig, 10e18, address(this), ZERO_BYTES); - bytes memory hookData = abi.encode(lpm.transferFrom.selector, address(this), tokenId); + bytes memory hookData = abi.encode(IERC721(address(lpm)).transferFrom.selector, address(this), tokenId); bytes memory actions = getMintEncoded(reenterConfig, 10e18, address(this), hookData); // approve hook as it should not revert because it does not have permissions - lpm.approve(address(reenterHook), tokenId); + IERC721(address(lpm)).approve(address(reenterHook), tokenId); // should revert since the pool manager is unlocked vm.expectRevert( diff --git a/test/position-managers/PositionManager.t.sol b/test/position-managers/PositionManager.t.sol index 7db64e9b..a0dc970d 100644 --- a/test/position-managers/PositionManager.t.sol +++ b/test/position-managers/PositionManager.t.sol @@ -19,10 +19,10 @@ import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {IERC721} from "forge-std/interfaces/IERC721.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {DeltaResolver} from "../../src/base/DeltaResolver.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; import {SlippageCheck} from "../../src/libraries/SlippageCheck.sol"; @@ -124,7 +124,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(tokenId, 1); assertEq(lpm.nextTokenId(), 2); - assertEq(lpm.ownerOf(tokenId), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), address(this)); uint256 liquidity = lpm.getPositionLiquidity(tokenId); @@ -159,7 +159,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1After = currency1.balanceOfSelf(); assertEq(tokenId, 1); - assertEq(lpm.ownerOf(1), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(1), address(this)); assertEq(uint256(int256(-delta.amount0())), amount0Desired); assertEq(uint256(int256(-delta.amount1())), amount1Desired); @@ -194,7 +194,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { uint256 balance1After = currency1.balanceOfSelf(); assertEq(tokenId, 1); - assertEq(lpm.ownerOf(1), alice); + assertEq(IERC721(address(lpm)).ownerOf(1), alice); assertEq(uint256(int256(-delta.amount0())), amount0Desired); assertEq(uint256(int256(-delta.amount1())), amount1Desired); @@ -219,7 +219,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta delta = getLastDelta(); assertEq(tokenId, 1); - assertEq(lpm.ownerOf(tokenId), alice); + assertEq(IERC721(address(lpm)).ownerOf(tokenId), alice); // alice was not the payer assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0()))); @@ -355,7 +355,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); - assertEq(lpm.ownerOf(1), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(1), address(this)); uint256 liquidity = lpm.getPositionLiquidity(tokenId); @@ -381,7 +381,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // 721 will revert if the token does not exist vm.expectRevert(); - lpm.ownerOf(1); + IERC721(address(lpm)).ownerOf(1); // no tokens were lost, TODO: fuzzer showing off by 1 sometimes // Potentially because we round down in core. I believe this is known in V3. But let's check! @@ -399,7 +399,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { PositionConfig memory config = PositionConfig({poolKey: key, tickLower: params.tickLower, tickUpper: params.tickUpper}); assertEq(tokenId, 1); - assertEq(lpm.ownerOf(1), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(1), address(this)); uint256 liquidity = lpm.getPositionLiquidity(tokenId); @@ -432,7 +432,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // OZ 721 will revert if the token does not exist vm.expectRevert(); - lpm.ownerOf(1); + IERC721(address(lpm)).ownerOf(1); // no tokens were lost, TODO: fuzzer showing off by 1 sometimes // Potentially because we round down in core. I believe this is known in V3. But let's check! @@ -750,7 +750,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { BalanceDelta mintDelta = getLastDelta(); // transfer to alice - lpm.transferFrom(address(this), alice, tokenId); + IERC721(address(lpm)).transferFrom(address(this), alice, tokenId); // alice can burn the position bytes memory calls = getBurnEncoded(tokenId, config, ZERO_BYTES); @@ -763,7 +763,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { // token was burned and does not exist anymore vm.expectRevert(); - lpm.ownerOf(tokenId); + IERC721(address(lpm)).ownerOf(tokenId); // alice received the principal liquidity assertApproxEqAbs(currency0.balanceOf(alice) - balance0BeforeAlice, uint128(-mintDelta.amount0()), 1 wei); @@ -782,7 +782,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { donateRouter.donate(key, feeRevenue0, feeRevenue1, ZERO_BYTES); // transfer to alice - lpm.transferFrom(address(this), alice, tokenId); + IERC721(address(lpm)).transferFrom(address(this), alice, tokenId); // alice can collect the fees uint256 balance0BeforeAlice = currency0.balanceOf(alice); @@ -806,7 +806,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { mint(config, liquidity, ActionConstants.MSG_SENDER, ZERO_BYTES); // transfer to alice - lpm.transferFrom(address(this), alice, tokenId); + IERC721(address(lpm)).transferFrom(address(this), alice, tokenId); // alice increases liquidity and is the payer uint256 balance0BeforeAlice = currency0.balanceOf(alice); @@ -846,7 +846,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { donateRouter.donate(key, feeRevenue0, feeRevenue1, ZERO_BYTES); // transfer to alice - lpm.transferFrom(address(this), alice, tokenId); + IERC721(address(lpm)).transferFrom(address(this), alice, tokenId); { // alice decreases liquidity and is the recipient @@ -980,7 +980,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq( currency1.balanceOfSelf(), balanceBefore1 - uint256(-int256(deltaDecrease.amount1() + deltaMint.amount1())) ); - assertEq(lpm.ownerOf(tokenIdMint), address(this)); + assertEq(IERC721(address(lpm)).ownerOf(tokenIdMint), address(this)); assertLt(currency1.balanceOfSelf(), balanceBefore1); // currency1 was owed assertLt(uint256(int256(deltaDecrease.amount1())), uint256(int256(-deltaMint.amount1()))); // amount1 in the second position was greater than amount1 in the first position } diff --git a/test/shared/Deploy.sol b/test/shared/Deploy.sol new file mode 100644 index 00000000..4b671d8c --- /dev/null +++ b/test/shared/Deploy.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {Vm} from "forge-std/Vm.sol"; +import {IPositionDescriptor} from "../../src/interfaces/IPositionDescriptor.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {IV4Quoter} from "../../src/interfaces/IV4Quoter.sol"; +import {IStateView} from "../../src/interfaces/IStateView.sol"; + +library Deploy { + Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function positionManager( + address poolManager, + address permit2, + uint256 unsubscribeGasLimit, + address positionDescriptor_, + address wrappedNative, + bytes memory salt + ) internal returns (IPositionManager manager) { + bytes memory args = abi.encode(poolManager, permit2, unsubscribeGasLimit, positionDescriptor_, wrappedNative); + bytes memory initcode = abi.encodePacked(vm.getCode("PositionManager.sol:PositionManager"), args); + assembly { + manager := create2(0, add(initcode, 0x20), mload(initcode), salt) + } + } + + function stateView(address poolManager, bytes memory salt) internal returns (IStateView stateView_) { + bytes memory args = abi.encode(poolManager); + bytes memory initcode = abi.encodePacked(vm.getCode("StateView.sol:StateView"), args); + assembly { + stateView_ := create2(0, add(initcode, 0x20), mload(initcode), salt) + } + } + + function v4Quoter(address poolManager, bytes memory salt) internal returns (IV4Quoter quoter) { + bytes memory args = abi.encode(poolManager); + bytes memory initcode = abi.encodePacked(vm.getCode("V4Quoter.sol:V4Quoter"), args); + assembly { + quoter := create2(0, add(initcode, 0x20), mload(initcode), salt) + } + } + + function positionDescriptor( + address poolManager, + address wrappedNative, + string memory nativeCurrencyLabel, + bytes memory salt + ) internal returns (IPositionDescriptor descriptor) { + bytes memory args = abi.encode(poolManager, wrappedNative, nativeCurrencyLabel); + bytes memory initcode = abi.encodePacked(vm.getCode("PositionDescriptor.sol:PositionDescriptor"), args); + assembly { + descriptor := create2(0, add(initcode, 0x20), mload(initcode), salt) + } + } +} diff --git a/test/shared/FeeMath.sol b/test/shared/FeeMath.sol index bb593eeb..b1b547e2 100644 --- a/test/shared/FeeMath.sol +++ b/test/shared/FeeMath.sol @@ -12,7 +12,6 @@ import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {PositionConfig} from "../shared/PositionConfig.sol"; library FeeMath { diff --git a/test/shared/LiquidityOperations.sol b/test/shared/LiquidityOperations.sol index 80d2d81e..9c65223c 100644 --- a/test/shared/LiquidityOperations.sol +++ b/test/shared/LiquidityOperations.sol @@ -9,7 +9,8 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {PositionManager, Actions} from "../../src/PositionManager.sol"; +import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; import {PositionConfig} from "./PositionConfig.sol"; import {Planner, Plan} from "../shared/Planner.sol"; import {HookSavesDelta} from "./HookSavesDelta.sol"; @@ -18,7 +19,7 @@ abstract contract LiquidityOperations is CommonBase { using Planner for Plan; using SafeCast for *; - PositionManager lpm; + IPositionManager lpm; uint256 _deadline = block.timestamp + 1; diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 0d5ca4fc..f457207f 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.20; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {IPositionManager} from "../../src/interfaces/IPositionManager.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {ActionConstants} from "../../src/libraries/ActionConstants.sol"; diff --git a/test/shared/PosmTestSetup.sol b/test/shared/PosmTestSetup.sol index bce86b97..8798220e 100644 --- a/test/shared/PosmTestSetup.sol +++ b/test/shared/PosmTestSetup.sol @@ -8,14 +8,13 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -import {PositionManager} from "../../src/PositionManager.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; import {HookSavesDelta} from "./HookSavesDelta.sol"; import {HookModifyLiquidities} from "./HookModifyLiquidities.sol"; -import {PositionDescriptor} from "../../src/PositionDescriptor.sol"; +import {Deploy, IPositionDescriptor} from "./Deploy.sol"; import {ERC721PermitHash} from "../../src/libraries/ERC721PermitHash.sol"; import {IWETH9} from "../../src/interfaces/external/IWETH9.sol"; import {WETH} from "solmate/src/tokens/WETH.sol"; @@ -29,7 +28,7 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { uint256 constant STARTING_USER_BALANCE = 10_000_000 ether; IAllowanceTransfer permit2; - PositionDescriptor public positionDescriptor; + IPositionDescriptor 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())); @@ -68,8 +67,11 @@ contract PosmTestSetup is Test, Deployers, DeployPermit2, LiquidityOperations { function deployPosm(IPoolManager poolManager) internal { // 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, _WETH9); + positionDescriptor = + Deploy.positionDescriptor(address(poolManager), 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, "ETH", hex"00"); + lpm = Deploy.positionManager( + address(poolManager), address(permit2), 100_000, address(positionDescriptor), address(_WETH9), hex"03" + ); } function seedBalance(address to) internal { diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index 67b8f601..b29972b9 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -14,7 +14,6 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -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";