diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index 3411e3a3..f5b9e8bf 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2681 \ 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 b972d13c..b47b8dc4 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -22914 \ 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 9792572d..46616951 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -23161 \ 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 79eded72..dba60802 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2727 \ 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 3a8f8b89..c90bb2fe 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -21874 \ 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 07de8526..1d23504b 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -22172 \ 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 34d11379..3559f242 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -2069 \ 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..a06e3c2a 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -407968 \ No newline at end of file +383728 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index d1198e0f..2b27c34c 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -201962 \ No newline at end of file +178095 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index aef75115..21f41442 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -153306 \ No newline at end of file +126975 \ 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..96a5fd25 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -197519 \ No newline at end of file +168275 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 8e473407..e175f88c 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -379147 \ No newline at end of file +344152 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 3f185fb2..c1dd6247 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -111940 \ No newline at end of file +87904 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 68f6f4d2..2b2f14de 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -151523 \ No newline at end of file +125777 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 09fe751f..3dada479 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -254652 \ 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 8e9ce402..f623cfa5 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -245352 \ 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 38036bf4..137baa16 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -54861 \ 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 2d134998..e6dc42ce 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -45561 \ 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 ed35f5a6..e4e9e6b2 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -72306 \ 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 2b11cfa9..5996d53e 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -6473 \ 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 34d11379..3559f242 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -2069 \ 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 34d11379..3559f242 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -2069 \ 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 0bd03724..24efe8f4 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -86537 \ 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 34d11379..3559f242 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -2069 \ 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 3411e3a3..f5b9e8bf 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2681 \ 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 d7915ea2..76e5b53e 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -6665 \ 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 16e3b167..f124ce2d 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -6175 \ 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 18c6d0f8..9dab3404 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -3365 \ 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 f424f4ac..e1410edd 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5482 \ No newline at end of file +5727 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index e797c153..ef0d7154 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -188383 \ No newline at end of file +155453 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 9e23b3e3..348db162 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -266802 \ No newline at end of file +231596 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index ebb5d10f..dff2f2de 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -352577 \ No newline at end of file +315113 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 6bd80d8b..b9cb5776 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -186643 \ No newline at end of file +161384 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index ed6dbcf9..8e257ab6 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -187893 \ No newline at end of file +156465 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index f75e93a4..1d999b8f 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -267441 \ No newline at end of file +233506 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 6cd5c9ac..6331afd1 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -352616 \ No newline at end of file +315986 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 72632a8a..9dbfcd03 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -186092 \ No newline at end of file +160355 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 0807b6ef..aacec050 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -145810 \ 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 c0292130..653f7fa9 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; +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 unlockCallback(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(); @@ -85,11 +80,12 @@ abstract contract BaseHook is IHooks { revert HookNotImplemented(); } - function beforeRemoveLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) - external - virtual - returns (bytes4) - { + function beforeRemoveLiquidity( + address, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external virtual returns (bytes4) { revert HookNotImplemented(); } diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 1c4b9600..69c93f17 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -34,12 +34,7 @@ abstract contract V4Router is IV4Router, IUnlockCallback { } /// @inheritdoc IUnlockCallback - function unlockCallback(bytes calldata encodedSwapInfo) - external - override - poolManagerOnly - returns (bytes memory) - { + function unlockCallback(bytes calldata encodedSwapInfo) external override poolManagerOnly returns (bytes memory) { SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); if (swapInfo.swapType == SwapType.ExactInput) { @@ -61,7 +56,7 @@ abstract contract V4Router is IV4Router, IUnlockCallback { _swap( params.poolKey, params.zeroForOne, - int256(int128(params.amountIn)), + int256(-int128(params.amountIn)), params.sqrtPriceLimitX96, msgSender, true, @@ -78,10 +73,10 @@ abstract contract V4Router is IV4Router, IUnlockCallback { 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 +97,7 @@ abstract contract V4Router is IV4Router, IUnlockCallback { _swap( params.poolKey, params.zeroForOne, - -int256(int128(params.amountOut)), + int256(int128(params.amountOut)), params.sqrtPriceLimitX96, msgSender, true, @@ -120,10 +115,10 @@ abstract contract V4Router is IV4Router, IUnlockCallback { (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 +157,13 @@ abstract contract V4Router is IV4Router, IUnlockCallback { ); if (zeroForOne) { - 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())); + 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())); } else { - 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())); + 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())); } } diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index f09ba44d..47c2620e 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -87,12 +87,12 @@ contract FullRange is BaseHook, IUnlockCallback { _; } - 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, beforeAddLiquidity: true, - beforeRemoveLiquidity: true, + beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, @@ -153,7 +153,7 @@ contract FullRange is BaseHook, IUnlockCallback { 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(); } } @@ -230,17 +230,6 @@ contract FullRange is BaseHook, IUnlockCallback { return FullRange.beforeAddLiquidity.selector; } - function beforeRemoveLiquidity( - address sender, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - bytes calldata - ) external view override returns (bytes4) { - if (sender != address(this)) revert SenderMustBeHook(); - - return FullRange.beforeRemoveLiquidity.selector; - } - function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override @@ -260,14 +249,12 @@ contract FullRange is BaseHook, IUnlockCallback { internal returns (BalanceDelta delta) { - delta = abi.decode( - poolManager.unlock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta) - ); + delta = abi.decode(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 { @@ -284,8 +271,8 @@ contract FullRange is BaseHook, IUnlockCallback { } function _takeDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - poolManager.take(key.currency0, sender, uint256(uint128(-delta.amount0()))); - poolManager.take(key.currency1, sender, uint256(uint128(-delta.amount1()))); + poolManager.take(key.currency0, sender, uint256(uint128(delta.amount0()))); + poolManager.take(key.currency1, sender, uint256(uint128(delta.amount1()))); } function _removeLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) @@ -316,8 +303,6 @@ contract FullRange is BaseHook, IUnlockCallback { 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 (msg.sender != address(this)) revert SenderMustBeHook(); CallbackData memory data = abi.decode(rawData, (CallbackData)); BalanceDelta delta; @@ -345,7 +330,7 @@ contract FullRange is BaseHook, IUnlockCallback { 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(); @@ -355,7 +340,7 @@ contract FullRange is BaseHook, IUnlockCallback { key, IPoolManager.SwapParams({ zeroForOne: newSqrtPriceX96 < sqrtPriceX96, - amountSpecified: MAX_INT, + amountSpecified: -MAX_INT - 1, // equivalent of type(int256).min sqrtPriceLimitX96: newSqrtPriceX96 }), ZERO_BYTES @@ -365,8 +350,8 @@ contract FullRange is BaseHook, IUnlockCallback { newSqrtPriceX96, TickMath.getSqrtRatioAtTick(MIN_TICK), TickMath.getSqrtRatioAtTick(MAX_TICK), - uint256(uint128(-balanceDelta.amount0())), - uint256(uint128(-balanceDelta.amount1())) + uint256(uint128(balanceDelta.amount0())), + uint256(uint128(balanceDelta.amount1())) ); BalanceDelta balanceDeltaAfter = poolManager.modifyLiquidity( @@ -380,8 +365,8 @@ contract FullRange is BaseHook, IUnlockCallback { ); // 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 9d3af14a..419a643d 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -60,7 +60,7 @@ 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, @@ -118,7 +118,6 @@ contract GeomeanOracle is BaseHook { 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) @@ -130,18 +129,11 @@ contract GeomeanOracle is BaseHook { function beforeRemoveLiquidity( address, - PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata params, + PoolKey calldata, + IPoolManager.ModifyLiquidityParams calldata, 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.beforeRemoveLiquidity.selector; + ) external view override poolManagerOnly returns (bytes4) { + revert OraclePoolMustLockLiquidity(); } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index e5dd2230..89e16c2f 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -73,7 +73,7 @@ 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, @@ -156,12 +156,8 @@ contract LimitOrder is BaseHook { epochInfo.filled = true; - (uint256 amount0, uint256 amount1) = abi.decode( - poolManager.unlock( - 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; @@ -191,9 +187,9 @@ contract LimitOrder is BaseHook { } } - function lockAcquiredFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) - external - selfOnly + function _lockAcquiredFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) + private + poolManagerOnly returns (uint128 amount0, uint128 amount1) { BalanceDelta delta = poolManager.modifyLiquidity( @@ -206,11 +202,11 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) { - poolManager.mint(address(this), key.currency0.toId(), amount0 = uint128(-delta.amount0())); + if (delta.amount0() > 0) { + poolManager.mint(address(this), key.currency0.toId(), amount0 = uint128(delta.amount0())); } - if (delta.amount1() < 0) { - poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(-delta.amount1())); + if (delta.amount1() > 0) { + poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(delta.amount1())); } } @@ -266,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 { @@ -279,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); } @@ -297,8 +293,6 @@ contract LimitOrder is BaseHook { uint128 liquidity = epochInfo.liquidity[msg.sender]; if (liquidity == 0) revert ZeroLiquidity(); delete epochInfo.liquidity[msg.sender]; - uint128 liquidityTotal = epochInfo.liquidityTotal; - epochInfo.liquidityTotal = liquidityTotal - liquidity; uint256 amount0Fee; uint256 amount1Fee; @@ -306,12 +300,12 @@ contract LimitOrder is BaseHook { 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; @@ -358,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())); } } @@ -375,15 +369,13 @@ contract LimitOrder is BaseHook { if (liquidity == 0) revert ZeroLiquidity(); delete epochInfo.liquidity[msg.sender]; - uint256 token0Total = epochInfo.token0Total; - uint256 token1Total = epochInfo.token1Total; uint128 liquidityTotal = epochInfo.liquidityTotal; - amount0 = FullMath.mulDiv(token0Total, liquidity, liquidityTotal); - amount1 = FullMath.mulDiv(token1Total, liquidity, liquidityTotal); + amount0 = FullMath.mulDiv(epochInfo.token0Total, liquidity, liquidityTotal); + amount1 = FullMath.mulDiv(epochInfo.token1Total, liquidity, liquidityTotal); - epochInfo.token0Total = token0Total - amount0; - epochInfo.token1Total = token1Total - amount1; + epochInfo.token0Total -= amount0; + epochInfo.token1Total -= amount1; epochInfo.liquidityTotal = liquidityTotal - liquidity; poolManager.unlock( diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 07d18f9d..b57ee223 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -60,12 +60,12 @@ 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, beforeAddLiquidity: true, - beforeRemoveLiquidity: true, + beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, @@ -97,16 +97,6 @@ contract TWAMM is BaseHook, ITWAMM { return BaseHook.beforeAddLiquidity.selector; } - function beforeRemoveLiquidity( - address, - PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata, - bytes calldata - ) external override poolManagerOnly returns (bytes4) { - executeTWAMMOrders(key); - return BaseHook.beforeRemoveLiquidity.selector; - } - function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override @@ -310,32 +300,27 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function unlockCallback(bytes calldata rawData) - external - override - poolManagerOnly - returns (bytes memory) - { + function unlockCallback(bytes calldata rawData) 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 a4409f7c..d5e260cd 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -14,12 +14,6 @@ contract VolatilityOracle is BaseHook { 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); @@ -29,10 +23,10 @@ contract VolatilityOracle is BaseHook { 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, + afterInitialize: true, beforeAddLiquidity: false, beforeRemoveLiquidity: false, afterAddLiquidity: false, @@ -53,4 +47,20 @@ contract VolatilityOracle is BaseHook { 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/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 a26b8e27..9338eef5 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.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, 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(); @@ -77,8 +89,6 @@ contract Quoter is IQuoter, IUnlockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; - try manager.unlock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; @@ -107,9 +117,6 @@ contract Quoter is IQuoter, IUnlockCallback { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); } - if (msg.sender != 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, IUnlockCallback { /// @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,39 +165,42 @@ contract Quoter is IQuoter, IUnlockCallback { 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)) } } @@ -213,15 +211,15 @@ contract Quoter is IQuoter, IUnlockCallback { (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, IUnlockCallback { 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) { + // 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, IUnlockCallback { } /// @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,6 +321,10 @@ contract Quoter is IQuoter, IUnlockCallback { }), hookData ); + // only exactOut case + if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? deltas.amount1() : deltas.amount0())) { + revert InsufficientAmountOut(); + } (sqrtPriceX96After, tickAfter,,) = manager.getSlot0(poolKey.toId()); } 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 068c477a..4e95a213 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,6 @@ src = 'contracts' out = 'foundry-out' solc_version = '0.8.24' optimizer_runs = 1000000 -via_ir = true ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] evm_version = "cancun" diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index c7f30c42..9b41d773 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -65,7 +65,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { MockERC20 token2; FullRangeImplementation fullRange = FullRangeImplementation( - address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_SWAP_FLAG)) + address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_SWAP_FLAG)) ); PoolId id; @@ -170,7 +170,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function testFullRange_addLiquidity_InitialAddFuzz(uint256 amount) public { manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - if (amount < LOCKED_LIQUIDITY) { + if (amount <= LOCKED_LIQUIDITY) { vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -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}); @@ -752,7 +752,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } } - function testFullRange_BeforeModifyLiquidityFailsWithWrongMsgSender() public { + function testFullRange_BeforeModifyPositionFailsWithWrongMsgSender() public { manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); vm.expectRevert(FullRange.SenderMustBeHook.selector); diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index 6d110397..90ba1902 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -27,8 +27,8 @@ contract TestGeomeanOracle is Test, Deployers { GeomeanOracleImplementation geomeanOracle = GeomeanOracleImplementation( address( uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_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 ) ) ); @@ -115,7 +115,7 @@ contract TestGeomeanOracle is Test, Deployers { assertEq(secondsPerLiquidityCumulativeX128s[0], 0); } - function testBeforeModifyLiquidityNoObservations() public { + function testBeforeModifyPositionNoObservations() public { manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); modifyLiquidityRouter.modifyLiquidity( key, @@ -137,7 +137,7 @@ contract TestGeomeanOracle is Test, Deployers { assertEq(observation.secondsPerLiquidityCumulativeX128, 0); } - function testBeforeModifyLiquidityObservation() public { + function testBeforeModifyPositionObservation() public { manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyLiquidityRouter.modifyLiquidity( @@ -160,7 +160,7 @@ contract TestGeomeanOracle is Test, Deployers { assertEq(observation.secondsPerLiquidityCumulativeX128, 680564733841876926926749214863536422912); } - function testBeforeModifyLiquidityObservationAndCardinality() public { + function testBeforeModifyPositionObservationAndCardinality() public { manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds geomeanOracle.increaseCardinalityNext(key, 2); @@ -197,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 a15ddda3..262e097b 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -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,7 +191,7 @@ 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 ); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 2ba809dd..7fb14c26 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -53,14 +53,18 @@ contract QuoterTest is Test, Deployers { quoter = new Quoter(address(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)); @@ -115,9 +119,9 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoaded, 2); } - // nested self-call into unlockCallback reverts + // 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.unlockCallback(abi.encodeWithSelector(quoter.unlockCallback.selector, address(this), "0x")); } diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 80bac156..96941963 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.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_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG)); - TWAMM twamm = TWAMM( - address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_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; @@ -78,8 +75,12 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { token1.approve(address(modifyLiquidityRouter), 100 ether); token0.mint(address(this), 100 ether); token1.mint(address(this), 100 ether); - modifyLiquidityRouter.modifyLiquidity(poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether), ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity(poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether), ZERO_BYTES); + 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.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether), diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index edd5e233..828259df 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -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); diff --git a/test/shared/implementation/FullRangeImplementation.sol b/test/shared/implementation/FullRangeImplementation.sol index ab9c06d2..20161e82 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 b13ae46d..af4fd14b 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 490b38b5..ed6958e9 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 1eb56154..63a30b88 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 8b18d342..05c67fd1 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -36,8 +36,7 @@ contract HookEnabledSwapRouter is PoolTestBase { bytes memory hookData ) external payable returns (BalanceDelta delta) { delta = abi.decode( - manager.unlock(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; @@ -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); } }