diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index a08fb8e1..f5b9e8bf 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2687 \ 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 bb219663..b47b8dc4 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -22933 \ 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 6eb59a1d..46616951 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -23180 \ 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 94c197e9..dba60802 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2738 \ 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 75080690..c90bb2fe 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -21892 \ 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 9b54c31b..1d23504b 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -22191 \ 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 2a55d550..3559f242 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -2070 \ 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 94ac0e08..505e4836 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -407968 \ No newline at end of file +383800 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index d1198e0f..cca6215c 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -201962 \ No newline at end of file +178167 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index aef75115..5c4f7a43 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -153306 \ No newline at end of file +127046 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 3b5a43d1..b126274c 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1112212 \ 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 58273980..15ff79a0 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -197519 \ No newline at end of file +168345 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 8e473407..c9e257b7 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -379147 \ No newline at end of file +344222 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 3f185fb2..0cf267ac 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -111940 \ No newline at end of file +87975 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 68f6f4d2..8fd750fa 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -151523 \ No newline at end of file +125848 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index f484e31f..3dada479 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -254660 \ 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 83917a8d..f623cfa5 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -245360 \ 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 8f98b8b1..137baa16 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -54869 \ 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 ee2ae68d..e6dc42ce 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -45569 \ 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 1e8b26e0..e4e9e6b2 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -72316 \ 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 a695bf26..5996d53e 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -6492 \ 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 2a55d550..3559f242 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -2070 \ 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 2a55d550..3559f242 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -2070 \ 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 5265bba3..24efe8f4 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -86878 \ 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 2a55d550..3559f242 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -2070 \ 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 a08fb8e1..f5b9e8bf 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2687 \ 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 d0974c4f..76e5b53e 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -6684 \ 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 05796bbf..f124ce2d 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -6193 \ 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 ed8dd329..9dab3404 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -3382 \ No newline at end of file +2522 \ No newline at end of file diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 905d0d5a..93ea0763 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5027 +5726 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index e114b3c9..062daa00 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -195446 +155521 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index e9d735d6..c4462302 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -273998 +231664 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 2e9064c8..387555b3 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -352561 +315181 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 7384adea..b338febf 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -193868 +161452 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index dd633666..ee6989ff 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -194612 +156533 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index c9bea318..ff0cd29c 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -274060 +233574 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 31b7906b..69c36608 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -353539 +316054 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index b483e973..b24252fd 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -193088 +160423 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 1ba4a8d1..aacec050 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -145648 \ No newline at end of file +122867 \ No newline at end of file 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 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/.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 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 b931bd6a..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.ModifyPositionParams calldata params + 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 941de34c..653f7fa9 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,21 +40,16 @@ 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) - 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(); @@ -77,7 +72,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 +80,29 @@ abstract contract BaseHook is IHooks { revert HookNotImplemented(); } - function afterModifyPosition( + function beforeRemoveLiquidity( + address, + PoolKey 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.ModifyPositionParams calldata, + IPoolManager.ModifyLiquidityParams calldata, BalanceDelta, bytes calldata ) external virtual returns (bytes4) { diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 8b14c3fa..db4247d4 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.sol"; +import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; @@ -14,32 +14,23 @@ import {IV4Router} from "./interfaces/IV4Router.sol"; /// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools -abstract contract V4Router is IV4Router, ILockCallback { +abstract contract V4Router is IV4Router, IUnlockCallback { using CurrencyLibrary for Currency; IPoolManager immutable poolManager; - /// @dev Only the pool manager may call this function - modifier poolManagerOnly() { - if (msg.sender != address(poolManager)) revert NotPoolManager(); - _; - } - constructor(IPoolManager _poolManager) { poolManager = _poolManager; } function _v4Swap(SwapType swapType, bytes memory params) internal { - poolManager.lock(address(this), abi.encode(SwapInfo(swapType, msg.sender, params))); + poolManager.unlock(abi.encode(SwapInfo(swapType, msg.sender, params))); } - /// @inheritdoc ILockCallback - function lockAcquired(address, /*lockCaller*/ bytes calldata encodedSwapInfo) - external - override - poolManagerOnly - returns (bytes memory) - { + /// @inheritdoc IUnlockCallback + function unlockCallback(bytes calldata encodedSwapInfo) external override returns (bytes memory) { + if (msg.sender != address(poolManager)) revert NotPoolManager(); + SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); if (swapInfo.swapType == SwapType.ExactInput) { @@ -61,7 +52,7 @@ abstract contract V4Router is IV4Router, ILockCallback { _swap( params.poolKey, params.zeroForOne, - int256(int128(params.amountIn)), + int256(-int128(params.amountIn)), params.sqrtPriceLimitX96, msgSender, true, @@ -78,10 +69,10 @@ abstract contract V4Router is IV4Router, ILockCallback { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); amountOut = uint128( - -_swap( + _swap( poolKey, zeroForOne, - int256(int128(params.amountIn)), + int256(-int128(params.amountIn)), 0, msgSender, i == 0, @@ -102,7 +93,7 @@ abstract contract V4Router is IV4Router, ILockCallback { _swap( params.poolKey, params.zeroForOne, - -int256(int128(params.amountOut)), + int256(int128(params.amountOut)), params.sqrtPriceLimitX96, msgSender, true, @@ -120,10 +111,10 @@ abstract contract V4Router is IV4Router, ILockCallback { (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); amountIn = uint128( - _swap( + -_swap( poolKey, !oneForZero, - -int256(int128(params.amountOut)), + int256(int128(params.amountOut)), 0, msgSender, i == 1, @@ -162,13 +153,13 @@ abstract contract V4Router is IV4Router, ILockCallback { ); if (zeroForOne) { - reciprocalAmount = amountSpecified > 0 ? delta.amount1() : delta.amount0(); + reciprocalAmount = amountSpecified < 0 ? delta.amount1() : delta.amount0(); if (settle) _payAndSettle(poolKey.currency0, msgSender, delta.amount0()); - if (take) poolManager.take(poolKey.currency1, msgSender, uint128(-delta.amount1())); + if (take) poolManager.take(poolKey.currency1, msgSender, uint128(delta.amount1())); } else { - reciprocalAmount = amountSpecified > 0 ? delta.amount0() : delta.amount1(); + reciprocalAmount = amountSpecified < 0 ? delta.amount0() : delta.amount1(); if (settle) _payAndSettle(poolKey.currency1, msgSender, delta.amount1()); - if (take) poolManager.take(poolKey.currency0, msgSender, uint128(-delta.amount0())); + if (take) poolManager.take(poolKey.currency0, msgSender, uint128(delta.amount0())); } } @@ -186,7 +177,7 @@ abstract contract V4Router is IV4Router, ILockCallback { } function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private { - _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(settleAmount))); + _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(-settleAmount))); poolManager.settle(currency); } diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 1bd0cfe2..614cde2b 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -11,7 +11,7 @@ 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 {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"; @@ -23,7 +23,7 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import "../../libraries/LiquidityAmounts.sol"; -contract FullRange is BaseHook, ILockCallback { +contract FullRange is BaseHook, IUnlockCallback { using CurrencyLibrary for Currency; using PoolIdLibrary for PoolKey; using SafeCast for uint256; @@ -50,7 +50,7 @@ contract FullRange is BaseHook, ILockCallback { struct CallbackData { address sender; PoolKey key; - IPoolManager.ModifyPositionParams params; + IPoolManager.ModifyLiquidityParams params; } struct PoolInfo { @@ -87,18 +87,18 @@ 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, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -117,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(); @@ -136,9 +136,9 @@ contract FullRange is BaseHook, ILockCallback { if (poolLiquidity == 0 && liquidity <= MINIMUM_LIQUIDITY) { revert LiquidityDoesntMeetMinimum(); } - BalanceDelta addedDelta = modifyPosition( + BalanceDelta addedDelta = modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: liquidity.toInt256() @@ -153,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(); } } @@ -174,15 +174,15 @@ 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.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: -(params.liquidity.toInt256()) @@ -219,15 +219,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,18 +245,16 @@ contract FullRange is BaseHook, ILockCallback { return IHooks.beforeSwap.selector; } - function modifyPosition(PoolKey memory key, IPoolManager.ModifyPositionParams 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.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())); + _settleDelta(sender, key.currency0, uint128(-delta.amount0())); + _settleDelta(sender, key.currency1, uint128(-delta.amount1())); } function _settleDelta(address sender, Currency currency, uint128 amount) internal { @@ -273,11 +271,11 @@ 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.ModifyPositionParams memory params) + function _removeLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) internal returns (BalanceDelta delta) { @@ -295,18 +293,16 @@ 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; } - function lockAcquired(address sender, bytes calldata rawData) + function unlockCallback(bytes calldata rawData) external - override(ILockCallback, BaseHook) + override(IUnlockCallback, 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; @@ -314,7 +310,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 +318,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()) @@ -334,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 @@ -354,13 +350,13 @@ 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.modifyPosition( + BalanceDelta balanceDeltaAfter = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: liquidity.toInt256() @@ -369,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 35389d0f..c0f1c096 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -60,18 +60,18 @@ 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, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -103,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); @@ -112,20 +112,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) @@ -148,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 2a5287bf..3f7441aa 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -73,18 +73,18 @@ 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, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -109,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,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; @@ -192,14 +187,14 @@ 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.modifyPosition( + BalanceDelta delta = poolManager.modifyLiquidity( key, - IPoolManager.ModifyPositionParams({ + IPoolManager.ModifyLiquidityParams({ tickLower: tickLower, tickUpper: tickLower + key.tickSpacing, liquidityDelta: liquidityDelta @@ -207,11 +202,11 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) { - poolManager.mint(key.currency0, address(this), 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(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + if (delta.amount1() > 0) { + poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(delta.amount1())); } } @@ -221,8 +216,7 @@ contract LimitOrder is BaseHook { { if (liquidity == 0) revert ZeroLiquidity(); - poolManager.lock( - address(this), + poolManager.unlock( abi.encodeCall(this.lockAcquiredPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender)) ); @@ -258,9 +252,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 @@ -268,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 { @@ -281,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); } @@ -299,22 +293,19 @@ 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; (amount0, amount1, amount0Fee, amount1Fee) = abi.decode( - poolManager.lock( - address(this), + poolManager.unlock( 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; @@ -332,28 +323,28 @@ contract LimitOrder is BaseHook { ) external selfOnly returns (uint256 amount0, uint256 amount1, uint128 amount0Fee, uint128 amount1Fee) { int24 tickUpper = tickLower + key.tickSpacing; - // because `modifyPosition` includes not just principal value but also fees, we cannot allocate + // because `modifyLiquidity` includes not just principal value but also fees, we cannot allocate // the proceeds pro-rata. if we were to do so, users who have been in a limit order that's partially filled // 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())); + if (deltaFee.amount0() > 0) { + 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())); + if (deltaFee.amount1() > 0) { + 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 @@ -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())); } } @@ -378,19 +369,16 @@ 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( - address(this), + poolManager.unlock( abi.encodeCall(this.lockAcquiredWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to)) ); @@ -405,11 +393,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..b57ee223 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -60,18 +60,18 @@ 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, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -87,14 +87,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) @@ -134,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( @@ -142,8 +142,8 @@ 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.unlock( + abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)) ); } } @@ -300,32 +300,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 unlockCallback(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 657c9fae..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); @@ -30,18 +23,18 @@ 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, + afterInitialize: true, + beforeAddLiquidity: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false, - noOp: false, - accessLock: false + afterDonate: false }); } @@ -54,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/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol index f3c24660..5fab998b 100644 --- a/contracts/interfaces/IPeripheryPayments.sol +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index feda3d8e..90a390fc 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -14,9 +14,10 @@ interface IQuoter { error InvalidLockAcquiredSender(); error InvalidLockCaller(); error InvalidQuoteBatchParams(); + error InsufficientAmountOut(); error LockFailure(); error NotSelf(); - error UnexpectedRevertBytes(); + error UnexpectedRevertBytes(bytes revertData); struct PoolDeltas { int128 currency0Delta; diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index aceee007..9338eef5 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"; @@ -12,28 +12,40 @@ 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 {PathKeyLib} from "../libraries/PathKey.sol"; +import {PathKey, PathKeyLib} from "../libraries/PathKey.sol"; -contract Quoter is IQuoter, ILockCallback { +contract Quoter is IQuoter, IUnlockCallback { using Hooks for IHooks; using PoolIdLibrary for PoolKey; + using PathKeyLib for PathKey; /// @dev cache used to check a safety condition in exact output swaps. - uint256 private amountOutCached; + uint128 private amountOutCached; // v4 Singleton contract IPoolManager public immutable manager; - /// @dev custom error function selector length - uint256 internal constant MINIMUM_CUSTOM_ERROR_LENGTH = 4; - - /// @dev function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes - uint256 internal constant MINIMUM_REASON_LENGTH = 68; - /// @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; + 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(); @@ -50,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.unlock(abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} catch (bytes memory reason) { return _handleRevertSingle(reason); } @@ -65,7 +77,7 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} + try manager.unlock(abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } @@ -77,9 +89,7 @@ contract Quoter is IQuoter, ILockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; - - try manager.lock(address(this), 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); @@ -96,20 +106,17 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(address(this), 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(address lockCaller, 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(); } - if (lockCaller != address(this)) { - revert InvalidLockCaller(); - } (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; @@ -124,19 +131,7 @@ contract Quoter is IQuoter, ILockCallback { /// @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) { - //if InvalidLockAcquiredSender() - if (reason.length == MINIMUM_CUSTOM_ERROR_LENGTH) { - assembly { - revert(reason, 4) - } - } - if (reason.length < MINIMUM_REASON_LENGTH) { - revert UnexpectedRevertBytes(); - } - assembly { - reason := add(reason, 0x04) - } - revert(abi.decode(reason, (string))); + revert UnexpectedRevertBytes(reason); } return reason; } @@ -170,58 +165,61 @@ 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) = - PathKeyLib.getPoolAndSwapDirection(params.path[i], 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)) } } /// @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); @@ -235,58 +233,67 @@ 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 : 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) = _swap( - poolKey, - !oneForZero, - -int256(int128(i == pathLength ? params.exactAmount : prevAmountIn)), - 0, - params.path[i - 1].hookData - ); + (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = + _swap(poolKey, !oneForZero, int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); - (int128 deltaIn, int128 deltaOut) = - !oneForZero ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); - deltaAmounts[i - 1] += deltaIn; - deltaAmounts[i] += deltaOut; + // 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()); + result.deltaAmounts[i - 1] += cache.deltaIn; + result.deltaAmounts[i] += cache.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.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)) } } /// @dev quote an ExactOutput swap on a pool, then revert with the result function _quoteExactOutputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { - (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); + // 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)), + 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(); + deltaAmounts[0] = -deltas.amount0(); + deltaAmounts[1] = -deltas.amount1(); uint32 initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(manager, params.poolKey, tickBefore, tickAfter); @@ -297,6 +304,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @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, @@ -313,7 +321,11 @@ contract Quoter is IQuoter, ILockCallback { }), hookData ); - (sqrtPriceX96After, tickAfter,) = manager.getSlot0(poolKey.toId()); + // 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 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..4e95a213 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,15 +1,13 @@ [profile.default] src = 'contracts' out = 'foundry-out' -solc_version = '0.8.20' -via_ir = true +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..1dda6f50 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 83557113a0425eb3d81570c30e7a5ce550037149 +Subproject commit 1dda6f5095e88a82a6249089fbc827a4a12abcf5 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 3d3f6800..dc970b9a 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"; @@ -34,7 +34,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { int24 tickSpacing, IHooks hooks ); - event ModifyPosition( + event ModifyLiquidity( PoolId indexed poolId, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta ); event Swap( @@ -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; @@ -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,12 +753,12 @@ 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); - 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..05255e93 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,21 +56,21 @@ 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 { - 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 @@ -80,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 @@ -88,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); @@ -96,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); @@ -105,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) = @@ -117,10 +116,10 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionNoObservations() public { - initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); - modifyPositionRouter.modifyPosition( + manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + modifyLiquidityRouter.modifyLiquidity( key, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 ), ZERO_BYTES @@ -139,11 +138,11 @@ 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 - modifyPositionRouter.modifyPosition( + modifyLiquidityRouter.modifyLiquidity( key, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000 ), ZERO_BYTES @@ -162,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); @@ -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 { + manager.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/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 056b0818..7fb14c26 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,16 +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(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) + 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: salt2}("Test1", "1", 18); + + vm.etch(address(0x2222), address(token0).code); + token1 = MockERC20(address(0x2222)); token1.mint(address(this), 2 ** 128); - token2 = new MockERC20("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)); @@ -117,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.unlockCallback(abi.encodeWithSelector(quoter.unlockCallback.selector, address(this), "0x")); } function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { @@ -538,12 +542,12 @@ 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.modifyPosition( + positionManager.modifyLiquidity( poolKey, - IPoolManager.ModifyPositionParams( + IPoolManager.ModifyLiquidityParams( MIN_TICK, MAX_TICK, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() @@ -553,28 +557,28 @@ 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.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 @@ -583,32 +587,32 @@ 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); 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..96941963 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 ); } @@ -92,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); } @@ -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/V4Router.t.sol b/test/V4Router.t.sol index 684512e0..6e23d76b 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -11,7 +11,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 {IV4Router} from "../contracts/interfaces/IV4Router.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {V4RouterImplementation} from "./shared/implementation/V4RouterImplementation.sol"; @@ -22,7 +22,7 @@ import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; contract V4RouterTest is Test, Deployers, GasSnapshot { using CurrencyLibrary for Currency; - PoolModifyPositionTest positionManager; + PoolModifyLiquidityTest positionManager; V4RouterImplementation router; MockERC20 token0; @@ -40,7 +40,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { deployFreshManagerAndRouters(); router = new V4RouterImplementation(manager); - positionManager = new PoolModifyPositionTest(manager); + positionManager = new PoolModifyLiquidityTest(manager); token0 = new MockERC20("Test0", "0", 18); token0.mint(address(this), 2 ** 128); @@ -76,15 +76,15 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); snapStart("RouterExactInputSingle"); router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance0 - newBalance0, amountIn); assertEq(newBalance1 - prevBalance1, expectedAmountOut); @@ -97,13 +97,13 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance1 - newBalance1, amountIn); assertEq(newBalance0 - prevBalance0, expectedAmountOut); @@ -214,15 +214,15 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); snapStart("RouterExactOutputSingle"); router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance0 - newBalance0, expectedAmountIn); assertEq(newBalance1 - prevBalance1, amountOut); @@ -235,13 +235,13 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance1 - newBalance1, expectedAmountIn); assertEq(newBalance0 - prevBalance0, amountOut); @@ -358,10 +358,10 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { } 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.modifyPosition(poolKey, IPoolManager.ModifyPositionParams(-887220, 887220, 200 ether), "0x"); + positionManager.modifyLiquidity(poolKey, IPoolManager.ModifyLiquidityParams(-887220, 887220, 200 ether), "0x"); } function toCurrency(MockERC20 token) internal pure returns (Currency) { 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 diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol index 54832b4a..a080a309 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; +pragma solidity ^0.8.19; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.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.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(address, /*sender*/ 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)); @@ -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); } }