diff --git a/.env b/.env new file mode 100644 index 00000000..7859e840 --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +FOUNDRY_FUZZ_SEED=0x4444 + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + export FOUNDRY_SOLC="./lib/v4-core/bin/solc-static-linux" +elif [[ "$OSTYPE" == "darwin"* ]]; then + export FOUNDRY_SOLC="./lib/v4-core/bin/solc-mac" +fi diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index 9463411b..bc61a749 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2771 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 638f8744..7706f4dd 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -21068 \ No newline at end of file +23377 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 1bc3059d..8afa5484 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -21318 \ No newline at end of file +23624 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index a5bb2393..f66ebbd5 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2076 \ No newline at end of file +2798 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index db768f3a..9db3df4e 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -20164 \ No newline at end of file +22396 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index c04b75bb..b2f26cf1 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -20458 \ No newline at end of file +22695 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 2d5250a5..ef62f828 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -412696 \ No newline at end of file +410761 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index 032a6a3b..b3688dfa 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -206962 \ No newline at end of file +204683 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 9d59ac16..2b5ad7d2 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -154763 \ No newline at end of file +156432 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index e0b3ab13..c2b5d0ef 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -879542 \ No newline at end of file +897565 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 920384a4..bc1c95e2 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200095 \ No newline at end of file +200057 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 5ee38978..b5d7708e 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -379287 \ No newline at end of file +386095 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 436848b5..9e12e78d 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -112303 \ No newline at end of file +114700 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index d48620c7..d9365d02 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -153038 \ No newline at end of file +154641 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 61763356..3aa3cfac 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -233028 \ No newline at end of file +254711 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 4f1264df..50fc054a 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -223717 \ No newline at end of file +245393 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 3d85d6d7..15a052b9 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -32886 \ No newline at end of file +54893 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index bc6dc069..d6664238 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -23586 \ No newline at end of file +45575 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index da81ec04..3039612c 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -51411 \ No newline at end of file +72361 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index f61a3565..ae13ac3f 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -5571 \ No newline at end of file +6618 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index 41599c5d..b63da1de 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -75965 \ No newline at end of file +88543 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index 9463411b..bc61a749 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2771 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index 0b1caa8d..ba7fb703 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -5746 \ No newline at end of file +6807 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index bee097af..3ee11622 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -5277 \ No newline at end of file +6319 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index a51f76e9..204ec243 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -2615 \ No newline at end of file +3466 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 9adc49a6..0aef60be 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -123576 \ No newline at end of file +146158 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 280df88b..f6d99f2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,6 @@ jobs: version: nightly - name: Run tests - run: forge test -vvv + run: forge test -vvv --via-ir env: FOUNDRY_PROFILE: ci diff --git a/.gitignore b/.gitignore index de5c2c73..785fb393 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ cache/ -foundry-out/ \ No newline at end of file +foundry-out/ +.vscode/ \ No newline at end of file diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index 8d463807..941de34c 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; abstract contract BaseHook is IHooks { error NotPoolManager(); @@ -40,16 +40,21 @@ abstract contract BaseHook is IHooks { _; } - function getHooksCalls() public pure virtual returns (Hooks.Calls memory); + function getHooksCalls() public pure virtual returns (Hooks.Permissions memory); // this function is virtual so that we can override it during testing, // which allows us to deploy an implementation to any address // and then etch the bytecode into the correct address function validateHookAddress(BaseHook _this) internal pure virtual { - Hooks.validateHookAddress(_this, getHooksCalls()); + Hooks.validateHookPermissions(_this, getHooksCalls()); } - function lockAcquired(bytes calldata data) external virtual poolManagerOnly returns (bytes memory) { + function lockAcquired(address, /*sender*/ bytes calldata data) + external + virtual + poolManagerOnly + returns (bytes memory) + { (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); diff --git a/contracts/base/PeripheryPayments.sol b/contracts/base/PeripheryPayments.sol index f272da34..24466924 100644 --- a/contracts/base/PeripheryPayments.sol +++ b/contracts/base/PeripheryPayments.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {ERC20} from "solmate/tokens/ERC20.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {IPeripheryPayments} from "../interfaces/IPeripheryPayments.sol"; diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 6c5b08ec..1bd0cfe2 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback/ILockCallback.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; import {UniswapV4ERC20} from "../../libraries/UniswapV4ERC20.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -87,8 +87,8 @@ contract FullRange is BaseHook, ILockCallback { _; } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: true, @@ -96,7 +96,9 @@ contract FullRange is BaseHook, ILockCallback { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -115,7 +117,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -172,7 +174,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -247,7 +249,9 @@ contract FullRange is BaseHook, ILockCallback { internal returns (BalanceDelta delta) { - delta = abi.decode(poolManager.lock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); + delta = abi.decode( + poolManager.lock(address(this), abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta) + ); } function _settleDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { @@ -295,12 +299,14 @@ contract FullRange is BaseHook, ILockCallback { pool.hasAccruedFees = false; } - function lockAcquired(bytes calldata rawData) + function lockAcquired(address sender, bytes calldata rawData) external override(ILockCallback, BaseHook) poolManagerOnly returns (bytes memory) { + // Now that manager can be called by EOAs with a lock target, it's necessary for lockAcquired to check the original sender if it wants to trust the data passed through. + if (sender != address(this)) revert SenderMustBeHook(); CallbackData memory data = abi.decode(rawData, (CallbackData)); BalanceDelta delta; @@ -332,7 +338,7 @@ contract FullRange is BaseHook, ILockCallback { ) * FixedPointMathLib.sqrt(FixedPoint96.Q96) ).toUint160(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); poolManager.swap( key, diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index 5c78e785..35389d0f 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../../libraries/Oracle.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; /// @notice A hook for a pool that allows a Uniswap pool to act as an oracle. Pools that use this hook must have full range /// tick spacing and liquidity is always permanently locked in these pools. This is the suggested configuration @@ -60,8 +60,8 @@ contract GeomeanOracle is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeModifyPosition: true, @@ -69,7 +69,9 @@ contract GeomeanOracle is BaseHook { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -101,7 +103,7 @@ contract GeomeanOracle is BaseHook { /// @dev Called before any action that potentially modifies pool price or liquidity, such as swap or modify position function _updatePool(PoolKey calldata key) private { PoolId id = key.toId(); - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); @@ -146,7 +148,7 @@ contract GeomeanOracle is BaseHook { ObservationState memory state = states[id]; - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 8eff6c68..2a5287bf 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; type Epoch is uint232; @@ -73,8 +73,8 @@ contract LimitOrder is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeModifyPosition: false, @@ -82,7 +82,9 @@ contract LimitOrder is BaseHook { beforeSwap: false, afterSwap: true, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -107,7 +109,7 @@ contract LimitOrder is BaseHook { } function getTick(PoolId poolId) private view returns (int24 tick) { - (, tick,,) = poolManager.getSlot0(poolId); + (, tick,) = poolManager.getSlot0(poolId); } function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { @@ -156,6 +158,7 @@ contract LimitOrder is BaseHook { (uint256 amount0, uint256 amount1) = abi.decode( poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredFill, (key, lower, -int256(uint256(epochInfo.liquidityTotal)))) ), (uint256, uint256) @@ -204,8 +207,12 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) poolManager.mint(key.currency0, address(this), amount0 = uint128(-delta.amount0())); - if (delta.amount1() < 0) poolManager.mint(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + if (delta.amount0() < 0) { + poolManager.mint(key.currency0, address(this), amount0 = uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + poolManager.mint(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + } } function place(PoolKey calldata key, int24 tickLower, bool zeroForOne, uint128 liquidity) @@ -215,6 +222,7 @@ contract LimitOrder is BaseHook { if (liquidity == 0) revert ZeroLiquidity(); poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender)) ); @@ -298,6 +306,7 @@ contract LimitOrder is BaseHook { uint256 amount1Fee; (amount0, amount1, amount0Fee, amount1Fee) = abi.decode( poolManager.lock( + address(this), abi.encodeCall( this.lockAcquiredKill, (key, tickLower, -int256(uint256(liquidity)), to, liquidity == liquidityTotal) @@ -352,8 +361,12 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) poolManager.take(key.currency0, to, amount0 = uint128(-delta.amount0())); - if (delta.amount1() < 0) poolManager.take(key.currency1, to, amount1 = uint128(-delta.amount1())); + if (delta.amount0() < 0) { + poolManager.take(key.currency0, to, amount0 = uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + poolManager.take(key.currency1, to, amount1 = uint128(-delta.amount1())); + } } function withdraw(Epoch epoch, address to) external returns (uint256 amount0, uint256 amount1) { @@ -377,6 +390,7 @@ contract LimitOrder is BaseHook { epochInfo.liquidityTotal = liquidityTotal - liquidity; poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to)) ); @@ -391,15 +405,11 @@ contract LimitOrder is BaseHook { address to ) external selfOnly { if (token0Amount > 0) { - poolManager.safeTransferFrom( - address(this), address(poolManager), uint256(uint160(Currency.unwrap(currency0))), token0Amount, "" - ); + poolManager.burn(currency0, token0Amount); poolManager.take(currency0, to, token0Amount); } if (token1Amount > 0) { - poolManager.safeTransferFrom( - address(this), address(poolManager), uint256(uint160(Currency.unwrap(currency1))), token1Amount, "" - ); + poolManager.burn(currency1, token1Amount); poolManager.take(currency1, to, token1Amount); } } diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 55d44888..4fd5dd74 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -1,24 +1,24 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickBitmap} from "@uniswap/v4-core/contracts/libraries/TickBitmap.sol"; -import {SqrtPriceMath} from "@uniswap/v4-core/contracts/libraries/SqrtPriceMath.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickBitmap} from "@uniswap/v4-core/src/libraries/TickBitmap.sol"; +import {SqrtPriceMath} from "@uniswap/v4-core/src/libraries/SqrtPriceMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {ITWAMM} from "../../interfaces/ITWAMM.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {TransferHelper} from "../../libraries/TransferHelper.sol"; import {TwammMath} from "../../libraries/TWAMM/TwammMath.sol"; import {OrderPool} from "../../libraries/TWAMM/OrderPool.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolGetters} from "../../libraries/PoolGetters.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract TWAMM is BaseHook, ITWAMM { using TransferHelper for IERC20Minimal; @@ -60,8 +60,8 @@ contract TWAMM is BaseHook, ITWAMM { expirationInterval = _expirationInterval; } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: true, @@ -69,7 +69,9 @@ contract TWAMM is BaseHook, ITWAMM { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -129,16 +131,10 @@ contract TWAMM is BaseHook, ITWAMM { self.lastVirtualOrderTimestamp = block.timestamp; } - struct CallbackData { - address sender; - PoolKey key; - IPoolManager.SwapParams params; - } - /// @inheritdoc ITWAMM function executeTWAMMOrders(PoolKey memory key) public { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); State storage twamm = twammStates[poolId]; (bool zeroForOne, uint160 sqrtPriceLimitX96) = _executeTWAMMOrders( @@ -146,7 +142,9 @@ contract TWAMM is BaseHook, ITWAMM { ); if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { - poolManager.lock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96))); + poolManager.lock( + address(this), abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)) + ); } } @@ -302,7 +300,12 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function lockAcquired(bytes calldata rawData) external override poolManagerOnly returns (bytes memory) { + function lockAcquired(address, /*sender*/ bytes calldata rawData) + external + override + poolManagerOnly + returns (bytes memory) + { (PoolKey memory key, IPoolManager.SwapParams memory swapParams) = abi.decode(rawData, (PoolKey, IPoolManager.SwapParams)); @@ -516,7 +519,7 @@ contract TWAMM is BaseHook, ITWAMM { _isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96); if (crossingInitializedTick) { - int128 liquidityNetAtTick = poolManager.getNetLiquidityAtTick(poolKey.toId(), tick); + int128 liquidityNetAtTick = poolManager.getPoolTickInfo(poolKey.toId(), tick).liquidityNet; uint160 initializedSqrtPrice = TickMath.getSqrtRatioAtTick(tick); uint256 swapDelta0 = SqrtPriceMath.getAmount0Delta( @@ -600,7 +603,7 @@ contract TWAMM is BaseHook, ITWAMM { unchecked { // update pool - int128 liquidityNet = poolManager.getNetLiquidityAtTick(poolKey.toId(), params.initializedTick); + int128 liquidityNet = poolManager.getPoolTickInfo(poolKey.toId(), params.initializedTick).liquidityNet; if (initializedSqrtPrice < params.pool.sqrtPriceX96) liquidityNet = -liquidityNet; params.pool.liquidity = liquidityNet < 0 ? params.pool.liquidity - uint128(-liquidityNet) diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index 0a7e696d..657c9fae 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IDynamicFeeManager} from "@uniswap/v4-core/contracts/interfaces/IDynamicFeeManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {FeeLibrary} from "@uniswap/v4-core/contracts/libraries/FeeLibrary.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IDynamicFeeManager} from "@uniswap/v4-core/src/interfaces/IDynamicFeeManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract VolatilityOracle is BaseHook, IDynamicFeeManager { using FeeLibrary for uint24; @@ -15,11 +15,7 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { uint32 deployTimestamp; - function getFee(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) - external - view - returns (uint24) - { + function getFee(address, PoolKey calldata) external view returns (uint24) { uint24 startingFee = 3000; uint32 lapsed = _blockTimestamp() - deployTimestamp; return startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute @@ -34,8 +30,8 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { deployTimestamp = _blockTimestamp(); } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: false, @@ -43,7 +39,9 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol index 765b980f..f3c24660 100644 --- a/contracts/interfaces/IPeripheryPayments.sol +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; /// @title Periphery Payments /// @notice Functions to ease deposits and withdrawals of ETH diff --git a/contracts/interfaces/ITWAMM.sol b/contracts/interfaces/ITWAMM.sol index 570617b6..3b932d3c 100644 --- a/contracts/interfaces/ITWAMM.sol +++ b/contracts/interfaces/ITWAMM.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; interface ITWAMM { /// @notice Thrown when account other than owner attempts to interact with an order diff --git a/contracts/libraries/LiquidityAmounts.sol b/contracts/libraries/LiquidityAmounts.sol index b2c8b54c..845cc6e0 100644 --- a/contracts/libraries/LiquidityAmounts.sol +++ b/contracts/libraries/LiquidityAmounts.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; +import "@uniswap/v4-core/src/libraries/FullMath.sol"; +import "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; /// @title Liquidity amount functions /// @notice Provides functions for computing liquidity amounts from token amounts and prices diff --git a/contracts/libraries/PoolGetters.sol b/contracts/libraries/PoolGetters.sol index d2c7fbf2..e3cb318b 100644 --- a/contracts/libraries/PoolGetters.sol +++ b/contracts/libraries/PoolGetters.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Pool} from "@uniswap/v4-core/contracts/libraries/Pool.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {BitMath} from "@uniswap/v4-core/contracts/libraries/BitMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; /// @title Helper functions to access pool information +/// TODO: Expose other getters on core with extsload. Only use when extsload is available and storage layout is frozen. library PoolGetters { uint256 constant POOL_SLOT = 10; uint256 constant TICKS_OFFSET = 4; @@ -62,7 +63,7 @@ library PoolGetters { // all the 1s at or to the right of the current bitPos uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); // uint256 masked = self[wordPos] & mask; - uint256 masked = getTickBitmapAtWord(poolManager, poolId, wordPos) & mask; + uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word initialized = masked != 0; @@ -75,7 +76,7 @@ library PoolGetters { (int16 wordPos, uint8 bitPos) = position(compressed + 1); // all the 1s at or to the left of the bitPos uint256 mask = ~((1 << bitPos) - 1); - uint256 masked = getTickBitmapAtWord(poolManager, poolId, wordPos) & mask; + uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; // if there are no initialized ticks to the left of the current tick, return leftmost in the word initialized = masked != 0; diff --git a/contracts/libraries/TWAMM/TwammMath.sol b/contracts/libraries/TWAMM/TwammMath.sol index 133a68c7..a5994b51 100644 --- a/contracts/libraries/TWAMM/TwammMath.sol +++ b/contracts/libraries/TWAMM/TwammMath.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.15; import {ABDKMathQuad} from "./ABDKMathQuad.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; /// @title TWAMM Math - Pure functions for TWAMM math calculations library TwammMath { diff --git a/contracts/libraries/TransferHelper.sol b/contracts/libraries/TransferHelper.sol index 5b1833a7..9ab40d9e 100644 --- a/contracts/libraries/TransferHelper.sol +++ b/contracts/libraries/TransferHelper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.15; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; /// @title TransferHelper /// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false diff --git a/foundry.toml b/foundry.toml index b3132187..302fc02b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,8 +5,10 @@ solc_version = '0.8.20' optimizer_runs = 800 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] +cancun = true [profile.ci] fuzz_runs = 100000 +solc = "./lib/v4-core/bin/solc-static-linux" # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/lib/v4-core b/lib/v4-core index 0095e084..83557113 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 0095e0848098c3e32e016eac6d2537b67aa47358 +Subproject commit 83557113a0425eb3d81570c30e7a5ce550037149 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index fa9d13ed..3d3f6800 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -3,22 +3,23 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {FullRange} from "../contracts/hooks/examples/FullRange.sol"; import {FullRangeImplementation} from "./shared/implementation/FullRangeImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; contract TestFullRange is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; @@ -47,6 +48,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { uint24 fee ); + HookEnabledSwapRouter router; /// @dev Min tick for full range with tick spacing of 60 int24 internal constant MIN_TICK = -887220; /// @dev Max tick for full range with tick spacing of 60 @@ -62,15 +64,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { MockERC20 token1; MockERC20 token2; - Currency currency0; - Currency currency1; - - PoolManager manager; FullRangeImplementation fullRange = FullRangeImplementation( address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG)) ); - PoolKey key; PoolId id; PoolKey key2; @@ -80,15 +77,13 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey keyWithLiq; PoolId idWithLiq; - PoolModifyPositionTest modifyPositionRouter; - PoolSwapTest swapRouter; - function setUp() public { - token0 = new MockERC20("TestA", "A", 18, 2 ** 128); - token1 = new MockERC20("TestB", "B", 18, 2 ** 128); - token2 = new MockERC20("TestC", "C", 18, 2 ** 128); - - manager = new PoolManager(500000); + deployFreshManagerAndRouters(); + router = new HookEnabledSwapRouter(manager); + MockERC20[] memory tokens = deployTokens(3, 2 ** 128); + token0 = tokens[0]; + token1 = tokens[1]; + token2 = tokens[2]; FullRangeImplementation impl = new FullRangeImplementation(manager, fullRange); vm.etch(address(fullRange), address(impl).code); @@ -102,17 +97,14 @@ contract TestFullRange is Test, Deployers, GasSnapshot { keyWithLiq = createPoolKey(token0, token2); idWithLiq = keyWithLiq.toId(); - modifyPositionRouter = new PoolModifyPositionTest(manager); - swapRouter = new PoolSwapTest(manager); - token0.approve(address(fullRange), type(uint256).max); token1.approve(address(fullRange), type(uint256).max); token2.approve(address(fullRange), type(uint256).max); - token0.approve(address(swapRouter), type(uint256).max); - token1.approve(address(swapRouter), type(uint256).max); - token2.approve(address(swapRouter), type(uint256).max); + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); + token2.approve(address(router), type(uint256).max); - manager.initialize(keyWithLiq, SQRT_RATIO_1_1, ZERO_BYTES); + initPool(keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( keyWithLiq.currency0, @@ -135,7 +127,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { emit Initialize(id, testKey.currency0, testKey.currency1, testKey.fee, testKey.tickSpacing, testKey.hooks); snapStart("FullRangeInitialize"); - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); snapEnd(); (, address liquidityToken) = fullRange.poolInfo(id); @@ -147,11 +139,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey memory wrongKey = PoolKey(key.currency0, key.currency1, 0, TICK_SPACING + 1, fullRange); vm.expectRevert(FullRange.TickSpacingNotDefault.selector); - manager.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); } function testFullRange_addLiquidity_InitialAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -177,7 +169,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_InitialAddFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); if (amount < LOCKED_LIQUIDITY) { vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); fullRange.addLiquidity( @@ -252,7 +244,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_SwapThenAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -273,16 +265,16 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.expectEmit(true, true, true, true); emit Swap( - id, address(swapRouter), 1 ether, -906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 + id, address(router), 1 ether, -906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 ); IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeSwap"); - swapRouter.swap(key, params, settings, ZERO_BYTES); + router.swap(key, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); @@ -306,7 +298,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_FailsIfTooMuchSlippage() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -316,10 +308,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, settings, ZERO_BYTES); + router.swap(key, params, settings, ZERO_BYTES); vm.expectRevert(FullRange.TooMuchSlippage.selector); fullRange.addLiquidity( @@ -331,7 +323,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function testFullRange_swap_TwoSwaps() public { PoolKey memory testKey = key; - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -341,18 +333,18 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeFirstSwap"); - swapRouter.swap(testKey, params, settings, ZERO_BYTES); + router.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); snapStart("FullRangeSecondSwap"); - swapRouter.swap(testKey, params, settings, ZERO_BYTES); + router.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (hasAccruedFees,) = fullRange.poolInfo(id); @@ -360,8 +352,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_swap_TwoPools() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - manager.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -377,11 +369,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10000000, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); - swapRouter.swap(key2, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key2, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -416,7 +408,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_InitialRemoveFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -464,7 +456,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_FailsIfNoLiquidity() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -476,7 +468,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SucceedsWithPartial() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOfSelf(); uint256 prevBalance1 = key.currency1.balanceOfSelf(); @@ -511,7 +503,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_DiffRatios() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -560,10 +552,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(keyWithLiq, params, testSettings, ZERO_BYTES); + router.swap(keyWithLiq, params, testSettings, ZERO_BYTES); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -579,7 +571,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_RemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -634,7 +626,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.prank(address(2)); token1.approve(address(fullRange), type(uint256).max); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); // Test contract adds liquidity @@ -687,10 +679,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100 ether, sqrtPriceLimitX96: SQRT_RATIO_1_4}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -712,7 +704,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SwapRemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -742,10 +734,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { sqrtPriceLimitX96: SQRT_RATIO_1_4 }); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); // Test contract removes liquidity, succeeds UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -761,7 +753,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_BeforeModifyPositionFailsWithWrongMsgSender() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); vm.expectRevert(FullRange.SenderMustBeHook.selector); modifyPositionRouter.modifyPosition( diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index bd0e0c05..aa5e5c6d 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -3,22 +3,21 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GetSender} from "./shared/GetSender.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {GeomeanOracle} from "../contracts/hooks/examples/GeomeanOracle.sol"; import {GeomeanOracleImplementation} from "./shared/implementation/GeomeanOracleImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {TestERC20} from "@uniswap/v4-core/contracts/test/TestERC20.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../contracts/libraries/Oracle.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -contract TestGeomeanOracle is Test, Deployers, TokenFixture { +contract TestGeomeanOracle is Test, Deployers { using PoolIdLibrary for PoolKey; int24 constant MAX_TICK_SPACING = 32767; @@ -26,7 +25,6 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { TestERC20 token0; TestERC20 token1; - PoolManager manager; GeomeanOracleImplementation geomeanOracle = GeomeanOracleImplementation( address( uint160( @@ -35,18 +33,15 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { ) ) ); - PoolKey key; PoolId id; - PoolModifyPositionTest modifyPositionRouter; - function setUp() public { - initializeTokens(); + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); - vm.record(); GeomeanOracleImplementation impl = new GeomeanOracleImplementation(manager, geomeanOracle); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -71,12 +66,12 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeInitializeAllowsPoolCreation() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); } function testBeforeInitializeRevertsIfFee() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - manager.initialize( + initializeRouter.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 1, MAX_TICK_SPACING, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -85,7 +80,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { function testBeforeInitializeRevertsIfNotMaxTickSpacing() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - manager.initialize( + initializeRouter.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 0, 60, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -93,7 +88,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testAfterInitializeState() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); assertEq(observationState.index, 0); assertEq(observationState.cardinality, 1); @@ -101,7 +96,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testAfterInitializeObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); assertTrue(observation.initialized); assertEq(observation.blockTimestamp, 1); @@ -110,7 +105,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testAfterInitializeObserve0() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); uint32[] memory secondsAgo = new uint32[](1); secondsAgo[0] = 0; (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = @@ -122,7 +117,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeModifyPositionNoObservations() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); modifyPositionRouter.modifyPosition( key, IPoolManager.ModifyPositionParams( @@ -144,7 +139,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeModifyPositionObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyPositionRouter.modifyPosition( key, @@ -167,7 +162,7 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { } function testBeforeModifyPositionObservationAndCardinality() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds geomeanOracle.increaseCardinalityNext(key, 2); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 27613654..94cca602 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -3,41 +3,38 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GetSender} from "./shared/GetSender.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {LimitOrder, Epoch, EpochLibrary} from "../contracts/hooks/examples/LimitOrder.sol"; import {LimitOrderImplementation} from "./shared/implementation/LimitOrderImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {TestERC20} from "@uniswap/v4-core/contracts/test/TestERC20.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; - -contract TestLimitOrder is Test, Deployers, TokenFixture { +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; + +contract TestLimitOrder is Test, Deployers { using PoolIdLibrary for PoolKey; uint160 constant SQRT_RATIO_10_1 = 250541448375047931186413801569; + HookEnabledSwapRouter router; TestERC20 token0; TestERC20 token1; - PoolManager manager; LimitOrder limitOrder = LimitOrder(address(uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_SWAP_FLAG))); - PoolKey key; PoolId id; - PoolSwapTest swapRouter; - function setUp() public { - initializeTokens(); + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + + router = new HookEnabledSwapRouter(manager); token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); - vm.record(); LimitOrderImplementation impl = new LimitOrderImplementation(manager, limitOrder); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -50,16 +47,13 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { } } - key = PoolKey(currency0, currency1, 3000, 60, limitOrder); - id = key.toId(); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - - swapRouter = new PoolSwapTest(manager); + // key = PoolKey(currency0, currency1, 3000, 60, limitOrder); + (key, id) = initPoolAndAddLiquidity(currency0, currency1, limitOrder, 3000, SQRT_RATIO_1_1, ZERO_BYTES); token0.approve(address(limitOrder), type(uint256).max); token1.approve(address(limitOrder), type(uint256).max); - token0.approve(address(swapRouter), type(uint256).max); - token1.approve(address(swapRouter), type(uint256).max); + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); } function testGetTickLowerLast() public { @@ -69,7 +63,7 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testGetTickLowerLastWithDifferentPrice() public { PoolKey memory differentKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 61, limitOrder); - manager.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); + initializeRouter.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); assertEq(limitOrder.getTickLowerLast(differentKey.toId()), 22997); } @@ -107,10 +101,10 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei - swapRouter.swap( + router.swap( key, - IPoolManager.SwapParams(false, 1, SQRT_RATIO_1_1 + 1), - PoolSwapTest.TestSettings(true, true), + IPoolManager.SwapParams(false, 1 ether, SQRT_RATIO_1_1 + 1), + HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); @@ -133,8 +127,11 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { function testNotZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei - swapRouter.swap( - key, IPoolManager.SwapParams(true, 1, SQRT_RATIO_1_1 - 1), PoolSwapTest.TestSettings(true, true), ZERO_BYTES + router.swap( + key, + IPoolManager.SwapParams(true, 1 ether, SQRT_RATIO_1_1 - 1), + HookEnabledSwapRouter.TestSettings(true, true), + ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); limitOrder.place(key, -60, false, 1000000); @@ -192,15 +189,15 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { uint128 liquidity = 1000000; limitOrder.place(key, tickLower, zeroForOne, liquidity); - swapRouter.swap( + router.swap( key, IPoolManager.SwapParams(false, 1e18, TickMath.getSqrtRatioAtTick(60)), - PoolSwapTest.TestSettings(true, true), + HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); assertEq(limitOrder.getTickLowerLast(id), 60); - (, int24 tick,,) = manager.getSlot0(id); + (, int24 tick,) = manager.getSlot0(id); assertEq(tick, 60); (bool filled,,, uint256 token0Total, uint256 token1Total,) = limitOrder.epochInfos(Epoch.wrap(1)); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 84ed9716..17ed64a1 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -3,26 +3,25 @@ pragma solidity ^0.8.15; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {TWAMMImplementation} from "./shared/implementation/TWAMMImplementation.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {PoolDonateTest} from "@uniswap/v4-core/contracts/test/PoolDonateTest.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TWAMM} from "../contracts/hooks/examples/TWAMM.sol"; import {ITWAMM} from "../contracts/interfaces/ITWAMM.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { +contract TWAMMTest is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; using CurrencyLibrary for Currency; @@ -49,10 +48,6 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG)) ); // TWAMM twamm; - PoolManager manager; - PoolModifyPositionTest modifyPositionRouter; - PoolSwapTest swapRouter; - PoolDonateTest donateRouter; address hookAddress; MockERC20 token0; MockERC20 token1; @@ -60,10 +55,11 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { PoolId poolId; function setUp() public { - initializeTokens(); + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = MockERC20(Currency.unwrap(currency0)); token1 = MockERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); TWAMMImplementation impl = new TWAMMImplementation(manager, 10_000, twamm); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -76,12 +72,7 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { } } - modifyPositionRouter = new PoolModifyPositionTest(IPoolManager(address(manager))); - swapRouter = new PoolSwapTest(IPoolManager(address(manager))); - - poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 60, twamm); - poolId = poolKey.toId(); - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_RATIO_1_1, ZERO_BYTES); token0.approve(address(modifyPositionRouter), 100 ether); token1.approve(address(modifyPositionRouter), 100 ether); @@ -100,7 +91,8 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { (PoolKey memory initKey, PoolId initId) = newPoolKeyWithTWAMM(twamm); assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); vm.warp(10000); - manager.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); + + initializeRouter.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); } @@ -242,7 +234,7 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); // takes 10% off the remaining half (so 80% of original sellrate) - assertEq(updatedSellRate, originalSellRate * 80 / 100); + assertEq(updatedSellRate, (originalSellRate * 80) / 100); assertEq(token0Owed, uint256(-amountDelta)); assertEq(token1Owed, orderAmount / 2); } @@ -267,7 +259,7 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); // takes 10% off the remaining half (so 80% of original sellrate) - assertEq(updatedSellRate, originalSellRate * 80 / 100); + assertEq(updatedSellRate, (originalSellRate * 80) / 100); assertEq(token0Owed, orderAmount / 2); assertEq(token1Owed, uint256(-amountDelta)); } @@ -416,8 +408,8 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { } function newPoolKeyWithTWAMM(IHooks hooks) public returns (PoolKey memory, PoolId) { - MockERC20[] memory tokens = deployTokens(2, 2 ** 255); - PoolKey memory key = PoolKey(Currency.wrap(address(tokens[0])), Currency.wrap(address(tokens[1])), 0, 60, hooks); + (Currency _token0, Currency _token1) = deployMintAndApprove2Currencies(); + PoolKey memory key = PoolKey(_token0, _token1, 0, 60, hooks); return (key, key.toId()); } diff --git a/test/shared/implementation/FullRangeImplementation.sol b/test/shared/implementation/FullRangeImplementation.sol index fcd8ae3f..63592f5c 100644 --- a/test/shared/implementation/FullRangeImplementation.sol +++ b/test/shared/implementation/FullRangeImplementation.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {FullRange} from "../../../contracts/hooks/examples/FullRange.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract FullRangeImplementation is FullRange { constructor(IPoolManager _poolManager, FullRange addressToEtch) FullRange(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/GeomeanOracleImplementation.sol b/test/shared/implementation/GeomeanOracleImplementation.sol index 06a95fa2..0c964671 100644 --- a/test/shared/implementation/GeomeanOracleImplementation.sol +++ b/test/shared/implementation/GeomeanOracleImplementation.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {GeomeanOracle} from "../../../contracts/hooks/examples/GeomeanOracle.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract GeomeanOracleImplementation is GeomeanOracle { uint32 public time; constructor(IPoolManager _poolManager, GeomeanOracle addressToEtch) GeomeanOracle(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/LimitOrderImplementation.sol b/test/shared/implementation/LimitOrderImplementation.sol index 340cfc42..c0f5a5f8 100644 --- a/test/shared/implementation/LimitOrderImplementation.sol +++ b/test/shared/implementation/LimitOrderImplementation.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {LimitOrder} from "../../../contracts/hooks/examples/LimitOrder.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract LimitOrderImplementation is LimitOrder { constructor(IPoolManager _poolManager, LimitOrder addressToEtch) LimitOrder(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/TWAMMImplementation.sol b/test/shared/implementation/TWAMMImplementation.sol index 012ca541..27a9e10c 100644 --- a/test/shared/implementation/TWAMMImplementation.sol +++ b/test/shared/implementation/TWAMMImplementation.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {TWAMM} from "../../../contracts/hooks/examples/TWAMM.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract TWAMMImplementation is TWAMM { constructor(IPoolManager poolManager, uint256 interval, TWAMM addressToEtch) TWAMM(poolManager, interval) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol new file mode 100644 index 00000000..54832b4a --- /dev/null +++ b/test/utils/HookEnabledSwapRouter.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolTestBase} from "@uniswap/v4-core/src/test/PoolTestBase.sol"; +import {Test} from "forge-std/Test.sol"; + +contract HookEnabledSwapRouter is PoolTestBase { + using CurrencyLibrary for Currency; + + error NoSwapOccurred(); + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + struct CallbackData { + address sender; + TestSettings testSettings; + PoolKey key; + IPoolManager.SwapParams params; + bytes hookData; + } + + struct TestSettings { + bool withdrawTokens; + bool settleUsingTransfer; + } + + function swap( + PoolKey memory key, + IPoolManager.SwapParams memory params, + TestSettings memory testSettings, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + delta = abi.decode( + manager.lock(address(this), abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + + function lockAcquired(address, /*sender*/ bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + BalanceDelta delta = manager.swap(data.key, data.params, data.hookData); + + // Make sure youve added liquidity to the test pool! + if (BalanceDelta.unwrap(delta) == 0) revert NoSwapOccurred(); + + if (data.params.zeroForOne) { + _settle(data.key.currency0, data.sender, delta.amount0(), data.testSettings.settleUsingTransfer); + if (delta.amount1() < 0) { + _take(data.key.currency1, data.sender, delta.amount1(), data.testSettings.withdrawTokens); + } + } else { + _settle(data.key.currency1, data.sender, delta.amount1(), data.testSettings.settleUsingTransfer); + if (delta.amount0() < 0) { + _take(data.key.currency0, data.sender, delta.amount0(), data.testSettings.withdrawTokens); + } + } + + return abi.encode(delta); + } +}