From 581d96dfd8b281cafe351205bf6d2d65efb4df90 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:01:20 -0400 Subject: [PATCH 01/13] Update to v4-core latest (#64) * first pass on using new router function singatures * updated v4-core * updated .getSlot0, as it returns less data now * snapshots --- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/hooks/examples/FullRange.sol | 6 ++--- contracts/hooks/examples/GeomeanOracle.sol | 4 ++-- contracts/hooks/examples/LimitOrder.sol | 2 +- contracts/hooks/examples/TWAMM.sol | 2 +- lib/v4-core | 2 +- test/FullRange.t.sol | 22 ++++++++++--------- test/GeomeanOracle.t.sol | 9 +++++--- test/LimitOrder.t.sol | 12 ++++++---- test/TWAMM.t.sol | 12 ++++++---- 18 files changed, 51 insertions(+), 38 deletions(-) diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 9f392fe8..2d5250a5 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -413041 \ No newline at end of file +412696 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index 96e70776..032a6a3b 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -207307 \ No newline at end of file +206962 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 47ea1890..9d59ac16 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -153143 \ No newline at end of file +154763 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 7b6d6e9c..e0b3ab13 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -879088 \ No newline at end of file +879542 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index b839fdb4..920384a4 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200439 \ No newline at end of file +200095 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 0138b0f1..5ee38978 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -380062 \ No newline at end of file +379287 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index cadd0b38..436848b5 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -110682 \ No newline at end of file +112303 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 7233e853..d48620c7 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -151418 \ No newline at end of file +153038 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 7cdc684f..9adc49a6 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -123914 \ No newline at end of file +123576 \ No newline at end of file diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 49826da8..6c5b08ec 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -115,7 +115,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -172,7 +172,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -332,7 +332,7 @@ contract FullRange is BaseHook, ILockCallback { ) * FixedPointMathLib.sqrt(FixedPoint96.Q96) ).toUint160(); - (uint160 sqrtPriceX96,,,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); poolManager.swap( key, diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index a2572f73..5c78e785 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -101,7 +101,7 @@ contract GeomeanOracle is BaseHook { /// @dev Called before any action that potentially modifies pool price or liquidity, such as swap or modify position function _updatePool(PoolKey calldata key) private { PoolId id = key.toId(); - (, int24 tick,,,,) = poolManager.getSlot0(id); + (, int24 tick,,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); @@ -146,7 +146,7 @@ contract GeomeanOracle is BaseHook { ObservationState memory state = states[id]; - (, int24 tick,,,,) = poolManager.getSlot0(id); + (, int24 tick,,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 9f305f2a..8eff6c68 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -107,7 +107,7 @@ contract LimitOrder is BaseHook { } function getTick(PoolId poolId) private view returns (int24 tick) { - (, tick,,,,) = poolManager.getSlot0(poolId); + (, tick,,) = poolManager.getSlot0(poolId); } function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 08d7e026..55d44888 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -138,7 +138,7 @@ contract TWAMM is BaseHook, ITWAMM { /// @inheritdoc ITWAMM function executeTWAMMOrders(PoolKey memory key) public { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); State storage twamm = twammStates[poolId]; (bool zeroForOne, uint160 sqrtPriceLimitX96) = _executeTWAMMOrders( diff --git a/lib/v4-core b/lib/v4-core index 73938802..0095e084 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 73938802cad600beb07bd805cc9883e25bf87261 +Subproject commit 0095e0848098c3e32e016eac6d2537b67aa47358 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 8cf9bb3d..fa9d13ed 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -282,7 +282,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeSwap"); - swapRouter.swap(key, params, settings); + swapRouter.swap(key, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); @@ -319,7 +319,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings memory settings = PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, settings); + swapRouter.swap(key, params, settings, ZERO_BYTES); vm.expectRevert(FullRange.TooMuchSlippage.selector); fullRange.addLiquidity( @@ -345,14 +345,14 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeFirstSwap"); - swapRouter.swap(testKey, params, settings); + swapRouter.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); snapStart("FullRangeSecondSwap"); - swapRouter.swap(testKey, params, settings); + swapRouter.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (hasAccruedFees,) = fullRange.poolInfo(id); @@ -380,8 +380,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings); - swapRouter.swap(key2, params, testSettings); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); + swapRouter.swap(key2, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -563,7 +563,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(keyWithLiq, params, testSettings); + swapRouter.swap(keyWithLiq, params, testSettings, ZERO_BYTES); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -690,7 +690,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -745,7 +745,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolSwapTest.TestSettings memory testSettings = PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings); + swapRouter.swap(key, params, testSettings, ZERO_BYTES); // Test contract removes liquidity, succeeds UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -765,7 +765,9 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.expectRevert(FullRange.SenderMustBeHook.selector); modifyPositionRouter.modifyPosition( - key, IPoolManager.ModifyPositionParams({tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: 100}) + key, + IPoolManager.ModifyPositionParams({tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: 100}), + ZERO_BYTES ); } diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index 523498bf..bd0e0c05 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -127,7 +127,8 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { key, IPoolManager.ModifyPositionParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 - ) + ), + ZERO_BYTES ); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); @@ -149,7 +150,8 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { key, IPoolManager.ModifyPositionParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 - ) + ), + ZERO_BYTES ); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); @@ -177,7 +179,8 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { key, IPoolManager.ModifyPositionParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 - ) + ), + ZERO_BYTES ); // cardinality is updated diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 9939c7e2..27613654 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -108,7 +108,10 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei swapRouter.swap( - key, IPoolManager.SwapParams(false, 1, SQRT_RATIO_1_1 + 1), PoolSwapTest.TestSettings(true, true) + key, + IPoolManager.SwapParams(false, 1, SQRT_RATIO_1_1 + 1), + PoolSwapTest.TestSettings(true, true), + ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); limitOrder.place(key, 0, true, 1000000); @@ -131,7 +134,7 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testNotZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei swapRouter.swap( - key, IPoolManager.SwapParams(true, 1, SQRT_RATIO_1_1 - 1), PoolSwapTest.TestSettings(true, true) + key, IPoolManager.SwapParams(true, 1, SQRT_RATIO_1_1 - 1), PoolSwapTest.TestSettings(true, true), ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); limitOrder.place(key, -60, false, 1000000); @@ -192,11 +195,12 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { swapRouter.swap( key, IPoolManager.SwapParams(false, 1e18, TickMath.getSqrtRatioAtTick(60)), - PoolSwapTest.TestSettings(true, true) + PoolSwapTest.TestSettings(true, true), + ZERO_BYTES ); assertEq(limitOrder.getTickLowerLast(id), 60); - (, int24 tick,,,,) = manager.getSlot0(id); + (, int24 tick,,) = manager.getSlot0(id); assertEq(tick, 60); (bool filled,,, uint256 token0Total, uint256 token1Total,) = limitOrder.epochInfos(Epoch.wrap(1)); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 7aef0156..84ed9716 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -87,10 +87,12 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { token1.approve(address(modifyPositionRouter), 100 ether); token0.mint(address(this), 100 ether); token1.mint(address(this), 100 ether); - modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-60, 60, 10 ether)); - modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-120, 120, 10 ether)); + modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-60, 60, 10 ether), ZERO_BYTES); + modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-120, 120, 10 ether), ZERO_BYTES); modifyPositionRouter.modifyPosition( - poolKey, IPoolManager.ModifyPositionParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether) + poolKey, + IPoolManager.ModifyPositionParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether), + ZERO_BYTES ); } @@ -367,7 +369,9 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { token0.approve(address(twamm), 100e18); token1.approve(address(twamm), 100e18); - modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-2400, 2400, 10 ether)); + modifyPositionRouter.modifyPosition( + poolKey, IPoolManager.ModifyPositionParams(-2400, 2400, 10 ether), ZERO_BYTES + ); vm.warp(10000); twamm.submitOrder(poolKey, orderKey1, orderAmount); From 886403181f707f9645d59d47180cca042bc4eb87 Mon Sep 17 00:00:00 2001 From: Tina <59578595+tinaszheng@users.noreply.github.com> Date: Tue, 28 Nov 2023 11:41:00 -0500 Subject: [PATCH 02/13] add base contracts and interfaces (#75) --- contracts/base/Multicall.sol | 33 +++++++++++ contracts/base/PeripheryPayments.sol | 41 ++++++++++++++ contracts/base/PeripheryValidation.sol | 11 ++++ contracts/base/SelfPermit.sol | 52 +++++++++++++++++ contracts/interfaces/IMulticall.sol | 12 ++++ contracts/interfaces/IPeripheryPayments.sol | 17 ++++++ contracts/interfaces/ISelfPermit.sol | 56 +++++++++++++++++++ contracts/interfaces/external/IERC1271.sol | 16 ++++++ .../external/IERC20PermitAllowed.sol | 27 +++++++++ 9 files changed, 265 insertions(+) create mode 100644 contracts/base/Multicall.sol create mode 100644 contracts/base/PeripheryPayments.sol create mode 100644 contracts/base/PeripheryValidation.sol create mode 100644 contracts/base/SelfPermit.sol create mode 100644 contracts/interfaces/IMulticall.sol create mode 100644 contracts/interfaces/IPeripheryPayments.sol create mode 100644 contracts/interfaces/ISelfPermit.sol create mode 100644 contracts/interfaces/external/IERC1271.sol create mode 100644 contracts/interfaces/external/IERC20PermitAllowed.sol diff --git a/contracts/base/Multicall.sol b/contracts/base/Multicall.sol new file mode 100644 index 00000000..bd926766 --- /dev/null +++ b/contracts/base/Multicall.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.19; + +import {IMulticall} from "../interfaces/IMulticall.sol"; + +/// @title Multicall +/// @notice Enables calling multiple methods in a single call to the contract +abstract contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // handle custom errors + if (result.length == 4) { + assembly { + revert(add(result, 0x20), mload(result)) + } + } + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/contracts/base/PeripheryPayments.sol b/contracts/base/PeripheryPayments.sol new file mode 100644 index 00000000..f272da34 --- /dev/null +++ b/contracts/base/PeripheryPayments.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ERC20} from "solmate/tokens/ERC20.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; +import {IPeripheryPayments} from "../interfaces/IPeripheryPayments.sol"; + +abstract contract PeripheryPayments is IPeripheryPayments { + using CurrencyLibrary for Currency; + using SafeTransferLib for address; + using SafeTransferLib for ERC20; + + error InsufficientToken(); + error NativeTokenTransferFrom(); + + /// @inheritdoc IPeripheryPayments + function sweepToken(Currency currency, uint256 amountMinimum, address recipient) public payable override { + uint256 balanceCurrency = currency.balanceOfSelf(); + if (balanceCurrency < amountMinimum) revert InsufficientToken(); + + if (balanceCurrency > 0) { + currency.transfer(recipient, balanceCurrency); + } + } + + /// @param currency The currency to pay + /// @param payer The entity that must pay + /// @param recipient The entity that will receive payment + /// @param value The amount to pay + function pay(Currency currency, address payer, address recipient, uint256 value) internal { + if (payer == address(this)) { + // pay with tokens already in the contract (for the exact input multihop case) + currency.transfer(recipient, value); + } else { + if (currency.isNative()) revert NativeTokenTransferFrom(); + // pull payment + ERC20(Currency.unwrap(currency)).safeTransferFrom(payer, recipient, value); + } + } +} diff --git a/contracts/base/PeripheryValidation.sol b/contracts/base/PeripheryValidation.sol new file mode 100644 index 00000000..b8ea81d4 --- /dev/null +++ b/contracts/base/PeripheryValidation.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +abstract contract PeripheryValidation { + error TransactionTooOld(); + + modifier checkDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert TransactionTooOld(); + _; + } +} diff --git a/contracts/base/SelfPermit.sol b/contracts/base/SelfPermit.sol new file mode 100644 index 00000000..40449636 --- /dev/null +++ b/contracts/base/SelfPermit.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; + +import {IERC20PermitAllowed} from "../interfaces/external/IERC20PermitAllowed.sol"; +import {ISelfPermit} from "../interfaces/ISelfPermit.sol"; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function +/// that requires an approval in a single transaction. +abstract contract SelfPermit is ISelfPermit { + /// @inheritdoc ISelfPermit + function selfPermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + public + payable + override + { + IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitIfNecessary(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable + override + { + if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + public + payable + override + { + IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowedIfNecessary(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable + override + { + if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max) { + selfPermitAllowed(token, nonce, expiry, v, r, s); + } + } +} diff --git a/contracts/interfaces/IMulticall.sol b/contracts/interfaces/IMulticall.sol new file mode 100644 index 00000000..dfa9db24 --- /dev/null +++ b/contracts/interfaces/IMulticall.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + /// @dev The `msg.value` should not be trusted for any method callable from multicall. + /// @param data The encoded function data for each of the calls to make to this contract + /// @return results The results from each of the calls passed in via data + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol new file mode 100644 index 00000000..765b980f --- /dev/null +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; + +/// @title Periphery Payments +/// @notice Functions to ease deposits and withdrawals of ETH +interface IPeripheryPayments { + // TODO: figure out if we still need unwrapWETH9 from v3? + + /// @notice Transfers the full amount of a token held by this contract to recipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users + /// @param currency The contract address of the token which will be transferred to `recipient` + /// @param amountMinimum The minimum amount of token required for a transfer + /// @param recipient The destination address of the token + function sweepToken(Currency currency, uint256 amountMinimum, address recipient) external payable; +} diff --git a/contracts/interfaces/ISelfPermit.sol b/contracts/interfaces/ISelfPermit.sol new file mode 100644 index 00000000..cb2445f5 --- /dev/null +++ b/contracts/interfaces/ISelfPermit.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// Can be used instead of #selfPermit to prevent calls from failing due to a frontrun of a call to #selfPermit + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitIfNecessary(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// Can be used instead of #selfPermitAllowed to prevent calls from failing due to a frontrun of a call to #selfPermitAllowed. + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowedIfNecessary(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable; +} diff --git a/contracts/interfaces/external/IERC1271.sol b/contracts/interfaces/external/IERC1271.sol new file mode 100644 index 00000000..dcb30cb8 --- /dev/null +++ b/contracts/interfaces/external/IERC1271.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Interface for verifying contract-based account signatures +/// @notice Interface that verifies provided signature for the data +/// @dev Interface defined by EIP-1271 +interface IERC1271 { + /// @notice Returns whether the provided signature is valid for the provided data + /// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes. + /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5). + /// MUST allow external calls. + /// @param hash Hash of the data to be signed + /// @param signature Signature byte array associated with _data + /// @return magicValue The bytes4 magic value 0x1626ba7e + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} diff --git a/contracts/interfaces/external/IERC20PermitAllowed.sol b/contracts/interfaces/external/IERC20PermitAllowed.sol new file mode 100644 index 00000000..7f2cf657 --- /dev/null +++ b/contracts/interfaces/external/IERC20PermitAllowed.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Interface for permit +/// @notice Interface used by DAI/CHAI for permit +interface IERC20PermitAllowed { + /// @notice Approve the spender to spend some tokens via the holder signature + /// @dev This is the permit interface used by DAI and CHAI + /// @param holder The address of the token holder, the token owner + /// @param spender The address of the token spender + /// @param nonce The holder's nonce, increases at each call to permit + /// @param expiry The timestamp at which the permit is no longer valid + /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} From 0b46125bb9673bd9e8cfaeee49e90d99452adc79 Mon Sep 17 00:00:00 2001 From: marktoda Date: Tue, 19 Dec 2023 10:11:05 -0500 Subject: [PATCH 03/13] Update v4 core (#74) * feat: update v4-core This commit updates v4 core to latest and fixes integration issues * fix: tests * fix: update tests * fix: test router was borked * fix: alice comments * update to latest core * use prev values * change twamm to use pool getters * changes after merging main * use --via-ir in cli * fix formatting * fix FullRange/TWAMM hook --------- Co-authored-by: Sara Reynolds --- .env | 7 + .../FullOracleObserve0After5Seconds.snap | 2 +- .../FullOracleObserve200By13.snap | 2 +- .../FullOracleObserve200By13Plus5.snap | 2 +- .../FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .../FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .../OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .../OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- ...eObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .../OracleObserveCurrentTime.snap | 2 +- ...racleObserveCurrentTimeCounterfactual.snap | 2 +- .../OracleObserveLast20Seconds.snap | 2 +- .../OracleObserveLatestEqual.snap | 2 +- .../OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .../OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- .github/workflows/test.yml | 2 +- .gitignore | 3 +- contracts/BaseHook.sol | 21 ++- contracts/base/PeripheryPayments.sol | 2 +- contracts/hooks/examples/FullRange.sol | 50 ++++--- contracts/hooks/examples/GeomeanOracle.sol | 22 +-- contracts/hooks/examples/LimitOrder.sol | 56 ++++--- contracts/hooks/examples/TWAMM.sol | 55 +++---- contracts/hooks/examples/VolatilityOracle.sol | 24 ++- contracts/interfaces/IPeripheryPayments.sol | 2 +- contracts/interfaces/ITWAMM.sol | 10 +- contracts/libraries/LiquidityAmounts.sol | 4 +- contracts/libraries/PoolGetters.sol | 13 +- contracts/libraries/TWAMM/TwammMath.sol | 6 +- contracts/libraries/TransferHelper.sol | 2 +- foundry.toml | 2 + lib/v4-core | 2 +- test/FullRange.t.sol | 140 +++++++++--------- test/GeomeanOracle.t.sol | 51 +++---- test/LimitOrder.t.sol | 69 +++++---- test/TWAMM.t.sol | 58 ++++---- .../FullRangeImplementation.sol | 6 +- .../GeomeanOracleImplementation.sol | 6 +- .../LimitOrderImplementation.sol | 6 +- .../implementation/TWAMMImplementation.sol | 6 +- test/utils/HookEnabledSwapRouter.sol | 71 +++++++++ 57 files changed, 419 insertions(+), 337 deletions(-) create mode 100644 .env create mode 100644 test/utils/HookEnabledSwapRouter.sol diff --git a/.env b/.env new file mode 100644 index 00000000..7859e840 --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +FOUNDRY_FUZZ_SEED=0x4444 + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + export FOUNDRY_SOLC="./lib/v4-core/bin/solc-static-linux" +elif [[ "$OSTYPE" == "darwin"* ]]; then + export FOUNDRY_SOLC="./lib/v4-core/bin/solc-mac" +fi diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index 9463411b..bc61a749 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2771 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 638f8744..7706f4dd 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -21068 \ No newline at end of file +23377 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 1bc3059d..8afa5484 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -21318 \ No newline at end of file +23624 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index a5bb2393..f66ebbd5 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2076 \ No newline at end of file +2798 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index db768f3a..9db3df4e 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -20164 \ No newline at end of file +22396 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index c04b75bb..b2f26cf1 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -20458 \ No newline at end of file +22695 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 2d5250a5..ef62f828 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -412696 \ No newline at end of file +410761 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index 032a6a3b..b3688dfa 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -206962 \ No newline at end of file +204683 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 9d59ac16..2b5ad7d2 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -154763 \ No newline at end of file +156432 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index e0b3ab13..c2b5d0ef 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -879542 \ No newline at end of file +897565 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 920384a4..bc1c95e2 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200095 \ No newline at end of file +200057 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 5ee38978..b5d7708e 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -379287 \ No newline at end of file +386095 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 436848b5..9e12e78d 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -112303 \ No newline at end of file +114700 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index d48620c7..d9365d02 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -153038 \ No newline at end of file +154641 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 61763356..3aa3cfac 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -233028 \ No newline at end of file +254711 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 4f1264df..50fc054a 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -223717 \ No newline at end of file +245393 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 3d85d6d7..15a052b9 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -32886 \ No newline at end of file +54893 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index bc6dc069..d6664238 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -23586 \ No newline at end of file +45575 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index da81ec04..3039612c 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -51411 \ No newline at end of file +72361 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index f61a3565..ae13ac3f 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -5571 \ No newline at end of file +6618 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index 41599c5d..b63da1de 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -75965 \ No newline at end of file +88543 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index 9463411b..bc61a749 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2771 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index 0b1caa8d..ba7fb703 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -5746 \ No newline at end of file +6807 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index bee097af..3ee11622 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -5277 \ No newline at end of file +6319 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index a51f76e9..204ec243 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -2615 \ No newline at end of file +3466 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 9adc49a6..0aef60be 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -123576 \ No newline at end of file +146158 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 280df88b..f6d99f2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,6 @@ jobs: version: nightly - name: Run tests - run: forge test -vvv + run: forge test -vvv --via-ir env: FOUNDRY_PROFILE: ci diff --git a/.gitignore b/.gitignore index de5c2c73..785fb393 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ cache/ -foundry-out/ \ No newline at end of file +foundry-out/ +.vscode/ \ No newline at end of file diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index 8d463807..941de34c 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; abstract contract BaseHook is IHooks { error NotPoolManager(); @@ -40,16 +40,21 @@ abstract contract BaseHook is IHooks { _; } - function getHooksCalls() public pure virtual returns (Hooks.Calls memory); + function getHooksCalls() public pure virtual returns (Hooks.Permissions memory); // this function is virtual so that we can override it during testing, // which allows us to deploy an implementation to any address // and then etch the bytecode into the correct address function validateHookAddress(BaseHook _this) internal pure virtual { - Hooks.validateHookAddress(_this, getHooksCalls()); + Hooks.validateHookPermissions(_this, getHooksCalls()); } - function lockAcquired(bytes calldata data) external virtual poolManagerOnly returns (bytes memory) { + function lockAcquired(address, /*sender*/ bytes calldata data) + external + virtual + poolManagerOnly + returns (bytes memory) + { (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); diff --git a/contracts/base/PeripheryPayments.sol b/contracts/base/PeripheryPayments.sol index f272da34..24466924 100644 --- a/contracts/base/PeripheryPayments.sol +++ b/contracts/base/PeripheryPayments.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {ERC20} from "solmate/tokens/ERC20.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {IPeripheryPayments} from "../interfaces/IPeripheryPayments.sol"; diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 6c5b08ec..1bd0cfe2 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback/ILockCallback.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; import {UniswapV4ERC20} from "../../libraries/UniswapV4ERC20.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -87,8 +87,8 @@ contract FullRange is BaseHook, ILockCallback { _; } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: true, @@ -96,7 +96,9 @@ contract FullRange is BaseHook, ILockCallback { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -115,7 +117,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -172,7 +174,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -247,7 +249,9 @@ contract FullRange is BaseHook, ILockCallback { internal returns (BalanceDelta delta) { - delta = abi.decode(poolManager.lock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); + delta = abi.decode( + poolManager.lock(address(this), abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta) + ); } function _settleDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { @@ -295,12 +299,14 @@ contract FullRange is BaseHook, ILockCallback { pool.hasAccruedFees = false; } - function lockAcquired(bytes calldata rawData) + function lockAcquired(address sender, bytes calldata rawData) external override(ILockCallback, BaseHook) poolManagerOnly returns (bytes memory) { + // Now that manager can be called by EOAs with a lock target, it's necessary for lockAcquired to check the original sender if it wants to trust the data passed through. + if (sender != address(this)) revert SenderMustBeHook(); CallbackData memory data = abi.decode(rawData, (CallbackData)); BalanceDelta delta; @@ -332,7 +338,7 @@ contract FullRange is BaseHook, ILockCallback { ) * FixedPointMathLib.sqrt(FixedPoint96.Q96) ).toUint160(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); poolManager.swap( key, diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index 5c78e785..35389d0f 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../../libraries/Oracle.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; /// @notice A hook for a pool that allows a Uniswap pool to act as an oracle. Pools that use this hook must have full range /// tick spacing and liquidity is always permanently locked in these pools. This is the suggested configuration @@ -60,8 +60,8 @@ contract GeomeanOracle is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeModifyPosition: true, @@ -69,7 +69,9 @@ contract GeomeanOracle is BaseHook { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -101,7 +103,7 @@ contract GeomeanOracle is BaseHook { /// @dev Called before any action that potentially modifies pool price or liquidity, such as swap or modify position function _updatePool(PoolKey calldata key) private { PoolId id = key.toId(); - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); @@ -146,7 +148,7 @@ contract GeomeanOracle is BaseHook { ObservationState memory state = states[id]; - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 8eff6c68..2a5287bf 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; type Epoch is uint232; @@ -73,8 +73,8 @@ contract LimitOrder is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeModifyPosition: false, @@ -82,7 +82,9 @@ contract LimitOrder is BaseHook { beforeSwap: false, afterSwap: true, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -107,7 +109,7 @@ contract LimitOrder is BaseHook { } function getTick(PoolId poolId) private view returns (int24 tick) { - (, tick,,) = poolManager.getSlot0(poolId); + (, tick,) = poolManager.getSlot0(poolId); } function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { @@ -156,6 +158,7 @@ contract LimitOrder is BaseHook { (uint256 amount0, uint256 amount1) = abi.decode( poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredFill, (key, lower, -int256(uint256(epochInfo.liquidityTotal)))) ), (uint256, uint256) @@ -204,8 +207,12 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) poolManager.mint(key.currency0, address(this), amount0 = uint128(-delta.amount0())); - if (delta.amount1() < 0) poolManager.mint(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + if (delta.amount0() < 0) { + poolManager.mint(key.currency0, address(this), amount0 = uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + poolManager.mint(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + } } function place(PoolKey calldata key, int24 tickLower, bool zeroForOne, uint128 liquidity) @@ -215,6 +222,7 @@ contract LimitOrder is BaseHook { if (liquidity == 0) revert ZeroLiquidity(); poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender)) ); @@ -298,6 +306,7 @@ contract LimitOrder is BaseHook { uint256 amount1Fee; (amount0, amount1, amount0Fee, amount1Fee) = abi.decode( poolManager.lock( + address(this), abi.encodeCall( this.lockAcquiredKill, (key, tickLower, -int256(uint256(liquidity)), to, liquidity == liquidityTotal) @@ -352,8 +361,12 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) poolManager.take(key.currency0, to, amount0 = uint128(-delta.amount0())); - if (delta.amount1() < 0) poolManager.take(key.currency1, to, amount1 = uint128(-delta.amount1())); + if (delta.amount0() < 0) { + poolManager.take(key.currency0, to, amount0 = uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + poolManager.take(key.currency1, to, amount1 = uint128(-delta.amount1())); + } } function withdraw(Epoch epoch, address to) external returns (uint256 amount0, uint256 amount1) { @@ -377,6 +390,7 @@ contract LimitOrder is BaseHook { epochInfo.liquidityTotal = liquidityTotal - liquidity; poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to)) ); @@ -391,15 +405,11 @@ contract LimitOrder is BaseHook { address to ) external selfOnly { if (token0Amount > 0) { - poolManager.safeTransferFrom( - address(this), address(poolManager), uint256(uint160(Currency.unwrap(currency0))), token0Amount, "" - ); + poolManager.burn(currency0, token0Amount); poolManager.take(currency0, to, token0Amount); } if (token1Amount > 0) { - poolManager.safeTransferFrom( - address(this), address(poolManager), uint256(uint160(Currency.unwrap(currency1))), token1Amount, "" - ); + poolManager.burn(currency1, token1Amount); poolManager.take(currency1, to, token1Amount); } } diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 55d44888..4fd5dd74 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -1,24 +1,24 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickBitmap} from "@uniswap/v4-core/contracts/libraries/TickBitmap.sol"; -import {SqrtPriceMath} from "@uniswap/v4-core/contracts/libraries/SqrtPriceMath.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickBitmap} from "@uniswap/v4-core/src/libraries/TickBitmap.sol"; +import {SqrtPriceMath} from "@uniswap/v4-core/src/libraries/SqrtPriceMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {ITWAMM} from "../../interfaces/ITWAMM.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {TransferHelper} from "../../libraries/TransferHelper.sol"; import {TwammMath} from "../../libraries/TWAMM/TwammMath.sol"; import {OrderPool} from "../../libraries/TWAMM/OrderPool.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolGetters} from "../../libraries/PoolGetters.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract TWAMM is BaseHook, ITWAMM { using TransferHelper for IERC20Minimal; @@ -60,8 +60,8 @@ contract TWAMM is BaseHook, ITWAMM { expirationInterval = _expirationInterval; } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: true, @@ -69,7 +69,9 @@ contract TWAMM is BaseHook, ITWAMM { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -129,16 +131,10 @@ contract TWAMM is BaseHook, ITWAMM { self.lastVirtualOrderTimestamp = block.timestamp; } - struct CallbackData { - address sender; - PoolKey key; - IPoolManager.SwapParams params; - } - /// @inheritdoc ITWAMM function executeTWAMMOrders(PoolKey memory key) public { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); State storage twamm = twammStates[poolId]; (bool zeroForOne, uint160 sqrtPriceLimitX96) = _executeTWAMMOrders( @@ -146,7 +142,9 @@ contract TWAMM is BaseHook, ITWAMM { ); if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { - poolManager.lock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96))); + poolManager.lock( + address(this), abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)) + ); } } @@ -302,7 +300,12 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function lockAcquired(bytes calldata rawData) external override poolManagerOnly returns (bytes memory) { + function lockAcquired(address, /*sender*/ bytes calldata rawData) + external + override + poolManagerOnly + returns (bytes memory) + { (PoolKey memory key, IPoolManager.SwapParams memory swapParams) = abi.decode(rawData, (PoolKey, IPoolManager.SwapParams)); @@ -516,7 +519,7 @@ contract TWAMM is BaseHook, ITWAMM { _isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96); if (crossingInitializedTick) { - int128 liquidityNetAtTick = poolManager.getNetLiquidityAtTick(poolKey.toId(), tick); + int128 liquidityNetAtTick = poolManager.getPoolTickInfo(poolKey.toId(), tick).liquidityNet; uint160 initializedSqrtPrice = TickMath.getSqrtRatioAtTick(tick); uint256 swapDelta0 = SqrtPriceMath.getAmount0Delta( @@ -600,7 +603,7 @@ contract TWAMM is BaseHook, ITWAMM { unchecked { // update pool - int128 liquidityNet = poolManager.getNetLiquidityAtTick(poolKey.toId(), params.initializedTick); + int128 liquidityNet = poolManager.getPoolTickInfo(poolKey.toId(), params.initializedTick).liquidityNet; if (initializedSqrtPrice < params.pool.sqrtPriceX96) liquidityNet = -liquidityNet; params.pool.liquidity = liquidityNet < 0 ? params.pool.liquidity - uint128(-liquidityNet) diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index 0a7e696d..657c9fae 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IDynamicFeeManager} from "@uniswap/v4-core/contracts/interfaces/IDynamicFeeManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {FeeLibrary} from "@uniswap/v4-core/contracts/libraries/FeeLibrary.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IDynamicFeeManager} from "@uniswap/v4-core/src/interfaces/IDynamicFeeManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract VolatilityOracle is BaseHook, IDynamicFeeManager { using FeeLibrary for uint24; @@ -15,11 +15,7 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { uint32 deployTimestamp; - function getFee(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) - external - view - returns (uint24) - { + function getFee(address, PoolKey calldata) external view returns (uint24) { uint24 startingFee = 3000; uint32 lapsed = _blockTimestamp() - deployTimestamp; return startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute @@ -34,8 +30,8 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { deployTimestamp = _blockTimestamp(); } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: false, @@ -43,7 +39,9 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol index 765b980f..f3c24660 100644 --- a/contracts/interfaces/IPeripheryPayments.sol +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; /// @title Periphery Payments /// @notice Functions to ease deposits and withdrawals of ETH diff --git a/contracts/interfaces/ITWAMM.sol b/contracts/interfaces/ITWAMM.sol index 570617b6..3b932d3c 100644 --- a/contracts/interfaces/ITWAMM.sol +++ b/contracts/interfaces/ITWAMM.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; interface ITWAMM { /// @notice Thrown when account other than owner attempts to interact with an order diff --git a/contracts/libraries/LiquidityAmounts.sol b/contracts/libraries/LiquidityAmounts.sol index b2c8b54c..845cc6e0 100644 --- a/contracts/libraries/LiquidityAmounts.sol +++ b/contracts/libraries/LiquidityAmounts.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; +import "@uniswap/v4-core/src/libraries/FullMath.sol"; +import "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; /// @title Liquidity amount functions /// @notice Provides functions for computing liquidity amounts from token amounts and prices diff --git a/contracts/libraries/PoolGetters.sol b/contracts/libraries/PoolGetters.sol index d2c7fbf2..e3cb318b 100644 --- a/contracts/libraries/PoolGetters.sol +++ b/contracts/libraries/PoolGetters.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Pool} from "@uniswap/v4-core/contracts/libraries/Pool.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {BitMath} from "@uniswap/v4-core/contracts/libraries/BitMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; /// @title Helper functions to access pool information +/// TODO: Expose other getters on core with extsload. Only use when extsload is available and storage layout is frozen. library PoolGetters { uint256 constant POOL_SLOT = 10; uint256 constant TICKS_OFFSET = 4; @@ -62,7 +63,7 @@ library PoolGetters { // all the 1s at or to the right of the current bitPos uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); // uint256 masked = self[wordPos] & mask; - uint256 masked = getTickBitmapAtWord(poolManager, poolId, wordPos) & mask; + uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word initialized = masked != 0; @@ -75,7 +76,7 @@ library PoolGetters { (int16 wordPos, uint8 bitPos) = position(compressed + 1); // all the 1s at or to the left of the bitPos uint256 mask = ~((1 << bitPos) - 1); - uint256 masked = getTickBitmapAtWord(poolManager, poolId, wordPos) & mask; + uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; // if there are no initialized ticks to the left of the current tick, return leftmost in the word initialized = masked != 0; diff --git a/contracts/libraries/TWAMM/TwammMath.sol b/contracts/libraries/TWAMM/TwammMath.sol index 133a68c7..a5994b51 100644 --- a/contracts/libraries/TWAMM/TwammMath.sol +++ b/contracts/libraries/TWAMM/TwammMath.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.15; import {ABDKMathQuad} from "./ABDKMathQuad.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; /// @title TWAMM Math - Pure functions for TWAMM math calculations library TwammMath { diff --git a/contracts/libraries/TransferHelper.sol b/contracts/libraries/TransferHelper.sol index 5b1833a7..9ab40d9e 100644 --- a/contracts/libraries/TransferHelper.sol +++ b/contracts/libraries/TransferHelper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.15; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; /// @title TransferHelper /// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false diff --git a/foundry.toml b/foundry.toml index b3132187..302fc02b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,8 +5,10 @@ solc_version = '0.8.20' optimizer_runs = 800 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] +cancun = true [profile.ci] fuzz_runs = 100000 +solc = "./lib/v4-core/bin/solc-static-linux" # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/lib/v4-core b/lib/v4-core index 0095e084..83557113 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 0095e0848098c3e32e016eac6d2537b67aa47358 +Subproject commit 83557113a0425eb3d81570c30e7a5ce550037149 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index fa9d13ed..3d3f6800 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -3,22 +3,23 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {FullRange} from "../contracts/hooks/examples/FullRange.sol"; import {FullRangeImplementation} from "./shared/implementation/FullRangeImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; contract TestFullRange is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; @@ -47,6 +48,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { uint24 fee ); + HookEnabledSwapRouter router; /// @dev Min tick for full range with tick spacing of 60 int24 internal constant MIN_TICK = -887220; /// @dev Max tick for full range with tick spacing of 60 @@ -62,15 +64,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { MockERC20 token1; MockERC20 token2; - Currency currency0; - Currency currency1; - - PoolManager manager; FullRangeImplementation fullRange = FullRangeImplementation( address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG)) ); - PoolKey key; PoolId id; PoolKey key2; @@ -80,15 +77,13 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey keyWithLiq; PoolId idWithLiq; - PoolModifyPositionTest modifyPositionRouter; - PoolSwapTest swapRouter; - function setUp() public { - token0 = new MockERC20("TestA", "A", 18, 2 ** 128); - token1 = new MockERC20("TestB", "B", 18, 2 ** 128); - token2 = new MockERC20("TestC", "C", 18, 2 ** 128); - - manager = new PoolManager(500000); + deployFreshManagerAndRouters(); + router = new HookEnabledSwapRouter(manager); + MockERC20[] memory tokens = deployTokens(3, 2 ** 128); + token0 = tokens[0]; + token1 = tokens[1]; + token2 = tokens[2]; FullRangeImplementation impl = new FullRangeImplementation(manager, fullRange); vm.etch(address(fullRange), address(impl).code); @@ -102,17 +97,14 @@ contract TestFullRange is Test, Deployers, GasSnapshot { keyWithLiq = createPoolKey(token0, token2); idWithLiq = keyWithLiq.toId(); - modifyPositionRouter = new PoolModifyPositionTest(manager); - swapRouter = new PoolSwapTest(manager); - token0.approve(address(fullRange), type(uint256).max); token1.approve(address(fullRange), type(uint256).max); token2.approve(address(fullRange), type(uint256).max); - token0.approve(address(swapRouter), type(uint256).max); - token1.approve(address(swapRouter), type(uint256).max); - token2.approve(address(swapRouter), type(uint256).max); + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); + token2.approve(address(router), type(uint256).max); - manager.initialize(keyWithLiq, SQRT_RATIO_1_1, ZERO_BYTES); + initPool(keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( keyWithLiq.currency0, @@ -135,7 +127,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { emit Initialize(id, testKey.currency0, testKey.currency1, testKey.fee, testKey.tickSpacing, testKey.hooks); snapStart("FullRangeInitialize"); - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); snapEnd(); (, address liquidityToken) = fullRange.poolInfo(id); @@ -147,11 +139,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey memory wrongKey = PoolKey(key.currency0, key.currency1, 0, TICK_SPACING + 1, fullRange); vm.expectRevert(FullRange.TickSpacingNotDefault.selector); - manager.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); } function testFullRange_addLiquidity_InitialAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -177,7 +169,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_InitialAddFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); if (amount < LOCKED_LIQUIDITY) { vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); fullRange.addLiquidity( @@ -252,7 +244,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_SwapThenAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -273,16 +265,16 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.expectEmit(true, true, true, true); emit Swap( - id, address(swapRouter), 1 ether, -906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 + id, address(router), 1 ether, -906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 ); IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeSwap"); - swapRouter.swap(key, params, settings, ZERO_BYTES); + router.swap(key, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); @@ -306,7 +298,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_FailsIfTooMuchSlippage() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -316,10 +308,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, settings, ZERO_BYTES); + router.swap(key, params, settings, ZERO_BYTES); vm.expectRevert(FullRange.TooMuchSlippage.selector); fullRange.addLiquidity( @@ -331,7 +323,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function testFullRange_swap_TwoSwaps() public { PoolKey memory testKey = key; - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -341,18 +333,18 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeFirstSwap"); - swapRouter.swap(testKey, params, settings, ZERO_BYTES); + router.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); snapStart("FullRangeSecondSwap"); - swapRouter.swap(testKey, params, settings, ZERO_BYTES); + router.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (hasAccruedFees,) = fullRange.poolInfo(id); @@ -360,8 +352,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_swap_TwoPools() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - manager.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -377,11 +369,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10000000, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); - swapRouter.swap(key2, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key2, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -416,7 +408,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_InitialRemoveFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -464,7 +456,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_FailsIfNoLiquidity() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -476,7 +468,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SucceedsWithPartial() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOfSelf(); uint256 prevBalance1 = key.currency1.balanceOfSelf(); @@ -511,7 +503,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_DiffRatios() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -560,10 +552,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(keyWithLiq, params, testSettings, ZERO_BYTES); + router.swap(keyWithLiq, params, testSettings, ZERO_BYTES); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -579,7 +571,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_RemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -634,7 +626,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.prank(address(2)); token1.approve(address(fullRange), type(uint256).max); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); // Test contract adds liquidity @@ -687,10 +679,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100 ether, sqrtPriceLimitX96: SQRT_RATIO_1_4}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -712,7 +704,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SwapRemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -742,10 +734,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { sqrtPriceLimitX96: SQRT_RATIO_1_4 }); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); // Test contract removes liquidity, succeeds UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -761,7 +753,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_BeforeModifyPositionFailsWithWrongMsgSender() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); vm.expectRevert(FullRange.SenderMustBeHook.selector); modifyPositionRouter.modifyPosition( diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index bd0e0c05..aa5e5c6d 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -3,22 +3,21 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GetSender} from "./shared/GetSender.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {GeomeanOracle} from "../contracts/hooks/examples/GeomeanOracle.sol"; import {GeomeanOracleImplementation} from "./shared/implementation/GeomeanOracleImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {TestERC20} from "@uniswap/v4-core/contracts/test/TestERC20.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../contracts/libraries/Oracle.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -contract TestGeomeanOracle is Test, Deployers, TokenFixture { +contract TestGeomeanOracle is Test, Deployers { using PoolIdLibrary for PoolKey; int24 constant MAX_TICK_SPACING = 32767; @@ -26,7 +25,6 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { TestERC20 token0; TestERC20 token1; - PoolManager manager; GeomeanOracleImplementation geomeanOracle = GeomeanOracleImplementation( address( uint160( @@ -35,18 +33,15 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { ) ) ); - PoolKey key; PoolId id; - PoolModifyPositionTest modifyPositionRouter; - function setUp() public { - initializeTokens(); + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); - vm.record(); GeomeanOracleImplementation impl = new GeomeanOracleImplementation(manager, geomeanOracle); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -71,12 +66,12 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeInitializeAllowsPoolCreation() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); } function testBeforeInitializeRevertsIfFee() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - manager.initialize( + initializeRouter.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 1, MAX_TICK_SPACING, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -85,7 +80,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { function testBeforeInitializeRevertsIfNotMaxTickSpacing() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - manager.initialize( + initializeRouter.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 0, 60, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -93,7 +88,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testAfterInitializeState() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); assertEq(observationState.index, 0); assertEq(observationState.cardinality, 1); @@ -101,7 +96,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testAfterInitializeObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); assertTrue(observation.initialized); assertEq(observation.blockTimestamp, 1); @@ -110,7 +105,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testAfterInitializeObserve0() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); uint32[] memory secondsAgo = new uint32[](1); secondsAgo[0] = 0; (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = @@ -122,7 +117,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeModifyPositionNoObservations() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); modifyPositionRouter.modifyPosition( key, IPoolManager.ModifyPositionParams( @@ -144,7 +139,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeModifyPositionObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyPositionRouter.modifyPosition( key, @@ -167,7 +162,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeModifyPositionObservationAndCardinality() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds geomeanOracle.increaseCardinalityNext(key, 2); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 27613654..94cca602 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -3,41 +3,38 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GetSender} from "./shared/GetSender.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {LimitOrder, Epoch, EpochLibrary} from "../contracts/hooks/examples/LimitOrder.sol"; import {LimitOrderImplementation} from "./shared/implementation/LimitOrderImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {TestERC20} from "@uniswap/v4-core/contracts/test/TestERC20.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; - -contract TestLimitOrder is Test, Deployers, TokenFixture { +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; + +contract TestLimitOrder is Test, Deployers { using PoolIdLibrary for PoolKey; uint160 constant SQRT_RATIO_10_1 = 250541448375047931186413801569; + HookEnabledSwapRouter router; TestERC20 token0; TestERC20 token1; - PoolManager manager; LimitOrder limitOrder = LimitOrder(address(uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_SWAP_FLAG))); - PoolKey key; PoolId id; - PoolSwapTest swapRouter; - function setUp() public { - initializeTokens(); + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + + router = new HookEnabledSwapRouter(manager); token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); - vm.record(); LimitOrderImplementation impl = new LimitOrderImplementation(manager, limitOrder); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -50,16 +47,13 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { } } - key = PoolKey(currency0, currency1, 3000, 60, limitOrder); - id = key.toId(); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - - swapRouter = new PoolSwapTest(manager); + // key = PoolKey(currency0, currency1, 3000, 60, limitOrder); + (key, id) = initPoolAndAddLiquidity(currency0, currency1, limitOrder, 3000, SQRT_RATIO_1_1, ZERO_BYTES); token0.approve(address(limitOrder), type(uint256).max); token1.approve(address(limitOrder), type(uint256).max); - token0.approve(address(swapRouter), type(uint256).max); - token1.approve(address(swapRouter), type(uint256).max); + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); } function testGetTickLowerLast() public { @@ -69,7 +63,7 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testGetTickLowerLastWithDifferentPrice() public { PoolKey memory differentKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 61, limitOrder); - manager.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); + initializeRouter.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); assertEq(limitOrder.getTickLowerLast(differentKey.toId()), 22997); } @@ -107,10 +101,10 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei - swapRouter.swap( + router.swap( key, - IPoolManager.SwapParams(false, 1, SQRT_RATIO_1_1 + 1), - PoolSwapTest.TestSettings(true, true), + IPoolManager.SwapParams(false, 1 ether, SQRT_RATIO_1_1 + 1), + HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); @@ -133,8 +127,11 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testNotZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei - swapRouter.swap( - key, IPoolManager.SwapParams(true, 1, SQRT_RATIO_1_1 - 1), PoolSwapTest.TestSettings(true, true), ZERO_BYTES + router.swap( + key, + IPoolManager.SwapParams(true, 1 ether, SQRT_RATIO_1_1 - 1), + HookEnabledSwapRouter.TestSettings(true, true), + ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); limitOrder.place(key, -60, false, 1000000); @@ -192,15 +189,15 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { uint128 liquidity = 1000000; limitOrder.place(key, tickLower, zeroForOne, liquidity); - swapRouter.swap( + router.swap( key, IPoolManager.SwapParams(false, 1e18, TickMath.getSqrtRatioAtTick(60)), - PoolSwapTest.TestSettings(true, true), + HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); assertEq(limitOrder.getTickLowerLast(id), 60); - (, int24 tick,,) = manager.getSlot0(id); + (, int24 tick,) = manager.getSlot0(id); assertEq(tick, 60); (bool filled,,, uint256 token0Total, uint256 token1Total,) = limitOrder.epochInfos(Epoch.wrap(1)); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 84ed9716..17ed64a1 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -3,26 +3,25 @@ pragma solidity ^0.8.15; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {TWAMMImplementation} from "./shared/implementation/TWAMMImplementation.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {PoolDonateTest} from "@uniswap/v4-core/contracts/test/PoolDonateTest.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TWAMM} from "../contracts/hooks/examples/TWAMM.sol"; import {ITWAMM} from "../contracts/interfaces/ITWAMM.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { +contract TWAMMTest is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; using CurrencyLibrary for Currency; @@ -49,10 +48,6 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG)) ); // TWAMM twamm; - PoolManager manager; - PoolModifyPositionTest modifyPositionRouter; - PoolSwapTest swapRouter; - PoolDonateTest donateRouter; address hookAddress; MockERC20 token0; MockERC20 token1; @@ -60,10 +55,11 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { PoolId poolId; function setUp() public { - initializeTokens(); + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = MockERC20(Currency.unwrap(currency0)); token1 = MockERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); TWAMMImplementation impl = new TWAMMImplementation(manager, 10_000, twamm); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -76,12 +72,7 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { } } - modifyPositionRouter = new PoolModifyPositionTest(IPoolManager(address(manager))); - swapRouter = new PoolSwapTest(IPoolManager(address(manager))); - - poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 60, twamm); - poolId = poolKey.toId(); - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_RATIO_1_1, ZERO_BYTES); token0.approve(address(modifyPositionRouter), 100 ether); token1.approve(address(modifyPositionRouter), 100 ether); @@ -100,7 +91,8 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { (PoolKey memory initKey, PoolId initId) = newPoolKeyWithTWAMM(twamm); assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); vm.warp(10000); - manager.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); + + initializeRouter.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); } @@ -242,7 +234,7 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); // takes 10% off the remaining half (so 80% of original sellrate) - assertEq(updatedSellRate, originalSellRate * 80 / 100); + assertEq(updatedSellRate, (originalSellRate * 80) / 100); assertEq(token0Owed, uint256(-amountDelta)); assertEq(token1Owed, orderAmount / 2); } @@ -267,7 +259,7 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); // takes 10% off the remaining half (so 80% of original sellrate) - assertEq(updatedSellRate, originalSellRate * 80 / 100); + assertEq(updatedSellRate, (originalSellRate * 80) / 100); assertEq(token0Owed, orderAmount / 2); assertEq(token1Owed, uint256(-amountDelta)); } @@ -416,8 +408,8 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { } function newPoolKeyWithTWAMM(IHooks hooks) public returns (PoolKey memory, PoolId) { - MockERC20[] memory tokens = deployTokens(2, 2 ** 255); - PoolKey memory key = PoolKey(Currency.wrap(address(tokens[0])), Currency.wrap(address(tokens[1])), 0, 60, hooks); + (Currency _token0, Currency _token1) = deployMintAndApprove2Currencies(); + PoolKey memory key = PoolKey(_token0, _token1, 0, 60, hooks); return (key, key.toId()); } diff --git a/test/shared/implementation/FullRangeImplementation.sol b/test/shared/implementation/FullRangeImplementation.sol index fcd8ae3f..63592f5c 100644 --- a/test/shared/implementation/FullRangeImplementation.sol +++ b/test/shared/implementation/FullRangeImplementation.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {FullRange} from "../../../contracts/hooks/examples/FullRange.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract FullRangeImplementation is FullRange { constructor(IPoolManager _poolManager, FullRange addressToEtch) FullRange(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/GeomeanOracleImplementation.sol b/test/shared/implementation/GeomeanOracleImplementation.sol index 06a95fa2..0c964671 100644 --- a/test/shared/implementation/GeomeanOracleImplementation.sol +++ b/test/shared/implementation/GeomeanOracleImplementation.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {GeomeanOracle} from "../../../contracts/hooks/examples/GeomeanOracle.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract GeomeanOracleImplementation is GeomeanOracle { uint32 public time; constructor(IPoolManager _poolManager, GeomeanOracle addressToEtch) GeomeanOracle(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/LimitOrderImplementation.sol b/test/shared/implementation/LimitOrderImplementation.sol index 340cfc42..c0f5a5f8 100644 --- a/test/shared/implementation/LimitOrderImplementation.sol +++ b/test/shared/implementation/LimitOrderImplementation.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {LimitOrder} from "../../../contracts/hooks/examples/LimitOrder.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract LimitOrderImplementation is LimitOrder { constructor(IPoolManager _poolManager, LimitOrder addressToEtch) LimitOrder(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/TWAMMImplementation.sol b/test/shared/implementation/TWAMMImplementation.sol index 012ca541..27a9e10c 100644 --- a/test/shared/implementation/TWAMMImplementation.sol +++ b/test/shared/implementation/TWAMMImplementation.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {TWAMM} from "../../../contracts/hooks/examples/TWAMM.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract TWAMMImplementation is TWAMM { constructor(IPoolManager poolManager, uint256 interval, TWAMM addressToEtch) TWAMM(poolManager, interval) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol new file mode 100644 index 00000000..54832b4a --- /dev/null +++ b/test/utils/HookEnabledSwapRouter.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolTestBase} from "@uniswap/v4-core/src/test/PoolTestBase.sol"; +import {Test} from "forge-std/Test.sol"; + +contract HookEnabledSwapRouter is PoolTestBase { + using CurrencyLibrary for Currency; + + error NoSwapOccurred(); + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + struct CallbackData { + address sender; + TestSettings testSettings; + PoolKey key; + IPoolManager.SwapParams params; + bytes hookData; + } + + struct TestSettings { + bool withdrawTokens; + bool settleUsingTransfer; + } + + function swap( + PoolKey memory key, + IPoolManager.SwapParams memory params, + TestSettings memory testSettings, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + delta = abi.decode( + manager.lock(address(this), abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + + function lockAcquired(address, /*sender*/ bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + BalanceDelta delta = manager.swap(data.key, data.params, data.hookData); + + // Make sure youve added liquidity to the test pool! + if (BalanceDelta.unwrap(delta) == 0) revert NoSwapOccurred(); + + if (data.params.zeroForOne) { + _settle(data.key.currency0, data.sender, delta.amount0(), data.testSettings.settleUsingTransfer); + if (delta.amount1() < 0) { + _take(data.key.currency1, data.sender, delta.amount1(), data.testSettings.withdrawTokens); + } + } else { + _settle(data.key.currency1, data.sender, delta.amount1(), data.testSettings.settleUsingTransfer); + if (delta.amount0() < 0) { + _take(data.key.currency0, data.sender, delta.amount0(), data.testSettings.withdrawTokens); + } + } + + return abi.encode(delta); + } +} From 63d64fcd82bff9ec0bad89730ce28d7ffa8e4225 Mon Sep 17 00:00:00 2001 From: Zach Yang Date: Wed, 20 Dec 2023 15:21:35 -0800 Subject: [PATCH 04/13] feat: Revert style quoter (#73) * add PoolTicksCounter library * quoter exact input single * quoter test * return deltas instead * safe casting to correct types * QuoteExactInput skeleton * multiple entries * break handleRevert by type * quoteExactInput and unit tests * more QuoteExactInput tests * remove lgos * remove commented out struct * via-ir in ci * remove unused imports/functions * store iteration params locally instead of editing function input * pull out sqrtPriceLimit to its own function * PathKey to its own library * rename initializedTicksCrossed to initializedTicksLoaded * remove manual abi encoding in yul :p * fix linter warnings for Quoter * natspec for IQuoter * feat: update v4-core This commit updates v4 core to latest and fixes integration issues * fix: tests * style fixes * inheritdoc * ExactInSingleBatch * fix: update tests * fix: test router was borked * exact out * fix: alice comments * fix ExactOutput * add ExactOput unit tests * add quoteExactOutputBatch * remove solhint config * remove newline * add QuoteExactOutput in interface * refactor lockAcquired * move magic numbers to constants + doc * add more natspec * natspec * named imports * self-call branching * remove old code * remove console2 import * refactor PathKeyLib * amountOutCached * inherit ILockCallback * add base contracts and interfaces (#75) * remove unused errors * test lockAcquired reverts * remove ...Batch interface * REASON -> RESPONSE when valid * complete natspec * remove SwapInfo imports * rename to SwapParameters * move quoter structs into IQuoter interface * update to latest core * use prev values * change twamm to use pool getters * changes after merging main * use --via-ir in cli * fix formatting * fix FullRange/TWAMM hook * update ticks counter * update Quoter test * typo * typo * simplify handleRevertSingle * merge QuoteInput/OutputSingle structs * combine IQuoter structs * using ... ordering * update snapshots * move amountOutCached into inner call * using PathKeyLib for PathKey * fix amountOutCached * remove console2 import * resurface revert reason * clean up validateRevert * update natsppec * remove unused --------- Co-authored-by: Mark Toda Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com> Co-authored-by: Sara Reynolds --- .../FullOracleObserve0After5Seconds.snap | 2 +- .../FullOracleObserve200By13.snap | 2 +- .../FullOracleObserve200By13Plus5.snap | 2 +- .../FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .../FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .../OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .../OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- ...eObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .../OracleObserveCurrentTime.snap | 2 +- ...racleObserveCurrentTimeCounterfactual.snap | 2 +- .../OracleObserveLast20Seconds.snap | 2 +- .../OracleObserveLatestEqual.snap | 2 +- .../OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .../OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/interfaces/IQuoter.sol | 106 +++ contracts/lens/Quoter.sol | 317 +++++++++ contracts/libraries/LiquidityAmounts.sol | 2 +- contracts/libraries/PathKey.sol | 30 + contracts/libraries/PoolTicksCounter.sol | 103 +++ foundry.toml | 3 +- test/Quoter.t.sol | 662 ++++++++++++++++++ 37 files changed, 1251 insertions(+), 32 deletions(-) create mode 100644 contracts/interfaces/IQuoter.sol create mode 100644 contracts/lens/Quoter.sol create mode 100644 contracts/libraries/PathKey.sol create mode 100644 contracts/libraries/PoolTicksCounter.sol create mode 100644 test/Quoter.t.sol diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index bc61a749..a08fb8e1 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2771 \ No newline at end of file +2687 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 7706f4dd..bb219663 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -23377 \ No newline at end of file +22933 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 8afa5484..6eb59a1d 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -23624 \ No newline at end of file +23180 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index f66ebbd5..94c197e9 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2798 \ No newline at end of file +2738 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index 9db3df4e..75080690 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -22396 \ No newline at end of file +21892 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index b2f26cf1..9b54c31b 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -22695 \ No newline at end of file +22191 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index f91847e9..2a55d550 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -2130 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index ef62f828..94ac0e08 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -410761 \ No newline at end of file +407968 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index b3688dfa..d1198e0f 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -204683 \ No newline at end of file +201962 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 2b5ad7d2..aef75115 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -156432 \ No newline at end of file +153306 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index c2b5d0ef..3b5a43d1 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -897565 \ No newline at end of file +1112212 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index bc1c95e2..58273980 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200057 \ No newline at end of file +197519 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index b5d7708e..8e473407 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -386095 \ No newline at end of file +379147 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 9e12e78d..3f185fb2 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -114700 \ No newline at end of file +111940 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index d9365d02..68f6f4d2 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -154641 \ No newline at end of file +151523 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 3aa3cfac..f484e31f 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -254711 \ No newline at end of file +254660 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 50fc054a..83917a8d 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -245393 \ No newline at end of file +245360 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 15a052b9..8f98b8b1 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -54893 \ No newline at end of file +54869 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index d6664238..ee2ae68d 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -45575 \ No newline at end of file +45569 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index 3039612c..1e8b26e0 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -72361 \ No newline at end of file +72316 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index ae13ac3f..a695bf26 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -6618 \ No newline at end of file +6492 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index f91847e9..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -2130 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index f91847e9..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -2130 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index b63da1de..5265bba3 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -88543 \ No newline at end of file +86878 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index f91847e9..2a55d550 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -2130 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index bc61a749..a08fb8e1 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2771 \ No newline at end of file +2687 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index ba7fb703..d0974c4f 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -6807 \ No newline at end of file +6684 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index 3ee11622..05796bbf 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -6319 \ No newline at end of file +6193 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index 204ec243..ed8dd329 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -3466 \ No newline at end of file +3382 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 0aef60be..1ba4a8d1 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -146158 \ No newline at end of file +145648 \ No newline at end of file diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol new file mode 100644 index 00000000..90a390fc --- /dev/null +++ b/contracts/interfaces/IQuoter.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +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"; + +/// @title Quoter Interface +/// @notice Supports quoting the delta amounts from exact input or exact output swaps. +/// @notice For each pool also tells you the number of initialized ticks loaded and 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 IQuoter { + error InvalidLockAcquiredSender(); + error InvalidLockCaller(); + error InvalidQuoteBatchParams(); + error InsufficientAmountOut(); + error LockFailure(); + error NotSelf(); + error UnexpectedRevertBytes(bytes revertData); + + struct PoolDeltas { + int128 currency0Delta; + int128 currency1Delta; + } + + struct QuoteExactSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 exactAmount; + uint160 sqrtPriceLimitX96; + bytes hookData; + } + + struct QuoteExactParams { + Currency exactCurrency; + PathKey[] path; + address recipient; + uint128 exactAmount; + } + + /// @notice Returns the delta amounts for a given exact input swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// recipient The intended recipient of the output tokens + /// exactAmount The desired input amount + /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// hookData arbitrary hookData to pass into the associated hooks + /// @return deltaAmounts Delta amounts resulted from the swap + /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded + function quoteExactInputSingle(QuoteExactSingleParams calldata params) + external + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); + + /// @notice Returns the delta amounts along the swap path for a given exact input swap + /// @param params the params for the quote, encoded as 'QuoteExactInputParams' + /// currencyIn The input currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// recipient The intended recipient of the output tokens + /// exactAmount The desired input amount + /// @return deltaAmounts Delta amounts along the path resulted from the swap + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return initializedTicksLoadedList List of the initialized ticks that the swap loaded for each pool in the path + function quoteExactInput(QuoteExactParams memory params) + external + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ); + + /// @notice Returns the delta amounts for a given exact output swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// recipient The intended recipient of the output tokens + /// exactAmount The desired input amount + /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// hookData arbitrary hookData to pass into the associated hooks + /// @return deltaAmounts Delta amounts resulted from the swap + /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded + function quoteExactOutputSingle(QuoteExactSingleParams calldata params) + external + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); + + /// @notice Returns the delta amounts along the swap path for a given exact output swap + /// @param params the params for the quote, encoded as 'QuoteExactOutputParams' + /// currencyOut The output currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// recipient The intended recipient of the output tokens + /// exactAmount The desired output amount + /// @return deltaAmounts Delta amounts along the path resulted from the swap + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return initializedTicksLoadedList List of the initialized ticks that the swap loaded for each pool in the path + function quoteExactOutput(QuoteExactParams memory params) + external + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ); +} diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol new file mode 100644 index 00000000..8b2b16e0 --- /dev/null +++ b/contracts/lens/Quoter.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IQuoter} from "../interfaces/IQuoter.sol"; +import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; +import {PathKey, PathKeyLib} from "../libraries/PathKey.sol"; + +contract Quoter is IQuoter, ILockCallback { + using Hooks for IHooks; + using PoolIdLibrary for PoolKey; + using PathKeyLib for PathKey; + + /// @dev cache used to check a safety condition in exact output swaps. + uint128 private amountOutCached; + + // v4 Singleton contract + IPoolManager public immutable manager; + + /// @dev min valid reason is 3-words long + /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes + uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 96; + + /// @dev Only this address may call this function + modifier selfOnly() { + if (msg.sender != address(this)) revert NotSelf(); + _; + } + + constructor(address _poolManager) { + manager = IPoolManager(_poolManager); + } + + /// @inheritdoc IQuoter + function quoteExactInputSingle(QuoteExactSingleParams memory params) + public + override + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) + { + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} + catch (bytes memory reason) { + return _handleRevertSingle(reason); + } + } + + /// @inheritdoc IQuoter + function quoteExactInput(QuoteExactParams memory params) + external + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) + { + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} + catch (bytes memory reason) { + return _handleRevert(reason); + } + } + + /// @inheritdoc IQuoter + function quoteExactOutputSingle(QuoteExactSingleParams memory params) + public + override + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) + { + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} + catch (bytes memory reason) { + if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; + return _handleRevertSingle(reason); + } + } + + /// @inheritdoc IQuoter + function quoteExactOutput(QuoteExactParams memory params) + public + override + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) + { + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} + catch (bytes memory reason) { + return _handleRevert(reason); + } + } + + /// @inheritdoc ILockCallback + function lockAcquired(address lockCaller, bytes calldata data) external returns (bytes memory) { + if (msg.sender != address(manager)) { + revert InvalidLockAcquiredSender(); + } + if (lockCaller != address(this)) { + revert InvalidLockCaller(); + } + + (bool success, bytes memory returnData) = address(this).call(data); + if (success) return returnData; + if (returnData.length == 0) revert LockFailure(); + // if the call failed, bubble up the reason + /// @solidity memory-safe-assembly + assembly { + revert(add(returnData, 32), mload(returnData)) + } + } + + /// @dev check revert bytes and pass through if considered valid; otherwise revert with different message + function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { + if (reason.length < MINIMUM_VALID_RESPONSE_LENGTH) { + revert UnexpectedRevertBytes(reason); + } + return reason; + } + + /// @dev parse revert bytes from a single-pool quote + function _handleRevertSingle(bytes memory reason) + private + pure + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) + { + reason = validateRevertReason(reason); + (deltaAmounts, sqrtPriceX96After, initializedTicksLoaded) = abi.decode(reason, (int128[], uint160, uint32)); + } + + /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, sqrtPriceX96After, and initializedTicksLoaded + function _handleRevert(bytes memory reason) + private + pure + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) + { + reason = validateRevertReason(reason); + (deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList) = + abi.decode(reason, (int128[], uint160[], uint32[])); + } + + /// @dev quote an ExactInput swap along a path of tokens, then revert with the result + function _quoteExactInput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { + uint256 pathLength = params.path.length; + + int128[] memory deltaAmounts = new int128[](pathLength + 1); + uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); + uint32[] memory initializedTicksLoadedList = new uint32[](pathLength); + Currency prevCurrencyOut; + uint128 prevAmountOut; + + for (uint256 i = 0; i < pathLength; i++) { + (PoolKey memory poolKey, bool zeroForOne) = + params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : prevCurrencyOut); + (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); + + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + poolKey, + zeroForOne, + int256(int128(i == 0 ? params.exactAmount : prevAmountOut)), + 0, + params.path[i].hookData + ); + + (int128 deltaIn, int128 deltaOut) = + zeroForOne ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); + deltaAmounts[i] += deltaIn; + deltaAmounts[i + 1] += deltaOut; + + prevAmountOut = zeroForOne ? uint128(-curDeltas.amount1()) : uint128(-curDeltas.amount0()); + prevCurrencyOut = params.path[i].intermediateCurrency; + sqrtPriceX96AfterList[i] = sqrtPriceX96After; + initializedTicksLoadedList[i] = + PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); + } + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + assembly { + revert(add(0x20, result), mload(result)) + } + } + + /// @dev quote an ExactInput swap on a pool, then revert with the result + function _quoteExactInputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { + (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); + + (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + params.poolKey, + params.zeroForOne, + int256(int128(params.exactAmount)), + params.sqrtPriceLimitX96, + params.hookData + ); + + int128[] memory deltaAmounts = new int128[](2); + + deltaAmounts[0] = deltas.amount0(); + deltaAmounts[1] = deltas.amount1(); + + uint32 initializedTicksLoaded = + PoolTicksCounter.countInitializedTicksLoaded(manager, params.poolKey, tickBefore, tickAfter); + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); + assembly { + revert(add(0x20, result), mload(result)) + } + } + + /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result + function _quoteExactOutput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { + uint256 pathLength = params.path.length; + + int128[] memory deltaAmounts = new int128[](pathLength + 1); + uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); + uint32[] memory initializedTicksLoadedList = new uint32[](pathLength); + Currency prevCurrencyIn; + uint128 prevAmountIn; + uint128 curAmountOut; + + for (uint256 i = pathLength; i > 0; i--) { + curAmountOut = i == pathLength ? params.exactAmount : prevAmountIn; + amountOutCached = curAmountOut; + + (PoolKey memory poolKey, bool oneForZero) = PathKeyLib.getPoolAndSwapDirection( + params.path[i - 1], i == pathLength ? params.exactCurrency : prevCurrencyIn + ); + + (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); + + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = + _swap(poolKey, !oneForZero, -int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); + + // always clear because sqrtPriceLimitX96 is set to 0 always + delete amountOutCached; + (int128 deltaIn, int128 deltaOut) = + !oneForZero ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); + deltaAmounts[i - 1] += deltaIn; + deltaAmounts[i] += deltaOut; + + prevAmountIn = !oneForZero ? uint128(curDeltas.amount0()) : uint128(curDeltas.amount1()); + prevCurrencyIn = params.path[i - 1].intermediateCurrency; + sqrtPriceX96AfterList[i - 1] = sqrtPriceX96After; + initializedTicksLoadedList[i - 1] = + PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); + } + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + assembly { + revert(add(0x20, result), mload(result)) + } + } + + /// @dev quote an ExactOutput swap on a pool, then revert with the result + function _quoteExactOutputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { + // if no price limit has been specified, cache the output amount for comparison in the swap callback + if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; + + (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); + (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + params.poolKey, + params.zeroForOne, + -int256(uint256(params.exactAmount)), + params.sqrtPriceLimitX96, + params.hookData + ); + + if (amountOutCached != 0) delete amountOutCached; + int128[] memory deltaAmounts = new int128[](2); + + deltaAmounts[0] = deltas.amount0(); + deltaAmounts[1] = deltas.amount1(); + + uint32 initializedTicksLoaded = + PoolTicksCounter.countInitializedTicksLoaded(manager, params.poolKey, tickBefore, tickAfter); + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); + assembly { + revert(add(0x20, result), mload(result)) + } + } + + /// @dev Execute a swap and return the amounts delta, as well as relevant pool state + /// @notice if amountSpecified > 0, the swap is exactInput, otherwise exactOutput + function _swap( + PoolKey memory poolKey, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes memory hookData + ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { + deltas = manager.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: _sqrtPriceLimitOrDefault(sqrtPriceLimitX96, zeroForOne) + }), + hookData + ); + // only exactOut case + if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? -deltas.amount1() : -deltas.amount0())) { + revert InsufficientAmountOut(); + } + (sqrtPriceX96After, tickAfter,) = manager.getSlot0(poolKey.toId()); + } + + /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction + function _sqrtPriceLimitOrDefault(uint160 sqrtPriceLimitX96, bool zeroForOne) private pure returns (uint160) { + return sqrtPriceLimitX96 == 0 + ? zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 + : sqrtPriceLimitX96; + } +} diff --git a/contracts/libraries/LiquidityAmounts.sol b/contracts/libraries/LiquidityAmounts.sol index 845cc6e0..742e48f5 100644 --- a/contracts/libraries/LiquidityAmounts.sol +++ b/contracts/libraries/LiquidityAmounts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "@uniswap/v4-core/src/libraries/FullMath.sol"; import "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; diff --git a/contracts/libraries/PathKey.sol b/contracts/libraries/PathKey.sol new file mode 100644 index 00000000..f9d5da33 --- /dev/null +++ b/contracts/libraries/PathKey.sol @@ -0,0 +1,30 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +struct PathKey { + Currency intermediateCurrency; + uint24 fee; + int24 tickSpacing; + IHooks hooks; + bytes hookData; +} + +library PathKeyLib { + function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) + internal + pure + returns (PoolKey memory poolKey, bool zeroForOne) + { + (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency + ? (currencyIn, params.intermediateCurrency) + : (params.intermediateCurrency, currencyIn); + + zeroForOne = currencyIn == currency0; + poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); + } +} diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol new file mode 100644 index 00000000..b0e9ab5b --- /dev/null +++ b/contracts/libraries/PoolTicksCounter.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.8.20; + +import {PoolGetters} from "./PoolGetters.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; + +library PoolTicksCounter { + using PoolIdLibrary for PoolKey; + + /// @dev This function counts the number of initialized ticks that would incur a gas cost between tickBefore and tickAfter. + /// When tickBefore and/or tickAfter themselves are initialized, the logic over whether we should count them depends on the + /// direction of the swap. If we are swapping upwards (tickAfter > tickBefore) we don't want to count tickBefore but we do + /// want to count tickAfter. The opposite is true if we are swapping downwards. + function countInitializedTicksLoaded(IPoolManager self, PoolKey memory key, int24 tickBefore, int24 tickAfter) + internal + view + returns (uint32 initializedTicksLoaded) + { + int16 wordPosLower; + int16 wordPosHigher; + uint8 bitPosLower; + uint8 bitPosHigher; + bool tickBeforeInitialized; + bool tickAfterInitialized; + + { + // Get the key and offset in the tick bitmap of the active tick before and after the swap. + int16 wordPos = int16((tickBefore / key.tickSpacing) >> 8); + uint8 bitPos = uint8(uint24((tickBefore / key.tickSpacing) % 256)); + + int16 wordPosAfter = int16((tickAfter / key.tickSpacing) >> 8); + uint8 bitPosAfter = uint8(uint24((tickAfter / key.tickSpacing) % 256)); + + // In the case where tickAfter is initialized, we only want to count it if we are swapping downwards. + // If the initializable tick after the swap is initialized, our original tickAfter is a + // multiple of tick spacing, and we are swapping downwards we know that tickAfter is initialized + // and we shouldn't count it. + uint256 bmAfter = self.getPoolBitmapInfo(key.toId(), wordPosAfter); + //uint256 bmAfter = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosAfter); + tickAfterInitialized = + ((bmAfter & (1 << bitPosAfter)) > 0) && ((tickAfter % key.tickSpacing) == 0) && (tickBefore > tickAfter); + + // In the case where tickBefore is initialized, we only want to count it if we are swapping upwards. + // Use the same logic as above to decide whether we should count tickBefore or not. + uint256 bmBefore = self.getPoolBitmapInfo(key.toId(), wordPos); + //uint256 bmBefore = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPos); + tickBeforeInitialized = + ((bmBefore & (1 << bitPos)) > 0) && ((tickBefore % key.tickSpacing) == 0) && (tickBefore < tickAfter); + + if (wordPos < wordPosAfter || (wordPos == wordPosAfter && bitPos <= bitPosAfter)) { + wordPosLower = wordPos; + bitPosLower = bitPos; + wordPosHigher = wordPosAfter; + bitPosHigher = bitPosAfter; + } else { + wordPosLower = wordPosAfter; + bitPosLower = bitPosAfter; + wordPosHigher = wordPos; + bitPosHigher = bitPos; + } + } + + // Count the number of initialized ticks crossed by iterating through the tick bitmap. + // Our first mask should include the lower tick and everything to its left. + uint256 mask = type(uint256).max << bitPosLower; + while (wordPosLower <= wordPosHigher) { + // If we're on the final tick bitmap page, ensure we only count up to our + // ending tick. + if (wordPosLower == wordPosHigher) { + mask = mask & (type(uint256).max >> (255 - bitPosHigher)); + } + + //uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosLower); + uint256 bmLower = self.getPoolBitmapInfo(key.toId(), wordPosLower); + uint256 masked = bmLower & mask; + initializedTicksLoaded += countOneBits(masked); + wordPosLower++; + // Reset our mask so we consider all bits on the next iteration. + mask = type(uint256).max; + } + + if (tickAfterInitialized) { + initializedTicksLoaded -= 1; + } + + if (tickBeforeInitialized) { + initializedTicksLoaded -= 1; + } + + return initializedTicksLoaded; + } + + function countOneBits(uint256 x) private pure returns (uint16) { + uint16 bits = 0; + while (x != 0) { + bits++; + x &= (x - 1); + } + return bits; + } +} diff --git a/foundry.toml b/foundry.toml index 302fc02b..620d06a6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,8 @@ src = 'contracts' out = 'foundry-out' solc_version = '0.8.20' -optimizer_runs = 800 +via_ir = true +optimizer_runs = 1000000 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] cancun = true diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol new file mode 100644 index 00000000..056b0818 --- /dev/null +++ b/test/Quoter.t.sol @@ -0,0 +1,662 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {PathKey} from "../contracts/libraries/PathKey.sol"; +import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; +import {Quoter} from "../contracts/lens/Quoter.sol"; +import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; + +contract QuoterTest is Test, Deployers { + using SafeCast for *; + using PoolIdLibrary for PoolKey; + + // Min tick for full range with tick spacing of 60 + int24 internal constant MIN_TICK = -887220; + // Max tick for full range with tick spacing of 60 + int24 internal constant MAX_TICK = -MIN_TICK; + + uint160 internal constant SQRT_RATIO_100_102 = 78447570448055484695608110440; + uint160 internal constant SQRT_RATIO_102_100 = 80016521857016594389520272648; + + uint256 internal constant CONTROLLER_GAS_LIMIT = 500000; + + Quoter quoter; + + PoolModifyPositionTest positionManager; + + MockERC20 token0; + MockERC20 token1; + MockERC20 token2; + + PoolKey key01; + PoolKey key02; + PoolKey key12; + + MockERC20[] tokenPath; + + function setUp() public { + deployFreshManagerAndRouters(); + quoter = new Quoter(address(manager)); + positionManager = new PoolModifyPositionTest(manager); + + // salts are chosen so that address(token0) < address(token2) && address(1) < address(token2) + bytes32 salt1 = "ffff"; + bytes32 salt2 = "gm"; + token0 = new MockERC20{salt: salt1}("Test0", "0", 18); + token0.mint(address(this), 2 ** 128); + token1 = new MockERC20{salt: salt2}("Test1", "1", 18); + token1.mint(address(this), 2 ** 128); + token2 = new MockERC20("Test2", "2", 18); + token2.mint(address(this), 2 ** 128); + + key01 = createPoolKey(token0, token1, address(0)); + key02 = createPoolKey(token0, token2, address(0)); + key12 = createPoolKey(token1, token2, address(0)); + setupPool(key01); + setupPool(key12); + setupPoolMultiplePositions(key02); + } + + function testQuoter_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { + uint256 amountIn = 10000; + uint256 expectedAmountOut = 9871; + uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; + + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter + .quoteExactInputSingle( + IQuoter.QuoteExactSingleParams({ + poolKey: key02, + zeroForOne: true, + recipient: address(this), + exactAmount: uint128(amountIn), + sqrtPriceLimitX96: 0, + hookData: ZERO_BYTES + }) + ); + + assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); + assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); + assertEq(initializedTicksLoaded, 2); + } + + function testQuoter_quoteExactInputSingle_OneForZero_MultiplePositions() public { + uint256 amountIn = 10000; + uint256 expectedAmountOut = 9871; + uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; + + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter + .quoteExactInputSingle( + IQuoter.QuoteExactSingleParams({ + poolKey: key02, + zeroForOne: false, + recipient: address(this), + exactAmount: uint128(amountIn), + sqrtPriceLimitX96: 0, + hookData: ZERO_BYTES + }) + ); + + assertEq(uint128(-deltaAmounts[0]), expectedAmountOut); + assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); + assertEq(initializedTicksLoaded, 2); + } + + // nested self-call into lockAcquired reverts + function testQuoter_callLockAcquired_reverts() public { + vm.expectRevert(IQuoter.InvalidLockAcquiredSender.selector); + vm.prank(address(manager)); + quoter.lockAcquired(address(quoter), abi.encodeWithSelector(quoter.lockAcquired.selector, address(this), "0x")); + } + + function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 9871); + assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactInput_0to2_2TicksLoaded_initialiedAfter() public { + tokenPath.push(token0); + tokenPath.push(token2); + + // The swap amount is set such that the active tick after the swap is -120. + // -120 is an initialized tick for this pool. We check that we don't count it. + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 6143); + assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactInput_0to2_1TickLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + + // The swap amount is set such that the active tick after the swap is -60. + // -60 is an initialized tick for this pool. We check that we don't count it. + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 3971); + assertEq(sqrtPriceX96AfterList[0], 78926452400586371254602774705); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { + tokenPath.push(token0); + tokenPath.push(token2); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 8); + assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); + assertEq(initializedTicksLoadedList[0], 0); + } + + function testQuoter_quoteExactInput_0to2_0TickLoaded_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token0); + tokenPath.push(token2); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 8); + assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { + tokenPath.push(token2); + tokenPath.push(token0); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 9871); + assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactInput_2to0_2TicksLoaded_initialiedAfter() public { + tokenPath.push(token2); + tokenPath.push(token0); + + // The swap amount is set such that the active tick after the swap is 120. + // 120 is an initialized tick for this pool. We check that we don't count it. + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 6190); + assertEq(sqrtPriceX96AfterList[0], 79705728824507063507279123685); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactInput_2to0_0TickLoaded_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token2); + tokenPath.push(token0); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); + + // Tick 0 initialized. Tick after = 1 + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 198); + assertEq(sqrtPriceX96AfterList[0], 79235729830182478001034429156); + assertEq(initializedTicksLoadedList[0], 0); + } + + // 2->0 starting not initialized + function testQuoter_quoteExactInput_2to0_0TickLoaded_startingNotInitialized() public { + tokenPath.push(token2); + tokenPath.push(token0); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 101); + assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); + assertEq(initializedTicksLoadedList[0], 0); + } + + function testQuoter_quoteExactInput_2to1() public { + tokenPath.push(token2); + tokenPath.push(token1); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + assertEq(-deltaAmounts[1], 9871); + assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); + assertEq(initializedTicksLoadedList[0], 0); + } + + function testQuoter_quoteExactInput_0to2to1() public { + tokenPath.push(token0); + tokenPath.push(token2); + tokenPath.push(token1); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[2], 9745); + assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); + assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); + assertEq(initializedTicksLoadedList[0], 2); + assertEq(initializedTicksLoadedList[1], 0); + } + + function testQuoter_quoteExactOutputSingle_0to1() public { + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter + .quoteExactOutputSingle( + IQuoter.QuoteExactSingleParams({ + poolKey: key01, + zeroForOne: true, + recipient: address(this), + exactAmount: type(uint128).max, + sqrtPriceLimitX96: SQRT_RATIO_100_102, + hookData: ZERO_BYTES + }) + ); + + assertEq(deltaAmounts[0], 9981); + assertEq(sqrtPriceX96After, SQRT_RATIO_100_102); + assertEq(initializedTicksLoaded, 0); + } + + function testQuoter_quoteExactOutputSingle_1to0() public { + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter + .quoteExactOutputSingle( + IQuoter.QuoteExactSingleParams({ + poolKey: key01, + zeroForOne: false, + recipient: address(this), + exactAmount: type(uint128).max, + sqrtPriceLimitX96: SQRT_RATIO_102_100, + hookData: ZERO_BYTES + }) + ); + + assertEq(deltaAmounts[1], 9981); + assertEq(sqrtPriceX96After, SQRT_RATIO_102_100); + assertEq(initializedTicksLoaded, 0); + } + + function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 15273); + assertEq(sqrtPriceX96AfterList[0], 78055527257643669242286029831); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactOutput_0to2_1TickLoaded_initialiedAfter() public { + tokenPath.push(token0); + tokenPath.push(token2); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 6200); + assertEq(sqrtPriceX96AfterList[0], 78757225449310403327341205211); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_0to2_1TickLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 4029); + assertEq(sqrtPriceX96AfterList[0], 78924219757724709840818372098); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token0); + tokenPath.push(token2); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); + + // Tick 0 initialized. Tick after = 1 + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 102); + assertEq(sqrtPriceX96AfterList[0], 79224329176051641448521403903); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingNotInitialized() public { + tokenPath.push(token0); + tokenPath.push(token2); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 12); + assertEq(sqrtPriceX96AfterList[0], 79227408033628034983534698435); + assertEq(initializedTicksLoadedList[0], 0); + } + + function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { + tokenPath.push(token2); + tokenPath.push(token0); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 15273); + assertEq(sqrtPriceX96AfterList[0], 80418414376567919517220409857); + assertEq(initializedTicksLoadedList.length, 1); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactOutput_2to0_2TicksLoaded_initialiedAfter() public { + tokenPath.push(token2); + tokenPath.push(token0); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 6283); + assertEq(sqrtPriceX96AfterList[0], 79708304437530892332449657932); + assertEq(initializedTicksLoadedList.length, 1); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactOutput_2to0_1TickLoaded() public { + tokenPath.push(token2); + tokenPath.push(token0); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 6055); + assertEq(sqrtPriceX96AfterList[0], 79690640184021170956740081887); + assertEq(initializedTicksLoadedList.length, 1); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_2to1() public { + tokenPath.push(token2); + tokenPath.push(token1); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 10000); + assertEq(sqrtPriceX96AfterList[0], 80018020393569259756601362385); + assertEq(initializedTicksLoadedList.length, 1); + assertEq(initializedTicksLoadedList[0], 0); + } + + function testQuoter_quoteExactOutput_0to2to1() public { + tokenPath.push(token0); + tokenPath.push(token2); + tokenPath.push(token1); + + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 10000); + assertEq(deltaAmounts[1], 0); + assertEq(deltaAmounts[2], -9745); + assertEq(sqrtPriceX96AfterList[0], 78461888503179331029803316753); + assertEq(sqrtPriceX96AfterList[1], 80007838904387594703933785072); + assertEq(initializedTicksLoadedList.length, 2); + assertEq(initializedTicksLoadedList[0], 2); + assertEq(initializedTicksLoadedList[1], 0); + } + + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) + internal + pure + returns (PoolKey memory) + { + if (address(tokenA) > address(tokenB)) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, 60, IHooks(hookAddr)); + } + + function setupPool(PoolKey memory poolKey) internal { + initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + ), + ZERO_BYTES + ); + } + + function setupPoolMultiplePositions(PoolKey memory poolKey) internal { + initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + -60, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -60, 60, 100, 100).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + -120, 120, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 120, 100, 100).toInt256() + ), + ZERO_BYTES + ); + } + + function setupPoolWithZeroTickInitialized(PoolKey memory poolKey) internal { + PoolId poolId = poolKey.toId(); + (uint160 sqrtPriceX96,,) = manager.getSlot0(poolId); + if (sqrtPriceX96 == 0) { + initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + } + + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + 0, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, 0, 60, 100, 100).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + -120, 0, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 0, 100, 100).toInt256() + ), + ZERO_BYTES + ); + } + + function calculateLiquidityFromAmounts( + uint160 sqrtRatioX96, + int24 tickLower, + int24 tickUpper, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper); + liquidity = + LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1); + } + + function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) + internal + view + returns (IQuoter.QuoteExactParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = 0; i < _tokenPath.length - 1; i++) { + path[i] = PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); + } + + params.exactCurrency = Currency.wrap(address(_tokenPath[0])); + params.path = path; + params.recipient = address(this); + params.exactAmount = uint128(amountIn); + } + + function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) + internal + view + returns (IQuoter.QuoteExactParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = _tokenPath.length - 1; i > 0; i--) { + path[i - 1] = PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); + } + + params.exactCurrency = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); + params.path = path; + params.recipient = address(this); + params.exactAmount = uint128(amountOut); + } +} From e40e7f081b05ffa6ba047d2d07bd73edd1ad739e Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Fri, 16 Feb 2024 22:19:55 -0500 Subject: [PATCH 05/13] (Quoter) Avoid IR (#93) * avoid stack too deep * pack local variables into structs; remove need for IR * reorg struct * snapshots * forge fmt * restore settings * remove IR * ensure tokens are ordered properly by using salts * gas snapshot * remove console logs --- .../FullOracleObserve0After5Seconds.snap | 2 +- .../FullOracleObserve200By13.snap | 2 +- .../FullOracleObserve200By13Plus5.snap | 2 +- .../FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .../FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .../OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .../OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- ...eObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .../OracleObserveCurrentTime.snap | 2 +- ...racleObserveCurrentTimeCounterfactual.snap | 2 +- .../OracleObserveLast20Seconds.snap | 2 +- .../OracleObserveLatestEqual.snap | 2 +- .../OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .../OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/hooks/examples/LimitOrder.sol | 16 +-- contracts/lens/Quoter.sol | 107 +++++++++++------- contracts/libraries/PoolTicksCounter.sol | 54 +++++---- foundry.toml | 1 - test/Quoter.t.sol | 13 ++- 35 files changed, 137 insertions(+), 114 deletions(-) diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index a08fb8e1..8fa2b472 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2687 \ No newline at end of file +1922 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index bb219663..9e3ceb1e 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -22933 \ No newline at end of file +20282 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 6eb59a1d..0da8d066 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -23180 \ No newline at end of file +20520 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index 94c197e9..5ee5d632 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2738 \ No newline at end of file +2034 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index 75080690..3c45c181 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -21892 \ No newline at end of file +19330 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index 9b54c31b..eda3bfd7 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -22191 \ No newline at end of file +19612 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index 2a55d550..ce4798b6 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -2070 \ No newline at end of file +1483 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 94ac0e08..fac70738 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -407968 \ No newline at end of file +393062 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index d1198e0f..8ff9c7d3 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -201962 \ No newline at end of file +187418 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index aef75115..6350e081 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -153306 \ No newline at end of file +136762 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 3b5a43d1..44dce048 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1112212 \ No newline at end of file +1059719 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 58273980..232c6f67 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -197519 \ No newline at end of file +180886 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 8e473407..569e77f5 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -379147 \ No newline at end of file +373831 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 3f185fb2..6620403b 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -111940 \ No newline at end of file +97479 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 68f6f4d2..c7357ed2 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -151523 \ No newline at end of file +135037 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index f484e31f..c82060cc 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -254660 \ No newline at end of file +232968 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 83917a8d..d8ee5c94 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -245360 \ No newline at end of file +223657 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 8f98b8b1..3c7800f1 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -54869 \ No newline at end of file +32853 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index ee2ae68d..80d77105 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -45569 \ No newline at end of file +23553 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index 1e8b26e0..4262e6e4 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -72316 \ No newline at end of file +51321 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index a695bf26..e4417bef 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -6492 \ No newline at end of file +5397 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index 2a55d550..ce4798b6 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -2070 \ No newline at end of file +1483 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index 2a55d550..ce4798b6 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -2070 \ No newline at end of file +1483 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index 5265bba3..bbc0ec1f 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -86878 \ No newline at end of file +73451 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index 2a55d550..ce4798b6 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -2070 \ No newline at end of file +1483 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index a08fb8e1..8fa2b472 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2687 \ No newline at end of file +1922 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index d0974c4f..dd7e3e1f 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -6684 \ No newline at end of file +5572 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index 05796bbf..63bada90 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -6193 \ No newline at end of file +5115 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index ed8dd329..13e432b0 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -3382 \ No newline at end of file +2537 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 1ba4a8d1..7c2297c1 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -145648 \ No newline at end of file +122817 \ No newline at end of file diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 2a5287bf..8e9ddbf7 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -299,8 +299,6 @@ contract LimitOrder is BaseHook { uint128 liquidity = epochInfo.liquidity[msg.sender]; if (liquidity == 0) revert ZeroLiquidity(); delete epochInfo.liquidity[msg.sender]; - uint128 liquidityTotal = epochInfo.liquidityTotal; - epochInfo.liquidityTotal = liquidityTotal - liquidity; uint256 amount0Fee; uint256 amount1Fee; @@ -309,12 +307,12 @@ contract LimitOrder is BaseHook { address(this), abi.encodeCall( this.lockAcquiredKill, - (key, tickLower, -int256(uint256(liquidity)), to, liquidity == liquidityTotal) + (key, tickLower, -int256(uint256(liquidity)), to, liquidity == epochInfo.liquidityTotal) ) ), (uint256, uint256, uint256, uint256) ); - + epochInfo.liquidityTotal -= liquidity; unchecked { epochInfo.token0Total += amount0Fee; epochInfo.token1Total += amount1Fee; @@ -378,15 +376,13 @@ contract LimitOrder is BaseHook { if (liquidity == 0) revert ZeroLiquidity(); delete epochInfo.liquidity[msg.sender]; - uint256 token0Total = epochInfo.token0Total; - uint256 token1Total = epochInfo.token1Total; uint128 liquidityTotal = epochInfo.liquidityTotal; - amount0 = FullMath.mulDiv(token0Total, liquidity, liquidityTotal); - amount1 = FullMath.mulDiv(token1Total, liquidity, liquidityTotal); + amount0 = FullMath.mulDiv(epochInfo.token0Total, liquidity, liquidityTotal); + amount1 = FullMath.mulDiv(epochInfo.token1Total, liquidity, liquidityTotal); - epochInfo.token0Total = token0Total - amount0; - epochInfo.token1Total = token1Total - amount1; + epochInfo.token0Total -= amount0; + epochInfo.token1Total -= amount1; epochInfo.liquidityTotal = liquidityTotal - liquidity; poolManager.lock( diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 8b2b16e0..1f9350a8 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -29,6 +29,23 @@ contract Quoter is IQuoter, ILockCallback { /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 96; + struct QuoteResult { + int128[] deltaAmounts; + uint160[] sqrtPriceX96AfterList; + uint32[] initializedTicksLoadedList; + } + + struct QuoteCache { + BalanceDelta curDeltas; + uint128 prevAmount; + int128 deltaIn; + int128 deltaOut; + int24 tickBefore; + int24 tickAfter; + Currency prevCurrency; + uint160 sqrtPriceX96After; + } + /// @dev Only this address may call this function modifier selfOnly() { if (msg.sender != address(this)) revert NotSelf(); @@ -151,39 +168,42 @@ contract Quoter is IQuoter, ILockCallback { function _quoteExactInput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - int128[] memory deltaAmounts = new int128[](pathLength + 1); - uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); - uint32[] memory initializedTicksLoadedList = new uint32[](pathLength); - Currency prevCurrencyOut; - uint128 prevAmountOut; + QuoteResult memory result = QuoteResult({ + deltaAmounts: new int128[](pathLength + 1), + sqrtPriceX96AfterList: new uint160[](pathLength), + initializedTicksLoadedList: new uint32[](pathLength) + }); + QuoteCache memory cache; for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = - params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : prevCurrencyOut); - (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); + params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : cache.prevCurrency); + (, cache.tickBefore,) = manager.getSlot0(poolKey.toId()); - (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = _swap( poolKey, zeroForOne, - int256(int128(i == 0 ? params.exactAmount : prevAmountOut)), + int256(int128(i == 0 ? params.exactAmount : cache.prevAmount)), 0, params.path[i].hookData ); - (int128 deltaIn, int128 deltaOut) = - zeroForOne ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); - deltaAmounts[i] += deltaIn; - deltaAmounts[i + 1] += deltaOut; - - prevAmountOut = zeroForOne ? uint128(-curDeltas.amount1()) : uint128(-curDeltas.amount0()); - prevCurrencyOut = params.path[i].intermediateCurrency; - sqrtPriceX96AfterList[i] = sqrtPriceX96After; - initializedTicksLoadedList[i] = - PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); + (cache.deltaIn, cache.deltaOut) = zeroForOne + ? (cache.curDeltas.amount0(), cache.curDeltas.amount1()) + : (cache.curDeltas.amount1(), cache.curDeltas.amount0()); + result.deltaAmounts[i] += cache.deltaIn; + result.deltaAmounts[i + 1] += cache.deltaOut; + + cache.prevAmount = zeroForOne ? uint128(-cache.curDeltas.amount1()) : uint128(-cache.curDeltas.amount0()); + cache.prevCurrency = params.path[i].intermediateCurrency; + result.sqrtPriceX96AfterList[i] = cache.sqrtPriceX96After; + result.initializedTicksLoadedList[i] = + PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, cache.tickBefore, cache.tickAfter); } - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + bytes memory r = + abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList, result.initializedTicksLoadedList); assembly { - revert(add(0x20, result), mload(result)) + revert(add(0x20, r), mload(r)) } } @@ -216,42 +236,45 @@ contract Quoter is IQuoter, ILockCallback { function _quoteExactOutput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - int128[] memory deltaAmounts = new int128[](pathLength + 1); - uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); - uint32[] memory initializedTicksLoadedList = new uint32[](pathLength); - Currency prevCurrencyIn; - uint128 prevAmountIn; + QuoteResult memory result = QuoteResult({ + deltaAmounts: new int128[](pathLength + 1), + sqrtPriceX96AfterList: new uint160[](pathLength), + initializedTicksLoadedList: new uint32[](pathLength) + }); + QuoteCache memory cache; uint128 curAmountOut; for (uint256 i = pathLength; i > 0; i--) { - curAmountOut = i == pathLength ? params.exactAmount : prevAmountIn; + curAmountOut = i == pathLength ? params.exactAmount : cache.prevAmount; amountOutCached = curAmountOut; (PoolKey memory poolKey, bool oneForZero) = PathKeyLib.getPoolAndSwapDirection( - params.path[i - 1], i == pathLength ? params.exactCurrency : prevCurrencyIn + params.path[i - 1], i == pathLength ? params.exactCurrency : cache.prevCurrency ); - (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); + (, cache.tickBefore,) = manager.getSlot0(poolKey.toId()); - (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = + (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = _swap(poolKey, !oneForZero, -int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); // always clear because sqrtPriceLimitX96 is set to 0 always delete amountOutCached; - (int128 deltaIn, int128 deltaOut) = - !oneForZero ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); - deltaAmounts[i - 1] += deltaIn; - deltaAmounts[i] += deltaOut; - - prevAmountIn = !oneForZero ? uint128(curDeltas.amount0()) : uint128(curDeltas.amount1()); - prevCurrencyIn = params.path[i - 1].intermediateCurrency; - sqrtPriceX96AfterList[i - 1] = sqrtPriceX96After; - initializedTicksLoadedList[i - 1] = - PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); + (cache.deltaIn, cache.deltaOut) = !oneForZero + ? (cache.curDeltas.amount0(), cache.curDeltas.amount1()) + : (cache.curDeltas.amount1(), cache.curDeltas.amount0()); + result.deltaAmounts[i - 1] += cache.deltaIn; + result.deltaAmounts[i] += cache.deltaOut; + + cache.prevAmount = !oneForZero ? uint128(cache.curDeltas.amount0()) : uint128(cache.curDeltas.amount1()); + cache.prevCurrency = params.path[i - 1].intermediateCurrency; + result.sqrtPriceX96AfterList[i - 1] = cache.sqrtPriceX96After; + result.initializedTicksLoadedList[i - 1] = + PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, cache.tickBefore, cache.tickAfter); } - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + bytes memory r = + abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList, result.initializedTicksLoadedList); assembly { - revert(add(0x20, result), mload(result)) + revert(add(0x20, r), mload(r)) } } diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol index b0e9ab5b..077ef4a6 100644 --- a/contracts/libraries/PoolTicksCounter.sol +++ b/contracts/libraries/PoolTicksCounter.sol @@ -9,6 +9,15 @@ import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; library PoolTicksCounter { using PoolIdLibrary for PoolKey; + struct TickCache { + int16 wordPosLower; + int16 wordPosHigher; + uint8 bitPosLower; + uint8 bitPosHigher; + bool tickBeforeInitialized; + bool tickAfterInitialized; + } + /// @dev This function counts the number of initialized ticks that would incur a gas cost between tickBefore and tickAfter. /// When tickBefore and/or tickAfter themselves are initialized, the logic over whether we should count them depends on the /// direction of the swap. If we are swapping upwards (tickAfter > tickBefore) we don't want to count tickBefore but we do @@ -18,12 +27,7 @@ library PoolTicksCounter { view returns (uint32 initializedTicksLoaded) { - int16 wordPosLower; - int16 wordPosHigher; - uint8 bitPosLower; - uint8 bitPosHigher; - bool tickBeforeInitialized; - bool tickAfterInitialized; + TickCache memory cache; { // Get the key and offset in the tick bitmap of the active tick before and after the swap. @@ -39,53 +43,53 @@ library PoolTicksCounter { // and we shouldn't count it. uint256 bmAfter = self.getPoolBitmapInfo(key.toId(), wordPosAfter); //uint256 bmAfter = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosAfter); - tickAfterInitialized = + cache.tickAfterInitialized = ((bmAfter & (1 << bitPosAfter)) > 0) && ((tickAfter % key.tickSpacing) == 0) && (tickBefore > tickAfter); // In the case where tickBefore is initialized, we only want to count it if we are swapping upwards. // Use the same logic as above to decide whether we should count tickBefore or not. uint256 bmBefore = self.getPoolBitmapInfo(key.toId(), wordPos); //uint256 bmBefore = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPos); - tickBeforeInitialized = + cache.tickBeforeInitialized = ((bmBefore & (1 << bitPos)) > 0) && ((tickBefore % key.tickSpacing) == 0) && (tickBefore < tickAfter); if (wordPos < wordPosAfter || (wordPos == wordPosAfter && bitPos <= bitPosAfter)) { - wordPosLower = wordPos; - bitPosLower = bitPos; - wordPosHigher = wordPosAfter; - bitPosHigher = bitPosAfter; + cache.wordPosLower = wordPos; + cache.bitPosLower = bitPos; + cache.wordPosHigher = wordPosAfter; + cache.bitPosHigher = bitPosAfter; } else { - wordPosLower = wordPosAfter; - bitPosLower = bitPosAfter; - wordPosHigher = wordPos; - bitPosHigher = bitPos; + cache.wordPosLower = wordPosAfter; + cache.bitPosLower = bitPosAfter; + cache.wordPosHigher = wordPos; + cache.bitPosHigher = bitPos; } } // Count the number of initialized ticks crossed by iterating through the tick bitmap. // Our first mask should include the lower tick and everything to its left. - uint256 mask = type(uint256).max << bitPosLower; - while (wordPosLower <= wordPosHigher) { + uint256 mask = type(uint256).max << cache.bitPosLower; + while (cache.wordPosLower <= cache.wordPosHigher) { // If we're on the final tick bitmap page, ensure we only count up to our // ending tick. - if (wordPosLower == wordPosHigher) { - mask = mask & (type(uint256).max >> (255 - bitPosHigher)); + if (cache.wordPosLower == cache.wordPosHigher) { + mask = mask & (type(uint256).max >> (255 - cache.bitPosHigher)); } - //uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosLower); - uint256 bmLower = self.getPoolBitmapInfo(key.toId(), wordPosLower); + //uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), cache.wordPosLower); + uint256 bmLower = self.getPoolBitmapInfo(key.toId(), cache.wordPosLower); uint256 masked = bmLower & mask; initializedTicksLoaded += countOneBits(masked); - wordPosLower++; + cache.wordPosLower++; // Reset our mask so we consider all bits on the next iteration. mask = type(uint256).max; } - if (tickAfterInitialized) { + if (cache.tickAfterInitialized) { initializedTicksLoaded -= 1; } - if (tickBeforeInitialized) { + if (cache.tickBeforeInitialized) { initializedTicksLoaded -= 1; } diff --git a/foundry.toml b/foundry.toml index 620d06a6..d957fe5b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,6 @@ src = 'contracts' out = 'foundry-out' solc_version = '0.8.20' -via_ir = true optimizer_runs = 1000000 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 056b0818..31d266d4 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -53,14 +53,15 @@ contract QuoterTest is Test, Deployers { quoter = new Quoter(address(manager)); positionManager = new PoolModifyPositionTest(manager); - // salts are chosen so that address(token0) < address(token2) && address(1) < address(token2) - bytes32 salt1 = "ffff"; - bytes32 salt2 = "gm"; - token0 = new MockERC20{salt: salt1}("Test0", "0", 18); + // salts are chosen so that address(token0) < address(token1) && address(token1) < address(token2) + bytes32 salt0 = "1234"; + bytes32 salt1 = "gm uniswap"; + bytes32 salt2 = "ffff"; + token0 = new MockERC20{salt: salt0}("Test0", "0", 18); token0.mint(address(this), 2 ** 128); - token1 = new MockERC20{salt: salt2}("Test1", "1", 18); + token1 = new MockERC20{salt: salt1}("Test1", "1", 18); token1.mint(address(this), 2 ** 128); - token2 = new MockERC20("Test2", "2", 18); + token2 = new MockERC20{salt: salt2}("Test2", "2", 18); token2.mint(address(this), 2 ** 128); key01 = createPoolKey(token0, token1, address(0)); From 6045e2e7857daaceb9515c673429f917a3f68d08 Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Wed, 28 Feb 2024 11:32:36 -0500 Subject: [PATCH 06/13] chore: update v4-core:latest (#89) * update v4-core * update to new liquidity hooks * forge fmt; reuse v4-core justfile * snapshots * rename getHooksCalls --> getHookPermissions * enforce permanent liquidity with beforeRemoveLiquidity * snapshot * update v4-core (again) * snapshots with new v4-core * v4-core:latest * pin 0.8.24 * merge in remote; regenerate snapshots * remove justfile * repin cancun * pin token addresses using vm.etch * snapshots * forge fmt * remove via-ir and custom solc from CI * test nit --- .../FullOracleObserve0After5Seconds.snap | 2 +- .../FullOracleObserve200By13.snap | 2 +- .../FullOracleObserve200By13Plus5.snap | 2 +- .../FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .../FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .../OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .../OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- ...eObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .../OracleObserveCurrentTime.snap | 2 +- ...racleObserveCurrentTimeCounterfactual.snap | 2 +- .../OracleObserveLast20Seconds.snap | 2 +- .../OracleObserveLatestEqual.snap | 2 +- .../OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .../OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- .github/workflows/test.yml | 2 +- README.md | 2 +- contracts/BaseHook.sol | 31 +++++++++--- contracts/hooks/examples/FullRange.sol | 36 +++++++------- contracts/hooks/examples/GeomeanOracle.sol | 24 ++++++--- contracts/hooks/examples/LimitOrder.sol | 36 +++++++------- contracts/hooks/examples/TWAMM.sol | 14 +++--- contracts/hooks/examples/VolatilityOracle.sol | 8 +-- foundry.toml | 5 +- lib/v4-core | 2 +- test/FullRange.t.sol | 8 +-- test/GeomeanOracle.t.sol | 46 ++++++++++++----- test/Quoter.t.sol | 49 ++++++++++--------- test/TWAMM.t.sol | 29 +++++------ .../FullRangeImplementation.sol | 2 +- .../GeomeanOracleImplementation.sol | 2 +- .../LimitOrderImplementation.sol | 2 +- .../implementation/TWAMMImplementation.sol | 2 +- 48 files changed, 210 insertions(+), 150 deletions(-) diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index 8fa2b472..f5b9e8bf 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -1922 \ No newline at end of file +1912 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 9e3ceb1e..b47b8dc4 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -20282 \ No newline at end of file +20210 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 0da8d066..46616951 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -20520 \ No newline at end of file +20443 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index 5ee5d632..dba60802 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2034 \ No newline at end of file +2024 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index 3c45c181..c90bb2fe 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -19330 \ No newline at end of file +19279 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index eda3bfd7..1d23504b 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -19612 \ No newline at end of file +19555 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index ce4798b6..3559f242 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -1483 \ No newline at end of file +1477 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index fac70738..fda86345 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -393062 \ No newline at end of file +392772 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index 8ff9c7d3..ff9a3f08 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -187418 \ No newline at end of file +187139 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 6350e081..029a908d 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -136762 \ No newline at end of file +136542 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 44dce048..44c69e54 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1059719 \ No newline at end of file +1041060 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 232c6f67..6ff7a267 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -180886 \ No newline at end of file +175903 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 569e77f5..10fb1518 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -373831 \ No newline at end of file +363995 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 6620403b..c02e1eae 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -97479 \ No newline at end of file +97295 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index c7357ed2..8adf5f54 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -135037 \ No newline at end of file +134817 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index c82060cc..3dada479 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -232968 \ No newline at end of file +232960 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index d8ee5c94..f623cfa5 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -223657 \ No newline at end of file +223649 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 3c7800f1..137baa16 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -32853 \ No newline at end of file +32845 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index 80d77105..e6dc42ce 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -23553 \ No newline at end of file +23545 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index 4262e6e4..e4e9e6b2 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -51321 \ No newline at end of file +51310 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index e4417bef..5996d53e 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -5397 \ No newline at end of file +5368 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index ce4798b6..3559f242 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -1483 \ No newline at end of file +1477 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index ce4798b6..3559f242 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -1483 \ No newline at end of file +1477 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index bbc0ec1f..24efe8f4 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -73451 \ No newline at end of file +73037 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index ce4798b6..3559f242 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -1483 \ No newline at end of file +1477 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index 8fa2b472..f5b9e8bf 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -1922 \ No newline at end of file +1912 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index dd7e3e1f..76e5b53e 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -5572 \ No newline at end of file +5541 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index 63bada90..f124ce2d 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -5115 \ No newline at end of file +5092 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index 13e432b0..9dab3404 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -2537 \ No newline at end of file +2522 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 7c2297c1..1ac55f85 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -122817 \ No newline at end of file +122753 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6d99f2d..280df88b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,6 @@ jobs: version: nightly - name: Run tests - run: forge test -vvv --via-ir + run: forge test -vvv env: FOUNDRY_PROFILE: ci diff --git a/README.md b/README.md index b931bd6a..245785b4 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ contract CoolHook is BaseHook { function beforeModifyPosition( address, IPoolManager.PoolKey calldata key, - IPoolManager.ModifyPositionParams calldata params + IPoolManager.ModifyLiquidityParams calldata params ) external override poolManagerOnly returns (bytes4) { // hook logic return BaseHook.beforeModifyPosition.selector; diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index 941de34c..16fdf684 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.24; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; @@ -40,13 +40,13 @@ abstract contract BaseHook is IHooks { _; } - function getHooksCalls() public pure virtual returns (Hooks.Permissions memory); + function getHookPermissions() public pure virtual returns (Hooks.Permissions memory); // this function is virtual so that we can override it during testing, // which allows us to deploy an implementation to any address // and then etch the bytecode into the correct address function validateHookAddress(BaseHook _this) internal pure virtual { - Hooks.validateHookPermissions(_this, getHooksCalls()); + Hooks.validateHookPermissions(_this, getHookPermissions()); } function lockAcquired(address, /*sender*/ bytes calldata data) @@ -77,7 +77,7 @@ abstract contract BaseHook is IHooks { revert HookNotImplemented(); } - function beforeModifyPosition(address, PoolKey calldata, IPoolManager.ModifyPositionParams calldata, bytes calldata) + function beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) external virtual returns (bytes4) @@ -85,10 +85,29 @@ abstract contract BaseHook is IHooks { revert HookNotImplemented(); } - function afterModifyPosition( + function beforeRemoveLiquidity( address, PoolKey calldata, - IPoolManager.ModifyPositionParams calldata, + IPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external virtual returns (bytes4) { + revert HookNotImplemented(); + } + + function afterAddLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + bytes calldata + ) external virtual returns (bytes4) { + revert HookNotImplemented(); + } + + function afterRemoveLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata ) external virtual returns (bytes4) { diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 1bd0cfe2..a57d9dd0 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -50,7 +50,7 @@ contract FullRange is BaseHook, ILockCallback { struct CallbackData { address sender; PoolKey key; - IPoolManager.ModifyPositionParams params; + IPoolManager.ModifyLiquidityParams params; } struct PoolInfo { @@ -87,12 +87,14 @@ contract FullRange is BaseHook, ILockCallback { _; } - function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, - beforeModifyPosition: true, - afterModifyPosition: false, + beforeAddLiquidity: true, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, @@ -138,7 +140,7 @@ contract FullRange is BaseHook, ILockCallback { } BalanceDelta addedDelta = modifyPosition( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: liquidity.toInt256() @@ -182,7 +184,7 @@ contract FullRange is BaseHook, ILockCallback { delta = modifyPosition( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: -(params.liquidity.toInt256()) @@ -219,15 +221,15 @@ contract FullRange is BaseHook, ILockCallback { return FullRange.beforeInitialize.selector; } - function beforeModifyPosition( + function beforeAddLiquidity( address sender, PoolKey calldata, - IPoolManager.ModifyPositionParams calldata, + IPoolManager.ModifyLiquidityParams calldata, bytes calldata ) external view override returns (bytes4) { if (sender != address(this)) revert SenderMustBeHook(); - return FullRange.beforeModifyPosition.selector; + return FullRange.beforeAddLiquidity.selector; } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) @@ -245,7 +247,7 @@ contract FullRange is BaseHook, ILockCallback { return IHooks.beforeSwap.selector; } - function modifyPosition(PoolKey memory key, IPoolManager.ModifyPositionParams memory params) + function modifyPosition(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) internal returns (BalanceDelta delta) { @@ -277,7 +279,7 @@ contract FullRange is BaseHook, ILockCallback { poolManager.take(key.currency1, sender, uint256(uint128(-delta.amount1()))); } - function _removeLiquidity(PoolKey memory key, IPoolManager.ModifyPositionParams memory params) + function _removeLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) internal returns (BalanceDelta delta) { @@ -295,7 +297,7 @@ contract FullRange is BaseHook, ILockCallback { ); params.liquidityDelta = -(liquidityToRemove.toInt256()); - delta = poolManager.modifyPosition(key, params, ZERO_BYTES); + delta = poolManager.modifyLiquidity(key, params, ZERO_BYTES); pool.hasAccruedFees = false; } @@ -314,7 +316,7 @@ contract FullRange is BaseHook, ILockCallback { delta = _removeLiquidity(data.key, data.params); _takeDeltas(data.sender, data.key, delta); } else { - delta = poolManager.modifyPosition(data.key, data.params, ZERO_BYTES); + delta = poolManager.modifyLiquidity(data.key, data.params, ZERO_BYTES); _settleDeltas(data.sender, data.key, delta); } return abi.encode(delta); @@ -322,9 +324,9 @@ contract FullRange is BaseHook, ILockCallback { function _rebalance(PoolKey memory key) public { PoolId poolId = key.toId(); - BalanceDelta balanceDelta = poolManager.modifyPosition( + BalanceDelta balanceDelta = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: -(poolManager.getLiquidity(poolId).toInt256()) @@ -358,9 +360,9 @@ contract FullRange is BaseHook, ILockCallback { uint256(uint128(-balanceDelta.amount1())) ); - BalanceDelta balanceDeltaAfter = poolManager.modifyPosition( + BalanceDelta balanceDeltaAfter = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: liquidity.toInt256() diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index 35389d0f..9d53fb0a 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -60,12 +60,14 @@ contract GeomeanOracle is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, - beforeModifyPosition: true, - afterModifyPosition: false, + beforeAddLiquidity: true, + beforeRemoveLiquidity: true, + afterAddLiquidity: false, + afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, @@ -112,20 +114,28 @@ contract GeomeanOracle is BaseHook { ); } - function beforeModifyPosition( + function beforeAddLiquidity( address, PoolKey calldata key, - IPoolManager.ModifyPositionParams calldata params, + IPoolManager.ModifyLiquidityParams calldata params, bytes calldata ) external override poolManagerOnly returns (bytes4) { - if (params.liquidityDelta < 0) revert OraclePoolMustLockLiquidity(); int24 maxTickSpacing = poolManager.MAX_TICK_SPACING(); if ( params.tickLower != TickMath.minUsableTick(maxTickSpacing) || params.tickUpper != TickMath.maxUsableTick(maxTickSpacing) ) revert OraclePositionsMustBeFullRange(); _updatePool(key); - return GeomeanOracle.beforeModifyPosition.selector; + return GeomeanOracle.beforeAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external view override poolManagerOnly returns (bytes4) { + revert OraclePoolMustLockLiquidity(); } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 8e9ddbf7..c8d9316f 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -73,12 +73,14 @@ contract LimitOrder is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, - beforeModifyPosition: false, - afterModifyPosition: false, + beforeAddLiquidity: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, @@ -197,9 +199,9 @@ contract LimitOrder is BaseHook { selfOnly returns (uint128 amount0, uint128 amount1) { - BalanceDelta delta = poolManager.modifyPosition( + BalanceDelta delta = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, tickUpper: tickLower + key.tickSpacing, liquidityDelta: liquidityDelta @@ -208,10 +210,10 @@ contract LimitOrder is BaseHook { ); if (delta.amount0() < 0) { - poolManager.mint(key.currency0, address(this), amount0 = uint128(-delta.amount0())); + poolManager.mint(address(this), key.currency0.toId(), amount0 = uint128(-delta.amount0())); } if (delta.amount1() < 0) { - poolManager.mint(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(-delta.amount1())); } } @@ -258,9 +260,9 @@ contract LimitOrder is BaseHook { int256 liquidityDelta, address owner ) external selfOnly { - BalanceDelta delta = poolManager.modifyPosition( + BalanceDelta delta = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, tickUpper: tickLower + key.tickSpacing, liquidityDelta: liquidityDelta @@ -335,23 +337,23 @@ contract LimitOrder is BaseHook { // could be unfairly diluted by a user sychronously placing then killing a limit order to skim off fees. // to prevent this, we allocate all fee revenue to remaining limit order placers, unless this is the last order. if (!removingAllLiquidity) { - BalanceDelta deltaFee = poolManager.modifyPosition( + BalanceDelta deltaFee = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({tickLower: tickLower, tickUpper: tickUpper, liquidityDelta: 0}), + IPoolManager.ModifyLiquidityParams({tickLower: tickLower, tickUpper: tickUpper, liquidityDelta: 0}), ZERO_BYTES ); if (deltaFee.amount0() < 0) { - poolManager.mint(key.currency0, address(this), amount0Fee = uint128(-deltaFee.amount0())); + poolManager.mint(address(this), key.currency0.toId(), amount0Fee = uint128(-deltaFee.amount0())); } if (deltaFee.amount1() < 0) { - poolManager.mint(key.currency1, address(this), amount1Fee = uint128(-deltaFee.amount1())); + poolManager.mint(address(this), key.currency1.toId(), amount1Fee = uint128(-deltaFee.amount1())); } } - BalanceDelta delta = poolManager.modifyPosition( + BalanceDelta delta = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, tickUpper: tickUpper, liquidityDelta: liquidityDelta @@ -401,11 +403,11 @@ contract LimitOrder is BaseHook { address to ) external selfOnly { if (token0Amount > 0) { - poolManager.burn(currency0, token0Amount); + poolManager.burn(address(this), currency0.toId(), token0Amount); poolManager.take(currency0, to, token0Amount); } if (token1Amount > 0) { - poolManager.burn(currency1, token1Amount); + poolManager.burn(address(this), currency1.toId(), token1Amount); poolManager.take(currency1, to, token1Amount); } } diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 4fd5dd74..694c0b2c 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -60,12 +60,14 @@ contract TWAMM is BaseHook, ITWAMM { expirationInterval = _expirationInterval; } - function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, - beforeModifyPosition: true, - afterModifyPosition: false, + beforeAddLiquidity: true, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, @@ -87,14 +89,14 @@ contract TWAMM is BaseHook, ITWAMM { return BaseHook.beforeInitialize.selector; } - function beforeModifyPosition( + function beforeAddLiquidity( address, PoolKey calldata key, - IPoolManager.ModifyPositionParams calldata, + IPoolManager.ModifyLiquidityParams calldata, bytes calldata ) external override poolManagerOnly returns (bytes4) { executeTWAMMOrders(key); - return BaseHook.beforeModifyPosition.selector; + return BaseHook.beforeAddLiquidity.selector; } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index 657c9fae..df8bdde5 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -30,12 +30,14 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { deployTimestamp = _blockTimestamp(); } - function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, - beforeModifyPosition: false, - afterModifyPosition: false, + beforeAddLiquidity: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, beforeSwap: false, afterSwap: false, beforeDonate: false, diff --git a/foundry.toml b/foundry.toml index d957fe5b..4e95a213 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,14 +1,13 @@ [profile.default] src = 'contracts' out = 'foundry-out' -solc_version = '0.8.20' +solc_version = '0.8.24' optimizer_runs = 1000000 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] -cancun = true +evm_version = "cancun" [profile.ci] fuzz_runs = 100000 -solc = "./lib/v4-core/bin/solc-static-linux" # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/lib/v4-core b/lib/v4-core index 83557113..4a13732d 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 83557113a0425eb3d81570c30e7a5ce550037149 +Subproject commit 4a13732dc0b9a8c516d3639a78c54af3fc3db8d4 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 3d3f6800..076abab3 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -13,7 +13,7 @@ import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; @@ -65,7 +65,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { MockERC20 token2; FullRangeImplementation fullRange = FullRangeImplementation( - address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG)) + address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_SWAP_FLAG)) ); PoolId id; @@ -756,9 +756,9 @@ contract TestFullRange is Test, Deployers, GasSnapshot { initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); vm.expectRevert(FullRange.SenderMustBeHook.selector); - modifyPositionRouter.modifyPosition( + modifyLiquidityRouter.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: 100}), + IPoolManager.ModifyLiquidityParams({tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: 100}), ZERO_BYTES ); } diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index aa5e5c6d..ec74affc 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -12,7 +12,7 @@ import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../contracts/libraries/Oracle.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; @@ -21,15 +21,14 @@ contract TestGeomeanOracle is Test, Deployers { using PoolIdLibrary for PoolKey; int24 constant MAX_TICK_SPACING = 32767; - uint160 constant SQRT_RATIO_2_1 = 112045541949572279837463876454; TestERC20 token0; TestERC20 token1; GeomeanOracleImplementation geomeanOracle = GeomeanOracleImplementation( address( uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG - | Hooks.BEFORE_SWAP_FLAG + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG + | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_SWAP_FLAG ) ) ); @@ -57,12 +56,12 @@ contract TestGeomeanOracle is Test, Deployers { key = PoolKey(currency0, currency1, 0, MAX_TICK_SPACING, geomeanOracle); id = key.toId(); - modifyPositionRouter = new PoolModifyPositionTest(manager); + modifyLiquidityRouter = new PoolModifyLiquidityTest(manager); token0.approve(address(geomeanOracle), type(uint256).max); token1.approve(address(geomeanOracle), type(uint256).max); - token0.approve(address(modifyPositionRouter), type(uint256).max); - token1.approve(address(modifyPositionRouter), type(uint256).max); + token0.approve(address(modifyLiquidityRouter), type(uint256).max); + token1.approve(address(modifyLiquidityRouter), type(uint256).max); } function testBeforeInitializeAllowsPoolCreation() public { @@ -118,9 +117,9 @@ contract TestGeomeanOracle is Test, Deployers { function testBeforeModifyPositionNoObservations() public { initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); - modifyPositionRouter.modifyPosition( + modifyLiquidityRouter.modifyLiquidity( key, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 ), ZERO_BYTES @@ -141,9 +140,9 @@ contract TestGeomeanOracle is Test, Deployers { function testBeforeModifyPositionObservation() public { initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds - modifyPositionRouter.modifyPosition( + modifyLiquidityRouter.modifyLiquidity( key, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 ), ZERO_BYTES @@ -170,9 +169,9 @@ contract TestGeomeanOracle is Test, Deployers { assertEq(observationState.cardinality, 1); assertEq(observationState.cardinalityNext, 2); - modifyPositionRouter.modifyPosition( + modifyLiquidityRouter.modifyLiquidity( key, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 ), ZERO_BYTES @@ -198,4 +197,25 @@ contract TestGeomeanOracle is Test, Deployers { assertEq(observation.tickCumulative, 13862); assertEq(observation.secondsPerLiquidityCumulativeX128, 680564733841876926926749214863536422912); } + + function testPermanentLiquidity() public { + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + geomeanOracle.setTime(3); // advance 2 seconds + modifyLiquidityRouter.modifyLiquidity( + key, + IPoolManager.ModifyLiquidityParams( + TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 + ), + ZERO_BYTES + ); + + vm.expectRevert(GeomeanOracle.OraclePoolMustLockLiquidity.selector); + modifyLiquidityRouter.modifyLiquidity( + key, + IPoolManager.ModifyLiquidityParams( + TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), -1000 + ), + ZERO_BYTES + ); + } } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 31d266d4..87de52d5 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -12,7 +12,7 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; @@ -36,7 +36,7 @@ contract QuoterTest is Test, Deployers { Quoter quoter; - PoolModifyPositionTest positionManager; + PoolModifyLiquidityTest positionManager; MockERC20 token0; MockERC20 token1; @@ -51,17 +51,20 @@ contract QuoterTest is Test, Deployers { function setUp() public { deployFreshManagerAndRouters(); quoter = new Quoter(address(manager)); - positionManager = new PoolModifyPositionTest(manager); + positionManager = new PoolModifyLiquidityTest(manager); // salts are chosen so that address(token0) < address(token1) && address(token1) < address(token2) - bytes32 salt0 = "1234"; - bytes32 salt1 = "gm uniswap"; - bytes32 salt2 = "ffff"; - token0 = new MockERC20{salt: salt0}("Test0", "0", 18); + token0 = new MockERC20("Test0", "0", 18); + vm.etch(address(0x1111), address(token0).code); + token0 = MockERC20(address(0x1111)); token0.mint(address(this), 2 ** 128); - token1 = new MockERC20{salt: salt1}("Test1", "1", 18); + + vm.etch(address(0x2222), address(token0).code); + token1 = MockERC20(address(0x2222)); token1.mint(address(this), 2 ** 128); - token2 = new MockERC20{salt: salt2}("Test2", "2", 18); + + vm.etch(address(0x3333), address(token0).code); + token2 = MockERC20(address(0x3333)); token2.mint(address(this), 2 ** 128); key01 = createPoolKey(token0, token1, address(0)); @@ -542,9 +545,9 @@ contract QuoterTest is Test, Deployers { initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); - positionManager.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( MIN_TICK, MAX_TICK, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() @@ -557,25 +560,25 @@ contract QuoterTest is Test, Deployers { initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); - positionManager.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( MIN_TICK, MAX_TICK, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() ), ZERO_BYTES ); - positionManager.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( -60, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -60, 60, 100, 100).toInt256() ), ZERO_BYTES ); - positionManager.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( -120, 120, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 120, 100, 100).toInt256() ), ZERO_BYTES @@ -591,25 +594,25 @@ contract QuoterTest is Test, Deployers { MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); - positionManager.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( MIN_TICK, MAX_TICK, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() ), ZERO_BYTES ); - positionManager.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( 0, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, 0, 60, 100, 100).toInt256() ), ZERO_BYTES ); - positionManager.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( -120, 0, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 0, 100, 100).toInt256() ), ZERO_BYTES diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 17ed64a1..fdcf81d2 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -12,7 +12,7 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; @@ -43,11 +43,8 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { uint256 earningsFactorLast ); - // address constant TWAMMAddr = address(uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG)); - TWAMM twamm = TWAMM( - address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG)) - ); - // TWAMM twamm; + TWAMM twamm = + TWAMM(address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG))); address hookAddress; MockERC20 token0; MockERC20 token1; @@ -74,15 +71,19 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_RATIO_1_1, ZERO_BYTES); - token0.approve(address(modifyPositionRouter), 100 ether); - token1.approve(address(modifyPositionRouter), 100 ether); + token0.approve(address(modifyLiquidityRouter), 100 ether); + token1.approve(address(modifyLiquidityRouter), 100 ether); token0.mint(address(this), 100 ether); token1.mint(address(this), 100 ether); - modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-60, 60, 10 ether), ZERO_BYTES); - modifyPositionRouter.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-120, 120, 10 ether), ZERO_BYTES); - modifyPositionRouter.modifyPosition( + modifyLiquidityRouter.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether), ZERO_BYTES + ); + modifyLiquidityRouter.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether), ZERO_BYTES + ); + modifyLiquidityRouter.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether), + IPoolManager.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether), ZERO_BYTES ); } @@ -361,8 +362,8 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { token0.approve(address(twamm), 100e18); token1.approve(address(twamm), 100e18); - modifyPositionRouter.modifyPosition( - poolKey, IPoolManager.ModifyPositionParams(-2400, 2400, 10 ether), ZERO_BYTES + modifyLiquidityRouter.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams(-2400, 2400, 10 ether), ZERO_BYTES ); vm.warp(10000); diff --git a/test/shared/implementation/FullRangeImplementation.sol b/test/shared/implementation/FullRangeImplementation.sol index 63592f5c..2d4ce3cc 100644 --- a/test/shared/implementation/FullRangeImplementation.sol +++ b/test/shared/implementation/FullRangeImplementation.sol @@ -8,7 +8,7 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract FullRangeImplementation is FullRange { constructor(IPoolManager _poolManager, FullRange addressToEtch) FullRange(_poolManager) { - Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHookPermissions()); } // make this a no-op in testing diff --git a/test/shared/implementation/GeomeanOracleImplementation.sol b/test/shared/implementation/GeomeanOracleImplementation.sol index 0c964671..b953a3b6 100644 --- a/test/shared/implementation/GeomeanOracleImplementation.sol +++ b/test/shared/implementation/GeomeanOracleImplementation.sol @@ -10,7 +10,7 @@ contract GeomeanOracleImplementation is GeomeanOracle { uint32 public time; constructor(IPoolManager _poolManager, GeomeanOracle addressToEtch) GeomeanOracle(_poolManager) { - Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHookPermissions()); } // make this a no-op in testing diff --git a/test/shared/implementation/LimitOrderImplementation.sol b/test/shared/implementation/LimitOrderImplementation.sol index c0f5a5f8..11625771 100644 --- a/test/shared/implementation/LimitOrderImplementation.sol +++ b/test/shared/implementation/LimitOrderImplementation.sol @@ -8,7 +8,7 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract LimitOrderImplementation is LimitOrder { constructor(IPoolManager _poolManager, LimitOrder addressToEtch) LimitOrder(_poolManager) { - Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHookPermissions()); } // make this a no-op in testing diff --git a/test/shared/implementation/TWAMMImplementation.sol b/test/shared/implementation/TWAMMImplementation.sol index 27a9e10c..f217db8c 100644 --- a/test/shared/implementation/TWAMMImplementation.sol +++ b/test/shared/implementation/TWAMMImplementation.sol @@ -8,7 +8,7 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract TWAMMImplementation is TWAMM { constructor(IPoolManager poolManager, uint256 interval, TWAMM addressToEtch) TWAMM(poolManager, interval) { - Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHookPermissions()); } // make this a no-op in testing From c20e8940c318dd397492528c4d39fd98536622a7 Mon Sep 17 00:00:00 2001 From: 0x57 Date: Mon, 4 Mar 2024 22:07:17 +0800 Subject: [PATCH 07/13] Update v4-core submodule to use https (#97) Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 9e4b995c..d2dc450b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,7 @@ url = https://github.com/marktoda/forge-gas-snapshot [submodule "lib/v4-core"] path = lib/v4-core - url = git@github.com:Uniswap/v4-core.git + url = https://github.com/Uniswap/v4-core [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate From f15995f8dae5be2983d1b9648ea1ae01dd82d484 Mon Sep 17 00:00:00 2001 From: mr-uniswap <144828035+mr-uniswap@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:20:42 +0900 Subject: [PATCH 08/13] chore: add semgrep (#94) --- .github/workflows/semgrep.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 00000000..c773069b --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,22 @@ +name: Semgrep +on: + workflow_dispatch: {} + pull_request: {} + push: + branches: + - main + schedule: + # random HH:MM to avoid a load spike on GitHub Actions at 00:00 + - cron: '35 11 * * *' +jobs: + semgrep: + name: semgrep/ci + runs-on: ubuntu-20.04 + env: + SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} + container: + image: returntocorp/semgrep + if: (github.actor != 'dependabot[bot]') + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - run: semgrep ci From 6616b12db25257ffb3c562f131612ebb2fd89082 Mon Sep 17 00:00:00 2001 From: 0x57 Date: Sat, 16 Mar 2024 06:21:52 +0800 Subject: [PATCH 09/13] [Chore] Update v4-core:latest (#100) * Update v4-core * Update various examples, BaseHook, Quoter and tests * Remove nested locking for LimitOrder * Fix Quoter * update v4-core * fix: remove getLocker as its a bool now * update v4-core: flipped signs, push dynamic fees * fix: flip delta signs * flip delta signs * flip delta signs * flip delta signs * fix getSlot0 calls * snapshots * remove deadcode * remove unused param * update core * update for modifyLiquidity; misc doc updates * correct min int256 * allow for manual fee updates --------- Co-authored-by: saucepoint --- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- CONTRIBUTING.md | 2 +- README.md | 12 ++--- contracts/BaseHook.sol | 7 +-- contracts/hooks/examples/FullRange.sol | 46 +++++++--------- contracts/hooks/examples/GeomeanOracle.sol | 8 ++- contracts/hooks/examples/LimitOrder.sol | 54 ++++++++----------- contracts/hooks/examples/TWAMM.sol | 33 +++++------- contracts/hooks/examples/VolatilityOracle.sol | 35 +++++++----- contracts/lens/Quoter.sol | 53 +++++++++--------- lib/v4-core | 2 +- test/FullRange.t.sol | 40 +++++++------- test/GeomeanOracle.t.sol | 20 +++---- test/LimitOrder.t.sol | 10 ++-- test/Quoter.t.sol | 12 ++--- test/TWAMM.t.sol | 2 +- test/utils/HookEnabledSwapRouter.sol | 9 ++-- 25 files changed, 167 insertions(+), 196 deletions(-) diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index fda86345..cfdeb354 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -392772 \ No newline at end of file +384735 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index ff9a3f08..e1efe638 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -187139 \ No newline at end of file +179102 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 029a908d..fd04e1b1 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -136542 \ No newline at end of file +128152 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 44c69e54..b126274c 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1041060 \ No newline at end of file +1017530 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 6ff7a267..2cdf6c52 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -175903 \ No newline at end of file +169304 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 10fb1518..2ccb0b58 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -363995 \ No newline at end of file +345919 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index c02e1eae..51e5eb70 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -97295 \ No newline at end of file +89081 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 8adf5f54..bd033704 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -134817 \ No newline at end of file +126954 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 1ac55f85..9191f9b4 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -122753 \ No newline at end of file +122845 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27bceb4d..1364a2f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ There are many ways to contribute, but here are a few if you want a place to sta ## Opening an Issue -When opening an [issue](https://github.com/Uniswap/periphery-next/issues/new/choose), choose a template to start from: Bug Report or Feature Improvement. For bug reports, you should be able to reproduce the bug through tests or proof of concept integrations. For feature improvements, please title it with a concise problem statement and check that a similar request is not already open or already in progress. Not all issues may be deemed worth resolving, so please follow through with responding to any questions or comments that others may have regarding the issue. +When opening an [issue](https://github.com/Uniswap/v4-periphery/issues/new/choose), choose a template to start from: Bug Report or Feature Improvement. For bug reports, you should be able to reproduce the bug through tests or proof of concept integrations. For feature improvements, please title it with a concise problem statement and check that a similar request is not already open or already in progress. Not all issues may be deemed worth resolving, so please follow through with responding to any questions or comments that others may have regarding the issue. Feel free to tag the issue as a “good first issue” for any clean-up related issues, or small scoped changes to help encourage pull requests from first time contributors! diff --git a/README.md b/README.md index 245785b4..b3355a10 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Uniswap v4 is a new automated market maker protocol that provides extensibility ## Contributing -If you’re interested in contributing please see the [contribution guidelines](https://github.com/Uniswap/periphery-next/blob/main/CONTRIBUTING.md)! +If you’re interested in contributing please see the [contribution guidelines](https://github.com/Uniswap/v4-periphery/blob/main/CONTRIBUTING.md)! ## Repository Structure @@ -31,24 +31,24 @@ Eventually, some hooks that have been audited and are considered production-read To utilize the contracts and deploy to a local testnet, you can install the code in your repo with forge: ```solidity -forge install https://github.com/Uniswap/periphery-next +forge install https://github.com/Uniswap/v4-periphery ``` If you are building hooks, it may be useful to inherit from the `BaseHook` contract: ```solidity -import {BaseHook} from 'periphery-next/contracts/BaseHook.sol'; +import {BaseHook} from 'v4-periphery/contracts/BaseHook.sol'; contract CoolHook is BaseHook { // Override the hook callbacks you want on your hook - function beforeModifyPosition( + function beforeAddLiquidity( address, IPoolManager.PoolKey calldata key, IPoolManager.ModifyLiquidityParams calldata params ) external override poolManagerOnly returns (bytes4) { // hook logic - return BaseHook.beforeModifyPosition.selector; + return BaseHook.beforeAddLiquidity.selector; } } @@ -56,4 +56,4 @@ contract CoolHook is BaseHook { ## License -The license for Uniswap V4 Periphery is the GNU General Public License (GPL 2.0), see [LICENSE](https://github.com/Uniswap/periphery-next/blob/main/LICENSE). +The license for Uniswap V4 Periphery is the GNU General Public License (GPL 2.0), see [LICENSE](https://github.com/Uniswap/v4-periphery/blob/main/LICENSE). diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index 16fdf684..55670dab 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -49,12 +49,7 @@ abstract contract BaseHook is IHooks { Hooks.validateHookPermissions(_this, getHookPermissions()); } - function lockAcquired(address, /*sender*/ bytes calldata data) - external - virtual - poolManagerOnly - returns (bytes memory) - { + function lockAcquired(bytes calldata data) external virtual poolManagerOnly returns (bytes memory) { (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index a57d9dd0..820d0f93 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -98,9 +98,7 @@ contract FullRange is BaseHook, ILockCallback { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -119,7 +117,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -138,7 +136,7 @@ contract FullRange is BaseHook, ILockCallback { if (poolLiquidity == 0 && liquidity <= MINIMUM_LIQUIDITY) { revert LiquidityDoesntMeetMinimum(); } - BalanceDelta addedDelta = modifyPosition( + BalanceDelta addedDelta = modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, @@ -155,7 +153,7 @@ contract FullRange is BaseHook, ILockCallback { UniswapV4ERC20(pool.liquidityToken).mint(params.to, liquidity); - if (uint128(addedDelta.amount0()) < params.amount0Min || uint128(addedDelta.amount1()) < params.amount1Min) { + if (uint128(-addedDelta.amount0()) < params.amount0Min || uint128(-addedDelta.amount1()) < params.amount1Min) { revert TooMuchSlippage(); } } @@ -176,13 +174,13 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); UniswapV4ERC20 erc20 = UniswapV4ERC20(poolInfo[poolId].liquidityToken); - delta = modifyPosition( + delta = modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, @@ -247,18 +245,16 @@ contract FullRange is BaseHook, ILockCallback { return IHooks.beforeSwap.selector; } - function modifyPosition(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) + function modifyLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) internal returns (BalanceDelta delta) { - delta = abi.decode( - poolManager.lock(address(this), abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta) - ); + delta = abi.decode(poolManager.lock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); } function _settleDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - _settleDelta(sender, key.currency0, uint128(delta.amount0())); - _settleDelta(sender, key.currency1, uint128(delta.amount1())); + _settleDelta(sender, key.currency0, uint128(-delta.amount0())); + _settleDelta(sender, key.currency1, uint128(-delta.amount1())); } function _settleDelta(address sender, Currency currency, uint128 amount) internal { @@ -275,8 +271,8 @@ contract FullRange is BaseHook, ILockCallback { } function _takeDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - poolManager.take(key.currency0, sender, uint256(uint128(-delta.amount0()))); - poolManager.take(key.currency1, sender, uint256(uint128(-delta.amount1()))); + poolManager.take(key.currency0, sender, uint256(uint128(delta.amount0()))); + poolManager.take(key.currency1, sender, uint256(uint128(delta.amount1()))); } function _removeLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) @@ -301,14 +297,12 @@ contract FullRange is BaseHook, ILockCallback { pool.hasAccruedFees = false; } - function lockAcquired(address sender, bytes calldata rawData) + function lockAcquired(bytes calldata rawData) external override(ILockCallback, BaseHook) poolManagerOnly returns (bytes memory) { - // Now that manager can be called by EOAs with a lock target, it's necessary for lockAcquired to check the original sender if it wants to trust the data passed through. - if (sender != address(this)) revert SenderMustBeHook(); CallbackData memory data = abi.decode(rawData, (CallbackData)); BalanceDelta delta; @@ -336,17 +330,17 @@ contract FullRange is BaseHook, ILockCallback { uint160 newSqrtPriceX96 = ( FixedPointMathLib.sqrt( - FullMath.mulDiv(uint128(-balanceDelta.amount1()), FixedPoint96.Q96, uint128(-balanceDelta.amount0())) + FullMath.mulDiv(uint128(balanceDelta.amount1()), FixedPoint96.Q96, uint128(balanceDelta.amount0())) ) * FixedPointMathLib.sqrt(FixedPoint96.Q96) ).toUint160(); - (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); poolManager.swap( key, IPoolManager.SwapParams({ zeroForOne: newSqrtPriceX96 < sqrtPriceX96, - amountSpecified: MAX_INT, + amountSpecified: -MAX_INT - 1, // equivalent of type(int256).min sqrtPriceLimitX96: newSqrtPriceX96 }), ZERO_BYTES @@ -356,8 +350,8 @@ contract FullRange is BaseHook, ILockCallback { newSqrtPriceX96, TickMath.getSqrtRatioAtTick(MIN_TICK), TickMath.getSqrtRatioAtTick(MAX_TICK), - uint256(uint128(-balanceDelta.amount0())), - uint256(uint128(-balanceDelta.amount1())) + uint256(uint128(balanceDelta.amount0())), + uint256(uint128(balanceDelta.amount1())) ); BalanceDelta balanceDeltaAfter = poolManager.modifyLiquidity( @@ -371,8 +365,8 @@ contract FullRange is BaseHook, ILockCallback { ); // Donate any "dust" from the sqrtRatio change as fees - uint128 donateAmount0 = uint128(-balanceDelta.amount0() - balanceDeltaAfter.amount0()); - uint128 donateAmount1 = uint128(-balanceDelta.amount1() - balanceDeltaAfter.amount1()); + uint128 donateAmount0 = uint128(balanceDelta.amount0() + balanceDeltaAfter.amount0()); + uint128 donateAmount1 = uint128(balanceDelta.amount1() + balanceDeltaAfter.amount1()); poolManager.donate(key, donateAmount0, donateAmount1, ZERO_BYTES); } diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index 9d53fb0a..c0f1c096 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -71,9 +71,7 @@ contract GeomeanOracle is BaseHook { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -105,7 +103,7 @@ contract GeomeanOracle is BaseHook { /// @dev Called before any action that potentially modifies pool price or liquidity, such as swap or modify position function _updatePool(PoolKey calldata key) private { PoolId id = key.toId(); - (, int24 tick,) = poolManager.getSlot0(id); + (, int24 tick,,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); @@ -158,7 +156,7 @@ contract GeomeanOracle is BaseHook { ObservationState memory state = states[id]; - (, int24 tick,) = poolManager.getSlot0(id); + (, int24 tick,,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index c8d9316f..e6cf8e89 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -84,9 +84,7 @@ contract LimitOrder is BaseHook { beforeSwap: false, afterSwap: true, beforeDonate: false, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -111,7 +109,7 @@ contract LimitOrder is BaseHook { } function getTick(PoolId poolId) private view returns (int24 tick) { - (, tick,) = poolManager.getSlot0(poolId); + (, tick,,) = poolManager.getSlot0(poolId); } function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { @@ -158,13 +156,8 @@ contract LimitOrder is BaseHook { epochInfo.filled = true; - (uint256 amount0, uint256 amount1) = abi.decode( - poolManager.lock( - address(this), - abi.encodeCall(this.lockAcquiredFill, (key, lower, -int256(uint256(epochInfo.liquidityTotal)))) - ), - (uint256, uint256) - ); + (uint256 amount0, uint256 amount1) = + _lockAcquiredFill(key, lower, -int256(uint256(epochInfo.liquidityTotal))); unchecked { epochInfo.token0Total += amount0; @@ -194,9 +187,9 @@ contract LimitOrder is BaseHook { } } - function lockAcquiredFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) - external - selfOnly + function _lockAcquiredFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) + private + poolManagerOnly returns (uint128 amount0, uint128 amount1) { BalanceDelta delta = poolManager.modifyLiquidity( @@ -209,11 +202,11 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) { - poolManager.mint(address(this), key.currency0.toId(), amount0 = uint128(-delta.amount0())); + if (delta.amount0() > 0) { + poolManager.mint(address(this), key.currency0.toId(), amount0 = uint128(delta.amount0())); } - if (delta.amount1() < 0) { - poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(-delta.amount1())); + if (delta.amount1() > 0) { + poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(delta.amount1())); } } @@ -224,7 +217,6 @@ contract LimitOrder is BaseHook { if (liquidity == 0) revert ZeroLiquidity(); poolManager.lock( - address(this), abi.encodeCall(this.lockAcquiredPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender)) ); @@ -270,12 +262,12 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() > 0) { + if (delta.amount0() < 0) { if (delta.amount1() != 0) revert InRange(); if (!zeroForOne) revert CrossedRange(); // TODO use safeTransferFrom IERC20Minimal(Currency.unwrap(key.currency0)).transferFrom( - owner, address(poolManager), uint256(uint128(delta.amount0())) + owner, address(poolManager), uint256(uint128(-delta.amount0())) ); poolManager.settle(key.currency0); } else { @@ -283,7 +275,7 @@ contract LimitOrder is BaseHook { if (zeroForOne) revert CrossedRange(); // TODO use safeTransferFrom IERC20Minimal(Currency.unwrap(key.currency1)).transferFrom( - owner, address(poolManager), uint256(uint128(delta.amount1())) + owner, address(poolManager), uint256(uint128(-delta.amount1())) ); poolManager.settle(key.currency1); } @@ -306,7 +298,6 @@ contract LimitOrder is BaseHook { uint256 amount1Fee; (amount0, amount1, amount0Fee, amount1Fee) = abi.decode( poolManager.lock( - address(this), abi.encodeCall( this.lockAcquiredKill, (key, tickLower, -int256(uint256(liquidity)), to, liquidity == epochInfo.liquidityTotal) @@ -343,11 +334,11 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (deltaFee.amount0() < 0) { - poolManager.mint(address(this), key.currency0.toId(), amount0Fee = uint128(-deltaFee.amount0())); + if (deltaFee.amount0() > 0) { + poolManager.mint(address(this), key.currency0.toId(), amount0Fee = uint128(deltaFee.amount0())); } - if (deltaFee.amount1() < 0) { - poolManager.mint(address(this), key.currency1.toId(), amount1Fee = uint128(-deltaFee.amount1())); + if (deltaFee.amount1() > 0) { + poolManager.mint(address(this), key.currency1.toId(), amount1Fee = uint128(deltaFee.amount1())); } } @@ -361,11 +352,11 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) { - poolManager.take(key.currency0, to, amount0 = uint128(-delta.amount0())); + if (delta.amount0() > 0) { + poolManager.take(key.currency0, to, amount0 = uint128(delta.amount0())); } - if (delta.amount1() < 0) { - poolManager.take(key.currency1, to, amount1 = uint128(-delta.amount1())); + if (delta.amount1() > 0) { + poolManager.take(key.currency1, to, amount1 = uint128(delta.amount1())); } } @@ -388,7 +379,6 @@ contract LimitOrder is BaseHook { epochInfo.liquidityTotal = liquidityTotal - liquidity; poolManager.lock( - address(this), abi.encodeCall(this.lockAcquiredWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to)) ); diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 694c0b2c..85364915 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -71,9 +71,7 @@ contract TWAMM is BaseHook, ITWAMM { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -136,7 +134,7 @@ contract TWAMM is BaseHook, ITWAMM { /// @inheritdoc ITWAMM function executeTWAMMOrders(PoolKey memory key) public { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); State storage twamm = twammStates[poolId]; (bool zeroForOne, uint160 sqrtPriceLimitX96) = _executeTWAMMOrders( @@ -144,9 +142,7 @@ contract TWAMM is BaseHook, ITWAMM { ); if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { - poolManager.lock( - address(this), abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)) - ); + poolManager.lock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96))); } } @@ -302,32 +298,27 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function lockAcquired(address, /*sender*/ bytes calldata rawData) - external - override - poolManagerOnly - returns (bytes memory) - { + function lockAcquired(bytes calldata rawData) external override poolManagerOnly returns (bytes memory) { (PoolKey memory key, IPoolManager.SwapParams memory swapParams) = abi.decode(rawData, (PoolKey, IPoolManager.SwapParams)); BalanceDelta delta = poolManager.swap(key, swapParams, ZERO_BYTES); if (swapParams.zeroForOne) { - if (delta.amount0() > 0) { - key.currency0.transfer(address(poolManager), uint256(uint128(delta.amount0()))); + if (delta.amount0() < 0) { + key.currency0.transfer(address(poolManager), uint256(uint128(-delta.amount0()))); poolManager.settle(key.currency0); } - if (delta.amount1() < 0) { - poolManager.take(key.currency1, address(this), uint256(uint128(-delta.amount1()))); + if (delta.amount1() > 0) { + poolManager.take(key.currency1, address(this), uint256(uint128(delta.amount1()))); } } else { - if (delta.amount1() > 0) { - key.currency1.transfer(address(poolManager), uint256(uint128(delta.amount1()))); + if (delta.amount1() < 0) { + key.currency1.transfer(address(poolManager), uint256(uint128(-delta.amount1()))); poolManager.settle(key.currency1); } - if (delta.amount0() < 0) { - poolManager.take(key.currency0, address(this), uint256(uint128(-delta.amount0()))); + if (delta.amount0() > 0) { + poolManager.take(key.currency0, address(this), uint256(uint128(delta.amount0()))); } } return bytes(""); diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index df8bdde5..76a3e8ce 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -2,25 +2,18 @@ pragma solidity ^0.8.19; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {IDynamicFeeManager} from "@uniswap/v4-core/src/interfaces/IDynamicFeeManager.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; +import {SwapFeeLibrary} from "@uniswap/v4-core/src/libraries/SwapFeeLibrary.sol"; import {BaseHook} from "../../BaseHook.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -contract VolatilityOracle is BaseHook, IDynamicFeeManager { - using FeeLibrary for uint24; +contract VolatilityOracle is BaseHook { + using SwapFeeLibrary for uint24; error MustUseDynamicFee(); uint32 deployTimestamp; - function getFee(address, PoolKey calldata) external view returns (uint24) { - uint24 startingFee = 3000; - uint32 lapsed = _blockTimestamp() - deployTimestamp; - return startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute - } - /// @dev For mocking function _blockTimestamp() internal view virtual returns (uint32) { return uint32(block.timestamp); @@ -33,7 +26,7 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, - afterInitialize: false, + afterInitialize: true, beforeAddLiquidity: false, beforeRemoveLiquidity: false, afterAddLiquidity: false, @@ -41,9 +34,7 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -56,4 +47,20 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { if (!key.fee.isDynamicFee()) revert MustUseDynamicFee(); return VolatilityOracle.beforeInitialize.selector; } + + function setFee(PoolKey calldata key) public { + uint24 startingFee = 3000; + uint32 lapsed = _blockTimestamp() - deployTimestamp; + uint24 fee = startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute + poolManager.updateDynamicSwapFee(key, fee); // initial fee 0.30% + } + + function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata) + external + override + returns (bytes4) + { + setFee(key); + return BaseHook.afterInitialize.selector; + } } diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 1f9350a8..c039a7b7 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -62,7 +62,7 @@ contract Quoter is IQuoter, ILockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} catch (bytes memory reason) { return _handleRevertSingle(reason); } @@ -77,7 +77,7 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } @@ -89,7 +89,7 @@ contract Quoter is IQuoter, ILockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; return _handleRevertSingle(reason); @@ -106,20 +106,17 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } } /// @inheritdoc ILockCallback - function lockAcquired(address lockCaller, bytes calldata data) external returns (bytes memory) { + function lockAcquired(bytes calldata data) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); } - if (lockCaller != address(this)) { - revert InvalidLockCaller(); - } (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; @@ -178,23 +175,23 @@ contract Quoter is IQuoter, ILockCallback { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : cache.prevCurrency); - (, cache.tickBefore,) = manager.getSlot0(poolKey.toId()); + (, cache.tickBefore,,) = manager.getSlot0(poolKey.toId()); (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = _swap( poolKey, zeroForOne, - int256(int128(i == 0 ? params.exactAmount : cache.prevAmount)), + -int256(int128(i == 0 ? params.exactAmount : cache.prevAmount)), 0, params.path[i].hookData ); (cache.deltaIn, cache.deltaOut) = zeroForOne - ? (cache.curDeltas.amount0(), cache.curDeltas.amount1()) - : (cache.curDeltas.amount1(), cache.curDeltas.amount0()); + ? (-cache.curDeltas.amount0(), -cache.curDeltas.amount1()) + : (-cache.curDeltas.amount1(), -cache.curDeltas.amount0()); result.deltaAmounts[i] += cache.deltaIn; result.deltaAmounts[i + 1] += cache.deltaOut; - cache.prevAmount = zeroForOne ? uint128(-cache.curDeltas.amount1()) : uint128(-cache.curDeltas.amount0()); + cache.prevAmount = zeroForOne ? uint128(cache.curDeltas.amount1()) : uint128(cache.curDeltas.amount0()); cache.prevCurrency = params.path[i].intermediateCurrency; result.sqrtPriceX96AfterList[i] = cache.sqrtPriceX96After; result.initializedTicksLoadedList[i] = @@ -209,20 +206,20 @@ contract Quoter is IQuoter, ILockCallback { /// @dev quote an ExactInput swap on a pool, then revert with the result function _quoteExactInputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { - (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); + (, int24 tickBefore,,) = manager.getSlot0(params.poolKey.toId()); (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, - int256(int128(params.exactAmount)), + -int256(int128(params.exactAmount)), params.sqrtPriceLimitX96, params.hookData ); int128[] memory deltaAmounts = new int128[](2); - deltaAmounts[0] = deltas.amount0(); - deltaAmounts[1] = deltas.amount1(); + deltaAmounts[0] = -deltas.amount0(); + deltaAmounts[1] = -deltas.amount1(); uint32 initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(manager, params.poolKey, tickBefore, tickAfter); @@ -252,20 +249,20 @@ contract Quoter is IQuoter, ILockCallback { params.path[i - 1], i == pathLength ? params.exactCurrency : cache.prevCurrency ); - (, cache.tickBefore,) = manager.getSlot0(poolKey.toId()); + (, cache.tickBefore,,) = manager.getSlot0(poolKey.toId()); (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = - _swap(poolKey, !oneForZero, -int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); + _swap(poolKey, !oneForZero, int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); // always clear because sqrtPriceLimitX96 is set to 0 always delete amountOutCached; (cache.deltaIn, cache.deltaOut) = !oneForZero - ? (cache.curDeltas.amount0(), cache.curDeltas.amount1()) - : (cache.curDeltas.amount1(), cache.curDeltas.amount0()); + ? (-cache.curDeltas.amount0(), -cache.curDeltas.amount1()) + : (-cache.curDeltas.amount1(), -cache.curDeltas.amount0()); result.deltaAmounts[i - 1] += cache.deltaIn; result.deltaAmounts[i] += cache.deltaOut; - cache.prevAmount = !oneForZero ? uint128(cache.curDeltas.amount0()) : uint128(cache.curDeltas.amount1()); + cache.prevAmount = !oneForZero ? uint128(-cache.curDeltas.amount0()) : uint128(-cache.curDeltas.amount1()); cache.prevCurrency = params.path[i - 1].intermediateCurrency; result.sqrtPriceX96AfterList[i - 1] = cache.sqrtPriceX96After; result.initializedTicksLoadedList[i - 1] = @@ -283,11 +280,11 @@ contract Quoter is IQuoter, ILockCallback { // if no price limit has been specified, cache the output amount for comparison in the swap callback if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; - (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); + (, int24 tickBefore,,) = manager.getSlot0(params.poolKey.toId()); (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, - -int256(uint256(params.exactAmount)), + int256(uint256(params.exactAmount)), params.sqrtPriceLimitX96, params.hookData ); @@ -295,8 +292,8 @@ contract Quoter is IQuoter, ILockCallback { if (amountOutCached != 0) delete amountOutCached; int128[] memory deltaAmounts = new int128[](2); - deltaAmounts[0] = deltas.amount0(); - deltaAmounts[1] = deltas.amount1(); + deltaAmounts[0] = -deltas.amount0(); + deltaAmounts[1] = -deltas.amount1(); uint32 initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(manager, params.poolKey, tickBefore, tickAfter); @@ -325,10 +322,10 @@ contract Quoter is IQuoter, ILockCallback { hookData ); // only exactOut case - if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? -deltas.amount1() : -deltas.amount0())) { + if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? deltas.amount1() : deltas.amount0())) { revert InsufficientAmountOut(); } - (sqrtPriceX96After, tickAfter,) = manager.getSlot0(poolKey.toId()); + (sqrtPriceX96After, tickAfter,,) = manager.getSlot0(poolKey.toId()); } /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction diff --git a/lib/v4-core b/lib/v4-core index 4a13732d..f5674e46 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 4a13732dc0b9a8c516d3639a78c54af3fc3db8d4 +Subproject commit f5674e46720c0fc4606b287cccc583d56245e724 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 076abab3..f0867ba4 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -127,7 +127,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { emit Initialize(id, testKey.currency0, testKey.currency1, testKey.fee, testKey.tickSpacing, testKey.hooks); snapStart("FullRangeInitialize"); - initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); snapEnd(); (, address liquidityToken) = fullRange.poolInfo(id); @@ -139,11 +139,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey memory wrongKey = PoolKey(key.currency0, key.currency1, 0, TICK_SPACING + 1, fullRange); vm.expectRevert(FullRange.TickSpacingNotDefault.selector); - initializeRouter.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); } function testFullRange_addLiquidity_InitialAddSucceeds() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -169,8 +169,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_InitialAddFuzz(uint256 amount) public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - if (amount < LOCKED_LIQUIDITY) { + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + if (amount <= LOCKED_LIQUIDITY) { vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -244,7 +244,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_SwapThenAddSucceeds() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -265,11 +265,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.expectEmit(true, true, true, true); emit Swap( - id, address(router), 1 ether, -906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 + id, address(router), -1 ether, 906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 ); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); HookEnabledSwapRouter.TestSettings memory settings = HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); @@ -298,7 +298,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_FailsIfTooMuchSlippage() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -323,7 +323,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function testFullRange_swap_TwoSwaps() public { PoolKey memory testKey = key; - initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -352,8 +352,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_swap_TwoPools() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - initializeRouter.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -408,7 +408,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_InitialRemoveFuzz(uint256 amount) public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -456,7 +456,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_FailsIfNoLiquidity() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -468,7 +468,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SucceedsWithPartial() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOfSelf(); uint256 prevBalance1 = key.currency1.balanceOfSelf(); @@ -503,7 +503,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_DiffRatios() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -571,7 +571,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_RemoveAllFuzz(uint256 amount) public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -626,7 +626,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.prank(address(2)); token1.approve(address(fullRange), type(uint256).max); - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); // Test contract adds liquidity @@ -704,7 +704,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SwapRemoveAllFuzz(uint256 amount) public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -753,7 +753,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_BeforeModifyPositionFailsWithWrongMsgSender() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); vm.expectRevert(FullRange.SenderMustBeHook.selector); modifyLiquidityRouter.modifyLiquidity( diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index ec74affc..05255e93 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -65,12 +65,12 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeInitializeAllowsPoolCreation() public { - initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); } function testBeforeInitializeRevertsIfFee() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - initializeRouter.initialize( + manager.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 1, MAX_TICK_SPACING, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -79,7 +79,7 @@ contract TestGeomeanOracle is Test, Deployers { function testBeforeInitializeRevertsIfNotMaxTickSpacing() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - initializeRouter.initialize( + manager.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 0, 60, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -87,7 +87,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeState() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); assertEq(observationState.index, 0); assertEq(observationState.cardinality, 1); @@ -95,7 +95,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeObservation() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); assertTrue(observation.initialized); assertEq(observation.blockTimestamp, 1); @@ -104,7 +104,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeObserve0() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); uint32[] memory secondsAgo = new uint32[](1); secondsAgo[0] = 0; (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = @@ -116,7 +116,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionNoObservations() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams( @@ -138,7 +138,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionObservation() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyLiquidityRouter.modifyLiquidity( key, @@ -161,7 +161,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionObservationAndCardinality() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds geomeanOracle.increaseCardinalityNext(key, 2); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); @@ -199,7 +199,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testPermanentLiquidity() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyLiquidityRouter.modifyLiquidity( key, diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 94cca602..9b9e3116 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -63,7 +63,7 @@ contract TestLimitOrder is Test, Deployers { function testGetTickLowerLastWithDifferentPrice() public { PoolKey memory differentKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 61, limitOrder); - initializeRouter.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); + manager.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); assertEq(limitOrder.getTickLowerLast(differentKey.toId()), 22997); } @@ -103,7 +103,7 @@ contract TestLimitOrder is Test, Deployers { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei router.swap( key, - IPoolManager.SwapParams(false, 1 ether, SQRT_RATIO_1_1 + 1), + IPoolManager.SwapParams(false, -1 ether, SQRT_RATIO_1_1 + 1), HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); @@ -129,7 +129,7 @@ contract TestLimitOrder is Test, Deployers { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei router.swap( key, - IPoolManager.SwapParams(true, 1 ether, SQRT_RATIO_1_1 - 1), + IPoolManager.SwapParams(true, -1 ether, SQRT_RATIO_1_1 - 1), HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); @@ -191,13 +191,13 @@ contract TestLimitOrder is Test, Deployers { router.swap( key, - IPoolManager.SwapParams(false, 1e18, TickMath.getSqrtRatioAtTick(60)), + IPoolManager.SwapParams(false, -1e18, TickMath.getSqrtRatioAtTick(60)), HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); assertEq(limitOrder.getTickLowerLast(id), 60); - (, int24 tick,) = manager.getSlot0(id); + (, int24 tick,,) = manager.getSlot0(id); assertEq(tick, 60); (bool filled,,, uint256 token0Total, uint256 token1Total,) = limitOrder.epochInfos(Epoch.wrap(1)); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 87de52d5..f3d2ceb1 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -121,9 +121,9 @@ contract QuoterTest is Test, Deployers { // nested self-call into lockAcquired reverts function testQuoter_callLockAcquired_reverts() public { - vm.expectRevert(IQuoter.InvalidLockAcquiredSender.selector); + vm.expectRevert(IQuoter.LockFailure.selector); vm.prank(address(manager)); - quoter.lockAcquired(address(quoter), abi.encodeWithSelector(quoter.lockAcquired.selector, address(this), "0x")); + quoter.lockAcquired(abi.encodeWithSelector(quoter.lockAcquired.selector, address(this), "0x")); } function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { @@ -542,7 +542,7 @@ contract QuoterTest is Test, Deployers { } function setupPool(PoolKey memory poolKey) internal { - initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyLiquidity( @@ -557,7 +557,7 @@ contract QuoterTest is Test, Deployers { } function setupPoolMultiplePositions(PoolKey memory poolKey) internal { - initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyLiquidity( @@ -587,9 +587,9 @@ contract QuoterTest is Test, Deployers { function setupPoolWithZeroTickInitialized(PoolKey memory poolKey) internal { PoolId poolId = poolKey.toId(); - (uint160 sqrtPriceX96,,) = manager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); if (sqrtPriceX96 == 0) { - initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); } MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index fdcf81d2..96941963 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -93,7 +93,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); vm.warp(10000); - initializeRouter.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); } diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol index 54832b4a..4311439c 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -36,15 +36,14 @@ contract HookEnabledSwapRouter is PoolTestBase { bytes memory hookData ) external payable returns (BalanceDelta delta) { delta = abi.decode( - manager.lock(address(this), abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), - (BalanceDelta) + manager.lock(abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), (BalanceDelta) ); uint256 ethBalance = address(this).balance; if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); } - function lockAcquired(address, /*sender*/ bytes calldata rawData) external returns (bytes memory) { + function lockAcquired(bytes calldata rawData) external returns (bytes memory) { require(msg.sender == address(manager)); CallbackData memory data = abi.decode(rawData, (CallbackData)); @@ -56,12 +55,12 @@ contract HookEnabledSwapRouter is PoolTestBase { if (data.params.zeroForOne) { _settle(data.key.currency0, data.sender, delta.amount0(), data.testSettings.settleUsingTransfer); - if (delta.amount1() < 0) { + if (delta.amount1() > 0) { _take(data.key.currency1, data.sender, delta.amount1(), data.testSettings.withdrawTokens); } } else { _settle(data.key.currency1, data.sender, delta.amount1(), data.testSettings.settleUsingTransfer); - if (delta.amount0() < 0) { + if (delta.amount0() > 0) { _take(data.key.currency0, data.sender, delta.amount0(), data.testSettings.withdrawTokens); } } From ad7976af2181a80af0d381f4a8e9e234b880cc8f Mon Sep 17 00:00:00 2001 From: saucepoint <98790946+saucepoint@users.noreply.github.com> Date: Thu, 23 May 2024 13:21:47 -0400 Subject: [PATCH 10/13] chore: update v4-core:latest (#105) * update core * rename lockAcquired to unlockCallback * update core; temporary path hack in remappings * update v4-core; remove remapping * wip: fix compatibility * update core; fix renaming of swap fee to lp fee * update core; fix events * update core; address liquidity salt and modify liquidity return values * fix incorrect delta accounting when modifying liquidity * fix todo, use CurrencySettleTake * remove deadcode * update core; use StateLibrary; update sqrtRatio to sqrtPrice * fix beforeSwap return signatures * forge fmt; remove commented out code * update core (wow gas savings) * update core * update core * update core; hook flags LSB * update core * update core --- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/BaseHook.sol | 11 ++- contracts/hooks/examples/FullRange.sol | 70 +++++++------- contracts/hooks/examples/GeomeanOracle.sol | 13 ++- contracts/hooks/examples/LimitOrder.sol | 93 ++++++++++--------- contracts/hooks/examples/TWAMM.sol | 43 +++++---- contracts/hooks/examples/VolatilityOracle.sol | 12 ++- contracts/interfaces/IQuoter.sol | 2 +- contracts/lens/Quoter.sol | 22 +++-- contracts/libraries/PoolGetters.sol | 9 +- contracts/libraries/PoolTicksCounter.sol | 11 +-- lib/v4-core | 2 +- test/FullRange.t.sol | 72 +++++++------- test/GeomeanOracle.t.sol | 30 +++--- test/LimitOrder.t.sol | 27 +++--- test/Quoter.t.sol | 35 ++++--- test/TWAMM.t.sol | 12 +-- test/utils/HookEnabledSwapRouter.sol | 26 ++++-- 26 files changed, 282 insertions(+), 226 deletions(-) diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index cfdeb354..78823d57 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -384735 \ No newline at end of file +311243 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index e1efe638..cd5941c3 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -179102 \ No newline at end of file +123052 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index fd04e1b1..7180e028 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -128152 \ No newline at end of file +82356 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index b126274c..69d68c83 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1017530 \ No newline at end of file +1016091 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 2cdf6c52..c024925a 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -169304 \ No newline at end of file +110705 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 2ccb0b58..a9b58592 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -345919 \ No newline at end of file +240800 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 51e5eb70..551d00ec 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -89081 \ No newline at end of file +47770 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index bd033704..cc58180a 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -126954 \ No newline at end of file +81129 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 9191f9b4..16e68302 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -122845 \ No newline at end of file +122417 \ No newline at end of file diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index 55670dab..eb75502c 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -6,6 +6,7 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; abstract contract BaseHook is IHooks { error NotPoolManager(); @@ -49,7 +50,7 @@ abstract contract BaseHook is IHooks { Hooks.validateHookPermissions(_this, getHookPermissions()); } - function lockAcquired(bytes calldata data) external virtual poolManagerOnly returns (bytes memory) { + function unlockCallback(bytes calldata data) external virtual poolManagerOnly returns (bytes memory) { (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); @@ -95,7 +96,7 @@ abstract contract BaseHook is IHooks { IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata - ) external virtual returns (bytes4) { + ) external virtual returns (bytes4, BalanceDelta) { revert HookNotImplemented(); } @@ -105,14 +106,14 @@ abstract contract BaseHook is IHooks { IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata - ) external virtual returns (bytes4) { + ) external virtual returns (bytes4, BalanceDelta) { revert HookNotImplemented(); } function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) external virtual - returns (bytes4) + returns (bytes4, BeforeSwapDelta, uint24) { revert HookNotImplemented(); } @@ -120,7 +121,7 @@ abstract contract BaseHook is IHooks { function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) external virtual - returns (bytes4) + returns (bytes4, int128) { revert HookNotImplemented(); } diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 820d0f93..aa4b606d 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -8,10 +8,11 @@ import {BaseHook} from "../../BaseHook.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; -import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.sol"; +import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; @@ -20,14 +21,18 @@ import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; import "../../libraries/LiquidityAmounts.sol"; -contract FullRange is BaseHook, ILockCallback { +contract FullRange is BaseHook, IUnlockCallback { using CurrencyLibrary for Currency; + using CurrencySettleTake for Currency; using PoolIdLibrary for PoolKey; using SafeCast for uint256; using SafeCast for uint128; + using StateLibrary for IPoolManager; /// @notice Thrown when trying to interact with a non-initialized pool error PoolNotInitialized(); @@ -98,7 +103,11 @@ contract FullRange is BaseHook, ILockCallback { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }); } @@ -127,8 +136,8 @@ contract FullRange is BaseHook, ILockCallback { liquidity = LiquidityAmounts.getLiquidityForAmounts( sqrtPriceX96, - TickMath.getSqrtRatioAtTick(MIN_TICK), - TickMath.getSqrtRatioAtTick(MAX_TICK), + TickMath.getSqrtPriceAtTick(MIN_TICK), + TickMath.getSqrtPriceAtTick(MAX_TICK), params.amount0Desired, params.amount1Desired ); @@ -141,7 +150,8 @@ contract FullRange is BaseHook, ILockCallback { IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, - liquidityDelta: liquidity.toInt256() + liquidityDelta: liquidity.toInt256(), + salt: 0 }) ); @@ -185,7 +195,8 @@ contract FullRange is BaseHook, ILockCallback { IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, - liquidityDelta: -(params.liquidity.toInt256()) + liquidityDelta: -(params.liquidity.toInt256()), + salt: 0 }) ); @@ -233,7 +244,7 @@ contract FullRange is BaseHook, ILockCallback { function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override - returns (bytes4) + returns (bytes4, BeforeSwapDelta, uint24) { PoolId poolId = key.toId(); @@ -242,32 +253,19 @@ contract FullRange is BaseHook, ILockCallback { pool.hasAccruedFees = true; } - return IHooks.beforeSwap.selector; + return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function modifyLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) internal returns (BalanceDelta delta) { - delta = abi.decode(poolManager.lock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); + delta = abi.decode(poolManager.unlock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); } function _settleDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - _settleDelta(sender, key.currency0, uint128(-delta.amount0())); - _settleDelta(sender, key.currency1, uint128(-delta.amount1())); - } - - function _settleDelta(address sender, Currency currency, uint128 amount) internal { - if (currency.isNative()) { - poolManager.settle{value: amount}(currency); - } else { - if (sender == address(this)) { - currency.transfer(address(poolManager), amount); - } else { - IERC20Minimal(Currency.unwrap(currency)).transferFrom(sender, address(poolManager), amount); - } - poolManager.settle(currency); - } + key.currency0.settle(poolManager, sender, uint256(int256(-delta.amount0())), false); + key.currency1.settle(poolManager, sender, uint256(int256(-delta.amount1())), false); } function _takeDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { @@ -293,13 +291,13 @@ contract FullRange is BaseHook, ILockCallback { ); params.liquidityDelta = -(liquidityToRemove.toInt256()); - delta = poolManager.modifyLiquidity(key, params, ZERO_BYTES); + (delta,) = poolManager.modifyLiquidity(key, params, ZERO_BYTES); pool.hasAccruedFees = false; } - function lockAcquired(bytes calldata rawData) + function unlockCallback(bytes calldata rawData) external - override(ILockCallback, BaseHook) + override(IUnlockCallback, BaseHook) poolManagerOnly returns (bytes memory) { @@ -310,7 +308,7 @@ contract FullRange is BaseHook, ILockCallback { delta = _removeLiquidity(data.key, data.params); _takeDeltas(data.sender, data.key, delta); } else { - delta = poolManager.modifyLiquidity(data.key, data.params, ZERO_BYTES); + (delta,) = poolManager.modifyLiquidity(data.key, data.params, ZERO_BYTES); _settleDeltas(data.sender, data.key, delta); } return abi.encode(delta); @@ -318,12 +316,13 @@ contract FullRange is BaseHook, ILockCallback { function _rebalance(PoolKey memory key) public { PoolId poolId = key.toId(); - BalanceDelta balanceDelta = poolManager.modifyLiquidity( + (BalanceDelta balanceDelta,) = poolManager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, - liquidityDelta: -(poolManager.getLiquidity(poolId).toInt256()) + liquidityDelta: -(poolManager.getLiquidity(poolId).toInt256()), + salt: 0 }), ZERO_BYTES ); @@ -348,18 +347,19 @@ contract FullRange is BaseHook, ILockCallback { uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts( newSqrtPriceX96, - TickMath.getSqrtRatioAtTick(MIN_TICK), - TickMath.getSqrtRatioAtTick(MAX_TICK), + TickMath.getSqrtPriceAtTick(MIN_TICK), + TickMath.getSqrtPriceAtTick(MAX_TICK), uint256(uint128(balanceDelta.amount0())), uint256(uint128(balanceDelta.amount1())) ); - BalanceDelta balanceDeltaAfter = poolManager.modifyLiquidity( + (BalanceDelta balanceDeltaAfter,) = poolManager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, - liquidityDelta: liquidity.toInt256() + liquidityDelta: liquidity.toInt256(), + salt: 0 }), ZERO_BYTES ); diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index c0f1c096..ec8301a5 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -8,6 +8,8 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../../libraries/Oracle.sol"; import {BaseHook} from "../../BaseHook.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; /// @notice A hook for a pool that allows a Uniswap pool to act as an oracle. Pools that use this hook must have full range /// tick spacing and liquidity is always permanently locked in these pools. This is the suggested configuration @@ -15,6 +17,7 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract GeomeanOracle is BaseHook { using Oracle for Oracle.Observation[65535]; using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; /// @notice Oracle pools do not have fees because they exist to serve as an oracle for a pair of tokens error OnlyOneOraclePoolAllowed(); @@ -71,7 +74,11 @@ contract GeomeanOracle is BaseHook { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }); } @@ -140,10 +147,10 @@ contract GeomeanOracle is BaseHook { external override poolManagerOnly - returns (bytes4) + returns (bytes4, BeforeSwapDelta, uint24) { _updatePool(key); - return GeomeanOracle.beforeSwap.selector; + return (GeomeanOracle.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } /// @notice Observe the given pool for the timestamps diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index e6cf8e89..92d6489b 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -10,8 +10,10 @@ import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Mini import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {BaseHook} from "../../BaseHook.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; type Epoch is uint232; @@ -31,6 +33,8 @@ contract LimitOrder is BaseHook { using EpochLibrary for Epoch; using PoolIdLibrary for PoolKey; using CurrencyLibrary for Currency; + using CurrencySettleTake for Currency; + using StateLibrary for IPoolManager; error ZeroLiquidity(); error InRange(); @@ -84,7 +88,11 @@ contract LimitOrder is BaseHook { beforeSwap: false, afterSwap: true, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }); } @@ -134,9 +142,9 @@ contract LimitOrder is BaseHook { IPoolManager.SwapParams calldata params, BalanceDelta, bytes calldata - ) external override poolManagerOnly returns (bytes4) { + ) external override poolManagerOnly returns (bytes4, int128) { (int24 tickLower, int24 lower, int24 upper) = _getCrossedTicks(key.toId(), key.tickSpacing); - if (lower > upper) return LimitOrder.afterSwap.selector; + if (lower > upper) return (LimitOrder.afterSwap.selector, 0); // note that a zeroForOne swap means that the pool is actually gaining token0, so limit // order fills are the opposite of swap fills, hence the inversion below @@ -146,7 +154,7 @@ contract LimitOrder is BaseHook { } setTickLowerLast(key.toId(), tickLower); - return LimitOrder.afterSwap.selector; + return (LimitOrder.afterSwap.selector, 0); } function _fillEpoch(PoolKey calldata key, int24 lower, bool zeroForOne) internal { @@ -157,7 +165,7 @@ contract LimitOrder is BaseHook { epochInfo.filled = true; (uint256 amount0, uint256 amount1) = - _lockAcquiredFill(key, lower, -int256(uint256(epochInfo.liquidityTotal))); + _unlockCallbackFill(key, lower, -int256(uint256(epochInfo.liquidityTotal))); unchecked { epochInfo.token0Total += amount0; @@ -187,17 +195,18 @@ contract LimitOrder is BaseHook { } } - function _lockAcquiredFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) + function _unlockCallbackFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) private poolManagerOnly returns (uint128 amount0, uint128 amount1) { - BalanceDelta delta = poolManager.modifyLiquidity( + (BalanceDelta delta,) = poolManager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, tickUpper: tickLower + key.tickSpacing, - liquidityDelta: liquidityDelta + liquidityDelta: liquidityDelta, + salt: 0 }), ZERO_BYTES ); @@ -216,8 +225,10 @@ contract LimitOrder is BaseHook { { if (liquidity == 0) revert ZeroLiquidity(); - poolManager.lock( - abi.encodeCall(this.lockAcquiredPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender)) + poolManager.unlock( + abi.encodeCall( + this.unlockCallbackPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender) + ) ); EpochInfo storage epochInfo; @@ -245,19 +256,20 @@ contract LimitOrder is BaseHook { emit Place(msg.sender, epoch, key, tickLower, zeroForOne, liquidity); } - function lockAcquiredPlace( + function unlockCallbackPlace( PoolKey calldata key, int24 tickLower, bool zeroForOne, int256 liquidityDelta, address owner ) external selfOnly { - BalanceDelta delta = poolManager.modifyLiquidity( + (BalanceDelta delta,) = poolManager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, tickUpper: tickLower + key.tickSpacing, - liquidityDelta: liquidityDelta + liquidityDelta: liquidityDelta, + salt: 0 }), ZERO_BYTES ); @@ -265,26 +277,15 @@ contract LimitOrder is BaseHook { if (delta.amount0() < 0) { if (delta.amount1() != 0) revert InRange(); if (!zeroForOne) revert CrossedRange(); - // TODO use safeTransferFrom - IERC20Minimal(Currency.unwrap(key.currency0)).transferFrom( - owner, address(poolManager), uint256(uint128(-delta.amount0())) - ); - poolManager.settle(key.currency0); + key.currency0.settle(poolManager, owner, uint256(uint128(-delta.amount0())), false); } else { if (delta.amount0() != 0) revert InRange(); if (zeroForOne) revert CrossedRange(); - // TODO use safeTransferFrom - IERC20Minimal(Currency.unwrap(key.currency1)).transferFrom( - owner, address(poolManager), uint256(uint128(-delta.amount1())) - ); - poolManager.settle(key.currency1); + key.currency1.settle(poolManager, owner, uint256(uint128(-delta.amount1())), false); } } - function kill(PoolKey calldata key, int24 tickLower, bool zeroForOne, address to) - external - returns (uint256 amount0, uint256 amount1) - { + function kill(PoolKey calldata key, int24 tickLower, bool zeroForOne, address to) external { Epoch epoch = getEpoch(key, tickLower, zeroForOne); EpochInfo storage epochInfo = epochInfos[epoch]; @@ -296,14 +297,14 @@ contract LimitOrder is BaseHook { uint256 amount0Fee; uint256 amount1Fee; - (amount0, amount1, amount0Fee, amount1Fee) = abi.decode( - poolManager.lock( + (amount0Fee, amount1Fee) = abi.decode( + poolManager.unlock( abi.encodeCall( - this.lockAcquiredKill, + this.unlockCallbackKill, (key, tickLower, -int256(uint256(liquidity)), to, liquidity == epochInfo.liquidityTotal) ) ), - (uint256, uint256, uint256, uint256) + (uint256, uint256) ); epochInfo.liquidityTotal -= liquidity; unchecked { @@ -314,13 +315,13 @@ contract LimitOrder is BaseHook { emit Kill(msg.sender, epoch, key, tickLower, zeroForOne, liquidity); } - function lockAcquiredKill( + function unlockCallbackKill( PoolKey calldata key, int24 tickLower, int256 liquidityDelta, address to, bool removingAllLiquidity - ) external selfOnly returns (uint256 amount0, uint256 amount1, uint128 amount0Fee, uint128 amount1Fee) { + ) external selfOnly returns (uint128 amount0Fee, uint128 amount1Fee) { int24 tickUpper = tickLower + key.tickSpacing; // because `modifyPosition` includes not just principal value but also fees, we cannot allocate @@ -328,9 +329,14 @@ contract LimitOrder is BaseHook { // could be unfairly diluted by a user sychronously placing then killing a limit order to skim off fees. // to prevent this, we allocate all fee revenue to remaining limit order placers, unless this is the last order. if (!removingAllLiquidity) { - BalanceDelta deltaFee = poolManager.modifyLiquidity( + (, BalanceDelta deltaFee) = poolManager.modifyLiquidity( key, - IPoolManager.ModifyLiquidityParams({tickLower: tickLower, tickUpper: tickUpper, liquidityDelta: 0}), + IPoolManager.ModifyLiquidityParams({ + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: 0, + salt: 0 + }), ZERO_BYTES ); @@ -342,21 +348,22 @@ contract LimitOrder is BaseHook { } } - BalanceDelta delta = poolManager.modifyLiquidity( + (BalanceDelta delta,) = poolManager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, tickUpper: tickUpper, - liquidityDelta: liquidityDelta + liquidityDelta: liquidityDelta, + salt: 0 }), ZERO_BYTES ); if (delta.amount0() > 0) { - poolManager.take(key.currency0, to, amount0 = uint128(delta.amount0())); + key.currency0.take(poolManager, to, uint256(uint128(delta.amount0())), false); } if (delta.amount1() > 0) { - poolManager.take(key.currency1, to, amount1 = uint128(delta.amount1())); + key.currency1.take(poolManager, to, uint256(uint128(delta.amount1())), false); } } @@ -378,14 +385,16 @@ contract LimitOrder is BaseHook { epochInfo.token1Total -= amount1; epochInfo.liquidityTotal = liquidityTotal - liquidity; - poolManager.lock( - abi.encodeCall(this.lockAcquiredWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to)) + poolManager.unlock( + abi.encodeCall( + this.unlockCallbackWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to) + ) ); emit Withdraw(msg.sender, epoch, liquidity); } - function lockAcquiredWithdraw( + function unlockCallbackWithdraw( Currency currency0, Currency currency1, uint256 token0Amount, diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 85364915..0661dd54 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -19,10 +19,14 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolGetters} from "../../libraries/PoolGetters.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; contract TWAMM is BaseHook, ITWAMM { using TransferHelper for IERC20Minimal; using CurrencyLibrary for Currency; + using CurrencySettleTake for Currency; using OrderPool for OrderPool.State; using PoolIdLibrary for PoolKey; using TickMath for int24; @@ -30,6 +34,7 @@ contract TWAMM is BaseHook, ITWAMM { using SafeCast for uint256; using PoolGetters for IPoolManager; using TickBitmap for mapping(int16 => uint256); + using StateLibrary for IPoolManager; bytes internal constant ZERO_BYTES = bytes(""); @@ -71,7 +76,11 @@ contract TWAMM is BaseHook, ITWAMM { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }); } @@ -101,10 +110,10 @@ contract TWAMM is BaseHook, ITWAMM { external override poolManagerOnly - returns (bytes4) + returns (bytes4, BeforeSwapDelta, uint24) { executeTWAMMOrders(key); - return BaseHook.beforeSwap.selector; + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function lastVirtualOrderTimestamp(PoolId key) external view returns (uint256) { @@ -142,7 +151,9 @@ contract TWAMM is BaseHook, ITWAMM { ); if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { - poolManager.lock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96))); + poolManager.unlock( + abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)) + ); } } @@ -298,7 +309,7 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function lockAcquired(bytes calldata rawData) external override poolManagerOnly returns (bytes memory) { + function unlockCallback(bytes calldata rawData) external override poolManagerOnly returns (bytes memory) { (PoolKey memory key, IPoolManager.SwapParams memory swapParams) = abi.decode(rawData, (PoolKey, IPoolManager.SwapParams)); @@ -306,19 +317,17 @@ contract TWAMM is BaseHook, ITWAMM { if (swapParams.zeroForOne) { if (delta.amount0() < 0) { - key.currency0.transfer(address(poolManager), uint256(uint128(-delta.amount0()))); - poolManager.settle(key.currency0); + key.currency0.settle(poolManager, address(this), uint256(uint128(-delta.amount0())), false); } if (delta.amount1() > 0) { - poolManager.take(key.currency1, address(this), uint256(uint128(delta.amount1()))); + key.currency1.take(poolManager, address(this), uint256(uint128(delta.amount1())), false); } } else { if (delta.amount1() < 0) { - key.currency1.transfer(address(poolManager), uint256(uint128(-delta.amount1()))); - poolManager.settle(key.currency1); + key.currency1.settle(poolManager, address(this), uint256(uint128(-delta.amount1())), false); } if (delta.amount0() > 0) { - poolManager.take(key.currency0, address(this), uint256(uint128(delta.amount0()))); + key.currency0.take(poolManager, address(this), uint256(uint128(delta.amount0())), false); } } return bytes(""); @@ -512,8 +521,8 @@ contract TWAMM is BaseHook, ITWAMM { _isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96); if (crossingInitializedTick) { - int128 liquidityNetAtTick = poolManager.getPoolTickInfo(poolKey.toId(), tick).liquidityNet; - uint160 initializedSqrtPrice = TickMath.getSqrtRatioAtTick(tick); + (, int128 liquidityNetAtTick) = poolManager.getTickLiquidity(poolKey.toId(), tick); + uint160 initializedSqrtPrice = TickMath.getSqrtPriceAtTick(tick); uint256 swapDelta0 = SqrtPriceMath.getAmount0Delta( params.pool.sqrtPriceX96, initializedSqrtPrice, params.pool.liquidity, true @@ -570,7 +579,7 @@ contract TWAMM is BaseHook, ITWAMM { PoolKey memory poolKey, TickCrossingParams memory params ) private returns (PoolParamsOnExecute memory, uint256) { - uint160 initializedSqrtPrice = params.initializedTick.getSqrtRatioAtTick(); + uint160 initializedSqrtPrice = params.initializedTick.getSqrtPriceAtTick(); uint256 secondsUntilCrossingX96 = TwammMath.calculateTimeBetweenTicks( params.pool.liquidity, @@ -596,7 +605,7 @@ contract TWAMM is BaseHook, ITWAMM { unchecked { // update pool - int128 liquidityNet = poolManager.getPoolTickInfo(poolKey.toId(), params.initializedTick).liquidityNet; + (, int128 liquidityNet) = poolManager.getTickLiquidity(poolKey.toId(), params.initializedTick); if (initializedSqrtPrice < params.pool.sqrtPriceX96) liquidityNet = -liquidityNet; params.pool.liquidity = liquidityNet < 0 ? params.pool.liquidity - uint128(-liquidityNet) @@ -614,8 +623,8 @@ contract TWAMM is BaseHook, ITWAMM { uint160 nextSqrtPriceX96 ) internal view returns (bool crossingInitializedTick, int24 nextTickInit) { // use current price as a starting point for nextTickInit - nextTickInit = pool.sqrtPriceX96.getTickAtSqrtRatio(); - int24 targetTick = nextSqrtPriceX96.getTickAtSqrtRatio(); + nextTickInit = pool.sqrtPriceX96.getTickAtSqrtPrice(); + int24 targetTick = nextSqrtPriceX96.getTickAtSqrtPrice(); bool searchingLeft = nextSqrtPriceX96 < pool.sqrtPriceX96; bool nextTickInitFurtherThanTarget = false; // initialize as false diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index 76a3e8ce..ede61bf5 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.19; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {SwapFeeLibrary} from "@uniswap/v4-core/src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {BaseHook} from "../../BaseHook.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract VolatilityOracle is BaseHook { - using SwapFeeLibrary for uint24; + using LPFeeLibrary for uint24; error MustUseDynamicFee(); @@ -34,7 +34,11 @@ contract VolatilityOracle is BaseHook { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false }); } @@ -52,7 +56,7 @@ contract VolatilityOracle is BaseHook { uint24 startingFee = 3000; uint32 lapsed = _blockTimestamp() - deployTimestamp; uint24 fee = startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute - poolManager.updateDynamicSwapFee(key, fee); // initial fee 0.30% + poolManager.updateDynamicLPFee(key, fee); // initial fee 0.30% } function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 90a390fc..8774e548 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -11,7 +11,7 @@ import {PathKey} from "../libraries/PathKey.sol"; /// @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 IQuoter { - error InvalidLockAcquiredSender(); + error InvalidUnlockCallbackSender(); error InvalidLockCaller(); error InvalidQuoteBatchParams(); error InsufficientAmountOut(); diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index c039a7b7..9e9bfda2 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.sol"; +import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; @@ -13,11 +13,13 @@ import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {PathKey, PathKeyLib} from "../libraries/PathKey.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -contract Quoter is IQuoter, ILockCallback { +contract Quoter is IQuoter, IUnlockCallback { using Hooks for IHooks; using PoolIdLibrary for PoolKey; using PathKeyLib for PathKey; + using StateLibrary for IPoolManager; /// @dev cache used to check a safety condition in exact output swaps. uint128 private amountOutCached; @@ -62,7 +64,7 @@ contract Quoter is IQuoter, ILockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try manager.lock(abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} + try manager.unlock(abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} catch (bytes memory reason) { return _handleRevertSingle(reason); } @@ -77,7 +79,7 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} + try manager.unlock(abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } @@ -89,7 +91,7 @@ contract Quoter is IQuoter, ILockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try manager.lock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} + try manager.unlock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; return _handleRevertSingle(reason); @@ -106,16 +108,16 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} + try manager.unlock(abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } } - /// @inheritdoc ILockCallback - function lockAcquired(bytes calldata data) external returns (bytes memory) { + /// @inheritdoc IUnlockCallback + function unlockCallback(bytes calldata data) external returns (bytes memory) { if (msg.sender != address(manager)) { - revert InvalidLockAcquiredSender(); + revert InvalidUnlockCallbackSender(); } (bool success, bytes memory returnData) = address(this).call(data); @@ -331,7 +333,7 @@ contract Quoter is IQuoter, ILockCallback { /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction function _sqrtPriceLimitOrDefault(uint160 sqrtPriceLimitX96, bool zeroForOne) private pure returns (uint160) { return sqrtPriceLimitX96 == 0 - ? zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 + ? zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 : sqrtPriceLimitX96; } } diff --git a/contracts/libraries/PoolGetters.sol b/contracts/libraries/PoolGetters.sol index e3cb318b..df31f3c1 100644 --- a/contracts/libraries/PoolGetters.sol +++ b/contracts/libraries/PoolGetters.sol @@ -5,6 +5,7 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; /// @title Helper functions to access pool information /// TODO: Expose other getters on core with extsload. Only use when extsload is available and storage layout is frozen. @@ -13,6 +14,8 @@ library PoolGetters { uint256 constant TICKS_OFFSET = 4; uint256 constant TICK_BITMAP_OFFSET = 5; + using StateLibrary for IPoolManager; + function getNetLiquidityAtTick(IPoolManager poolManager, PoolId poolId, int24 tick) internal view @@ -63,7 +66,8 @@ library PoolGetters { // all the 1s at or to the right of the current bitPos uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); // uint256 masked = self[wordPos] & mask; - uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; + uint256 tickBitmap = poolManager.getTickBitmap(poolId, wordPos); + uint256 masked = tickBitmap & mask; // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word initialized = masked != 0; @@ -76,7 +80,8 @@ library PoolGetters { (int16 wordPos, uint8 bitPos) = position(compressed + 1); // all the 1s at or to the left of the bitPos uint256 mask = ~((1 << bitPos) - 1); - uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; + uint256 tickBitmap = poolManager.getTickBitmap(poolId, wordPos); + uint256 masked = tickBitmap & mask; // if there are no initialized ticks to the left of the current tick, return leftmost in the word initialized = masked != 0; diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol index 077ef4a6..60fdbbe5 100644 --- a/contracts/libraries/PoolTicksCounter.sol +++ b/contracts/libraries/PoolTicksCounter.sol @@ -5,9 +5,11 @@ import {PoolGetters} from "./PoolGetters.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; library PoolTicksCounter { using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; struct TickCache { int16 wordPosLower; @@ -41,15 +43,13 @@ library PoolTicksCounter { // If the initializable tick after the swap is initialized, our original tickAfter is a // multiple of tick spacing, and we are swapping downwards we know that tickAfter is initialized // and we shouldn't count it. - uint256 bmAfter = self.getPoolBitmapInfo(key.toId(), wordPosAfter); - //uint256 bmAfter = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosAfter); + uint256 bmAfter = self.getTickBitmap(key.toId(), wordPosAfter); cache.tickAfterInitialized = ((bmAfter & (1 << bitPosAfter)) > 0) && ((tickAfter % key.tickSpacing) == 0) && (tickBefore > tickAfter); // In the case where tickBefore is initialized, we only want to count it if we are swapping upwards. // Use the same logic as above to decide whether we should count tickBefore or not. - uint256 bmBefore = self.getPoolBitmapInfo(key.toId(), wordPos); - //uint256 bmBefore = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPos); + uint256 bmBefore = self.getTickBitmap(key.toId(), wordPos); cache.tickBeforeInitialized = ((bmBefore & (1 << bitPos)) > 0) && ((tickBefore % key.tickSpacing) == 0) && (tickBefore < tickAfter); @@ -76,8 +76,7 @@ library PoolTicksCounter { mask = mask & (type(uint256).max >> (255 - cache.bitPosHigher)); } - //uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), cache.wordPosLower); - uint256 bmLower = self.getPoolBitmapInfo(key.toId(), cache.wordPosLower); + uint256 bmLower = self.getTickBitmap(key.toId(), cache.wordPosLower); uint256 masked = bmLower & mask; initializedTicksLoaded += countOneBits(masked); cache.wordPosLower++; diff --git a/lib/v4-core b/lib/v4-core index f5674e46..3351c80e 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit f5674e46720c0fc4606b287cccc583d56245e724 +Subproject commit 3351c80e58e6300cb263d33a4efe75b88ad7b9b2 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index f0867ba4..5edec106 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -20,14 +20,16 @@ import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; contract TestFullRange is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; using SafeCast for uint256; using CurrencyLibrary for Currency; + using StateLibrary for IPoolManager; event Initialize( - PoolId indexed poolId, + PoolId poolId, Currency indexed currency0, Currency indexed currency1, uint24 fee, @@ -39,7 +41,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { ); event Swap( PoolId indexed id, - address indexed sender, + address sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, @@ -104,7 +106,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { token1.approve(address(router), type(uint256).max); token2.approve(address(router), type(uint256).max); - initPool(keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_RATIO_1_1, ZERO_BYTES); + initPool(keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_PRICE_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( keyWithLiq.currency0, @@ -127,7 +129,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { emit Initialize(id, testKey.currency0, testKey.currency1, testKey.fee, testKey.tickSpacing, testKey.hooks); snapStart("FullRangeInitialize"); - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(testKey, SQRT_PRICE_1_1, ZERO_BYTES); snapEnd(); (, address liquidityToken) = fullRange.poolInfo(id); @@ -139,11 +141,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey memory wrongKey = PoolKey(key.currency0, key.currency1, 0, TICK_SPACING + 1, fullRange); vm.expectRevert(FullRange.TickSpacingNotDefault.selector); - manager.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(wrongKey, SQRT_PRICE_1_1, ZERO_BYTES); } function testFullRange_addLiquidity_InitialAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -169,7 +171,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_InitialAddFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); if (amount <= LOCKED_LIQUIDITY) { vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); fullRange.addLiquidity( @@ -244,7 +246,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_SwapThenAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -269,9 +271,9 @@ contract TestFullRange is Test, Deployers, GasSnapshot { ); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -1 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); HookEnabledSwapRouter.TestSettings memory settings = - HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); snapStart("FullRangeSwap"); router.swap(key, params, settings, ZERO_BYTES); @@ -298,7 +300,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_FailsIfTooMuchSlippage() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -307,9 +309,9 @@ contract TestFullRange is Test, Deployers, GasSnapshot { ); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); HookEnabledSwapRouter.TestSettings memory settings = - HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); router.swap(key, params, settings, ZERO_BYTES); @@ -323,7 +325,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function testFullRange_swap_TwoSwaps() public { PoolKey memory testKey = key; - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(testKey, SQRT_PRICE_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -332,9 +334,9 @@ contract TestFullRange is Test, Deployers, GasSnapshot { ); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); HookEnabledSwapRouter.TestSettings memory settings = - HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); snapStart("FullRangeFirstSwap"); router.swap(testKey, params, settings, ZERO_BYTES); @@ -352,8 +354,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_swap_TwoPools() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - manager.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); + manager.initialize(key2, SQRT_PRICE_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -367,10 +369,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { ); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10000000, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10000000, sqrtPriceLimitX96: SQRT_PRICE_1_2}); HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); router.swap(key, params, testSettings, ZERO_BYTES); router.swap(key2, params, testSettings, ZERO_BYTES); @@ -408,7 +410,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_InitialRemoveFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -456,7 +458,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_FailsIfNoLiquidity() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -468,7 +470,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SucceedsWithPartial() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOfSelf(); uint256 prevBalance1 = key.currency1.balanceOfSelf(); @@ -503,7 +505,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_DiffRatios() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -550,10 +552,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { (, address liquidityToken) = fullRange.poolInfo(idWithLiq); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); router.swap(keyWithLiq, params, testSettings, ZERO_BYTES); @@ -571,7 +573,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_RemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -626,7 +628,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.prank(address(2)); token1.approve(address(fullRange), type(uint256).max); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); // Test contract adds liquidity @@ -677,10 +679,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { ); IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100 ether, sqrtPriceLimitX96: SQRT_RATIO_1_4}); + IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100 ether, sqrtPriceLimitX96: SQRT_PRICE_1_4}); HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); router.swap(key, params, testSettings, ZERO_BYTES); @@ -704,7 +706,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SwapRemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -731,11 +733,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ zeroForOne: true, amountSpecified: (FullMath.mulDiv(amount, 1, 4)).toInt256(), - sqrtPriceLimitX96: SQRT_RATIO_1_4 + sqrtPriceLimitX96: SQRT_PRICE_1_4 }); HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); router.swap(key, params, testSettings, ZERO_BYTES); @@ -753,12 +755,12 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_BeforeModifyPositionFailsWithWrongMsgSender() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); vm.expectRevert(FullRange.SenderMustBeHook.selector); modifyLiquidityRouter.modifyLiquidity( key, - IPoolManager.ModifyLiquidityParams({tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: 100}), + IPoolManager.ModifyLiquidityParams({tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: 100, salt: 0}), ZERO_BYTES ); } diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index 05255e93..e6ff1695 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -65,14 +65,14 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeInitializeAllowsPoolCreation() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); } function testBeforeInitializeRevertsIfFee() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); manager.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 1, MAX_TICK_SPACING, geomeanOracle), - SQRT_RATIO_1_1, + SQRT_PRICE_1_1, ZERO_BYTES ); } @@ -81,13 +81,13 @@ contract TestGeomeanOracle is Test, Deployers { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); manager.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 0, 60, geomeanOracle), - SQRT_RATIO_1_1, + SQRT_PRICE_1_1, ZERO_BYTES ); } function testAfterInitializeState() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); assertEq(observationState.index, 0); assertEq(observationState.cardinality, 1); @@ -95,7 +95,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); assertTrue(observation.initialized); assertEq(observation.blockTimestamp, 1); @@ -104,7 +104,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeObserve0() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); uint32[] memory secondsAgo = new uint32[](1); secondsAgo[0] = 0; (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = @@ -116,11 +116,11 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionNoObservations() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 + TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 ), ZERO_BYTES ); @@ -138,12 +138,12 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyLiquidityRouter.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 + TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 ), ZERO_BYTES ); @@ -161,7 +161,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionObservationAndCardinality() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds geomeanOracle.increaseCardinalityNext(key, 2); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); @@ -172,7 +172,7 @@ contract TestGeomeanOracle is Test, Deployers { modifyLiquidityRouter.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 + TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 ), ZERO_BYTES ); @@ -199,12 +199,12 @@ contract TestGeomeanOracle is Test, Deployers { } function testPermanentLiquidity() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyLiquidityRouter.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 + TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 ), ZERO_BYTES ); @@ -213,7 +213,7 @@ contract TestGeomeanOracle is Test, Deployers { modifyLiquidityRouter.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), -1000 + TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), -1000, 0 ), ZERO_BYTES ); diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 9b9e3116..17f5aecb 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -15,9 +15,11 @@ import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; contract TestLimitOrder is Test, Deployers { using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; uint160 constant SQRT_RATIO_10_1 = 250541448375047931186413801569; @@ -48,7 +50,7 @@ contract TestLimitOrder is Test, Deployers { } // key = PoolKey(currency0, currency1, 3000, 60, limitOrder); - (key, id) = initPoolAndAddLiquidity(currency0, currency1, limitOrder, 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (key, id) = initPoolAndAddLiquidity(currency0, currency1, limitOrder, 3000, SQRT_PRICE_1_1, ZERO_BYTES); token0.approve(address(limitOrder), type(uint256).max); token1.approve(address(limitOrder), type(uint256).max); @@ -82,7 +84,8 @@ contract TestLimitOrder is Test, Deployers { uint128 liquidity = 1000000; limitOrder.place(key, tickLower, zeroForOne, liquidity); assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - assertEq(manager.getLiquidity(id, address(limitOrder), tickLower, tickLower + 60), liquidity); + + assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity); } function testZeroForOneLeftBoundaryOfCurrentRange() public { @@ -91,7 +94,7 @@ contract TestLimitOrder is Test, Deployers { uint128 liquidity = 1000000; limitOrder.place(key, tickLower, zeroForOne, liquidity); assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - assertEq(manager.getLiquidity(id, address(limitOrder), tickLower, tickLower + 60), liquidity); + assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity); } function testZeroForOneCrossedRangeRevert() public { @@ -103,8 +106,8 @@ contract TestLimitOrder is Test, Deployers { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei router.swap( key, - IPoolManager.SwapParams(false, -1 ether, SQRT_RATIO_1_1 + 1), - HookEnabledSwapRouter.TestSettings(true, true), + IPoolManager.SwapParams(false, -1 ether, SQRT_PRICE_1_1 + 1), + HookEnabledSwapRouter.TestSettings(false, false), ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); @@ -117,7 +120,7 @@ contract TestLimitOrder is Test, Deployers { uint128 liquidity = 1000000; limitOrder.place(key, tickLower, zeroForOne, liquidity); assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - assertEq(manager.getLiquidity(id, address(limitOrder), tickLower, tickLower + 60), liquidity); + assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity); } function testNotZeroForOneCrossedRangeRevert() public { @@ -129,8 +132,8 @@ contract TestLimitOrder is Test, Deployers { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei router.swap( key, - IPoolManager.SwapParams(true, -1 ether, SQRT_RATIO_1_1 - 1), - HookEnabledSwapRouter.TestSettings(true, true), + IPoolManager.SwapParams(true, -1 ether, SQRT_PRICE_1_1 - 1), + HookEnabledSwapRouter.TestSettings(false, false), ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); @@ -151,7 +154,7 @@ contract TestLimitOrder is Test, Deployers { limitOrder.place(key, tickLower, zeroForOne, liquidity); vm.stopPrank(); assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - assertEq(manager.getLiquidity(id, address(limitOrder), tickLower, tickLower + 60), liquidity * 2); + assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity * 2); ( bool filled, @@ -191,8 +194,8 @@ contract TestLimitOrder is Test, Deployers { router.swap( key, - IPoolManager.SwapParams(false, -1e18, TickMath.getSqrtRatioAtTick(60)), - HookEnabledSwapRouter.TestSettings(true, true), + IPoolManager.SwapParams(false, -1e18, TickMath.getSqrtPriceAtTick(60)), + HookEnabledSwapRouter.TestSettings(false, false), ZERO_BYTES ); @@ -205,7 +208,7 @@ contract TestLimitOrder is Test, Deployers { assertTrue(filled); assertEq(token0Total, 0); assertEq(token1Total, 2996 + 17); // 3013, 2 wei of dust - assertEq(manager.getLiquidity(id, address(limitOrder), tickLower, tickLower + 60), 0); + assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, 0); vm.expectEmit(true, true, true, true, address(token1)); emit Transfer(address(manager), new GetSender().sender(), 2996 + 17); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index f3d2ceb1..0767cadd 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -19,10 +19,12 @@ import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; contract QuoterTest is Test, Deployers { using SafeCast for *; using PoolIdLibrary for PoolKey; + using StateLibrary for IPoolManager; // Min tick for full range with tick spacing of 60 int24 internal constant MIN_TICK = -887220; @@ -119,11 +121,11 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoaded, 2); } - // nested self-call into lockAcquired reverts - function testQuoter_callLockAcquired_reverts() public { + // nested self-call into unlockCallback reverts + function testQuoter_callUnlockCallback_reverts() public { vm.expectRevert(IQuoter.LockFailure.selector); vm.prank(address(manager)); - quoter.lockAcquired(abi.encodeWithSelector(quoter.lockAcquired.selector, address(this), "0x")); + quoter.unlockCallback(abi.encodeWithSelector(quoter.unlockCallback.selector, address(this), "0x")); } function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { @@ -542,7 +544,7 @@ contract QuoterTest is Test, Deployers { } function setupPool(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(poolKey, SQRT_PRICE_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyLiquidity( @@ -550,14 +552,15 @@ contract QuoterTest is Test, Deployers { IPoolManager.ModifyLiquidityParams( MIN_TICK, MAX_TICK, - calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), + 0 ), ZERO_BYTES ); } function setupPoolMultiplePositions(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(poolKey, SQRT_PRICE_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyLiquidity( @@ -565,21 +568,22 @@ contract QuoterTest is Test, Deployers { IPoolManager.ModifyLiquidityParams( MIN_TICK, MAX_TICK, - calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), + 0 ), ZERO_BYTES ); positionManager.modifyLiquidity( poolKey, IPoolManager.ModifyLiquidityParams( - -60, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -60, 60, 100, 100).toInt256() + -60, 60, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -60, 60, 100, 100).toInt256(), 0 ), ZERO_BYTES ); positionManager.modifyLiquidity( poolKey, IPoolManager.ModifyLiquidityParams( - -120, 120, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 120, 100, 100).toInt256() + -120, 120, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -120, 120, 100, 100).toInt256(), 0 ), ZERO_BYTES ); @@ -589,7 +593,7 @@ contract QuoterTest is Test, Deployers { PoolId poolId = poolKey.toId(); (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); if (sqrtPriceX96 == 0) { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(poolKey, SQRT_PRICE_1_1, ZERO_BYTES); } MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); @@ -599,21 +603,22 @@ contract QuoterTest is Test, Deployers { IPoolManager.ModifyLiquidityParams( MIN_TICK, MAX_TICK, - calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + calculateLiquidityFromAmounts(SQRT_PRICE_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256(), + 0 ), ZERO_BYTES ); positionManager.modifyLiquidity( poolKey, IPoolManager.ModifyLiquidityParams( - 0, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, 0, 60, 100, 100).toInt256() + 0, 60, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, 0, 60, 100, 100).toInt256(), 0 ), ZERO_BYTES ); positionManager.modifyLiquidity( poolKey, IPoolManager.ModifyLiquidityParams( - -120, 0, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 0, 100, 100).toInt256() + -120, 0, calculateLiquidityFromAmounts(SQRT_PRICE_1_1, -120, 0, 100, 100).toInt256(), 0 ), ZERO_BYTES ); @@ -626,8 +631,8 @@ contract QuoterTest is Test, Deployers { uint256 amount0, uint256 amount1 ) internal pure returns (uint128 liquidity) { - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper); + uint160 sqrtRatioAX96 = TickMath.getSqrtPriceAtTick(tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtPriceAtTick(tickUpper); liquidity = LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1); } diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 96941963..0f2f82e0 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -69,21 +69,21 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { } } - (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_RATIO_1_1, ZERO_BYTES); + (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_PRICE_1_1, ZERO_BYTES); token0.approve(address(modifyLiquidityRouter), 100 ether); token1.approve(address(modifyLiquidityRouter), 100 ether); token0.mint(address(this), 100 ether); token1.mint(address(this), 100 ether); modifyLiquidityRouter.modifyLiquidity( - poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether), ZERO_BYTES + poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether, 0), ZERO_BYTES ); modifyLiquidityRouter.modifyLiquidity( - poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether), ZERO_BYTES + poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0), ZERO_BYTES ); modifyLiquidityRouter.modifyLiquidity( poolKey, - IPoolManager.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether), + IPoolManager.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether, 0), ZERO_BYTES ); } @@ -93,7 +93,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); vm.warp(10000); - manager.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(initKey, SQRT_PRICE_1_1, ZERO_BYTES); assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); } @@ -363,7 +363,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { token0.approve(address(twamm), 100e18); token1.approve(address(twamm), 100e18); modifyLiquidityRouter.modifyLiquidity( - poolKey, IPoolManager.ModifyLiquidityParams(-2400, 2400, 10 ether), ZERO_BYTES + poolKey, IPoolManager.ModifyLiquidityParams(-2400, 2400, 10 ether, 0), ZERO_BYTES ); vm.warp(10000); diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol index 4311439c..15af7aad 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -8,9 +8,11 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolTestBase} from "@uniswap/v4-core/src/test/PoolTestBase.sol"; import {Test} from "forge-std/Test.sol"; +import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; contract HookEnabledSwapRouter is PoolTestBase { using CurrencyLibrary for Currency; + using CurrencySettleTake for Currency; error NoSwapOccurred(); @@ -25,8 +27,8 @@ contract HookEnabledSwapRouter is PoolTestBase { } struct TestSettings { - bool withdrawTokens; - bool settleUsingTransfer; + bool takeClaims; + bool settleUsingBurn; } function swap( @@ -36,14 +38,14 @@ contract HookEnabledSwapRouter is PoolTestBase { bytes memory hookData ) external payable returns (BalanceDelta delta) { delta = abi.decode( - manager.lock(abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), (BalanceDelta) + manager.unlock(abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), (BalanceDelta) ); uint256 ethBalance = address(this).balance; if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); } - function lockAcquired(bytes calldata rawData) external returns (bytes memory) { + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { require(msg.sender == address(manager)); CallbackData memory data = abi.decode(rawData, (CallbackData)); @@ -54,14 +56,22 @@ contract HookEnabledSwapRouter is PoolTestBase { if (BalanceDelta.unwrap(delta) == 0) revert NoSwapOccurred(); if (data.params.zeroForOne) { - _settle(data.key.currency0, data.sender, delta.amount0(), data.testSettings.settleUsingTransfer); + data.key.currency0.settle( + manager, data.sender, uint256(int256(-delta.amount0())), data.testSettings.settleUsingBurn + ); if (delta.amount1() > 0) { - _take(data.key.currency1, data.sender, delta.amount1(), data.testSettings.withdrawTokens); + data.key.currency1.take( + manager, data.sender, uint256(int256(delta.amount1())), data.testSettings.takeClaims + ); } } else { - _settle(data.key.currency1, data.sender, delta.amount1(), data.testSettings.settleUsingTransfer); + data.key.currency1.settle( + manager, data.sender, uint256(int256(-delta.amount1())), data.testSettings.settleUsingBurn + ); if (delta.amount0() > 0) { - _take(data.key.currency0, data.sender, delta.amount0(), data.testSettings.withdrawTokens); + data.key.currency0.take( + manager, data.sender, uint256(int256(delta.amount0())), data.testSettings.takeClaims + ); } } From 508277999222ee20207cd63efe50397760c2abe1 Mon Sep 17 00:00:00 2001 From: 0x57 Date: Fri, 31 May 2024 20:35:51 +0800 Subject: [PATCH 11/13] chore: update v4 core (#115) * Update v4-core * CurrencySettleTake -> CurrencySettler * Snapshots --- .forge-snapshots/FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .forge-snapshots/FullRangeRemoveLiquidity.snap | 2 +- .forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/hooks/examples/FullRange.sol | 4 ++-- contracts/hooks/examples/LimitOrder.sol | 4 ++-- contracts/hooks/examples/TWAMM.sol | 4 ++-- lib/v4-core | 2 +- test/utils/HookEnabledSwapRouter.sol | 4 ++-- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 78823d57..cd1e3c37 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -311243 \ No newline at end of file +311073 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index cd5941c3..b3de2b4e 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -123052 \ No newline at end of file +122882 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 7180e028..54d0d097 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -82356 \ No newline at end of file +80283 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 69d68c83..f81651f8 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1016091 \ No newline at end of file +1015169 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index c024925a..265c1dec 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -110705 \ No newline at end of file +110476 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index a9b58592..dcb62527 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -240800 \ No newline at end of file +239954 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 551d00ec..1bf183ef 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -47770 \ No newline at end of file +45993 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index cc58180a..5630ac05 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -81129 \ No newline at end of file +79414 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 16e68302..b2759d7f 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -122417 \ No newline at end of file +122355 \ No newline at end of file diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index aa4b606d..194be803 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -8,7 +8,7 @@ import {BaseHook} from "../../BaseHook.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; -import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; +import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; @@ -28,7 +28,7 @@ import "../../libraries/LiquidityAmounts.sol"; contract FullRange is BaseHook, IUnlockCallback { using CurrencyLibrary for Currency; - using CurrencySettleTake for Currency; + using CurrencySettler for Currency; using PoolIdLibrary for PoolKey; using SafeCast for uint256; using SafeCast for uint128; diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 92d6489b..9ee7a33c 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -10,7 +10,7 @@ import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Mini import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {BaseHook} from "../../BaseHook.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; +import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; @@ -33,7 +33,7 @@ contract LimitOrder is BaseHook { using EpochLibrary for Epoch; using PoolIdLibrary for PoolKey; using CurrencyLibrary for Currency; - using CurrencySettleTake for Currency; + using CurrencySettler for Currency; using StateLibrary for IPoolManager; error ZeroLiquidity(); diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 0661dd54..5704d765 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -19,14 +19,14 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolGetters} from "../../libraries/PoolGetters.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; +import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; contract TWAMM is BaseHook, ITWAMM { using TransferHelper for IERC20Minimal; using CurrencyLibrary for Currency; - using CurrencySettleTake for Currency; + using CurrencySettler for Currency; using OrderPool for OrderPool.State; using PoolIdLibrary for PoolKey; using TickMath for int24; diff --git a/lib/v4-core b/lib/v4-core index 3351c80e..6e6ce35b 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 3351c80e58e6300cb263d33a4efe75b88ad7b9b2 +Subproject commit 6e6ce35b69b15cb61bd8cb8488c7d064fab52886 diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol index 15af7aad..4021f453 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -8,11 +8,11 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolTestBase} from "@uniswap/v4-core/src/test/PoolTestBase.sol"; import {Test} from "forge-std/Test.sol"; -import {CurrencySettleTake} from "@uniswap/v4-core/src/libraries/CurrencySettleTake.sol"; +import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; contract HookEnabledSwapRouter is PoolTestBase { using CurrencyLibrary for Currency; - using CurrencySettleTake for Currency; + using CurrencySettler for Currency; error NoSwapOccurred(); From 1ca56ab7abc456a46e08b6ca6180bd68ec6a4770 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:53:46 -0400 Subject: [PATCH 12/13] add safecallback (#123) * add safecallback * use manager --- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/BaseHook.sol | 19 ++---- contracts/base/ImmutableState.sol | 12 ++++ contracts/base/SafeCallback.sol | 22 +++++++ contracts/hooks/examples/FullRange.sol | 45 ++++++-------- contracts/hooks/examples/GeomeanOracle.sol | 24 ++++---- contracts/hooks/examples/LimitOrder.sol | 50 +++++++-------- contracts/hooks/examples/TWAMM.sol | 61 +++++++++---------- contracts/hooks/examples/VolatilityOracle.sol | 4 +- 17 files changed, 136 insertions(+), 119 deletions(-) create mode 100644 contracts/base/ImmutableState.sol create mode 100644 contracts/base/SafeCallback.sol diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index cd1e3c37..404cf12a 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -311073 \ No newline at end of file +311181 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index b3de2b4e..a4a14676 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -122882 \ No newline at end of file +122990 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 54d0d097..da120795 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -80283 \ No newline at end of file +80220 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index f81651f8..7a0170eb 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1015169 \ No newline at end of file +1015181 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 265c1dec..feea4936 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -110476 \ No newline at end of file +110566 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index dcb62527..e0df7eb7 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -239954 \ No newline at end of file +240044 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 1bf183ef..e68df8d3 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -45993 \ No newline at end of file +45930 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 5630ac05..b50d0ea2 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -79414 \ No newline at end of file +79351 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index b2759d7f..eb3b0f6b 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -122355 \ No newline at end of file +122336 \ No newline at end of file diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index eb75502c..01fc4954 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -7,28 +7,19 @@ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {SafeCallback} from "./base/SafeCallback.sol"; +import {ImmutableState} from "./base/ImmutableState.sol"; -abstract contract BaseHook is IHooks { - error NotPoolManager(); +abstract contract BaseHook is IHooks, SafeCallback { error NotSelf(); error InvalidPool(); error LockFailure(); error HookNotImplemented(); - /// @notice The address of the pool manager - IPoolManager public immutable poolManager; - - constructor(IPoolManager _poolManager) { - poolManager = _poolManager; + constructor(IPoolManager _manager) ImmutableState(_manager) { validateHookAddress(this); } - /// @dev Only the pool manager may call this function - modifier poolManagerOnly() { - if (msg.sender != address(poolManager)) revert NotPoolManager(); - _; - } - /// @dev Only this address may call this function modifier selfOnly() { if (msg.sender != address(this)) revert NotSelf(); @@ -50,7 +41,7 @@ abstract contract BaseHook is IHooks { Hooks.validateHookPermissions(_this, getHookPermissions()); } - function unlockCallback(bytes calldata data) external virtual poolManagerOnly returns (bytes memory) { + function _unlockCallback(bytes calldata data) internal virtual override returns (bytes memory) { (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); diff --git a/contracts/base/ImmutableState.sol b/contracts/base/ImmutableState.sol new file mode 100644 index 00000000..cce37514 --- /dev/null +++ b/contracts/base/ImmutableState.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.19; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +contract ImmutableState { + IPoolManager public immutable manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } +} diff --git a/contracts/base/SafeCallback.sol b/contracts/base/SafeCallback.sol new file mode 100644 index 00000000..f985e67c --- /dev/null +++ b/contracts/base/SafeCallback.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {ImmutableState} from "./ImmutableState.sol"; + +abstract contract SafeCallback is ImmutableState, IUnlockCallback { + error NotManager(); + + modifier onlyByManager() { + if (msg.sender != address(manager)) revert NotManager(); + _; + } + + /// @dev We force the onlyByManager modifier by exposing a virtual function after the onlyByManager check. + function unlockCallback(bytes calldata data) external onlyByManager returns (bytes memory) { + return _unlockCallback(data); + } + + function _unlockCallback(bytes calldata data) internal virtual returns (bytes memory); +} diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 194be803..191593b8 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -26,7 +26,7 @@ import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/type import "../../libraries/LiquidityAmounts.sol"; -contract FullRange is BaseHook, IUnlockCallback { +contract FullRange is BaseHook { using CurrencyLibrary for Currency; using CurrencySettler for Currency; using PoolIdLibrary for PoolKey; @@ -85,7 +85,7 @@ contract FullRange is BaseHook, IUnlockCallback { mapping(PoolId => PoolInfo) public poolInfo; - constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + constructor(IPoolManager _manager) BaseHook(_manager) {} modifier ensure(uint256 deadline) { if (deadline < block.timestamp) revert ExpiredPastDeadline(); @@ -126,13 +126,13 @@ contract FullRange is BaseHook, IUnlockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); PoolInfo storage pool = poolInfo[poolId]; - uint128 poolLiquidity = poolManager.getLiquidity(poolId); + uint128 poolLiquidity = manager.getLiquidity(poolId); liquidity = LiquidityAmounts.getLiquidityForAmounts( sqrtPriceX96, @@ -184,7 +184,7 @@ contract FullRange is BaseHook, IUnlockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -260,17 +260,17 @@ contract FullRange is BaseHook, IUnlockCallback { internal returns (BalanceDelta delta) { - delta = abi.decode(poolManager.unlock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); + delta = abi.decode(manager.unlock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); } function _settleDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - key.currency0.settle(poolManager, sender, uint256(int256(-delta.amount0())), false); - key.currency1.settle(poolManager, sender, uint256(int256(-delta.amount1())), false); + key.currency0.settle(manager, sender, uint256(int256(-delta.amount0())), false); + key.currency1.settle(manager, sender, uint256(int256(-delta.amount1())), false); } function _takeDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - poolManager.take(key.currency0, sender, uint256(uint128(delta.amount0()))); - poolManager.take(key.currency1, sender, uint256(uint128(delta.amount1()))); + manager.take(key.currency0, sender, uint256(uint128(delta.amount0()))); + manager.take(key.currency1, sender, uint256(uint128(delta.amount1()))); } function _removeLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) @@ -286,21 +286,16 @@ contract FullRange is BaseHook, IUnlockCallback { uint256 liquidityToRemove = FullMath.mulDiv( uint256(-params.liquidityDelta), - poolManager.getLiquidity(poolId), + manager.getLiquidity(poolId), UniswapV4ERC20(pool.liquidityToken).totalSupply() ); params.liquidityDelta = -(liquidityToRemove.toInt256()); - (delta,) = poolManager.modifyLiquidity(key, params, ZERO_BYTES); + (delta,) = manager.modifyLiquidity(key, params, ZERO_BYTES); pool.hasAccruedFees = false; } - function unlockCallback(bytes calldata rawData) - external - override(IUnlockCallback, BaseHook) - poolManagerOnly - returns (bytes memory) - { + function _unlockCallback(bytes calldata rawData) internal override returns (bytes memory) { CallbackData memory data = abi.decode(rawData, (CallbackData)); BalanceDelta delta; @@ -308,7 +303,7 @@ contract FullRange is BaseHook, IUnlockCallback { delta = _removeLiquidity(data.key, data.params); _takeDeltas(data.sender, data.key, delta); } else { - (delta,) = poolManager.modifyLiquidity(data.key, data.params, ZERO_BYTES); + (delta,) = manager.modifyLiquidity(data.key, data.params, ZERO_BYTES); _settleDeltas(data.sender, data.key, delta); } return abi.encode(delta); @@ -316,12 +311,12 @@ contract FullRange is BaseHook, IUnlockCallback { function _rebalance(PoolKey memory key) public { PoolId poolId = key.toId(); - (BalanceDelta balanceDelta,) = poolManager.modifyLiquidity( + (BalanceDelta balanceDelta,) = manager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, - liquidityDelta: -(poolManager.getLiquidity(poolId).toInt256()), + liquidityDelta: -(manager.getLiquidity(poolId).toInt256()), salt: 0 }), ZERO_BYTES @@ -333,9 +328,9 @@ contract FullRange is BaseHook, IUnlockCallback { ) * FixedPointMathLib.sqrt(FixedPoint96.Q96) ).toUint160(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); - poolManager.swap( + manager.swap( key, IPoolManager.SwapParams({ zeroForOne: newSqrtPriceX96 < sqrtPriceX96, @@ -353,7 +348,7 @@ contract FullRange is BaseHook, IUnlockCallback { uint256(uint128(balanceDelta.amount1())) ); - (BalanceDelta balanceDeltaAfter,) = poolManager.modifyLiquidity( + (BalanceDelta balanceDeltaAfter,) = manager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, @@ -368,6 +363,6 @@ contract FullRange is BaseHook, IUnlockCallback { uint128 donateAmount0 = uint128(balanceDelta.amount0() + balanceDeltaAfter.amount0()); uint128 donateAmount1 = uint128(balanceDelta.amount1() + balanceDeltaAfter.amount1()); - poolManager.donate(key, donateAmount0, donateAmount1, ZERO_BYTES); + manager.donate(key, donateAmount0, donateAmount1, ZERO_BYTES); } } diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index ec8301a5..df5a9ad1 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -61,7 +61,7 @@ contract GeomeanOracle is BaseHook { return uint32(block.timestamp); } - constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + constructor(IPoolManager _manager) BaseHook(_manager) {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ @@ -86,20 +86,20 @@ contract GeomeanOracle is BaseHook { external view override - poolManagerOnly + onlyByManager returns (bytes4) { // This is to limit the fragmentation of pools using this oracle hook. In other words, // there may only be one pool per pair of tokens that use this hook. The tick spacing is set to the maximum // because we only allow max range liquidity in this pool. - if (key.fee != 0 || key.tickSpacing != poolManager.MAX_TICK_SPACING()) revert OnlyOneOraclePoolAllowed(); + if (key.fee != 0 || key.tickSpacing != manager.MAX_TICK_SPACING()) revert OnlyOneOraclePoolAllowed(); return GeomeanOracle.beforeInitialize.selector; } function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata) external override - poolManagerOnly + onlyByManager returns (bytes4) { PoolId id = key.toId(); @@ -110,9 +110,9 @@ contract GeomeanOracle is BaseHook { /// @dev Called before any action that potentially modifies pool price or liquidity, such as swap or modify position function _updatePool(PoolKey calldata key) private { PoolId id = key.toId(); - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,,) = manager.getSlot0(id); - uint128 liquidity = poolManager.getLiquidity(id); + uint128 liquidity = manager.getLiquidity(id); (states[id].index, states[id].cardinality) = observations[id].write( states[id].index, _blockTimestamp(), tick, liquidity, states[id].cardinality, states[id].cardinalityNext @@ -124,8 +124,8 @@ contract GeomeanOracle is BaseHook { PoolKey calldata key, IPoolManager.ModifyLiquidityParams calldata params, bytes calldata - ) external override poolManagerOnly returns (bytes4) { - int24 maxTickSpacing = poolManager.MAX_TICK_SPACING(); + ) external override onlyByManager returns (bytes4) { + int24 maxTickSpacing = manager.MAX_TICK_SPACING(); if ( params.tickLower != TickMath.minUsableTick(maxTickSpacing) || params.tickUpper != TickMath.maxUsableTick(maxTickSpacing) @@ -139,14 +139,14 @@ contract GeomeanOracle is BaseHook { PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata - ) external view override poolManagerOnly returns (bytes4) { + ) external view override onlyByManager returns (bytes4) { revert OraclePoolMustLockLiquidity(); } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override - poolManagerOnly + onlyByManager returns (bytes4, BeforeSwapDelta, uint24) { _updatePool(key); @@ -163,9 +163,9 @@ contract GeomeanOracle is BaseHook { ObservationState memory state = states[id]; - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,,) = manager.getSlot0(id); - uint128 liquidity = poolManager.getLiquidity(id); + uint128 liquidity = manager.getLiquidity(id); return observations[id].observe(_blockTimestamp(), secondsAgos, tick, state.index, liquidity, state.cardinality); } diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 9ee7a33c..2a8ca909 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -75,7 +75,7 @@ contract LimitOrder is BaseHook { mapping(bytes32 => Epoch) public epochs; mapping(Epoch => EpochInfo) public epochInfos; - constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + constructor(IPoolManager _manager) BaseHook(_manager) {} function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ @@ -117,7 +117,7 @@ contract LimitOrder is BaseHook { } function getTick(PoolId poolId) private view returns (int24 tick) { - (, tick,,) = poolManager.getSlot0(poolId); + (, tick,,) = manager.getSlot0(poolId); } function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { @@ -129,7 +129,7 @@ contract LimitOrder is BaseHook { function afterInitialize(address, PoolKey calldata key, uint160, int24 tick, bytes calldata) external override - poolManagerOnly + onlyByManager returns (bytes4) { setTickLowerLast(key.toId(), getTickLower(tick, key.tickSpacing)); @@ -142,7 +142,7 @@ contract LimitOrder is BaseHook { IPoolManager.SwapParams calldata params, BalanceDelta, bytes calldata - ) external override poolManagerOnly returns (bytes4, int128) { + ) external override onlyByManager returns (bytes4, int128) { (int24 tickLower, int24 lower, int24 upper) = _getCrossedTicks(key.toId(), key.tickSpacing); if (lower > upper) return (LimitOrder.afterSwap.selector, 0); @@ -197,10 +197,10 @@ contract LimitOrder is BaseHook { function _unlockCallbackFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) private - poolManagerOnly + onlyByManager returns (uint128 amount0, uint128 amount1) { - (BalanceDelta delta,) = poolManager.modifyLiquidity( + (BalanceDelta delta,) = manager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, @@ -212,10 +212,10 @@ contract LimitOrder is BaseHook { ); if (delta.amount0() > 0) { - poolManager.mint(address(this), key.currency0.toId(), amount0 = uint128(delta.amount0())); + manager.mint(address(this), key.currency0.toId(), amount0 = uint128(delta.amount0())); } if (delta.amount1() > 0) { - poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(delta.amount1())); + manager.mint(address(this), key.currency1.toId(), amount1 = uint128(delta.amount1())); } } @@ -225,7 +225,7 @@ contract LimitOrder is BaseHook { { if (liquidity == 0) revert ZeroLiquidity(); - poolManager.unlock( + manager.unlock( abi.encodeCall( this.unlockCallbackPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender) ) @@ -263,7 +263,7 @@ contract LimitOrder is BaseHook { int256 liquidityDelta, address owner ) external selfOnly { - (BalanceDelta delta,) = poolManager.modifyLiquidity( + (BalanceDelta delta,) = manager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, @@ -277,11 +277,11 @@ contract LimitOrder is BaseHook { if (delta.amount0() < 0) { if (delta.amount1() != 0) revert InRange(); if (!zeroForOne) revert CrossedRange(); - key.currency0.settle(poolManager, owner, uint256(uint128(-delta.amount0())), false); + key.currency0.settle(manager, owner, uint256(uint128(-delta.amount0())), false); } else { if (delta.amount0() != 0) revert InRange(); if (zeroForOne) revert CrossedRange(); - key.currency1.settle(poolManager, owner, uint256(uint128(-delta.amount1())), false); + key.currency1.settle(manager, owner, uint256(uint128(-delta.amount1())), false); } } @@ -298,7 +298,7 @@ contract LimitOrder is BaseHook { uint256 amount0Fee; uint256 amount1Fee; (amount0Fee, amount1Fee) = abi.decode( - poolManager.unlock( + manager.unlock( abi.encodeCall( this.unlockCallbackKill, (key, tickLower, -int256(uint256(liquidity)), to, liquidity == epochInfo.liquidityTotal) @@ -329,7 +329,7 @@ contract LimitOrder is BaseHook { // could be unfairly diluted by a user sychronously placing then killing a limit order to skim off fees. // to prevent this, we allocate all fee revenue to remaining limit order placers, unless this is the last order. if (!removingAllLiquidity) { - (, BalanceDelta deltaFee) = poolManager.modifyLiquidity( + (, BalanceDelta deltaFee) = manager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, @@ -341,14 +341,14 @@ contract LimitOrder is BaseHook { ); if (deltaFee.amount0() > 0) { - poolManager.mint(address(this), key.currency0.toId(), amount0Fee = uint128(deltaFee.amount0())); + manager.mint(address(this), key.currency0.toId(), amount0Fee = uint128(deltaFee.amount0())); } if (deltaFee.amount1() > 0) { - poolManager.mint(address(this), key.currency1.toId(), amount1Fee = uint128(deltaFee.amount1())); + manager.mint(address(this), key.currency1.toId(), amount1Fee = uint128(deltaFee.amount1())); } } - (BalanceDelta delta,) = poolManager.modifyLiquidity( + (BalanceDelta delta,) = manager.modifyLiquidity( key, IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, @@ -360,10 +360,10 @@ contract LimitOrder is BaseHook { ); if (delta.amount0() > 0) { - key.currency0.take(poolManager, to, uint256(uint128(delta.amount0())), false); + key.currency0.take(manager, to, uint256(uint128(delta.amount0())), false); } if (delta.amount1() > 0) { - key.currency1.take(poolManager, to, uint256(uint128(delta.amount1())), false); + key.currency1.take(manager, to, uint256(uint128(delta.amount1())), false); } } @@ -385,7 +385,7 @@ contract LimitOrder is BaseHook { epochInfo.token1Total -= amount1; epochInfo.liquidityTotal = liquidityTotal - liquidity; - poolManager.unlock( + manager.unlock( abi.encodeCall( this.unlockCallbackWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to) ) @@ -402,17 +402,17 @@ contract LimitOrder is BaseHook { address to ) external selfOnly { if (token0Amount > 0) { - poolManager.burn(address(this), currency0.toId(), token0Amount); - poolManager.take(currency0, to, token0Amount); + manager.burn(address(this), currency0.toId(), token0Amount); + manager.take(currency0, to, token0Amount); } if (token1Amount > 0) { - poolManager.burn(address(this), currency1.toId(), token1Amount); - poolManager.take(currency1, to, token1Amount); + manager.burn(address(this), currency1.toId(), token1Amount); + manager.take(currency1, to, token1Amount); } } function onERC1155Received(address, address, uint256, uint256, bytes calldata) external view returns (bytes4) { - if (msg.sender != address(poolManager)) revert NotPoolManagerToken(); + if (msg.sender != address(manager)) revert NotPoolManagerToken(); return IERC1155Receiver.onERC1155Received.selector; } } diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 5704d765..dc1f3b00 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -61,7 +61,7 @@ contract TWAMM is BaseHook, ITWAMM { // tokensOwed[token][owner] => amountOwed mapping(Currency => mapping(address => uint256)) public tokensOwed; - constructor(IPoolManager _poolManager, uint256 _expirationInterval) BaseHook(_poolManager) { + constructor(IPoolManager _manager, uint256 _expirationInterval) BaseHook(_manager) { expirationInterval = _expirationInterval; } @@ -88,7 +88,7 @@ contract TWAMM is BaseHook, ITWAMM { external virtual override - poolManagerOnly + onlyByManager returns (bytes4) { // one-time initialization enforced in PoolManager @@ -101,7 +101,7 @@ contract TWAMM is BaseHook, ITWAMM { PoolKey calldata key, IPoolManager.ModifyLiquidityParams calldata, bytes calldata - ) external override poolManagerOnly returns (bytes4) { + ) external override onlyByManager returns (bytes4) { executeTWAMMOrders(key); return BaseHook.beforeAddLiquidity.selector; } @@ -109,7 +109,7 @@ contract TWAMM is BaseHook, ITWAMM { function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override - poolManagerOnly + onlyByManager returns (bytes4, BeforeSwapDelta, uint24) { executeTWAMMOrders(key); @@ -143,17 +143,14 @@ contract TWAMM is BaseHook, ITWAMM { /// @inheritdoc ITWAMM function executeTWAMMOrders(PoolKey memory key) public { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); State storage twamm = twammStates[poolId]; - (bool zeroForOne, uint160 sqrtPriceLimitX96) = _executeTWAMMOrders( - twamm, poolManager, key, PoolParamsOnExecute(sqrtPriceX96, poolManager.getLiquidity(poolId)) - ); + (bool zeroForOne, uint160 sqrtPriceLimitX96) = + _executeTWAMMOrders(twamm, manager, key, PoolParamsOnExecute(sqrtPriceX96, manager.getLiquidity(poolId))); if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { - poolManager.unlock( - abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)) - ); + manager.unlock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96))); } } @@ -309,25 +306,25 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function unlockCallback(bytes calldata rawData) external override poolManagerOnly returns (bytes memory) { + function _unlockCallback(bytes calldata rawData) internal override returns (bytes memory) { (PoolKey memory key, IPoolManager.SwapParams memory swapParams) = abi.decode(rawData, (PoolKey, IPoolManager.SwapParams)); - BalanceDelta delta = poolManager.swap(key, swapParams, ZERO_BYTES); + BalanceDelta delta = manager.swap(key, swapParams, ZERO_BYTES); if (swapParams.zeroForOne) { if (delta.amount0() < 0) { - key.currency0.settle(poolManager, address(this), uint256(uint128(-delta.amount0())), false); + key.currency0.settle(manager, address(this), uint256(uint128(-delta.amount0())), false); } if (delta.amount1() > 0) { - key.currency1.take(poolManager, address(this), uint256(uint128(delta.amount1())), false); + key.currency1.take(manager, address(this), uint256(uint128(delta.amount1())), false); } } else { if (delta.amount1() < 0) { - key.currency1.settle(poolManager, address(this), uint256(uint128(-delta.amount1())), false); + key.currency1.settle(manager, address(this), uint256(uint128(-delta.amount1())), false); } if (delta.amount0() > 0) { - key.currency0.take(poolManager, address(this), uint256(uint128(delta.amount0())), false); + key.currency0.take(manager, address(this), uint256(uint128(delta.amount0())), false); } } return bytes(""); @@ -346,7 +343,7 @@ contract TWAMM is BaseHook, ITWAMM { /// @param pool The relevant state of the pool function _executeTWAMMOrders( State storage self, - IPoolManager poolManager, + IPoolManager manager, PoolKey memory key, PoolParamsOnExecute memory pool ) internal returns (bool zeroForOne, uint160 newSqrtPriceX96) { @@ -371,7 +368,7 @@ contract TWAMM is BaseHook, ITWAMM { if (orderPool0For1.sellRateCurrent != 0 && orderPool1For0.sellRateCurrent != 0) { pool = _advanceToNewTimestamp( self, - poolManager, + manager, key, AdvanceParams( expirationInterval, @@ -383,7 +380,7 @@ contract TWAMM is BaseHook, ITWAMM { } else { pool = _advanceTimestampForSinglePoolSell( self, - poolManager, + manager, key, AdvanceSingleParams( expirationInterval, @@ -405,14 +402,14 @@ contract TWAMM is BaseHook, ITWAMM { if (orderPool0For1.sellRateCurrent != 0 && orderPool1For0.sellRateCurrent != 0) { pool = _advanceToNewTimestamp( self, - poolManager, + manager, key, AdvanceParams(expirationInterval, block.timestamp, block.timestamp - prevTimestamp, pool) ); } else { pool = _advanceTimestampForSinglePoolSell( self, - poolManager, + manager, key, AdvanceSingleParams( expirationInterval, @@ -440,7 +437,7 @@ contract TWAMM is BaseHook, ITWAMM { function _advanceToNewTimestamp( State storage self, - IPoolManager poolManager, + IPoolManager manager, PoolKey memory poolKey, AdvanceParams memory params ) private returns (PoolParamsOnExecute memory) { @@ -462,13 +459,13 @@ contract TWAMM is BaseHook, ITWAMM { finalSqrtPriceX96 = TwammMath.getNewSqrtPriceX96(executionParams); (bool crossingInitializedTick, int24 tick) = - _isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96); + _isCrossingInitializedTick(params.pool, manager, poolKey, finalSqrtPriceX96); unchecked { if (crossingInitializedTick) { uint256 secondsUntilCrossingX96; (params.pool, secondsUntilCrossingX96) = _advanceTimeThroughTickCrossing( self, - poolManager, + manager, poolKey, TickCrossingParams(tick, params.nextTimestamp, secondsElapsedX96, params.pool) ); @@ -503,7 +500,7 @@ contract TWAMM is BaseHook, ITWAMM { function _advanceTimestampForSinglePoolSell( State storage self, - IPoolManager poolManager, + IPoolManager manager, PoolKey memory poolKey, AdvanceSingleParams memory params ) private returns (PoolParamsOnExecute memory) { @@ -518,10 +515,10 @@ contract TWAMM is BaseHook, ITWAMM { ); (bool crossingInitializedTick, int24 tick) = - _isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96); + _isCrossingInitializedTick(params.pool, manager, poolKey, finalSqrtPriceX96); if (crossingInitializedTick) { - (, int128 liquidityNetAtTick) = poolManager.getTickLiquidity(poolKey.toId(), tick); + (, int128 liquidityNetAtTick) = manager.getTickLiquidity(poolKey.toId(), tick); uint160 initializedSqrtPrice = TickMath.getSqrtPriceAtTick(tick); uint256 swapDelta0 = SqrtPriceMath.getAmount0Delta( @@ -575,7 +572,7 @@ contract TWAMM is BaseHook, ITWAMM { function _advanceTimeThroughTickCrossing( State storage self, - IPoolManager poolManager, + IPoolManager manager, PoolKey memory poolKey, TickCrossingParams memory params ) private returns (PoolParamsOnExecute memory, uint256) { @@ -605,7 +602,7 @@ contract TWAMM is BaseHook, ITWAMM { unchecked { // update pool - (, int128 liquidityNet) = poolManager.getTickLiquidity(poolKey.toId(), params.initializedTick); + (, int128 liquidityNet) = manager.getTickLiquidity(poolKey.toId(), params.initializedTick); if (initializedSqrtPrice < params.pool.sqrtPriceX96) liquidityNet = -liquidityNet; params.pool.liquidity = liquidityNet < 0 ? params.pool.liquidity - uint128(-liquidityNet) @@ -618,7 +615,7 @@ contract TWAMM is BaseHook, ITWAMM { function _isCrossingInitializedTick( PoolParamsOnExecute memory pool, - IPoolManager poolManager, + IPoolManager manager, PoolKey memory poolKey, uint160 nextSqrtPriceX96 ) internal view returns (bool crossingInitializedTick, int24 nextTickInit) { @@ -634,7 +631,7 @@ contract TWAMM is BaseHook, ITWAMM { unchecked { if (searchingLeft) nextTickInit -= 1; } - (nextTickInit, crossingInitializedTick) = poolManager.getNextInitializedTickWithinOneWord( + (nextTickInit, crossingInitializedTick) = manager.getNextInitializedTickWithinOneWord( poolKey.toId(), nextTickInit, poolKey.tickSpacing, searchingLeft ); nextTickInitFurtherThanTarget = searchingLeft ? nextTickInit <= targetTick : nextTickInit > targetTick; diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index ede61bf5..1a42e121 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -19,7 +19,7 @@ contract VolatilityOracle is BaseHook { return uint32(block.timestamp); } - constructor(IPoolManager _poolManager) BaseHook(_poolManager) { + constructor(IPoolManager _manager) BaseHook(_manager) { deployTimestamp = _blockTimestamp(); } @@ -56,7 +56,7 @@ contract VolatilityOracle is BaseHook { uint24 startingFee = 3000; uint32 lapsed = _blockTimestamp() - deployTimestamp; uint24 fee = startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute - poolManager.updateDynamicLPFee(key, fee); // initial fee 0.30% + manager.updateDynamicLPFee(key, fee); // initial fee 0.30% } function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata) From 861171363d3af46363b50ba022b535ff942cade1 Mon Sep 17 00:00:00 2001 From: Junion <69495294+Jun1on@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:29:09 -0400 Subject: [PATCH 13/13] make deployTimestamp immutable (#125) --- contracts/hooks/examples/VolatilityOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index 1a42e121..2900632f 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -12,7 +12,7 @@ contract VolatilityOracle is BaseHook { error MustUseDynamicFee(); - uint32 deployTimestamp; + uint32 immutable deployTimestamp; /// @dev For mocking function _blockTimestamp() internal view virtual returns (uint32) {