From f24202469e90e539306b39eaaf6b605f0d421fd8 Mon Sep 17 00:00:00 2001 From: Alice <34962750+hensha256@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:55:05 +0100 Subject: [PATCH] Clean up repo (#159) * Clean up repo * Move safe callback --- .env | 7 - README.md | 22 +- contracts/base/PeripheryPayments.sol | 41 - contracts/base/PeripheryValidation.sol | 11 - contracts/hooks/examples/FullRange.sol | 368 ---- contracts/hooks/examples/GeomeanOracle.sol | 186 -- contracts/hooks/examples/LimitOrder.sol | 418 ----- contracts/hooks/examples/TWAMM.sol | 654 ------- contracts/hooks/examples/VolatilityOracle.sol | 70 - contracts/interfaces/IPeripheryPayments.sol | 17 - contracts/interfaces/ITWAMM.sol | 136 -- contracts/interfaces/external/IERC1271.sol | 16 - contracts/libraries/LiquidityAmounts.sol | 134 -- contracts/libraries/Oracle.sol | 337 ---- contracts/libraries/PoolGetters.sol | 106 -- contracts/libraries/TWAMM/ABDKMathQuad.sol | 1546 ----------------- contracts/libraries/TWAMM/OrderPool.sol | 34 - contracts/libraries/TWAMM/TwammMath.sol | 179 -- contracts/libraries/TransferHelper.sol | 54 - contracts/libraries/UniswapV4ERC20.sol | 16 - foundry.toml | 2 +- {contracts => src}/base/ImmutableState.sol | 0 {contracts => src}/base/Multicall.sol | 0 {contracts => src}/base/SafeCallback.sol | 0 {contracts => src}/base/SelfPermit.sol | 0 {contracts => src/base/hooks}/BaseHook.sol | 4 +- {contracts => src}/interfaces/IMulticall.sol | 0 {contracts => src}/interfaces/IQuoter.sol | 0 {contracts => src}/interfaces/ISelfPermit.sol | 0 .../external/IERC20PermitAllowed.sol | 0 {contracts => src}/lens/Quoter.sol | 0 {contracts => src}/libraries/PathKey.sol | 0 .../libraries/PoolTicksCounter.sol | 1 - test/FullRange.t.sol | 782 --------- test/GeomeanOracle.t.sol | 237 --- test/LimitOrder.t.sol | 222 --- test/Oracle.t.sol | 867 --------- test/Quoter.t.sol | 18 +- test/TWAMM.t.sol | 432 ----- test/shared/GetSender.sol | 8 - .../FullRangeImplementation.sol | 16 - .../GeomeanOracleImplementation.sol | 26 - .../LimitOrderImplementation.sol | 16 - .../implementation/OracleImplementation.sol | 95 - .../implementation/TWAMMImplementation.sol | 16 - test/utils/HookEnabledSwapRouter.sol | 80 - 46 files changed, 14 insertions(+), 7160 deletions(-) delete mode 100644 .env delete mode 100644 contracts/base/PeripheryPayments.sol delete mode 100644 contracts/base/PeripheryValidation.sol delete mode 100644 contracts/hooks/examples/FullRange.sol delete mode 100644 contracts/hooks/examples/GeomeanOracle.sol delete mode 100644 contracts/hooks/examples/LimitOrder.sol delete mode 100644 contracts/hooks/examples/TWAMM.sol delete mode 100644 contracts/hooks/examples/VolatilityOracle.sol delete mode 100644 contracts/interfaces/IPeripheryPayments.sol delete mode 100644 contracts/interfaces/ITWAMM.sol delete mode 100644 contracts/interfaces/external/IERC1271.sol delete mode 100644 contracts/libraries/LiquidityAmounts.sol delete mode 100644 contracts/libraries/Oracle.sol delete mode 100644 contracts/libraries/PoolGetters.sol delete mode 100644 contracts/libraries/TWAMM/ABDKMathQuad.sol delete mode 100644 contracts/libraries/TWAMM/OrderPool.sol delete mode 100644 contracts/libraries/TWAMM/TwammMath.sol delete mode 100644 contracts/libraries/TransferHelper.sol delete mode 100644 contracts/libraries/UniswapV4ERC20.sol rename {contracts => src}/base/ImmutableState.sol (100%) rename {contracts => src}/base/Multicall.sol (100%) rename {contracts => src}/base/SafeCallback.sol (100%) rename {contracts => src}/base/SelfPermit.sol (100%) rename {contracts => src/base/hooks}/BaseHook.sol (97%) rename {contracts => src}/interfaces/IMulticall.sol (100%) rename {contracts => src}/interfaces/IQuoter.sol (100%) rename {contracts => src}/interfaces/ISelfPermit.sol (100%) rename {contracts => src}/interfaces/external/IERC20PermitAllowed.sol (100%) rename {contracts => src}/lens/Quoter.sol (100%) rename {contracts => src}/libraries/PathKey.sol (100%) rename {contracts => src}/libraries/PoolTicksCounter.sol (99%) delete mode 100644 test/FullRange.t.sol delete mode 100644 test/GeomeanOracle.t.sol delete mode 100644 test/LimitOrder.t.sol delete mode 100644 test/Oracle.t.sol delete mode 100644 test/TWAMM.t.sol delete mode 100644 test/shared/GetSender.sol delete mode 100644 test/shared/implementation/FullRangeImplementation.sol delete mode 100644 test/shared/implementation/GeomeanOracleImplementation.sol delete mode 100644 test/shared/implementation/LimitOrderImplementation.sol delete mode 100644 test/shared/implementation/OracleImplementation.sol delete mode 100644 test/shared/implementation/TWAMMImplementation.sol delete mode 100644 test/utils/HookEnabledSwapRouter.sol diff --git a/.env b/.env deleted file mode 100644 index 7859e840..00000000 --- a/.env +++ /dev/null @@ -1,7 +0,0 @@ -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/README.md b/README.md index b3355a10..4e0da581 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,6 @@ Uniswap v4 is a new automated market maker protocol that provides extensibility If you’re interested in contributing please see the [contribution guidelines](https://github.com/Uniswap/v4-periphery/blob/main/CONTRIBUTING.md)! -## Repository Structure - -```solidity -contracts/ -----hooks/ - ----examples/ - | GeomeanOracle.sol - | LimitOrder.sol - | TWAMM.sol - | VolatilityOracle.sol -----libraries/ - | Oracle.sol -BaseHook.sol -test/ -``` - -To showcase the power of hooks, this repository provides some interesting examples in the `/hooks/examples/` folder. Note that none of the contracts in this repository are fully production-ready, and the final design for some of the example hooks could look different. - -Eventually, some hooks that have been audited and are considered production-ready will be placed in the root `hooks` folder. Not all hooks will be safe or valuable to users. This repository will maintain a limited set of hook contracts. Even a well-designed and audited hook contract may not be accepted in this repo. - ## Local Deployment and Usage To utilize the contracts and deploy to a local testnet, you can install the code in your repo with forge: @@ -38,7 +18,7 @@ If you are building hooks, it may be useful to inherit from the `BaseHook` contr ```solidity -import {BaseHook} from 'v4-periphery/contracts/BaseHook.sol'; +import {BaseHook} from 'v4-periphery/src/base/hooks/BaseHook.sol'; contract CoolHook is BaseHook { // Override the hook callbacks you want on your hook diff --git a/contracts/base/PeripheryPayments.sol b/contracts/base/PeripheryPayments.sol deleted file mode 100644 index 24466924..00000000 --- a/contracts/base/PeripheryPayments.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {ERC20} from "solmate/tokens/ERC20.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"; - -abstract contract PeripheryPayments is IPeripheryPayments { - using CurrencyLibrary for Currency; - using SafeTransferLib for address; - using SafeTransferLib for ERC20; - - error InsufficientToken(); - error NativeTokenTransferFrom(); - - /// @inheritdoc IPeripheryPayments - function sweepToken(Currency currency, uint256 amountMinimum, address recipient) public payable override { - uint256 balanceCurrency = currency.balanceOfSelf(); - if (balanceCurrency < amountMinimum) revert InsufficientToken(); - - if (balanceCurrency > 0) { - currency.transfer(recipient, balanceCurrency); - } - } - - /// @param currency The currency to pay - /// @param payer The entity that must pay - /// @param recipient The entity that will receive payment - /// @param value The amount to pay - function pay(Currency currency, address payer, address recipient, uint256 value) internal { - if (payer == address(this)) { - // pay with tokens already in the contract (for the exact input multihop case) - currency.transfer(recipient, value); - } else { - if (currency.isNative()) revert NativeTokenTransferFrom(); - // pull payment - ERC20(Currency.unwrap(currency)).safeTransferFrom(payer, recipient, value); - } - } -} diff --git a/contracts/base/PeripheryValidation.sol b/contracts/base/PeripheryValidation.sol deleted file mode 100644 index b8ea81d4..00000000 --- a/contracts/base/PeripheryValidation.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -abstract contract PeripheryValidation { - error TransactionTooOld(); - - modifier checkDeadline(uint256 deadline) { - if (block.timestamp > deadline) revert TransactionTooOld(); - _; - } -} diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol deleted file mode 100644 index 191593b8..00000000 --- a/contracts/hooks/examples/FullRange.sol +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -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/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 {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; -import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; -import {UniswapV4ERC20} from "../../libraries/UniswapV4ERC20.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"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; - -import "../../libraries/LiquidityAmounts.sol"; - -contract FullRange is BaseHook { - using CurrencyLibrary for Currency; - using CurrencySettler for Currency; - using PoolIdLibrary for PoolKey; - using SafeCast for uint256; - using SafeCast for uint128; - using StateLibrary for IPoolManager; - - /// @notice Thrown when trying to interact with a non-initialized pool - error PoolNotInitialized(); - error TickSpacingNotDefault(); - error LiquidityDoesntMeetMinimum(); - error SenderMustBeHook(); - error ExpiredPastDeadline(); - error TooMuchSlippage(); - - bytes internal constant ZERO_BYTES = bytes(""); - - /// @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 - int24 internal constant MAX_TICK = -MIN_TICK; - - int256 internal constant MAX_INT = type(int256).max; - uint16 internal constant MINIMUM_LIQUIDITY = 1000; - - struct CallbackData { - address sender; - PoolKey key; - IPoolManager.ModifyLiquidityParams params; - } - - struct PoolInfo { - bool hasAccruedFees; - address liquidityToken; - } - - struct AddLiquidityParams { - Currency currency0; - Currency currency1; - uint24 fee; - uint256 amount0Desired; - uint256 amount1Desired; - uint256 amount0Min; - uint256 amount1Min; - address to; - uint256 deadline; - } - - struct RemoveLiquidityParams { - Currency currency0; - Currency currency1; - uint24 fee; - uint256 liquidity; - uint256 deadline; - } - - mapping(PoolId => PoolInfo) public poolInfo; - - constructor(IPoolManager _manager) BaseHook(_manager) {} - - modifier ensure(uint256 deadline) { - if (deadline < block.timestamp) revert ExpiredPastDeadline(); - _; - } - - function getHookPermissions() public pure override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: true, - afterInitialize: false, - beforeAddLiquidity: true, - beforeRemoveLiquidity: false, - afterAddLiquidity: false, - afterRemoveLiquidity: false, - beforeSwap: true, - afterSwap: false, - beforeDonate: false, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); - } - - function addLiquidity(AddLiquidityParams calldata params) - external - ensure(params.deadline) - returns (uint128 liquidity) - { - PoolKey memory key = PoolKey({ - currency0: params.currency0, - currency1: params.currency1, - fee: params.fee, - tickSpacing: 60, - hooks: IHooks(address(this)) - }); - - PoolId poolId = key.toId(); - - (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); - - if (sqrtPriceX96 == 0) revert PoolNotInitialized(); - - PoolInfo storage pool = poolInfo[poolId]; - - uint128 poolLiquidity = manager.getLiquidity(poolId); - - liquidity = LiquidityAmounts.getLiquidityForAmounts( - sqrtPriceX96, - TickMath.getSqrtPriceAtTick(MIN_TICK), - TickMath.getSqrtPriceAtTick(MAX_TICK), - params.amount0Desired, - params.amount1Desired - ); - - if (poolLiquidity == 0 && liquidity <= MINIMUM_LIQUIDITY) { - revert LiquidityDoesntMeetMinimum(); - } - BalanceDelta addedDelta = modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: MIN_TICK, - tickUpper: MAX_TICK, - liquidityDelta: liquidity.toInt256(), - salt: 0 - }) - ); - - if (poolLiquidity == 0) { - // permanently lock the first MINIMUM_LIQUIDITY tokens - liquidity -= MINIMUM_LIQUIDITY; - UniswapV4ERC20(pool.liquidityToken).mint(address(0), MINIMUM_LIQUIDITY); - } - - UniswapV4ERC20(pool.liquidityToken).mint(params.to, liquidity); - - if (uint128(-addedDelta.amount0()) < params.amount0Min || uint128(-addedDelta.amount1()) < params.amount1Min) { - revert TooMuchSlippage(); - } - } - - function removeLiquidity(RemoveLiquidityParams calldata params) - public - virtual - ensure(params.deadline) - returns (BalanceDelta delta) - { - PoolKey memory key = PoolKey({ - currency0: params.currency0, - currency1: params.currency1, - fee: params.fee, - tickSpacing: 60, - hooks: IHooks(address(this)) - }); - - PoolId poolId = key.toId(); - - (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); - - if (sqrtPriceX96 == 0) revert PoolNotInitialized(); - - UniswapV4ERC20 erc20 = UniswapV4ERC20(poolInfo[poolId].liquidityToken); - - delta = modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: MIN_TICK, - tickUpper: MAX_TICK, - liquidityDelta: -(params.liquidity.toInt256()), - salt: 0 - }) - ); - - erc20.burn(msg.sender, params.liquidity); - } - - function beforeInitialize(address, PoolKey calldata key, uint160, bytes calldata) - external - override - returns (bytes4) - { - if (key.tickSpacing != 60) revert TickSpacingNotDefault(); - - PoolId poolId = key.toId(); - - string memory tokenSymbol = string( - abi.encodePacked( - "UniV4", - "-", - IERC20Metadata(Currency.unwrap(key.currency0)).symbol(), - "-", - IERC20Metadata(Currency.unwrap(key.currency1)).symbol(), - "-", - Strings.toString(uint256(key.fee)) - ) - ); - address poolToken = address(new UniswapV4ERC20(tokenSymbol, tokenSymbol)); - - poolInfo[poolId] = PoolInfo({hasAccruedFees: false, liquidityToken: poolToken}); - - return FullRange.beforeInitialize.selector; - } - - function beforeAddLiquidity( - address sender, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - bytes calldata - ) external view override returns (bytes4) { - if (sender != address(this)) revert SenderMustBeHook(); - - return FullRange.beforeAddLiquidity.selector; - } - - function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) - external - override - returns (bytes4, BeforeSwapDelta, uint24) - { - PoolId poolId = key.toId(); - - if (!poolInfo[poolId].hasAccruedFees) { - PoolInfo storage pool = poolInfo[poolId]; - pool.hasAccruedFees = true; - } - - return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); - } - - function modifyLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) - internal - returns (BalanceDelta delta) - { - delta = abi.decode(manager.unlock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); - } - - function _settleDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - key.currency0.settle(manager, sender, uint256(int256(-delta.amount0())), false); - key.currency1.settle(manager, sender, uint256(int256(-delta.amount1())), false); - } - - function _takeDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { - manager.take(key.currency0, sender, uint256(uint128(delta.amount0()))); - manager.take(key.currency1, sender, uint256(uint128(delta.amount1()))); - } - - function _removeLiquidity(PoolKey memory key, IPoolManager.ModifyLiquidityParams memory params) - internal - returns (BalanceDelta delta) - { - PoolId poolId = key.toId(); - PoolInfo storage pool = poolInfo[poolId]; - - if (pool.hasAccruedFees) { - _rebalance(key); - } - - uint256 liquidityToRemove = FullMath.mulDiv( - uint256(-params.liquidityDelta), - manager.getLiquidity(poolId), - UniswapV4ERC20(pool.liquidityToken).totalSupply() - ); - - params.liquidityDelta = -(liquidityToRemove.toInt256()); - (delta,) = manager.modifyLiquidity(key, params, ZERO_BYTES); - pool.hasAccruedFees = false; - } - - function _unlockCallback(bytes calldata rawData) internal override returns (bytes memory) { - CallbackData memory data = abi.decode(rawData, (CallbackData)); - BalanceDelta delta; - - if (data.params.liquidityDelta < 0) { - delta = _removeLiquidity(data.key, data.params); - _takeDeltas(data.sender, data.key, delta); - } else { - (delta,) = manager.modifyLiquidity(data.key, data.params, ZERO_BYTES); - _settleDeltas(data.sender, data.key, delta); - } - return abi.encode(delta); - } - - function _rebalance(PoolKey memory key) public { - PoolId poolId = key.toId(); - (BalanceDelta balanceDelta,) = manager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: MIN_TICK, - tickUpper: MAX_TICK, - liquidityDelta: -(manager.getLiquidity(poolId).toInt256()), - salt: 0 - }), - ZERO_BYTES - ); - - uint160 newSqrtPriceX96 = ( - FixedPointMathLib.sqrt( - FullMath.mulDiv(uint128(balanceDelta.amount1()), FixedPoint96.Q96, uint128(balanceDelta.amount0())) - ) * FixedPointMathLib.sqrt(FixedPoint96.Q96) - ).toUint160(); - - (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); - - manager.swap( - key, - IPoolManager.SwapParams({ - zeroForOne: newSqrtPriceX96 < sqrtPriceX96, - amountSpecified: -MAX_INT - 1, // equivalent of type(int256).min - sqrtPriceLimitX96: newSqrtPriceX96 - }), - ZERO_BYTES - ); - - uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts( - newSqrtPriceX96, - TickMath.getSqrtPriceAtTick(MIN_TICK), - TickMath.getSqrtPriceAtTick(MAX_TICK), - uint256(uint128(balanceDelta.amount0())), - uint256(uint128(balanceDelta.amount1())) - ); - - (BalanceDelta balanceDeltaAfter,) = manager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: MIN_TICK, - tickUpper: MAX_TICK, - liquidityDelta: liquidity.toInt256(), - salt: 0 - }), - ZERO_BYTES - ); - - // Donate any "dust" from the sqrtRatio change as fees - uint128 donateAmount0 = uint128(balanceDelta.amount0() + balanceDeltaAfter.amount0()); - uint128 donateAmount1 = uint128(balanceDelta.amount1() + balanceDeltaAfter.amount1()); - - manager.donate(key, donateAmount0, donateAmount1, ZERO_BYTES); - } -} diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol deleted file mode 100644 index df5a9ad1..00000000 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -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/src/types/PoolKey.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; - -/// @notice A hook for a pool that allows a Uniswap pool to act as an oracle. Pools that use this hook must have full range -/// tick spacing and liquidity is always permanently locked in these pools. This is the suggested configuration -/// for protocols that wish to use a V3 style geomean oracle. -contract GeomeanOracle is BaseHook { - using Oracle for Oracle.Observation[65535]; - using PoolIdLibrary for PoolKey; - using StateLibrary for IPoolManager; - - /// @notice Oracle pools do not have fees because they exist to serve as an oracle for a pair of tokens - error OnlyOneOraclePoolAllowed(); - - /// @notice Oracle positions must be full range - error OraclePositionsMustBeFullRange(); - - /// @notice Oracle pools must have liquidity locked so that they cannot become more susceptible to price manipulation - error OraclePoolMustLockLiquidity(); - - /// @member index The index of the last written observation for the pool - /// @member cardinality The cardinality of the observations array for the pool - /// @member cardinalityNext The cardinality target of the observations array for the pool, which will replace cardinality when enough observations are written - struct ObservationState { - uint16 index; - uint16 cardinality; - uint16 cardinalityNext; - } - - /// @notice The list of observations for a given pool ID - mapping(PoolId => Oracle.Observation[65535]) public observations; - /// @notice The current observation array state for the given pool ID - mapping(PoolId => ObservationState) public states; - - /// @notice Returns the observation for the given pool key and observation index - function getObservation(PoolKey calldata key, uint256 index) - external - view - returns (Oracle.Observation memory observation) - { - observation = observations[PoolId.wrap(keccak256(abi.encode(key)))][index]; - } - - /// @notice Returns the state for the given pool key - function getState(PoolKey calldata key) external view returns (ObservationState memory state) { - state = states[PoolId.wrap(keccak256(abi.encode(key)))]; - } - - /// @dev For mocking - function _blockTimestamp() internal view virtual returns (uint32) { - return uint32(block.timestamp); - } - - constructor(IPoolManager _manager) BaseHook(_manager) {} - - function getHookPermissions() public pure override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: true, - afterInitialize: true, - beforeAddLiquidity: true, - beforeRemoveLiquidity: true, - afterAddLiquidity: false, - afterRemoveLiquidity: false, - beforeSwap: true, - afterSwap: false, - beforeDonate: false, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); - } - - function beforeInitialize(address, PoolKey calldata key, uint160, bytes calldata) - external - view - override - onlyByManager - returns (bytes4) - { - // This is to limit the fragmentation of pools using this oracle hook. In other words, - // there may only be one pool per pair of tokens that use this hook. The tick spacing is set to the maximum - // because we only allow max range liquidity in this pool. - if (key.fee != 0 || key.tickSpacing != manager.MAX_TICK_SPACING()) revert OnlyOneOraclePoolAllowed(); - return GeomeanOracle.beforeInitialize.selector; - } - - function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata) - external - override - onlyByManager - returns (bytes4) - { - PoolId id = key.toId(); - (states[id].cardinality, states[id].cardinalityNext) = observations[id].initialize(_blockTimestamp()); - return GeomeanOracle.afterInitialize.selector; - } - - /// @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,,) = manager.getSlot0(id); - - uint128 liquidity = manager.getLiquidity(id); - - (states[id].index, states[id].cardinality) = observations[id].write( - states[id].index, _blockTimestamp(), tick, liquidity, states[id].cardinality, states[id].cardinalityNext - ); - } - - function beforeAddLiquidity( - address, - PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata params, - bytes calldata - ) external override onlyByManager returns (bytes4) { - int24 maxTickSpacing = manager.MAX_TICK_SPACING(); - if ( - params.tickLower != TickMath.minUsableTick(maxTickSpacing) - || params.tickUpper != TickMath.maxUsableTick(maxTickSpacing) - ) revert OraclePositionsMustBeFullRange(); - _updatePool(key); - return GeomeanOracle.beforeAddLiquidity.selector; - } - - function beforeRemoveLiquidity( - address, - PoolKey calldata, - IPoolManager.ModifyLiquidityParams calldata, - bytes calldata - ) external view override onlyByManager returns (bytes4) { - revert OraclePoolMustLockLiquidity(); - } - - function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) - external - override - onlyByManager - returns (bytes4, BeforeSwapDelta, uint24) - { - _updatePool(key); - return (GeomeanOracle.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); - } - - /// @notice Observe the given pool for the timestamps - function observe(PoolKey calldata key, uint32[] calldata secondsAgos) - external - view - returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) - { - PoolId id = key.toId(); - - ObservationState memory state = states[id]; - - (, int24 tick,,) = manager.getSlot0(id); - - uint128 liquidity = manager.getLiquidity(id); - - return observations[id].observe(_blockTimestamp(), secondsAgos, tick, state.index, liquidity, state.cardinality); - } - - /// @notice Increase the cardinality target for the given pool - function increaseCardinalityNext(PoolKey calldata key, uint16 cardinalityNext) - external - returns (uint16 cardinalityNextOld, uint16 cardinalityNextNew) - { - PoolId id = PoolId.wrap(keccak256(abi.encode(key))); - - ObservationState storage state = states[id]; - - cardinalityNextOld = state.cardinalityNext; - cardinalityNextNew = observations[id].grow(cardinalityNextOld, cardinalityNext); - state.cardinalityNext = cardinalityNextNew; - } -} diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol deleted file mode 100644 index 2a8ca909..00000000 --- a/contracts/hooks/examples/LimitOrder.sol +++ /dev/null @@ -1,418 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -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/src/types/Currency.sol"; -import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; - -type Epoch is uint232; - -library EpochLibrary { - function equals(Epoch a, Epoch b) internal pure returns (bool) { - return Epoch.unwrap(a) == Epoch.unwrap(b); - } - - function unsafeIncrement(Epoch a) internal pure returns (Epoch) { - unchecked { - return Epoch.wrap(Epoch.unwrap(a) + 1); - } - } -} - -contract LimitOrder is BaseHook { - using EpochLibrary for Epoch; - using PoolIdLibrary for PoolKey; - using CurrencyLibrary for Currency; - using CurrencySettler for Currency; - using StateLibrary for IPoolManager; - - error ZeroLiquidity(); - error InRange(); - error CrossedRange(); - error Filled(); - error NotFilled(); - error NotPoolManagerToken(); - - event Place( - address indexed owner, Epoch indexed epoch, PoolKey key, int24 tickLower, bool zeroForOne, uint128 liquidity - ); - - event Fill(Epoch indexed epoch, PoolKey key, int24 tickLower, bool zeroForOne); - - event Kill( - address indexed owner, Epoch indexed epoch, PoolKey key, int24 tickLower, bool zeroForOne, uint128 liquidity - ); - - event Withdraw(address indexed owner, Epoch indexed epoch, uint128 liquidity); - - bytes internal constant ZERO_BYTES = bytes(""); - - Epoch private constant EPOCH_DEFAULT = Epoch.wrap(0); - - mapping(PoolId => int24) public tickLowerLasts; - Epoch public epochNext = Epoch.wrap(1); - - struct EpochInfo { - bool filled; - Currency currency0; - Currency currency1; - uint256 token0Total; - uint256 token1Total; - uint128 liquidityTotal; - mapping(address => uint128) liquidity; - } - - mapping(bytes32 => Epoch) public epochs; - mapping(Epoch => EpochInfo) public epochInfos; - - constructor(IPoolManager _manager) BaseHook(_manager) {} - - function getHookPermissions() public pure override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: false, - afterInitialize: true, - beforeAddLiquidity: false, - beforeRemoveLiquidity: false, - afterAddLiquidity: false, - afterRemoveLiquidity: false, - beforeSwap: false, - afterSwap: true, - beforeDonate: false, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); - } - - function getTickLowerLast(PoolId poolId) public view returns (int24) { - return tickLowerLasts[poolId]; - } - - function setTickLowerLast(PoolId poolId, int24 tickLower) private { - tickLowerLasts[poolId] = tickLower; - } - - function getEpoch(PoolKey memory key, int24 tickLower, bool zeroForOne) public view returns (Epoch) { - return epochs[keccak256(abi.encode(key, tickLower, zeroForOne))]; - } - - function setEpoch(PoolKey memory key, int24 tickLower, bool zeroForOne, Epoch epoch) private { - epochs[keccak256(abi.encode(key, tickLower, zeroForOne))] = epoch; - } - - function getEpochLiquidity(Epoch epoch, address owner) external view returns (uint256) { - return epochInfos[epoch].liquidity[owner]; - } - - function getTick(PoolId poolId) private view returns (int24 tick) { - (, tick,,) = manager.getSlot0(poolId); - } - - function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { - int24 compressed = tick / tickSpacing; - if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity - return compressed * tickSpacing; - } - - function afterInitialize(address, PoolKey calldata key, uint160, int24 tick, bytes calldata) - external - override - onlyByManager - returns (bytes4) - { - setTickLowerLast(key.toId(), getTickLower(tick, key.tickSpacing)); - return LimitOrder.afterInitialize.selector; - } - - function afterSwap( - address, - PoolKey calldata key, - IPoolManager.SwapParams calldata params, - BalanceDelta, - bytes calldata - ) external override onlyByManager returns (bytes4, int128) { - (int24 tickLower, int24 lower, int24 upper) = _getCrossedTicks(key.toId(), key.tickSpacing); - if (lower > upper) return (LimitOrder.afterSwap.selector, 0); - - // note that a zeroForOne swap means that the pool is actually gaining token0, so limit - // order fills are the opposite of swap fills, hence the inversion below - bool zeroForOne = !params.zeroForOne; - for (; lower <= upper; lower += key.tickSpacing) { - _fillEpoch(key, lower, zeroForOne); - } - - setTickLowerLast(key.toId(), tickLower); - return (LimitOrder.afterSwap.selector, 0); - } - - function _fillEpoch(PoolKey calldata key, int24 lower, bool zeroForOne) internal { - Epoch epoch = getEpoch(key, lower, zeroForOne); - if (!epoch.equals(EPOCH_DEFAULT)) { - EpochInfo storage epochInfo = epochInfos[epoch]; - - epochInfo.filled = true; - - (uint256 amount0, uint256 amount1) = - _unlockCallbackFill(key, lower, -int256(uint256(epochInfo.liquidityTotal))); - - unchecked { - epochInfo.token0Total += amount0; - epochInfo.token1Total += amount1; - } - - setEpoch(key, lower, zeroForOne, EPOCH_DEFAULT); - - emit Fill(epoch, key, lower, zeroForOne); - } - } - - function _getCrossedTicks(PoolId poolId, int24 tickSpacing) - internal - view - returns (int24 tickLower, int24 lower, int24 upper) - { - tickLower = getTickLower(getTick(poolId), tickSpacing); - int24 tickLowerLast = getTickLowerLast(poolId); - - if (tickLower < tickLowerLast) { - lower = tickLower + tickSpacing; - upper = tickLowerLast; - } else { - lower = tickLowerLast; - upper = tickLower - tickSpacing; - } - } - - function _unlockCallbackFill(PoolKey calldata key, int24 tickLower, int256 liquidityDelta) - private - onlyByManager - returns (uint128 amount0, uint128 amount1) - { - (BalanceDelta delta,) = manager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: tickLower, - tickUpper: tickLower + key.tickSpacing, - liquidityDelta: liquidityDelta, - salt: 0 - }), - ZERO_BYTES - ); - - if (delta.amount0() > 0) { - manager.mint(address(this), key.currency0.toId(), amount0 = uint128(delta.amount0())); - } - if (delta.amount1() > 0) { - manager.mint(address(this), key.currency1.toId(), amount1 = uint128(delta.amount1())); - } - } - - function place(PoolKey calldata key, int24 tickLower, bool zeroForOne, uint128 liquidity) - external - onlyValidPools(key.hooks) - { - if (liquidity == 0) revert ZeroLiquidity(); - - manager.unlock( - abi.encodeCall( - this.unlockCallbackPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender) - ) - ); - - EpochInfo storage epochInfo; - Epoch epoch = getEpoch(key, tickLower, zeroForOne); - if (epoch.equals(EPOCH_DEFAULT)) { - unchecked { - setEpoch(key, tickLower, zeroForOne, epoch = epochNext); - // since epoch was just assigned the current value of epochNext, - // this is equivalent to epochNext++, which is what's intended, - // and it saves an SLOAD - epochNext = epoch.unsafeIncrement(); - } - epochInfo = epochInfos[epoch]; - epochInfo.currency0 = key.currency0; - epochInfo.currency1 = key.currency1; - } else { - epochInfo = epochInfos[epoch]; - } - - unchecked { - epochInfo.liquidityTotal += liquidity; - epochInfo.liquidity[msg.sender] += liquidity; - } - - emit Place(msg.sender, epoch, key, tickLower, zeroForOne, liquidity); - } - - function unlockCallbackPlace( - PoolKey calldata key, - int24 tickLower, - bool zeroForOne, - int256 liquidityDelta, - address owner - ) external selfOnly { - (BalanceDelta delta,) = manager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: tickLower, - tickUpper: tickLower + key.tickSpacing, - liquidityDelta: liquidityDelta, - salt: 0 - }), - ZERO_BYTES - ); - - if (delta.amount0() < 0) { - if (delta.amount1() != 0) revert InRange(); - if (!zeroForOne) revert CrossedRange(); - key.currency0.settle(manager, owner, uint256(uint128(-delta.amount0())), false); - } else { - if (delta.amount0() != 0) revert InRange(); - if (zeroForOne) revert CrossedRange(); - key.currency1.settle(manager, owner, uint256(uint128(-delta.amount1())), false); - } - } - - function kill(PoolKey calldata key, int24 tickLower, bool zeroForOne, address to) external { - Epoch epoch = getEpoch(key, tickLower, zeroForOne); - EpochInfo storage epochInfo = epochInfos[epoch]; - - if (epochInfo.filled) revert Filled(); - - uint128 liquidity = epochInfo.liquidity[msg.sender]; - if (liquidity == 0) revert ZeroLiquidity(); - delete epochInfo.liquidity[msg.sender]; - - uint256 amount0Fee; - uint256 amount1Fee; - (amount0Fee, amount1Fee) = abi.decode( - manager.unlock( - abi.encodeCall( - this.unlockCallbackKill, - (key, tickLower, -int256(uint256(liquidity)), to, liquidity == epochInfo.liquidityTotal) - ) - ), - (uint256, uint256) - ); - epochInfo.liquidityTotal -= liquidity; - unchecked { - epochInfo.token0Total += amount0Fee; - epochInfo.token1Total += amount1Fee; - } - - emit Kill(msg.sender, epoch, key, tickLower, zeroForOne, liquidity); - } - - function unlockCallbackKill( - PoolKey calldata key, - int24 tickLower, - int256 liquidityDelta, - address to, - bool removingAllLiquidity - ) external selfOnly returns (uint128 amount0Fee, uint128 amount1Fee) { - int24 tickUpper = tickLower + key.tickSpacing; - - // because `modifyPosition` includes not just principal value but also fees, we cannot allocate - // the proceeds pro-rata. if we were to do so, users who have been in a limit order that's partially filled - // could be unfairly diluted by a user sychronously placing then killing a limit order to skim off fees. - // to prevent this, we allocate all fee revenue to remaining limit order placers, unless this is the last order. - if (!removingAllLiquidity) { - (, BalanceDelta deltaFee) = manager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: tickLower, - tickUpper: tickUpper, - liquidityDelta: 0, - salt: 0 - }), - ZERO_BYTES - ); - - if (deltaFee.amount0() > 0) { - manager.mint(address(this), key.currency0.toId(), amount0Fee = uint128(deltaFee.amount0())); - } - if (deltaFee.amount1() > 0) { - manager.mint(address(this), key.currency1.toId(), amount1Fee = uint128(deltaFee.amount1())); - } - } - - (BalanceDelta delta,) = manager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: tickLower, - tickUpper: tickUpper, - liquidityDelta: liquidityDelta, - salt: 0 - }), - ZERO_BYTES - ); - - if (delta.amount0() > 0) { - key.currency0.take(manager, to, uint256(uint128(delta.amount0())), false); - } - if (delta.amount1() > 0) { - key.currency1.take(manager, to, uint256(uint128(delta.amount1())), false); - } - } - - function withdraw(Epoch epoch, address to) external returns (uint256 amount0, uint256 amount1) { - EpochInfo storage epochInfo = epochInfos[epoch]; - - if (!epochInfo.filled) revert NotFilled(); - - uint128 liquidity = epochInfo.liquidity[msg.sender]; - if (liquidity == 0) revert ZeroLiquidity(); - delete epochInfo.liquidity[msg.sender]; - - uint128 liquidityTotal = epochInfo.liquidityTotal; - - amount0 = FullMath.mulDiv(epochInfo.token0Total, liquidity, liquidityTotal); - amount1 = FullMath.mulDiv(epochInfo.token1Total, liquidity, liquidityTotal); - - epochInfo.token0Total -= amount0; - epochInfo.token1Total -= amount1; - epochInfo.liquidityTotal = liquidityTotal - liquidity; - - manager.unlock( - abi.encodeCall( - this.unlockCallbackWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to) - ) - ); - - emit Withdraw(msg.sender, epoch, liquidity); - } - - function unlockCallbackWithdraw( - Currency currency0, - Currency currency1, - uint256 token0Amount, - uint256 token1Amount, - address to - ) external selfOnly { - if (token0Amount > 0) { - manager.burn(address(this), currency0.toId(), token0Amount); - manager.take(currency0, to, token0Amount); - } - if (token1Amount > 0) { - manager.burn(address(this), currency1.toId(), token1Amount); - manager.take(currency1, to, token1Amount); - } - } - - function onERC1155Received(address, address, uint256, uint256, bytes calldata) external view returns (bytes4) { - if (msg.sender != address(manager)) revert NotPoolManagerToken(); - return IERC1155Receiver.onERC1155Received.selector; - } -} diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol deleted file mode 100644 index dc1f3b00..00000000 --- a/contracts/hooks/examples/TWAMM.sol +++ /dev/null @@ -1,654 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.15; - -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/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/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/src/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; -import {PoolGetters} from "../../libraries/PoolGetters.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; -import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; - -contract TWAMM is BaseHook, ITWAMM { - using TransferHelper for IERC20Minimal; - using CurrencyLibrary for Currency; - using CurrencySettler for Currency; - using OrderPool for OrderPool.State; - using PoolIdLibrary for PoolKey; - using TickMath for int24; - using TickMath for uint160; - using SafeCast for uint256; - using PoolGetters for IPoolManager; - using TickBitmap for mapping(int16 => uint256); - using StateLibrary for IPoolManager; - - bytes internal constant ZERO_BYTES = bytes(""); - - int256 internal constant MIN_DELTA = -1; - bool internal constant ZERO_FOR_ONE = true; - bool internal constant ONE_FOR_ZERO = false; - - /// @notice Contains full state related to the TWAMM - /// @member lastVirtualOrderTimestamp Last timestamp in which virtual orders were executed - /// @member orderPool0For1 Order pool trading token0 for token1 of pool - /// @member orderPool1For0 Order pool trading token1 for token0 of pool - /// @member orders Mapping of orderId to individual orders on pool - struct State { - uint256 lastVirtualOrderTimestamp; - OrderPool.State orderPool0For1; - OrderPool.State orderPool1For0; - mapping(bytes32 => Order) orders; - } - - /// @inheritdoc ITWAMM - uint256 public immutable expirationInterval; - // twammStates[poolId] => Twamm.State - mapping(PoolId => State) internal twammStates; - // tokensOwed[token][owner] => amountOwed - mapping(Currency => mapping(address => uint256)) public tokensOwed; - - constructor(IPoolManager _manager, uint256 _expirationInterval) BaseHook(_manager) { - expirationInterval = _expirationInterval; - } - - function getHookPermissions() public pure override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: true, - afterInitialize: false, - beforeAddLiquidity: true, - beforeRemoveLiquidity: false, - afterAddLiquidity: false, - afterRemoveLiquidity: false, - beforeSwap: true, - afterSwap: false, - beforeDonate: false, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); - } - - function beforeInitialize(address, PoolKey calldata key, uint160, bytes calldata) - external - virtual - override - onlyByManager - returns (bytes4) - { - // one-time initialization enforced in PoolManager - initialize(_getTWAMM(key)); - return BaseHook.beforeInitialize.selector; - } - - function beforeAddLiquidity( - address, - PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata, - bytes calldata - ) external override onlyByManager returns (bytes4) { - executeTWAMMOrders(key); - return BaseHook.beforeAddLiquidity.selector; - } - - function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) - external - override - onlyByManager - returns (bytes4, BeforeSwapDelta, uint24) - { - executeTWAMMOrders(key); - return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); - } - - function lastVirtualOrderTimestamp(PoolId key) external view returns (uint256) { - return twammStates[key].lastVirtualOrderTimestamp; - } - - function getOrder(PoolKey calldata poolKey, OrderKey calldata orderKey) external view returns (Order memory) { - return _getOrder(twammStates[PoolId.wrap(keccak256(abi.encode(poolKey)))], orderKey); - } - - function getOrderPool(PoolKey calldata key, bool zeroForOne) - external - view - returns (uint256 sellRateCurrent, uint256 earningsFactorCurrent) - { - State storage twamm = _getTWAMM(key); - return zeroForOne - ? (twamm.orderPool0For1.sellRateCurrent, twamm.orderPool0For1.earningsFactorCurrent) - : (twamm.orderPool1For0.sellRateCurrent, twamm.orderPool1For0.earningsFactorCurrent); - } - - /// @notice Initialize TWAMM state - function initialize(State storage self) internal { - self.lastVirtualOrderTimestamp = block.timestamp; - } - - /// @inheritdoc ITWAMM - function executeTWAMMOrders(PoolKey memory key) public { - PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); - State storage twamm = twammStates[poolId]; - - (bool zeroForOne, uint160 sqrtPriceLimitX96) = - _executeTWAMMOrders(twamm, manager, key, PoolParamsOnExecute(sqrtPriceX96, manager.getLiquidity(poolId))); - - if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { - manager.unlock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96))); - } - } - - /// @inheritdoc ITWAMM - function submitOrder(PoolKey calldata key, OrderKey memory orderKey, uint256 amountIn) - external - returns (bytes32 orderId) - { - PoolId poolId = PoolId.wrap(keccak256(abi.encode(key))); - State storage twamm = twammStates[poolId]; - executeTWAMMOrders(key); - - uint256 sellRate; - unchecked { - // checks done in TWAMM library - uint256 duration = orderKey.expiration - block.timestamp; - sellRate = amountIn / duration; - orderId = _submitOrder(twamm, orderKey, sellRate); - IERC20Minimal(orderKey.zeroForOne ? Currency.unwrap(key.currency0) : Currency.unwrap(key.currency1)) - .safeTransferFrom(msg.sender, address(this), sellRate * duration); - } - - emit SubmitOrder( - poolId, - orderKey.owner, - orderKey.expiration, - orderKey.zeroForOne, - sellRate, - _getOrder(twamm, orderKey).earningsFactorLast - ); - } - - /// @notice Submits a new long term order into the TWAMM - /// @dev executeTWAMMOrders must be executed up to current timestamp before calling submitOrder - /// @param orderKey The OrderKey for the new order - function _submitOrder(State storage self, OrderKey memory orderKey, uint256 sellRate) - internal - returns (bytes32 orderId) - { - if (orderKey.owner != msg.sender) revert MustBeOwner(orderKey.owner, msg.sender); - if (self.lastVirtualOrderTimestamp == 0) revert NotInitialized(); - if (orderKey.expiration <= block.timestamp) revert ExpirationLessThanBlocktime(orderKey.expiration); - if (sellRate == 0) revert SellRateCannotBeZero(); - if (orderKey.expiration % expirationInterval != 0) revert ExpirationNotOnInterval(orderKey.expiration); - - orderId = _orderId(orderKey); - if (self.orders[orderId].sellRate != 0) revert OrderAlreadyExists(orderKey); - - OrderPool.State storage orderPool = orderKey.zeroForOne ? self.orderPool0For1 : self.orderPool1For0; - - unchecked { - orderPool.sellRateCurrent += sellRate; - orderPool.sellRateEndingAtInterval[orderKey.expiration] += sellRate; - } - - self.orders[orderId] = Order({sellRate: sellRate, earningsFactorLast: orderPool.earningsFactorCurrent}); - } - - /// @inheritdoc ITWAMM - function updateOrder(PoolKey memory key, OrderKey memory orderKey, int256 amountDelta) - external - returns (uint256 tokens0Owed, uint256 tokens1Owed) - { - PoolId poolId = PoolId.wrap(keccak256(abi.encode(key))); - State storage twamm = twammStates[poolId]; - - executeTWAMMOrders(key); - - // This call reverts if the caller is not the owner of the order - (uint256 buyTokensOwed, uint256 sellTokensOwed, uint256 newSellrate, uint256 newEarningsFactorLast) = - _updateOrder(twamm, orderKey, amountDelta); - - if (orderKey.zeroForOne) { - tokens0Owed += sellTokensOwed; - tokens1Owed += buyTokensOwed; - } else { - tokens0Owed += buyTokensOwed; - tokens1Owed += sellTokensOwed; - } - - tokensOwed[key.currency0][orderKey.owner] += tokens0Owed; - tokensOwed[key.currency1][orderKey.owner] += tokens1Owed; - - if (amountDelta > 0) { - IERC20Minimal(orderKey.zeroForOne ? Currency.unwrap(key.currency0) : Currency.unwrap(key.currency1)) - .safeTransferFrom(msg.sender, address(this), uint256(amountDelta)); - } - - emit UpdateOrder( - poolId, orderKey.owner, orderKey.expiration, orderKey.zeroForOne, newSellrate, newEarningsFactorLast - ); - } - - function _updateOrder(State storage self, OrderKey memory orderKey, int256 amountDelta) - internal - returns (uint256 buyTokensOwed, uint256 sellTokensOwed, uint256 newSellRate, uint256 earningsFactorLast) - { - Order storage order = _getOrder(self, orderKey); - OrderPool.State storage orderPool = orderKey.zeroForOne ? self.orderPool0For1 : self.orderPool1For0; - - if (orderKey.owner != msg.sender) revert MustBeOwner(orderKey.owner, msg.sender); - if (order.sellRate == 0) revert OrderDoesNotExist(orderKey); - if (amountDelta != 0 && orderKey.expiration <= block.timestamp) revert CannotModifyCompletedOrder(orderKey); - - unchecked { - uint256 earningsFactor = orderPool.earningsFactorCurrent - order.earningsFactorLast; - buyTokensOwed = (earningsFactor * order.sellRate) >> FixedPoint96.RESOLUTION; - earningsFactorLast = orderPool.earningsFactorCurrent; - order.earningsFactorLast = earningsFactorLast; - - if (orderKey.expiration <= block.timestamp) { - delete self.orders[_orderId(orderKey)]; - } - - if (amountDelta != 0) { - uint256 duration = orderKey.expiration - block.timestamp; - uint256 unsoldAmount = order.sellRate * duration; - if (amountDelta == MIN_DELTA) amountDelta = -(unsoldAmount.toInt256()); - int256 newSellAmount = unsoldAmount.toInt256() + amountDelta; - if (newSellAmount < 0) revert InvalidAmountDelta(orderKey, unsoldAmount, amountDelta); - - newSellRate = uint256(newSellAmount) / duration; - - if (amountDelta < 0) { - uint256 sellRateDelta = order.sellRate - newSellRate; - orderPool.sellRateCurrent -= sellRateDelta; - orderPool.sellRateEndingAtInterval[orderKey.expiration] -= sellRateDelta; - sellTokensOwed = uint256(-amountDelta); - } else { - uint256 sellRateDelta = newSellRate - order.sellRate; - orderPool.sellRateCurrent += sellRateDelta; - orderPool.sellRateEndingAtInterval[orderKey.expiration] += sellRateDelta; - } - if (newSellRate == 0) { - delete self.orders[_orderId(orderKey)]; - } else { - order.sellRate = newSellRate; - } - } - } - } - - /// @inheritdoc ITWAMM - function claimTokens(Currency token, address to, uint256 amountRequested) - external - returns (uint256 amountTransferred) - { - uint256 currentBalance = token.balanceOfSelf(); - amountTransferred = tokensOwed[token][msg.sender]; - if (amountRequested != 0 && amountRequested < amountTransferred) amountTransferred = amountRequested; - if (currentBalance < amountTransferred) amountTransferred = currentBalance; // to catch precision errors - tokensOwed[token][msg.sender] -= amountTransferred; - IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); - } - - function _unlockCallback(bytes calldata rawData) internal override returns (bytes memory) { - (PoolKey memory key, IPoolManager.SwapParams memory swapParams) = - abi.decode(rawData, (PoolKey, IPoolManager.SwapParams)); - - BalanceDelta delta = manager.swap(key, swapParams, ZERO_BYTES); - - if (swapParams.zeroForOne) { - if (delta.amount0() < 0) { - key.currency0.settle(manager, address(this), uint256(uint128(-delta.amount0())), false); - } - if (delta.amount1() > 0) { - key.currency1.take(manager, address(this), uint256(uint128(delta.amount1())), false); - } - } else { - if (delta.amount1() < 0) { - key.currency1.settle(manager, address(this), uint256(uint128(-delta.amount1())), false); - } - if (delta.amount0() > 0) { - key.currency0.take(manager, address(this), uint256(uint128(delta.amount0())), false); - } - } - return bytes(""); - } - - function _getTWAMM(PoolKey memory key) private view returns (State storage) { - return twammStates[PoolId.wrap(keccak256(abi.encode(key)))]; - } - - struct PoolParamsOnExecute { - uint160 sqrtPriceX96; - uint128 liquidity; - } - - /// @notice Executes all existing long term orders in the TWAMM - /// @param pool The relevant state of the pool - function _executeTWAMMOrders( - State storage self, - IPoolManager manager, - PoolKey memory key, - PoolParamsOnExecute memory pool - ) internal returns (bool zeroForOne, uint160 newSqrtPriceX96) { - if (!_hasOutstandingOrders(self)) { - self.lastVirtualOrderTimestamp = block.timestamp; - return (false, 0); - } - - uint160 initialSqrtPriceX96 = pool.sqrtPriceX96; - uint256 prevTimestamp = self.lastVirtualOrderTimestamp; - uint256 nextExpirationTimestamp = prevTimestamp + (expirationInterval - (prevTimestamp % expirationInterval)); - - OrderPool.State storage orderPool0For1 = self.orderPool0For1; - OrderPool.State storage orderPool1For0 = self.orderPool1For0; - - unchecked { - while (nextExpirationTimestamp <= block.timestamp) { - if ( - orderPool0For1.sellRateEndingAtInterval[nextExpirationTimestamp] > 0 - || orderPool1For0.sellRateEndingAtInterval[nextExpirationTimestamp] > 0 - ) { - if (orderPool0For1.sellRateCurrent != 0 && orderPool1For0.sellRateCurrent != 0) { - pool = _advanceToNewTimestamp( - self, - manager, - key, - AdvanceParams( - expirationInterval, - nextExpirationTimestamp, - nextExpirationTimestamp - prevTimestamp, - pool - ) - ); - } else { - pool = _advanceTimestampForSinglePoolSell( - self, - manager, - key, - AdvanceSingleParams( - expirationInterval, - nextExpirationTimestamp, - nextExpirationTimestamp - prevTimestamp, - pool, - orderPool0For1.sellRateCurrent != 0 - ) - ); - } - prevTimestamp = nextExpirationTimestamp; - } - nextExpirationTimestamp += expirationInterval; - - if (!_hasOutstandingOrders(self)) break; - } - - if (prevTimestamp < block.timestamp && _hasOutstandingOrders(self)) { - if (orderPool0For1.sellRateCurrent != 0 && orderPool1For0.sellRateCurrent != 0) { - pool = _advanceToNewTimestamp( - self, - manager, - key, - AdvanceParams(expirationInterval, block.timestamp, block.timestamp - prevTimestamp, pool) - ); - } else { - pool = _advanceTimestampForSinglePoolSell( - self, - manager, - key, - AdvanceSingleParams( - expirationInterval, - block.timestamp, - block.timestamp - prevTimestamp, - pool, - orderPool0For1.sellRateCurrent != 0 - ) - ); - } - } - } - - self.lastVirtualOrderTimestamp = block.timestamp; - newSqrtPriceX96 = pool.sqrtPriceX96; - zeroForOne = initialSqrtPriceX96 > newSqrtPriceX96; - } - - struct AdvanceParams { - uint256 expirationInterval; - uint256 nextTimestamp; - uint256 secondsElapsed; - PoolParamsOnExecute pool; - } - - function _advanceToNewTimestamp( - State storage self, - IPoolManager manager, - PoolKey memory poolKey, - AdvanceParams memory params - ) private returns (PoolParamsOnExecute memory) { - uint160 finalSqrtPriceX96; - uint256 secondsElapsedX96 = params.secondsElapsed * FixedPoint96.Q96; - - OrderPool.State storage orderPool0For1 = self.orderPool0For1; - OrderPool.State storage orderPool1For0 = self.orderPool1For0; - - while (true) { - TwammMath.ExecutionUpdateParams memory executionParams = TwammMath.ExecutionUpdateParams( - secondsElapsedX96, - params.pool.sqrtPriceX96, - params.pool.liquidity, - orderPool0For1.sellRateCurrent, - orderPool1For0.sellRateCurrent - ); - - finalSqrtPriceX96 = TwammMath.getNewSqrtPriceX96(executionParams); - - (bool crossingInitializedTick, int24 tick) = - _isCrossingInitializedTick(params.pool, manager, poolKey, finalSqrtPriceX96); - unchecked { - if (crossingInitializedTick) { - uint256 secondsUntilCrossingX96; - (params.pool, secondsUntilCrossingX96) = _advanceTimeThroughTickCrossing( - self, - manager, - poolKey, - TickCrossingParams(tick, params.nextTimestamp, secondsElapsedX96, params.pool) - ); - secondsElapsedX96 = secondsElapsedX96 - secondsUntilCrossingX96; - } else { - (uint256 earningsFactorPool0, uint256 earningsFactorPool1) = - TwammMath.calculateEarningsUpdates(executionParams, finalSqrtPriceX96); - - if (params.nextTimestamp % params.expirationInterval == 0) { - orderPool0For1.advanceToInterval(params.nextTimestamp, earningsFactorPool0); - orderPool1For0.advanceToInterval(params.nextTimestamp, earningsFactorPool1); - } else { - orderPool0For1.advanceToCurrentTime(earningsFactorPool0); - orderPool1For0.advanceToCurrentTime(earningsFactorPool1); - } - params.pool.sqrtPriceX96 = finalSqrtPriceX96; - break; - } - } - } - - return params.pool; - } - - struct AdvanceSingleParams { - uint256 expirationInterval; - uint256 nextTimestamp; - uint256 secondsElapsed; - PoolParamsOnExecute pool; - bool zeroForOne; - } - - function _advanceTimestampForSinglePoolSell( - State storage self, - IPoolManager manager, - PoolKey memory poolKey, - AdvanceSingleParams memory params - ) private returns (PoolParamsOnExecute memory) { - OrderPool.State storage orderPool = params.zeroForOne ? self.orderPool0For1 : self.orderPool1For0; - uint256 sellRateCurrent = orderPool.sellRateCurrent; - uint256 amountSelling = sellRateCurrent * params.secondsElapsed; - uint256 totalEarnings; - - while (true) { - uint160 finalSqrtPriceX96 = SqrtPriceMath.getNextSqrtPriceFromInput( - params.pool.sqrtPriceX96, params.pool.liquidity, amountSelling, params.zeroForOne - ); - - (bool crossingInitializedTick, int24 tick) = - _isCrossingInitializedTick(params.pool, manager, poolKey, finalSqrtPriceX96); - - if (crossingInitializedTick) { - (, int128 liquidityNetAtTick) = manager.getTickLiquidity(poolKey.toId(), tick); - uint160 initializedSqrtPrice = TickMath.getSqrtPriceAtTick(tick); - - uint256 swapDelta0 = SqrtPriceMath.getAmount0Delta( - params.pool.sqrtPriceX96, initializedSqrtPrice, params.pool.liquidity, true - ); - uint256 swapDelta1 = SqrtPriceMath.getAmount1Delta( - params.pool.sqrtPriceX96, initializedSqrtPrice, params.pool.liquidity, true - ); - - params.pool.liquidity = params.zeroForOne - ? params.pool.liquidity - uint128(liquidityNetAtTick) - : params.pool.liquidity + uint128(-liquidityNetAtTick); - params.pool.sqrtPriceX96 = initializedSqrtPrice; - - unchecked { - totalEarnings += params.zeroForOne ? swapDelta1 : swapDelta0; - amountSelling -= params.zeroForOne ? swapDelta0 : swapDelta1; - } - } else { - if (params.zeroForOne) { - totalEarnings += SqrtPriceMath.getAmount1Delta( - params.pool.sqrtPriceX96, finalSqrtPriceX96, params.pool.liquidity, true - ); - } else { - totalEarnings += SqrtPriceMath.getAmount0Delta( - params.pool.sqrtPriceX96, finalSqrtPriceX96, params.pool.liquidity, true - ); - } - - uint256 accruedEarningsFactor = (totalEarnings * FixedPoint96.Q96) / sellRateCurrent; - - if (params.nextTimestamp % params.expirationInterval == 0) { - orderPool.advanceToInterval(params.nextTimestamp, accruedEarningsFactor); - } else { - orderPool.advanceToCurrentTime(accruedEarningsFactor); - } - params.pool.sqrtPriceX96 = finalSqrtPriceX96; - break; - } - } - - return params.pool; - } - - struct TickCrossingParams { - int24 initializedTick; - uint256 nextTimestamp; - uint256 secondsElapsedX96; - PoolParamsOnExecute pool; - } - - function _advanceTimeThroughTickCrossing( - State storage self, - IPoolManager manager, - PoolKey memory poolKey, - TickCrossingParams memory params - ) private returns (PoolParamsOnExecute memory, uint256) { - uint160 initializedSqrtPrice = params.initializedTick.getSqrtPriceAtTick(); - - uint256 secondsUntilCrossingX96 = TwammMath.calculateTimeBetweenTicks( - params.pool.liquidity, - params.pool.sqrtPriceX96, - initializedSqrtPrice, - self.orderPool0For1.sellRateCurrent, - self.orderPool1For0.sellRateCurrent - ); - - (uint256 earningsFactorPool0, uint256 earningsFactorPool1) = TwammMath.calculateEarningsUpdates( - TwammMath.ExecutionUpdateParams( - secondsUntilCrossingX96, - params.pool.sqrtPriceX96, - params.pool.liquidity, - self.orderPool0For1.sellRateCurrent, - self.orderPool1For0.sellRateCurrent - ), - initializedSqrtPrice - ); - - self.orderPool0For1.advanceToCurrentTime(earningsFactorPool0); - self.orderPool1For0.advanceToCurrentTime(earningsFactorPool1); - - unchecked { - // update pool - (, int128 liquidityNet) = manager.getTickLiquidity(poolKey.toId(), params.initializedTick); - if (initializedSqrtPrice < params.pool.sqrtPriceX96) liquidityNet = -liquidityNet; - params.pool.liquidity = liquidityNet < 0 - ? params.pool.liquidity - uint128(-liquidityNet) - : params.pool.liquidity + uint128(liquidityNet); - - params.pool.sqrtPriceX96 = initializedSqrtPrice; - } - return (params.pool, secondsUntilCrossingX96); - } - - function _isCrossingInitializedTick( - PoolParamsOnExecute memory pool, - IPoolManager manager, - PoolKey memory poolKey, - uint160 nextSqrtPriceX96 - ) internal view returns (bool crossingInitializedTick, int24 nextTickInit) { - // use current price as a starting point for nextTickInit - nextTickInit = pool.sqrtPriceX96.getTickAtSqrtPrice(); - int24 targetTick = nextSqrtPriceX96.getTickAtSqrtPrice(); - bool searchingLeft = nextSqrtPriceX96 < pool.sqrtPriceX96; - bool nextTickInitFurtherThanTarget = false; // initialize as false - - // nextTickInit returns the furthest tick within one word if no tick within that word is initialized - // so we must keep iterating if we haven't reached a tick further than our target tick - while (!nextTickInitFurtherThanTarget) { - unchecked { - if (searchingLeft) nextTickInit -= 1; - } - (nextTickInit, crossingInitializedTick) = manager.getNextInitializedTickWithinOneWord( - poolKey.toId(), nextTickInit, poolKey.tickSpacing, searchingLeft - ); - nextTickInitFurtherThanTarget = searchingLeft ? nextTickInit <= targetTick : nextTickInit > targetTick; - if (crossingInitializedTick == true) break; - } - if (nextTickInitFurtherThanTarget) crossingInitializedTick = false; - } - - function _getOrder(State storage self, OrderKey memory key) internal view returns (Order storage) { - return self.orders[_orderId(key)]; - } - - function _orderId(OrderKey memory key) private pure returns (bytes32) { - return keccak256(abi.encode(key)); - } - - function _hasOutstandingOrders(State storage self) internal view returns (bool) { - return self.orderPool0For1.sellRateCurrent != 0 || self.orderPool1For0.sellRateCurrent != 0; - } -} diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol deleted file mode 100644 index 2900632f..00000000 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; -import {BaseHook} from "../../BaseHook.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; - -contract VolatilityOracle is BaseHook { - using LPFeeLibrary for uint24; - - error MustUseDynamicFee(); - - uint32 immutable deployTimestamp; - - /// @dev For mocking - function _blockTimestamp() internal view virtual returns (uint32) { - return uint32(block.timestamp); - } - - constructor(IPoolManager _manager) BaseHook(_manager) { - deployTimestamp = _blockTimestamp(); - } - - function getHookPermissions() public pure override returns (Hooks.Permissions memory) { - return Hooks.Permissions({ - beforeInitialize: true, - afterInitialize: true, - beforeAddLiquidity: false, - beforeRemoveLiquidity: false, - afterAddLiquidity: false, - afterRemoveLiquidity: false, - beforeSwap: false, - afterSwap: false, - beforeDonate: false, - afterDonate: false, - beforeSwapReturnDelta: false, - afterSwapReturnDelta: false, - afterAddLiquidityReturnDelta: false, - afterRemoveLiquidityReturnDelta: false - }); - } - - function beforeInitialize(address, PoolKey calldata key, uint160, bytes calldata) - external - pure - override - returns (bytes4) - { - 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 - manager.updateDynamicLPFee(key, fee); // initial fee 0.30% - } - - function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata) - external - override - returns (bytes4) - { - setFee(key); - return BaseHook.afterInitialize.selector; - } -} diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol deleted file mode 100644 index f3c24660..00000000 --- a/contracts/interfaces/IPeripheryPayments.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; - -/// @title Periphery Payments -/// @notice Functions to ease deposits and withdrawals of ETH -interface IPeripheryPayments { - // TODO: figure out if we still need unwrapWETH9 from v3? - - /// @notice Transfers the full amount of a token held by this contract to recipient - /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users - /// @param currency The contract address of the token which will be transferred to `recipient` - /// @param amountMinimum The minimum amount of token required for a transfer - /// @param recipient The destination address of the token - function sweepToken(Currency currency, uint256 amountMinimum, address recipient) external payable; -} diff --git a/contracts/interfaces/ITWAMM.sol b/contracts/interfaces/ITWAMM.sol deleted file mode 100644 index 3b932d3c..00000000 --- a/contracts/interfaces/ITWAMM.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.15; - -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 - /// @param owner The owner of the order - /// @param currentAccount The invalid account attempting to interact with the order - error MustBeOwner(address owner, address currentAccount); - - /// @notice Thrown when trying to cancel an already completed order - /// @param orderKey The orderKey - error CannotModifyCompletedOrder(OrderKey orderKey); - - /// @notice Thrown when trying to submit an order with an expiration that isn't on the interval. - /// @param expiration The expiration timestamp of the order - error ExpirationNotOnInterval(uint256 expiration); - - /// @notice Thrown when trying to submit an order with an expiration time in the past. - /// @param expiration The expiration timestamp of the order - error ExpirationLessThanBlocktime(uint256 expiration); - - /// @notice Thrown when trying to submit an order without initializing TWAMM state first - error NotInitialized(); - - /// @notice Thrown when trying to submit an order that's already ongoing. - /// @param orderKey The already existing orderKey - error OrderAlreadyExists(OrderKey orderKey); - - /// @notice Thrown when trying to interact with an order that does not exist. - /// @param orderKey The already existing orderKey - error OrderDoesNotExist(OrderKey orderKey); - - /// @notice Thrown when trying to subtract more value from a long term order than exists - /// @param orderKey The orderKey - /// @param unsoldAmount The amount still unsold - /// @param amountDelta The amount delta for the order - error InvalidAmountDelta(OrderKey orderKey, uint256 unsoldAmount, int256 amountDelta); - - /// @notice Thrown when submitting an order with a sellRate of 0 - error SellRateCannotBeZero(); - - /// @notice Information associated with a long term order - /// @member sellRate Amount of tokens sold per interval - /// @member earningsFactorLast The accrued earnings factor from which to start claiming owed earnings for this order - struct Order { - uint256 sellRate; - uint256 earningsFactorLast; - } - - /// @notice Information that identifies an order - /// @member owner Owner of the order - /// @member expiration Timestamp when the order expires - /// @member zeroForOne Bool whether the order is zeroForOne - struct OrderKey { - address owner; - uint160 expiration; - bool zeroForOne; - } - - /// @notice Emitted when a new long term order is submitted - /// @param poolId The id of the corresponding pool - /// @param owner The owner of the new order - /// @param expiration The expiration timestamp of the order - /// @param zeroForOne Whether the order is selling token 0 for token 1 - /// @param sellRate The sell rate of tokens per second being sold in the order - /// @param earningsFactorLast The current earningsFactor of the order pool - event SubmitOrder( - PoolId indexed poolId, - address indexed owner, - uint160 expiration, - bool zeroForOne, - uint256 sellRate, - uint256 earningsFactorLast - ); - - /// @notice Emitted when a long term order is updated - /// @param poolId The id of the corresponding pool - /// @param owner The owner of the existing order - /// @param expiration The expiration timestamp of the order - /// @param zeroForOne Whether the order is selling token 0 for token 1 - /// @param sellRate The updated sellRate of tokens per second being sold in the order - /// @param earningsFactorLast The current earningsFactor of the order pool - /// (since updated orders will claim existing earnings) - event UpdateOrder( - PoolId indexed poolId, - address indexed owner, - uint160 expiration, - bool zeroForOne, - uint256 sellRate, - uint256 earningsFactorLast - ); - - /// @notice Time interval on which orders are allowed to expire. Conserves processing needed on execute. - function expirationInterval() external view returns (uint256); - - /// @notice Submits a new long term order into the TWAMM. Also executes TWAMM orders if not up to date. - /// @param key The PoolKey for which to identify the amm pool of the order - /// @param orderKey The OrderKey for the new order - /// @param amountIn The amount of sell token to add to the order. Some precision on amountIn may be lost up to the - /// magnitude of (orderKey.expiration - block.timestamp) - /// @return orderId The bytes32 ID of the order - function submitOrder(PoolKey calldata key, OrderKey calldata orderKey, uint256 amountIn) - external - returns (bytes32 orderId); - - /// @notice Update an existing long term order with current earnings, optionally modify the amount selling. - /// @param key The PoolKey for which to identify the amm pool of the order - /// @param orderKey The OrderKey for which to identify the order - /// @param amountDelta The delta for the order sell amount. Negative to remove from order, positive to add, or - /// -1 to remove full amount from order. - function updateOrder(PoolKey calldata key, OrderKey calldata orderKey, int256 amountDelta) - external - returns (uint256 tokens0Owed, uint256 tokens1Owed); - - /// @notice Claim tokens owed from TWAMM contract - /// @param token The token to claim - /// @param to The receipient of the claim - /// @param amountRequested The amount of tokens requested to claim. Set to 0 to claim all. - /// @return amountTransferred The total token amount to be collected - function claimTokens(Currency token, address to, uint256 amountRequested) - external - returns (uint256 amountTransferred); - - /// @notice Executes TWAMM orders on the pool, swapping on the pool itself to make up the difference between the - /// two TWAMM pools swapping against each other - /// @param key The pool key associated with the TWAMM - function executeTWAMMOrders(PoolKey memory key) external; - - function tokensOwed(Currency token, address owner) external returns (uint256); -} diff --git a/contracts/interfaces/external/IERC1271.sol b/contracts/interfaces/external/IERC1271.sol deleted file mode 100644 index dcb30cb8..00000000 --- a/contracts/interfaces/external/IERC1271.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.5.0; - -/// @title Interface for verifying contract-based account signatures -/// @notice Interface that verifies provided signature for the data -/// @dev Interface defined by EIP-1271 -interface IERC1271 { - /// @notice Returns whether the provided signature is valid for the provided data - /// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes. - /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5). - /// MUST allow external calls. - /// @param hash Hash of the data to be signed - /// @param signature Signature byte array associated with _data - /// @return magicValue The bytes4 magic value 0x1626ba7e - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); -} diff --git a/contracts/libraries/LiquidityAmounts.sol b/contracts/libraries/LiquidityAmounts.sol deleted file mode 100644 index 742e48f5..00000000 --- a/contracts/libraries/LiquidityAmounts.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; - -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 -library LiquidityAmounts { - /// @notice Downcasts uint256 to uint128 - /// @param x The uint258 to be downcasted - /// @return y The passed value, downcasted to uint128 - function toUint128(uint256 x) private pure returns (uint128 y) { - require((y = uint128(x)) == x); - } - - /// @notice Computes the amount of liquidity received for a given amount of token0 and price range - /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower)) - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary - /// @param amount0 The amount0 being sent in - /// @return liquidity The amount of returned liquidity - function getLiquidityForAmount0(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint256 amount0) - internal - pure - returns (uint128 liquidity) - { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96); - return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96)); - } - - /// @notice Computes the amount of liquidity received for a given amount of token1 and price range - /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)). - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary - /// @param amount1 The amount1 being sent in - /// @return liquidity The amount of returned liquidity - function getLiquidityForAmount1(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint256 amount1) - internal - pure - returns (uint128 liquidity) - { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96)); - } - - /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current - /// pool prices and the prices at the tick boundaries - /// @param sqrtRatioX96 A sqrt price representing the current pool prices - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary - /// @param amount0 The amount of token0 being sent in - /// @param amount1 The amount of token1 being sent in - /// @return liquidity The maximum amount of liquidity received - function getLiquidityForAmounts( - uint160 sqrtRatioX96, - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint256 amount0, - uint256 amount1 - ) internal pure returns (uint128 liquidity) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - if (sqrtRatioX96 <= sqrtRatioAX96) { - liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0); - } else if (sqrtRatioX96 < sqrtRatioBX96) { - uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0); - uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1); - - liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; - } else { - liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1); - } - } - - /// @notice Computes the amount of token0 for a given amount of liquidity and a price range - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary - /// @param liquidity The liquidity being valued - /// @return amount0 The amount of token0 - function getAmount0ForLiquidity(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity) - internal - pure - returns (uint256 amount0) - { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - return FullMath.mulDiv( - uint256(liquidity) << FixedPoint96.RESOLUTION, sqrtRatioBX96 - sqrtRatioAX96, sqrtRatioBX96 - ) / sqrtRatioAX96; - } - - /// @notice Computes the amount of token1 for a given amount of liquidity and a price range - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary - /// @param liquidity The liquidity being valued - /// @return amount1 The amount of token1 - function getAmount1ForLiquidity(uint160 sqrtRatioAX96, uint160 sqrtRatioBX96, uint128 liquidity) - internal - pure - returns (uint256 amount1) - { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); - } - - /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current - /// pool prices and the prices at the tick boundaries - /// @param sqrtRatioX96 A sqrt price representing the current pool prices - /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary - /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary - /// @param liquidity The liquidity being valued - /// @return amount0 The amount of token0 - /// @return amount1 The amount of token1 - function getAmountsForLiquidity( - uint160 sqrtRatioX96, - uint160 sqrtRatioAX96, - uint160 sqrtRatioBX96, - uint128 liquidity - ) internal pure returns (uint256 amount0, uint256 amount1) { - if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); - - if (sqrtRatioX96 <= sqrtRatioAX96) { - amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); - } else if (sqrtRatioX96 < sqrtRatioBX96) { - amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity); - amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity); - } else { - amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); - } - } -} diff --git a/contracts/libraries/Oracle.sol b/contracts/libraries/Oracle.sol deleted file mode 100644 index 822f356f..00000000 --- a/contracts/libraries/Oracle.sol +++ /dev/null @@ -1,337 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -/// @title Oracle -/// @notice Provides price and liquidity data useful for a wide variety of system designs -/// @dev Instances of stored oracle data, "observations", are collected in the oracle array -/// Every pool is initialized with an oracle array length of 1. Anyone can pay the SSTOREs to increase the -/// maximum length of the oracle array. New slots will be added when the array is fully populated. -/// Observations are overwritten when the full length of the oracle array is populated. -/// The most recent observation is available, independent of the length of the oracle array, by passing 0 to observe() -library Oracle { - /// @notice Thrown when trying to interact with an Oracle of a non-initialized pool - error OracleCardinalityCannotBeZero(); - - /// @notice Thrown when trying to observe a price that is older than the oldest recorded price - /// @param oldestTimestamp Timestamp of the oldest remaining observation - /// @param targetTimestamp Invalid timestamp targeted to be observed - error TargetPredatesOldestObservation(uint32 oldestTimestamp, uint32 targetTimestamp); - - struct Observation { - // the block timestamp of the observation - uint32 blockTimestamp; - // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized - int56 tickCumulative; - // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized - uint160 secondsPerLiquidityCumulativeX128; - // whether or not the observation is initialized - bool initialized; - } - - /// @notice Transforms a previous observation into a new observation, given the passage of time and the current tick and liquidity values - /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows - /// @param last The specified observation to be transformed - /// @param blockTimestamp The timestamp of the new observation - /// @param tick The active tick at the time of the new observation - /// @param liquidity The total in-range liquidity at the time of the new observation - /// @return Observation The newly populated observation - function transform(Observation memory last, uint32 blockTimestamp, int24 tick, uint128 liquidity) - private - pure - returns (Observation memory) - { - unchecked { - uint32 delta = blockTimestamp - last.blockTimestamp; - return Observation({ - blockTimestamp: blockTimestamp, - tickCumulative: last.tickCumulative + int56(tick) * int56(uint56(delta)), - secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128 - + ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)), - initialized: true - }); - } - } - - /// @notice Initialize the oracle array by writing the first slot. Called once for the lifecycle of the observations array - /// @param self The stored oracle array - /// @param time The time of the oracle initialization, via block.timestamp truncated to uint32 - /// @return cardinality The number of populated elements in the oracle array - /// @return cardinalityNext The new length of the oracle array, independent of population - function initialize(Observation[65535] storage self, uint32 time) - internal - returns (uint16 cardinality, uint16 cardinalityNext) - { - self[0] = Observation({ - blockTimestamp: time, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 0, - initialized: true - }); - return (1, 1); - } - - /// @notice Writes an oracle observation to the array - /// @dev Writable at most once per block. Index represents the most recently written element. cardinality and index must be tracked externally. - /// If the index is at the end of the allowable array length (according to cardinality), and the next cardinality - /// is greater than the current one, cardinality may be increased. This restriction is created to preserve ordering. - /// @param self The stored oracle array - /// @param index The index of the observation that was most recently written to the observations array - /// @param blockTimestamp The timestamp of the new observation - /// @param tick The active tick at the time of the new observation - /// @param liquidity The total in-range liquidity at the time of the new observation - /// @param cardinality The number of populated elements in the oracle array - /// @param cardinalityNext The new length of the oracle array, independent of population - /// @return indexUpdated The new index of the most recently written element in the oracle array - /// @return cardinalityUpdated The new cardinality of the oracle array - function write( - Observation[65535] storage self, - uint16 index, - uint32 blockTimestamp, - int24 tick, - uint128 liquidity, - uint16 cardinality, - uint16 cardinalityNext - ) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) { - unchecked { - Observation memory last = self[index]; - - // early return if we've already written an observation this block - if (last.blockTimestamp == blockTimestamp) return (index, cardinality); - - // if the conditions are right, we can bump the cardinality - if (cardinalityNext > cardinality && index == (cardinality - 1)) { - cardinalityUpdated = cardinalityNext; - } else { - cardinalityUpdated = cardinality; - } - - indexUpdated = (index + 1) % cardinalityUpdated; - self[indexUpdated] = transform(last, blockTimestamp, tick, liquidity); - } - } - - /// @notice Prepares the oracle array to store up to `next` observations - /// @param self The stored oracle array - /// @param current The current next cardinality of the oracle array - /// @param next The proposed next cardinality which will be populated in the oracle array - /// @return next The next cardinality which will be populated in the oracle array - function grow(Observation[65535] storage self, uint16 current, uint16 next) internal returns (uint16) { - unchecked { - if (current == 0) revert OracleCardinalityCannotBeZero(); - // no-op if the passed next value isn't greater than the current next value - if (next <= current) return current; - // store in each slot to prevent fresh SSTOREs in swaps - // this data will not be used because the initialized boolean is still false - for (uint16 i = current; i < next; i++) { - self[i].blockTimestamp = 1; - } - return next; - } - } - - /// @notice comparator for 32-bit timestamps - /// @dev safe for 0 or 1 overflows, a and b _must_ be chronologically before or equal to time - /// @param time A timestamp truncated to 32 bits - /// @param a A comparison timestamp from which to determine the relative position of `time` - /// @param b From which to determine the relative position of `time` - /// @return Whether `a` is chronologically <= `b` - function lte(uint32 time, uint32 a, uint32 b) private pure returns (bool) { - unchecked { - // if there hasn't been overflow, no need to adjust - if (a <= time && b <= time) return a <= b; - - uint256 aAdjusted = a > time ? a : a + 2 ** 32; - uint256 bAdjusted = b > time ? b : b + 2 ** 32; - - return aAdjusted <= bAdjusted; - } - } - - /// @notice Fetches the observations beforeOrAt and atOrAfter a target, i.e. where [beforeOrAt, atOrAfter] is satisfied. - /// The result may be the same observation, or adjacent observations. - /// @dev The answer must be contained in the array, used when the target is located within the stored observation - /// boundaries: older than the most recent observation and younger, or the same age as, the oldest observation - /// @param self The stored oracle array - /// @param time The current block.timestamp - /// @param target The timestamp at which the reserved observation should be for - /// @param index The index of the observation that was most recently written to the observations array - /// @param cardinality The number of populated elements in the oracle array - /// @return beforeOrAt The observation recorded before, or at, the target - /// @return atOrAfter The observation recorded at, or after, the target - function binarySearch(Observation[65535] storage self, uint32 time, uint32 target, uint16 index, uint16 cardinality) - private - view - returns (Observation memory beforeOrAt, Observation memory atOrAfter) - { - unchecked { - uint256 l = (index + 1) % cardinality; // oldest observation - uint256 r = l + cardinality - 1; // newest observation - uint256 i; - while (true) { - i = (l + r) / 2; - - beforeOrAt = self[i % cardinality]; - - // we've landed on an uninitialized tick, keep searching higher (more recently) - if (!beforeOrAt.initialized) { - l = i + 1; - continue; - } - - atOrAfter = self[(i + 1) % cardinality]; - - bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target); - - // check if we've found the answer! - if (targetAtOrAfter && lte(time, target, atOrAfter.blockTimestamp)) break; - - if (!targetAtOrAfter) r = i - 1; - else l = i + 1; - } - } - } - - /// @notice Fetches the observations beforeOrAt and atOrAfter a given target, i.e. where [beforeOrAt, atOrAfter] is satisfied - /// @dev Assumes there is at least 1 initialized observation. - /// Used by observeSingle() to compute the counterfactual accumulator values as of a given block timestamp. - /// @param self The stored oracle array - /// @param time The current block.timestamp - /// @param target The timestamp at which the reserved observation should be for - /// @param tick The active tick at the time of the returned or simulated observation - /// @param index The index of the observation that was most recently written to the observations array - /// @param liquidity The total pool liquidity at the time of the call - /// @param cardinality The number of populated elements in the oracle array - /// @return beforeOrAt The observation which occurred at, or before, the given timestamp - /// @return atOrAfter The observation which occurred at, or after, the given timestamp - function getSurroundingObservations( - Observation[65535] storage self, - uint32 time, - uint32 target, - int24 tick, - uint16 index, - uint128 liquidity, - uint16 cardinality - ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { - unchecked { - // optimistically set before to the newest observation - beforeOrAt = self[index]; - - // if the target is chronologically at or after the newest observation, we can early return - if (lte(time, beforeOrAt.blockTimestamp, target)) { - if (beforeOrAt.blockTimestamp == target) { - // if newest observation equals target, we're in the same block, so we can ignore atOrAfter - return (beforeOrAt, atOrAfter); - } else { - // otherwise, we need to transform - return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity)); - } - } - - // now, set before to the oldest observation - beforeOrAt = self[(index + 1) % cardinality]; - if (!beforeOrAt.initialized) beforeOrAt = self[0]; - - // ensure that the target is chronologically at or after the oldest observation - if (!lte(time, beforeOrAt.blockTimestamp, target)) { - revert TargetPredatesOldestObservation(beforeOrAt.blockTimestamp, target); - } - - // if we've reached this point, we have to binary search - return binarySearch(self, time, target, index, cardinality); - } - } - - /// @dev Reverts if an observation at or before the desired observation timestamp does not exist. - /// 0 may be passed as `secondsAgo' to return the current cumulative values. - /// If called with a timestamp falling between two observations, returns the counterfactual accumulator values - /// at exactly the timestamp between the two observations. - /// @param self The stored oracle array - /// @param time The current block timestamp - /// @param secondsAgo The amount of time to look back, in seconds, at which point to return an observation - /// @param tick The current tick - /// @param index The index of the observation that was most recently written to the observations array - /// @param liquidity The current in-range pool liquidity - /// @param cardinality The number of populated elements in the oracle array - /// @return tickCumulative The tick * time elapsed since the pool was first initialized, as of `secondsAgo` - /// @return secondsPerLiquidityCumulativeX128 The time elapsed / max(1, liquidity) since the pool was first initialized, as of `secondsAgo` - function observeSingle( - Observation[65535] storage self, - uint32 time, - uint32 secondsAgo, - int24 tick, - uint16 index, - uint128 liquidity, - uint16 cardinality - ) internal view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) { - unchecked { - if (secondsAgo == 0) { - Observation memory last = self[index]; - if (last.blockTimestamp != time) last = transform(last, time, tick, liquidity); - return (last.tickCumulative, last.secondsPerLiquidityCumulativeX128); - } - - uint32 target = time - secondsAgo; - - (Observation memory beforeOrAt, Observation memory atOrAfter) = - getSurroundingObservations(self, time, target, tick, index, liquidity, cardinality); - - if (target == beforeOrAt.blockTimestamp) { - // we're at the left boundary - return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128); - } else if (target == atOrAfter.blockTimestamp) { - // we're at the right boundary - return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128); - } else { - // we're in the middle - uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp; - uint32 targetDelta = target - beforeOrAt.blockTimestamp; - return ( - beforeOrAt.tickCumulative - + ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / int56(uint56(observationTimeDelta))) - * int56(uint56(targetDelta)), - beforeOrAt.secondsPerLiquidityCumulativeX128 - + uint160( - ( - uint256( - atOrAfter.secondsPerLiquidityCumulativeX128 - - beforeOrAt.secondsPerLiquidityCumulativeX128 - ) * targetDelta - ) / observationTimeDelta - ) - ); - } - } - } - - /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` - /// @dev Reverts if `secondsAgos` > oldest observation - /// @param self The stored oracle array - /// @param time The current block.timestamp - /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an observation - /// @param tick The current tick - /// @param index The index of the observation that was most recently written to the observations array - /// @param liquidity The current in-range pool liquidity - /// @param cardinality The number of populated elements in the oracle array - /// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo` - /// @return secondsPerLiquidityCumulativeX128s The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo` - function observe( - Observation[65535] storage self, - uint32 time, - uint32[] memory secondsAgos, - int24 tick, - uint16 index, - uint128 liquidity, - uint16 cardinality - ) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) { - unchecked { - if (cardinality == 0) revert OracleCardinalityCannotBeZero(); - - tickCumulatives = new int56[](secondsAgos.length); - secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length); - for (uint256 i = 0; i < secondsAgos.length; i++) { - (tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) = - observeSingle(self, time, secondsAgos[i], tick, index, liquidity, cardinality); - } - } - } -} diff --git a/contracts/libraries/PoolGetters.sol b/contracts/libraries/PoolGetters.sol deleted file mode 100644 index df31f3c1..00000000 --- a/contracts/libraries/PoolGetters.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; - -/// @title Helper functions to access pool information -/// TODO: Expose other getters on core with extsload. Only use when extsload is available and storage layout is frozen. -library PoolGetters { - uint256 constant POOL_SLOT = 10; - uint256 constant TICKS_OFFSET = 4; - uint256 constant TICK_BITMAP_OFFSET = 5; - - using StateLibrary for IPoolManager; - - function getNetLiquidityAtTick(IPoolManager poolManager, PoolId poolId, int24 tick) - internal - view - returns (int128 l) - { - bytes32 value = poolManager.extsload( - keccak256(abi.encode(tick, uint256(keccak256(abi.encode(poolId, POOL_SLOT))) + TICKS_OFFSET)) - ); - - assembly { - l := shr(128, and(value, shl(128, sub(shl(128, 1), 1)))) - } - } - - function getTickBitmapAtWord(IPoolManager poolManager, PoolId poolId, int16 word) - internal - view - returns (uint256 bm) - { - bm = uint256( - poolManager.extsload( - keccak256(abi.encode(word, uint256(keccak256(abi.encode(poolId, POOL_SLOT))) + TICK_BITMAP_OFFSET)) - ) - ); - } - - /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either - /// to the left (less than or equal to) or right (greater than) of the given tick - /// @param poolManager The mapping in which to compute the next initialized tick - /// @param tick The starting tick - /// @param tickSpacing The spacing between usable ticks - /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) - /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick - /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks - function getNextInitializedTickWithinOneWord( - IPoolManager poolManager, - PoolId poolId, - int24 tick, - int24 tickSpacing, - bool lte - ) internal view returns (int24 next, bool initialized) { - unchecked { - int24 compressed = tick / tickSpacing; - if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity - - if (lte) { - (int16 wordPos, uint8 bitPos) = position(compressed); - // 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 tickBitmap = poolManager.getTickBitmap(poolId, wordPos); - uint256 masked = tickBitmap & mask; - - // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word - initialized = masked != 0; - // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick - next = initialized - ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing - : (compressed - int24(uint24(bitPos))) * tickSpacing; - } else { - // start from the word of the next tick, since the current tick state doesn't matter - (int16 wordPos, uint8 bitPos) = position(compressed + 1); - // all the 1s at or to the left of the bitPos - uint256 mask = ~((1 << bitPos) - 1); - uint256 tickBitmap = poolManager.getTickBitmap(poolId, wordPos); - uint256 masked = tickBitmap & mask; - - // if there are no initialized ticks to the left of the current tick, return leftmost in the word - initialized = masked != 0; - // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick - next = initialized - ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing - : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; - } - } - } - - /// @notice Computes the position in the mapping where the initialized bit for a tick lives - /// @param tick The tick for which to compute the position - /// @return wordPos The key in the mapping containing the word in which the bit is stored - /// @return bitPos The bit position in the word where the flag is stored - function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { - unchecked { - wordPos = int16(tick >> 8); - bitPos = uint8(int8(tick % 256)); - } - } -} diff --git a/contracts/libraries/TWAMM/ABDKMathQuad.sol b/contracts/libraries/TWAMM/ABDKMathQuad.sol deleted file mode 100644 index 00fa5a05..00000000 --- a/contracts/libraries/TWAMM/ABDKMathQuad.sol +++ /dev/null @@ -1,1546 +0,0 @@ -// SPDX-License-Identifier: BSD-4-Clause -/* - * ABDK Math Quad Smart Contract Library. Copyright © 2019 by ABDK Consulting. - * Author: Mikhail Vladimirov - */ -pragma solidity ^0.8.0; - -/** - * Smart contract library of mathematical functions operating with IEEE 754 - * quadruple-precision binary floating-point numbers (quadruple precision - * numbers). As long as quadruple precision numbers are 16-bytes long, they are - * represented by bytes16 type. - */ -library ABDKMathQuad { - /* - * 0. - */ - bytes16 private constant POSITIVE_ZERO = 0x00000000000000000000000000000000; - - /* - * -0. - */ - bytes16 private constant NEGATIVE_ZERO = 0x80000000000000000000000000000000; - - /* - * +Infinity. - */ - bytes16 private constant POSITIVE_INFINITY = 0x7FFF0000000000000000000000000000; - - /* - * -Infinity. - */ - bytes16 private constant NEGATIVE_INFINITY = 0xFFFF0000000000000000000000000000; - - /* - * Canonical NaN value. - */ - bytes16 private constant NaN = 0x7FFF8000000000000000000000000000; - - /** - * Convert signed 256-bit integer number into quadruple precision number. - * - * @param x signed 256-bit integer number - * @return quadruple precision number - */ - function fromInt(int256 x) internal pure returns (bytes16) { - unchecked { - if (x == 0) { - return bytes16(0); - } else { - // We rely on overflow behavior here - uint256 result = uint256(x > 0 ? x : -x); - - uint256 msb = mostSignificantBit(result); - if (msb < 112) result <<= 112 - msb; - else if (msb > 112) result >>= msb - 112; - - result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16383 + msb << 112; - if (x < 0) result |= 0x80000000000000000000000000000000; - - return bytes16(uint128(result)); - } - } - } - - /** - * Convert quadruple precision number into signed 256-bit integer number - * rounding towards zero. Revert on overflow. - * - * @param x quadruple precision number - * @return signed 256-bit integer number - */ - function toInt(bytes16 x) internal pure returns (int256) { - unchecked { - uint256 exponent = uint128(x) >> 112 & 0x7FFF; - - require(exponent <= 16638); // Overflow - if (exponent < 16383) return 0; // Underflow - - uint256 result = uint256(uint128(x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 0x10000000000000000000000000000; - - if (exponent < 16495) result >>= 16495 - exponent; - else if (exponent > 16495) result <<= exponent - 16495; - - if (uint128(x) >= 0x80000000000000000000000000000000) { - // Negative - require(result <= 0x8000000000000000000000000000000000000000000000000000000000000000); - return -int256(result); // We rely on overflow behavior here - } else { - require(result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - return int256(result); - } - } - } - - /** - * Convert unsigned 256-bit integer number into quadruple precision number. - * - * @param x unsigned 256-bit integer number - * @return quadruple precision number - */ - function fromUInt(uint256 x) internal pure returns (bytes16) { - unchecked { - if (x == 0) { - return bytes16(0); - } else { - uint256 result = x; - - uint256 msb = mostSignificantBit(result); - if (msb < 112) result <<= 112 - msb; - else if (msb > 112) result >>= msb - 112; - - result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16383 + msb << 112; - - return bytes16(uint128(result)); - } - } - } - - /** - * Convert quadruple precision number into unsigned 256-bit integer number - * rounding towards zero. Revert on underflow. Note, that negative floating - * point numbers in range (-1.0 .. 0.0) may be converted to unsigned integer - * without error, because they are rounded to zero. - * - * @param x quadruple precision number - * @return unsigned 256-bit integer number - */ - function toUInt(bytes16 x) internal pure returns (uint256) { - unchecked { - uint256 exponent = uint128(x) >> 112 & 0x7FFF; - - if (exponent < 16383) return 0; // Underflow - - require(uint128(x) < 0x80000000000000000000000000000000); // Negative - - require(exponent <= 16638); // Overflow - uint256 result = uint256(uint128(x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 0x10000000000000000000000000000; - - if (exponent < 16495) result >>= 16495 - exponent; - else if (exponent > 16495) result <<= exponent - 16495; - - return result; - } - } - - /** - * Convert signed 128.128 bit fixed point number into quadruple precision - * number. - * - * @param x signed 128.128 bit fixed point number - * @return quadruple precision number - */ - function from128x128(int256 x) internal pure returns (bytes16) { - unchecked { - if (x == 0) { - return bytes16(0); - } else { - // We rely on overflow behavior here - uint256 result = uint256(x > 0 ? x : -x); - - uint256 msb = mostSignificantBit(result); - if (msb < 112) result <<= 112 - msb; - else if (msb > 112) result >>= msb - 112; - - result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16255 + msb << 112; - if (x < 0) result |= 0x80000000000000000000000000000000; - - return bytes16(uint128(result)); - } - } - } - - /** - * Convert quadruple precision number into signed 128.128 bit fixed point - * number. Revert on overflow. - * - * @param x quadruple precision number - * @return signed 128.128 bit fixed point number - */ - function to128x128(bytes16 x) internal pure returns (int256) { - unchecked { - uint256 exponent = uint128(x) >> 112 & 0x7FFF; - - require(exponent <= 16510); // Overflow - if (exponent < 16255) return 0; // Underflow - - uint256 result = uint256(uint128(x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 0x10000000000000000000000000000; - - if (exponent < 16367) result >>= 16367 - exponent; - else if (exponent > 16367) result <<= exponent - 16367; - - if (uint128(x) >= 0x80000000000000000000000000000000) { - // Negative - require(result <= 0x8000000000000000000000000000000000000000000000000000000000000000); - return -int256(result); // We rely on overflow behavior here - } else { - require(result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - return int256(result); - } - } - } - - /** - * Convert signed 64.64 bit fixed point number into quadruple precision - * number. - * - * @param x signed 64.64 bit fixed point number - * @return quadruple precision number - */ - function from64x64(int128 x) internal pure returns (bytes16) { - unchecked { - if (x == 0) { - return bytes16(0); - } else { - // We rely on overflow behavior here - uint256 result = uint128(x > 0 ? x : -x); - - uint256 msb = mostSignificantBit(result); - if (msb < 112) result <<= 112 - msb; - else if (msb > 112) result >>= msb - 112; - - result = result & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 16319 + msb << 112; - if (x < 0) result |= 0x80000000000000000000000000000000; - - return bytes16(uint128(result)); - } - } - } - - /** - * Convert quadruple precision number into signed 64.64 bit fixed point - * number. Revert on overflow. - * - * @param x quadruple precision number - * @return signed 64.64 bit fixed point number - */ - function to64x64(bytes16 x) internal pure returns (int128) { - unchecked { - uint256 exponent = uint128(x) >> 112 & 0x7FFF; - - require(exponent <= 16446); // Overflow - if (exponent < 16319) return 0; // Underflow - - uint256 result = uint256(uint128(x)) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF | 0x10000000000000000000000000000; - - if (exponent < 16431) result >>= 16431 - exponent; - else if (exponent > 16431) result <<= exponent - 16431; - - if (uint128(x) >= 0x80000000000000000000000000000000) { - // Negative - require(result <= 0x80000000000000000000000000000000); - return -int128(int256(result)); // We rely on overflow behavior here - } else { - require(result <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - return int128(int256(result)); - } - } - } - - /** - * Convert octuple precision number into quadruple precision number. - * - * @param x octuple precision number - * @return quadruple precision number - */ - function fromOctuple(bytes32 x) internal pure returns (bytes16) { - unchecked { - bool negative = x & 0x8000000000000000000000000000000000000000000000000000000000000000 > 0; - - uint256 exponent = uint256(x) >> 236 & 0x7FFFF; - uint256 significand = uint256(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - if (exponent == 0x7FFFF) { - if (significand > 0) return NaN; - else return negative ? NEGATIVE_INFINITY : POSITIVE_INFINITY; - } - - if (exponent > 278526) { - return negative ? NEGATIVE_INFINITY : POSITIVE_INFINITY; - } else if (exponent < 245649) { - return negative ? NEGATIVE_ZERO : POSITIVE_ZERO; - } else if (exponent < 245761) { - significand = - (significand | 0x100000000000000000000000000000000000000000000000000000000000) >> 245885 - exponent; - exponent = 0; - } else { - significand >>= 124; - exponent -= 245760; - } - - uint128 result = uint128(significand | exponent << 112); - if (negative) result |= 0x80000000000000000000000000000000; - - return bytes16(result); - } - } - - /** - * Convert quadruple precision number into octuple precision number. - * - * @param x quadruple precision number - * @return octuple precision number - */ - function toOctuple(bytes16 x) internal pure returns (bytes32) { - unchecked { - uint256 exponent = uint128(x) >> 112 & 0x7FFF; - - uint256 result = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - if (exponent == 0x7FFF) { - exponent = 0x7FFFF; - } // Infinity or NaN - else if (exponent == 0) { - if (result > 0) { - uint256 msb = mostSignificantBit(result); - result = result << 236 - msb & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - exponent = 245649 + msb; - } - } else { - result <<= 124; - exponent += 245760; - } - - result |= exponent << 236; - if (uint128(x) >= 0x80000000000000000000000000000000) { - result |= 0x8000000000000000000000000000000000000000000000000000000000000000; - } - - return bytes32(result); - } - } - - /** - * Convert double precision number into quadruple precision number. - * - * @param x double precision number - * @return quadruple precision number - */ - function fromDouble(bytes8 x) internal pure returns (bytes16) { - unchecked { - uint256 exponent = uint64(x) >> 52 & 0x7FF; - - uint256 result = uint64(x) & 0xFFFFFFFFFFFFF; - - if (exponent == 0x7FF) { - exponent = 0x7FFF; - } // Infinity or NaN - else if (exponent == 0) { - if (result > 0) { - uint256 msb = mostSignificantBit(result); - result = result << 112 - msb & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - exponent = 15309 + msb; - } - } else { - result <<= 60; - exponent += 15360; - } - - result |= exponent << 112; - if (x & 0x8000000000000000 > 0) { - result |= 0x80000000000000000000000000000000; - } - - return bytes16(uint128(result)); - } - } - - /** - * Convert quadruple precision number into double precision number. - * - * @param x quadruple precision number - * @return double precision number - */ - function toDouble(bytes16 x) internal pure returns (bytes8) { - unchecked { - bool negative = uint128(x) >= 0x80000000000000000000000000000000; - - uint256 exponent = uint128(x) >> 112 & 0x7FFF; - uint256 significand = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - if (exponent == 0x7FFF) { - if (significand > 0) { - return 0x7FF8000000000000; - } // NaN - else { - return negative - ? bytes8(0xFFF0000000000000) // -Infinity - : bytes8(0x7FF0000000000000); - } // Infinity - } - - if (exponent > 17406) { - return negative - ? bytes8(0xFFF0000000000000) // -Infinity - : bytes8(0x7FF0000000000000); - } // Infinity - else if (exponent < 15309) { - return negative - ? bytes8(0x8000000000000000) // -0 - : bytes8(0x0000000000000000); - } // 0 - else if (exponent < 15361) { - significand = (significand | 0x10000000000000000000000000000) >> 15421 - exponent; - exponent = 0; - } else { - significand >>= 60; - exponent -= 15360; - } - - uint64 result = uint64(significand | exponent << 52); - if (negative) result |= 0x8000000000000000; - - return bytes8(result); - } - } - - /** - * Test whether given quadruple precision number is NaN. - * - * @param x quadruple precision number - * @return true if x is NaN, false otherwise - */ - function isNaN(bytes16 x) internal pure returns (bool) { - unchecked { - return uint128(x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF > 0x7FFF0000000000000000000000000000; - } - } - - /** - * Test whether given quadruple precision number is positive or negative - * infinity. - * - * @param x quadruple precision number - * @return true if x is positive or negative infinity, false otherwise - */ - function isInfinity(bytes16 x) internal pure returns (bool) { - unchecked { - return uint128(x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x7FFF0000000000000000000000000000; - } - } - - /** - * Calculate sign of x, i.e. -1 if x is negative, 0 if x if zero, and 1 if x - * is positive. Note that sign (-0) is zero. Revert if x is NaN. - * - * @param x quadruple precision number - * @return sign of x - */ - function sign(bytes16 x) internal pure returns (int8) { - unchecked { - uint128 absoluteX = uint128(x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - require(absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN - - if (absoluteX == 0) return 0; - else if (uint128(x) >= 0x80000000000000000000000000000000) return -1; - else return 1; - } - } - - /** - * Calculate sign (x - y). Revert if either argument is NaN, or both - * arguments are infinities of the same sign. - * - * @param x quadruple precision number - * @param y quadruple precision number - * @return sign (x - y) - */ - function gt(bytes16 x, bytes16 y) internal pure returns (int8) { - unchecked { - uint128 absoluteX = uint128(x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - require(absoluteX <= 0x7FFF0000000000000000000000000000); // Not NaN - - uint128 absoluteY = uint128(y) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - require(absoluteY <= 0x7FFF0000000000000000000000000000); // Not NaN - - // Not infinities of the same sign - require(x != y || absoluteX < 0x7FFF0000000000000000000000000000); - - if (x == y) { - return 0; - } else { - bool negativeX = uint128(x) >= 0x80000000000000000000000000000000; - bool negativeY = uint128(y) >= 0x80000000000000000000000000000000; - - if (negativeX) { - if (negativeY) return absoluteX > absoluteY ? -1 : int8(1); - else return -1; - } else { - if (negativeY) return 1; - else return absoluteX > absoluteY ? int8(1) : -1; - } - } - } - } - - /** - * Test whether x equals y. NaN, infinity, and -infinity are not equal to - * anything. - * - * @param x quadruple precision number - * @param y quadruple precision number - * @return true if x equals to y, false otherwise - */ - function eq(bytes16 x, bytes16 y) internal pure returns (bool) { - unchecked { - if (x == y) { - return uint128(x) & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF < 0x7FFF0000000000000000000000000000; - } else { - return false; - } - } - } - - /** - * Calculate x + y. Special values behave in the following way: - * - * NaN + x = NaN for any x. - * Infinity + x = Infinity for any finite x. - * -Infinity + x = -Infinity for any finite x. - * Infinity + Infinity = Infinity. - * -Infinity + -Infinity = -Infinity. - * Infinity + -Infinity = -Infinity + Infinity = NaN. - * - * @param x quadruple precision number - * @param y quadruple precision number - * @return quadruple precision number - */ - function add(bytes16 x, bytes16 y) internal pure returns (bytes16) { - unchecked { - uint256 xExponent = uint128(x) >> 112 & 0x7FFF; - uint256 yExponent = uint128(y) >> 112 & 0x7FFF; - - if (xExponent == 0x7FFF) { - if (yExponent == 0x7FFF) { - if (x == y) return x; - else return NaN; - } else { - return x; - } - } else if (yExponent == 0x7FFF) { - return y; - } else { - bool xSign = uint128(x) >= 0x80000000000000000000000000000000; - uint256 xSignifier = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (xExponent == 0) xExponent = 1; - else xSignifier |= 0x10000000000000000000000000000; - - bool ySign = uint128(y) >= 0x80000000000000000000000000000000; - uint256 ySignifier = uint128(y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (yExponent == 0) yExponent = 1; - else ySignifier |= 0x10000000000000000000000000000; - - if (xSignifier == 0) { - return y == NEGATIVE_ZERO ? POSITIVE_ZERO : y; - } else if (ySignifier == 0) { - return x == NEGATIVE_ZERO ? POSITIVE_ZERO : x; - } else { - int256 delta = int256(xExponent) - int256(yExponent); - - if (xSign == ySign) { - if (delta > 112) { - return x; - } else if (delta > 0) { - ySignifier >>= uint256(delta); - } else if (delta < -112) { - return y; - } else if (delta < 0) { - xSignifier >>= uint256(-delta); - xExponent = yExponent; - } - - xSignifier += ySignifier; - - if (xSignifier >= 0x20000000000000000000000000000) { - xSignifier >>= 1; - xExponent += 1; - } - - if (xExponent == 0x7FFF) { - return xSign ? NEGATIVE_INFINITY : POSITIVE_INFINITY; - } else { - if (xSignifier < 0x10000000000000000000000000000) xExponent = 0; - else xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - return bytes16( - uint128( - (xSign ? 0x80000000000000000000000000000000 : 0) | (xExponent << 112) | xSignifier - ) - ); - } - } else { - if (delta > 0) { - xSignifier <<= 1; - xExponent -= 1; - } else if (delta < 0) { - ySignifier <<= 1; - xExponent = yExponent - 1; - } - - if (delta > 112) ySignifier = 1; - else if (delta > 1) ySignifier = (ySignifier - 1 >> uint256(delta - 1)) + 1; - else if (delta < -112) xSignifier = 1; - else if (delta < -1) xSignifier = (xSignifier - 1 >> uint256(-delta - 1)) + 1; - - if (xSignifier >= ySignifier) { - xSignifier -= ySignifier; - } else { - xSignifier = ySignifier - xSignifier; - xSign = ySign; - } - - if (xSignifier == 0) { - return POSITIVE_ZERO; - } - - uint256 msb = mostSignificantBit(xSignifier); - - if (msb == 113) { - xSignifier = xSignifier >> 1 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - xExponent += 1; - } else if (msb < 112) { - uint256 shift = 112 - msb; - if (xExponent > shift) { - xSignifier = xSignifier << shift & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - xExponent -= shift; - } else { - xSignifier <<= xExponent - 1; - xExponent = 0; - } - } else { - xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } - - if (xExponent == 0x7FFF) { - return xSign ? NEGATIVE_INFINITY : POSITIVE_INFINITY; - } else { - return bytes16( - uint128( - (xSign ? 0x80000000000000000000000000000000 : 0) | (xExponent << 112) | xSignifier - ) - ); - } - } - } - } - } - } - - /** - * Calculate x - y. Special values behave in the following way: - * - * NaN - x = NaN for any x. - * Infinity - x = Infinity for any finite x. - * -Infinity - x = -Infinity for any finite x. - * Infinity - -Infinity = Infinity. - * -Infinity - Infinity = -Infinity. - * Infinity - Infinity = -Infinity - -Infinity = NaN. - * - * @param x quadruple precision number - * @param y quadruple precision number - * @return quadruple precision number - */ - function sub(bytes16 x, bytes16 y) internal pure returns (bytes16) { - unchecked { - return add(x, y ^ 0x80000000000000000000000000000000); - } - } - - /** - * Calculate x * y. Special values behave in the following way: - * - * NaN * x = NaN for any x. - * Infinity * x = Infinity for any finite positive x. - * Infinity * x = -Infinity for any finite negative x. - * -Infinity * x = -Infinity for any finite positive x. - * -Infinity * x = Infinity for any finite negative x. - * Infinity * 0 = NaN. - * -Infinity * 0 = NaN. - * Infinity * Infinity = Infinity. - * Infinity * -Infinity = -Infinity. - * -Infinity * Infinity = -Infinity. - * -Infinity * -Infinity = Infinity. - * - * @param x quadruple precision number - * @param y quadruple precision number - * @return quadruple precision number - */ - function mul(bytes16 x, bytes16 y) internal pure returns (bytes16) { - unchecked { - uint256 xExponent = uint128(x) >> 112 & 0x7FFF; - uint256 yExponent = uint128(y) >> 112 & 0x7FFF; - - if (xExponent == 0x7FFF) { - if (yExponent == 0x7FFF) { - if (x == y) return x ^ y & 0x80000000000000000000000000000000; - else if (x ^ y == 0x80000000000000000000000000000000) return x | y; - else return NaN; - } else { - if (y & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN; - else return x ^ y & 0x80000000000000000000000000000000; - } - } else if (yExponent == 0x7FFF) { - if (x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN; - else return y ^ x & 0x80000000000000000000000000000000; - } else { - uint256 xSignifier = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (xExponent == 0) xExponent = 1; - else xSignifier |= 0x10000000000000000000000000000; - - uint256 ySignifier = uint128(y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (yExponent == 0) yExponent = 1; - else ySignifier |= 0x10000000000000000000000000000; - - xSignifier *= ySignifier; - if (xSignifier == 0) { - return (x ^ y) & 0x80000000000000000000000000000000 > 0 ? NEGATIVE_ZERO : POSITIVE_ZERO; - } - - xExponent += yExponent; - - uint256 msb = xSignifier >= 0x200000000000000000000000000000000000000000000000000000000 - ? 225 - : xSignifier >= 0x100000000000000000000000000000000000000000000000000000000 - ? 224 - : mostSignificantBit(xSignifier); - - if (xExponent + msb < 16496) { - // Underflow - xExponent = 0; - xSignifier = 0; - } else if (xExponent + msb < 16608) { - // Subnormal - if (xExponent < 16496) { - xSignifier >>= 16496 - xExponent; - } else if (xExponent > 16496) { - xSignifier <<= xExponent - 16496; - } - xExponent = 0; - } else if (xExponent + msb > 49373) { - xExponent = 0x7FFF; - xSignifier = 0; - } else { - if (msb > 112) { - xSignifier >>= msb - 112; - } else if (msb < 112) { - xSignifier <<= 112 - msb; - } - - xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - xExponent = xExponent + msb - 16607; - } - - return bytes16( - uint128(uint128((x ^ y) & 0x80000000000000000000000000000000) | xExponent << 112 | xSignifier) - ); - } - } - } - - /** - * Calculate x / y. Special values behave in the following way: - * - * NaN / x = NaN for any x. - * x / NaN = NaN for any x. - * Infinity / x = Infinity for any finite non-negative x. - * Infinity / x = -Infinity for any finite negative x including -0. - * -Infinity / x = -Infinity for any finite non-negative x. - * -Infinity / x = Infinity for any finite negative x including -0. - * x / Infinity = 0 for any finite non-negative x. - * x / -Infinity = -0 for any finite non-negative x. - * x / Infinity = -0 for any finite non-negative x including -0. - * x / -Infinity = 0 for any finite non-negative x including -0. - * - * Infinity / Infinity = NaN. - * Infinity / -Infinity = -NaN. - * -Infinity / Infinity = -NaN. - * -Infinity / -Infinity = NaN. - * - * Division by zero behaves in the following way: - * - * x / 0 = Infinity for any finite positive x. - * x / -0 = -Infinity for any finite positive x. - * x / 0 = -Infinity for any finite negative x. - * x / -0 = Infinity for any finite negative x. - * 0 / 0 = NaN. - * 0 / -0 = NaN. - * -0 / 0 = NaN. - * -0 / -0 = NaN. - * - * @param x quadruple precision number - * @param y quadruple precision number - * @return quadruple precision number - */ - function div(bytes16 x, bytes16 y) internal pure returns (bytes16) { - unchecked { - uint256 xExponent = uint128(x) >> 112 & 0x7FFF; - uint256 yExponent = uint128(y) >> 112 & 0x7FFF; - - if (xExponent == 0x7FFF) { - if (yExponent == 0x7FFF) return NaN; - else return x ^ y & 0x80000000000000000000000000000000; - } else if (yExponent == 0x7FFF) { - if (y & 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFF != 0) return NaN; - else return POSITIVE_ZERO | (x ^ y) & 0x80000000000000000000000000000000; - } else if (y & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) { - if (x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0) return NaN; - else return POSITIVE_INFINITY | (x ^ y) & 0x80000000000000000000000000000000; - } else { - uint256 ySignifier = uint128(y) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (yExponent == 0) yExponent = 1; - else ySignifier |= 0x10000000000000000000000000000; - - uint256 xSignifier = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (xExponent == 0) { - if (xSignifier != 0) { - uint256 shift = 226 - mostSignificantBit(xSignifier); - - xSignifier <<= shift; - - xExponent = 1; - yExponent += shift - 114; - } - } else { - xSignifier = (xSignifier | 0x10000000000000000000000000000) << 114; - } - - xSignifier = xSignifier / ySignifier; - if (xSignifier == 0) { - return (x ^ y) & 0x80000000000000000000000000000000 > 0 ? NEGATIVE_ZERO : POSITIVE_ZERO; - } - - assert(xSignifier >= 0x1000000000000000000000000000); - - uint256 msb = xSignifier >= 0x80000000000000000000000000000 - ? mostSignificantBit(xSignifier) - : xSignifier >= 0x40000000000000000000000000000 - ? 114 - : xSignifier >= 0x20000000000000000000000000000 ? 113 : 112; - - if (xExponent + msb > yExponent + 16497) { - // Overflow - xExponent = 0x7FFF; - xSignifier = 0; - } else if (xExponent + msb + 16380 < yExponent) { - // Underflow - xExponent = 0; - xSignifier = 0; - } else if (xExponent + msb + 16268 < yExponent) { - // Subnormal - if (xExponent + 16380 > yExponent) { - xSignifier <<= xExponent + 16380 - yExponent; - } else if (xExponent + 16380 < yExponent) { - xSignifier >>= yExponent - xExponent - 16380; - } - - xExponent = 0; - } else { - // Normal - if (msb > 112) { - xSignifier >>= msb - 112; - } - - xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - xExponent = xExponent + msb + 16269 - yExponent; - } - - return bytes16( - uint128(uint128((x ^ y) & 0x80000000000000000000000000000000) | xExponent << 112 | xSignifier) - ); - } - } - } - - /** - * Calculate -x. - * - * @param x quadruple precision number - * @return quadruple precision number - */ - function neg(bytes16 x) internal pure returns (bytes16) { - unchecked { - return x ^ 0x80000000000000000000000000000000; - } - } - - /** - * Calculate |x|. - * - * @param x quadruple precision number - * @return quadruple precision number - */ - function abs(bytes16 x) internal pure returns (bytes16) { - unchecked { - return x & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - } - } - - /** - * Calculate square root of x. Return NaN on negative x excluding -0. - * - * @param x quadruple precision number - * @return quadruple precision number - */ - function sqrt(bytes16 x) internal pure returns (bytes16) { - unchecked { - if (uint128(x) > 0x80000000000000000000000000000000) { - return NaN; - } else { - uint256 xExponent = uint128(x) >> 112 & 0x7FFF; - if (xExponent == 0x7FFF) { - return x; - } else { - uint256 xSignifier = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (xExponent == 0) xExponent = 1; - else xSignifier |= 0x10000000000000000000000000000; - - if (xSignifier == 0) return POSITIVE_ZERO; - - bool oddExponent = xExponent & 0x1 == 0; - xExponent = xExponent + 16383 >> 1; - - if (oddExponent) { - if (xSignifier >= 0x10000000000000000000000000000) { - xSignifier <<= 113; - } else { - uint256 msb = mostSignificantBit(xSignifier); - uint256 shift = (226 - msb) & 0xFE; - xSignifier <<= shift; - xExponent -= shift - 112 >> 1; - } - } else { - if (xSignifier >= 0x10000000000000000000000000000) { - xSignifier <<= 112; - } else { - uint256 msb = mostSignificantBit(xSignifier); - uint256 shift = (225 - msb) & 0xFE; - xSignifier <<= shift; - xExponent -= shift - 112 >> 1; - } - } - - uint256 r = 0x10000000000000000000000000000; - r = (r + xSignifier / r) >> 1; - r = (r + xSignifier / r) >> 1; - r = (r + xSignifier / r) >> 1; - r = (r + xSignifier / r) >> 1; - r = (r + xSignifier / r) >> 1; - r = (r + xSignifier / r) >> 1; - r = (r + xSignifier / r) >> 1; // Seven iterations should be enough - uint256 r1 = xSignifier / r; - if (r1 < r) r = r1; - - return bytes16(uint128(xExponent << 112 | r & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF)); - } - } - } - } - - /** - * Calculate binary logarithm of x. Return NaN on negative x excluding -0. - * - * @param x quadruple precision number - * @return quadruple precision number - */ - function log_2(bytes16 x) internal pure returns (bytes16) { - unchecked { - if (uint128(x) > 0x80000000000000000000000000000000) { - return NaN; - } else if (x == 0x3FFF0000000000000000000000000000) { - return POSITIVE_ZERO; - } else { - uint256 xExponent = uint128(x) >> 112 & 0x7FFF; - if (xExponent == 0x7FFF) { - return x; - } else { - uint256 xSignifier = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (xExponent == 0) xExponent = 1; - else xSignifier |= 0x10000000000000000000000000000; - - if (xSignifier == 0) return NEGATIVE_INFINITY; - - bool resultNegative; - uint256 resultExponent = 16495; - uint256 resultSignifier; - - if (xExponent >= 0x3FFF) { - resultNegative = false; - resultSignifier = xExponent - 0x3FFF; - xSignifier <<= 15; - } else { - resultNegative = true; - if (xSignifier >= 0x10000000000000000000000000000) { - resultSignifier = 0x3FFE - xExponent; - xSignifier <<= 15; - } else { - uint256 msb = mostSignificantBit(xSignifier); - resultSignifier = 16493 - msb; - xSignifier <<= 127 - msb; - } - } - - if (xSignifier == 0x80000000000000000000000000000000) { - if (resultNegative) resultSignifier += 1; - uint256 shift = 112 - mostSignificantBit(resultSignifier); - resultSignifier <<= shift; - resultExponent -= shift; - } else { - uint256 bb = resultNegative ? 1 : 0; - while (resultSignifier < 0x10000000000000000000000000000) { - resultSignifier <<= 1; - resultExponent -= 1; - - xSignifier *= xSignifier; - uint256 b = xSignifier >> 255; - resultSignifier += b ^ bb; - xSignifier >>= 127 + b; - } - } - - return bytes16( - uint128( - (resultNegative ? 0x80000000000000000000000000000000 : 0) | resultExponent << 112 - | resultSignifier & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) - ); - } - } - } - } - - /** - * Calculate natural logarithm of x. Return NaN on negative x excluding -0. - * - * @param x quadruple precision number - * @return quadruple precision number - */ - function ln(bytes16 x) internal pure returns (bytes16) { - unchecked { - return mul(log_2(x), 0x3FFE62E42FEFA39EF35793C7673007E5); - } - } - - /** - * Calculate 2^x. - * - * @param x quadruple precision number - * @return quadruple precision number - */ - function pow_2(bytes16 x) internal pure returns (bytes16) { - unchecked { - bool xNegative = uint128(x) > 0x80000000000000000000000000000000; - uint256 xExponent = uint128(x) >> 112 & 0x7FFF; - uint256 xSignifier = uint128(x) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - - if (xExponent == 0x7FFF && xSignifier != 0) { - return NaN; - } else if (xExponent > 16397) { - return xNegative ? POSITIVE_ZERO : POSITIVE_INFINITY; - } else if (xExponent < 16255) { - return 0x3FFF0000000000000000000000000000; - } else { - if (xExponent == 0) xExponent = 1; - else xSignifier |= 0x10000000000000000000000000000; - - if (xExponent > 16367) { - xSignifier <<= xExponent - 16367; - } else if (xExponent < 16367) { - xSignifier >>= 16367 - xExponent; - } - - if (xNegative && xSignifier > 0x406E00000000000000000000000000000000) { - return POSITIVE_ZERO; - } - - if (!xNegative && xSignifier > 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { - return POSITIVE_INFINITY; - } - - uint256 resultExponent = xSignifier >> 128; - xSignifier &= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - if (xNegative && xSignifier != 0) { - xSignifier = ~xSignifier; - resultExponent += 1; - } - - uint256 resultSignifier = 0x80000000000000000000000000000000; - if (xSignifier & 0x80000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128; - } - if (xSignifier & 0x40000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128; - } - if (xSignifier & 0x20000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128; - } - if (xSignifier & 0x10000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10B5586CF9890F6298B92B71842A98363 >> 128; - } - if (xSignifier & 0x8000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1059B0D31585743AE7C548EB68CA417FD >> 128; - } - if (xSignifier & 0x4000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128; - } - if (xSignifier & 0x2000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128; - } - if (xSignifier & 0x1000000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128; - } - if (xSignifier & 0x800000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128; - } - if (xSignifier & 0x400000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128; - } - if (xSignifier & 0x200000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100162F3904051FA128BCA9C55C31E5DF >> 128; - } - if (xSignifier & 0x100000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000B175EFFDC76BA38E31671CA939725 >> 128; - } - if (xSignifier & 0x80000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128; - } - if (xSignifier & 0x40000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128; - } - if (xSignifier & 0x20000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000162E525EE054754457D5995292026 >> 128; - } - if (xSignifier & 0x10000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000B17255775C040618BF4A4ADE83FC >> 128; - } - if (xSignifier & 0x8000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128; - } - if (xSignifier & 0x4000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128; - } - if (xSignifier & 0x2000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000162E43F4F831060E02D839A9D16D >> 128; - } - if (xSignifier & 0x1000000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000B1721BCFC99D9F890EA06911763 >> 128; - } - if (xSignifier & 0x800000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128; - } - if (xSignifier & 0x400000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128; - } - if (xSignifier & 0x200000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000162E430E5A18F6119E3C02282A5 >> 128; - } - if (xSignifier & 0x100000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000B1721835514B86E6D96EFD1BFE >> 128; - } - if (xSignifier & 0x80000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128; - } - if (xSignifier & 0x40000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000002C5C8601CC6B9E94213C72737A >> 128; - } - if (xSignifier & 0x20000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000162E42FFF037DF38AA2B219F06 >> 128; - } - if (xSignifier & 0x10000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000B17217FBA9C739AA5819F44F9 >> 128; - } - if (xSignifier & 0x8000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128; - } - if (xSignifier & 0x4000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128; - } - if (xSignifier & 0x2000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000162E42FF0999CE3541B9FFFCF >> 128; - } - if (xSignifier & 0x1000000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000B17217F80F4EF5AADDA45554 >> 128; - } - if (xSignifier & 0x800000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000058B90BFBF8479BD5A81B51AD >> 128; - } - if (xSignifier & 0x400000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128; - } - if (xSignifier & 0x200000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000162E42FEFB2FED257559BDAA >> 128; - } - if (xSignifier & 0x100000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128; - } - if (xSignifier & 0x80000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128; - } - if (xSignifier & 0x40000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128; - } - if (xSignifier & 0x20000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000162E42FEFA494F1478FDE05 >> 128; - } - if (xSignifier & 0x10000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000B17217F7D20CF927C8E94C >> 128; - } - if (xSignifier & 0x8000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128; - } - if (xSignifier & 0x4000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000002C5C85FDF477B662B26945 >> 128; - } - if (xSignifier & 0x2000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000162E42FEFA3AE53369388C >> 128; - } - if (xSignifier & 0x1000000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000B17217F7D1D351A389D40 >> 128; - } - if (xSignifier & 0x800000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128; - } - if (xSignifier & 0x400000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000002C5C85FDF4741BEA6E77E >> 128; - } - if (xSignifier & 0x200000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000162E42FEFA39FE95583C2 >> 128; - } - if (xSignifier & 0x100000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000B17217F7D1CFB72B45E1 >> 128; - } - if (xSignifier & 0x80000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128; - } - if (xSignifier & 0x40000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000002C5C85FDF473E242EA38 >> 128; - } - if (xSignifier & 0x20000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000162E42FEFA39F02B772C >> 128; - } - if (xSignifier & 0x10000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000B17217F7D1CF7D83C1A >> 128; - } - if (xSignifier & 0x8000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128; - } - if (xSignifier & 0x4000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000002C5C85FDF473DEA871F >> 128; - } - if (xSignifier & 0x2000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000162E42FEFA39EF44D91 >> 128; - } - if (xSignifier & 0x1000000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000B17217F7D1CF79E949 >> 128; - } - if (xSignifier & 0x800000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000058B90BFBE8E7BCE544 >> 128; - } - if (xSignifier & 0x400000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000002C5C85FDF473DE6ECA >> 128; - } - if (xSignifier & 0x200000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000162E42FEFA39EF366F >> 128; - } - if (xSignifier & 0x100000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000B17217F7D1CF79AFA >> 128; - } - if (xSignifier & 0x80000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000058B90BFBE8E7BCD6D >> 128; - } - if (xSignifier & 0x40000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000002C5C85FDF473DE6B2 >> 128; - } - if (xSignifier & 0x20000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000162E42FEFA39EF358 >> 128; - } - if (xSignifier & 0x10000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000B17217F7D1CF79AB >> 128; - } - if (xSignifier & 0x8000000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000058B90BFBE8E7BCD5 >> 128; - } - if (xSignifier & 0x4000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000002C5C85FDF473DE6A >> 128; - } - if (xSignifier & 0x2000000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000162E42FEFA39EF34 >> 128; - } - if (xSignifier & 0x1000000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000B17217F7D1CF799 >> 128; - } - if (xSignifier & 0x800000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000058B90BFBE8E7BCC >> 128; - } - if (xSignifier & 0x400000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000002C5C85FDF473DE5 >> 128; - } - if (xSignifier & 0x200000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000162E42FEFA39EF2 >> 128; - } - if (xSignifier & 0x100000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000B17217F7D1CF78 >> 128; - } - if (xSignifier & 0x80000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000058B90BFBE8E7BB >> 128; - } - if (xSignifier & 0x40000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000002C5C85FDF473DD >> 128; - } - if (xSignifier & 0x20000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000162E42FEFA39EE >> 128; - } - if (xSignifier & 0x10000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000B17217F7D1CF6 >> 128; - } - if (xSignifier & 0x8000000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000058B90BFBE8E7A >> 128; - } - if (xSignifier & 0x4000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000002C5C85FDF473C >> 128; - } - if (xSignifier & 0x2000000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000162E42FEFA39D >> 128; - } - if (xSignifier & 0x1000000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000B17217F7D1CE >> 128; - } - if (xSignifier & 0x800000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000058B90BFBE8E6 >> 128; - } - if (xSignifier & 0x400000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000002C5C85FDF472 >> 128; - } - if (xSignifier & 0x200000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000162E42FEFA38 >> 128; - } - if (xSignifier & 0x100000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000B17217F7D1B >> 128; - } - if (xSignifier & 0x80000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000058B90BFBE8D >> 128; - } - if (xSignifier & 0x40000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000002C5C85FDF46 >> 128; - } - if (xSignifier & 0x20000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000162E42FEFA2 >> 128; - } - if (xSignifier & 0x10000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000B17217F7D0 >> 128; - } - if (xSignifier & 0x8000000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000058B90BFBE7 >> 128; - } - if (xSignifier & 0x4000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000002C5C85FDF3 >> 128; - } - if (xSignifier & 0x2000000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000162E42FEF9 >> 128; - } - if (xSignifier & 0x1000000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000B17217F7C >> 128; - } - if (xSignifier & 0x800000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000058B90BFBD >> 128; - } - if (xSignifier & 0x400000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000002C5C85FDE >> 128; - } - if (xSignifier & 0x200000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000162E42FEE >> 128; - } - if (xSignifier & 0x100000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000B17217F6 >> 128; - } - if (xSignifier & 0x80000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000058B90BFA >> 128; - } - if (xSignifier & 0x40000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000002C5C85FC >> 128; - } - if (xSignifier & 0x20000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000162E42FD >> 128; - } - if (xSignifier & 0x10000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000B17217E >> 128; - } - if (xSignifier & 0x8000000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000058B90BE >> 128; - } - if (xSignifier & 0x4000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000002C5C85E >> 128; - } - if (xSignifier & 0x2000000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000162E42E >> 128; - } - if (xSignifier & 0x1000000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000B17216 >> 128; - } - if (xSignifier & 0x800000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000058B90A >> 128; - } - if (xSignifier & 0x400000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000002C5C84 >> 128; - } - if (xSignifier & 0x200000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000162E41 >> 128; - } - if (xSignifier & 0x100000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000000B1720 >> 128; - } - if (xSignifier & 0x80000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000058B8F >> 128; - } - if (xSignifier & 0x40000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000002C5C7 >> 128; - } - if (xSignifier & 0x20000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000000162E3 >> 128; - } - if (xSignifier & 0x10000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000000B171 >> 128; - } - if (xSignifier & 0x8000 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000000058B8 >> 128; - } - if (xSignifier & 0x4000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000002C5B >> 128; - } - if (xSignifier & 0x2000 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000000162D >> 128; - } - if (xSignifier & 0x1000 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000000B16 >> 128; - } - if (xSignifier & 0x800 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000000058A >> 128; - } - if (xSignifier & 0x400 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000000002C4 >> 128; - } - if (xSignifier & 0x200 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000000161 >> 128; - } - if (xSignifier & 0x100 > 0) { - resultSignifier = resultSignifier * 0x1000000000000000000000000000000B0 >> 128; - } - if (xSignifier & 0x80 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000000057 >> 128; - } - if (xSignifier & 0x40 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000000002B >> 128; - } - if (xSignifier & 0x20 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000000015 >> 128; - } - if (xSignifier & 0x10 > 0) { - resultSignifier = resultSignifier * 0x10000000000000000000000000000000A >> 128; - } - if (xSignifier & 0x8 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000000004 >> 128; - } - if (xSignifier & 0x4 > 0) { - resultSignifier = resultSignifier * 0x100000000000000000000000000000001 >> 128; - } - - if (!xNegative) { - resultSignifier = resultSignifier >> 15 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - resultExponent += 0x3FFF; - } else if (resultExponent <= 0x3FFE) { - resultSignifier = resultSignifier >> 15 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF; - resultExponent = 0x3FFF - resultExponent; - } else { - resultSignifier = resultSignifier >> resultExponent - 16367; - resultExponent = 0; - } - - return bytes16(uint128(resultExponent << 112 | resultSignifier)); - } - } - } - - /** - * Calculate e^x. - * - * @param x quadruple precision number - * @return quadruple precision number - */ - function exp(bytes16 x) internal pure returns (bytes16) { - unchecked { - return pow_2(mul(x, 0x3FFF71547652B82FE1777D0FFDA0D23A)); - } - } - - /** - * Get index of the most significant non-zero bit in binary representation of - * x. Reverts if x is zero. - * - * @return index of the most significant non-zero bit in binary representation - * of x - */ - function mostSignificantBit(uint256 x) private pure returns (uint256) { - unchecked { - require(x > 0); - - uint256 result = 0; - - if (x >= 0x100000000000000000000000000000000) { - x >>= 128; - result += 128; - } - if (x >= 0x10000000000000000) { - x >>= 64; - result += 64; - } - if (x >= 0x100000000) { - x >>= 32; - result += 32; - } - if (x >= 0x10000) { - x >>= 16; - result += 16; - } - if (x >= 0x100) { - x >>= 8; - result += 8; - } - if (x >= 0x10) { - x >>= 4; - result += 4; - } - if (x >= 0x4) { - x >>= 2; - result += 2; - } - if (x >= 0x2) result += 1; // No need to shift x anymore - - return result; - } - } -} diff --git a/contracts/libraries/TWAMM/OrderPool.sol b/contracts/libraries/TWAMM/OrderPool.sol deleted file mode 100644 index 4822dbff..00000000 --- a/contracts/libraries/TWAMM/OrderPool.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.15; - -/// @title TWAMM OrderPool - Represents an OrderPool inside of a TWAMM -library OrderPool { - /// @notice Information related to a long term order pool. - /// @member sellRateCurrent The total current sell rate (sellAmount / second) among all orders - /// @member sellRateEndingAtInterval Mapping (timestamp => sellRate) The amount of expiring sellRate at this interval - /// @member earningsFactor Sum of (salesEarnings_k / salesRate_k) over every period k. Stored as Fixed Point X96. - /// @member earningsFactorAtInterval Mapping (timestamp => sellRate) The earnings factor accrued by a certain time interval. Stored as Fixed Point X96. - struct State { - uint256 sellRateCurrent; - mapping(uint256 => uint256) sellRateEndingAtInterval; - // - uint256 earningsFactorCurrent; - mapping(uint256 => uint256) earningsFactorAtInterval; - } - - // Performs all updates on an OrderPool that must happen when hitting an expiration interval with expiring orders - function advanceToInterval(State storage self, uint256 expiration, uint256 earningsFactor) internal { - unchecked { - self.earningsFactorCurrent += earningsFactor; - self.earningsFactorAtInterval[expiration] = self.earningsFactorCurrent; - self.sellRateCurrent -= self.sellRateEndingAtInterval[expiration]; - } - } - - // Performs all the updates on an OrderPool that must happen when updating to the current time not on an interval - function advanceToCurrentTime(State storage self, uint256 earningsFactor) internal { - unchecked { - self.earningsFactorCurrent += earningsFactor; - } - } -} diff --git a/contracts/libraries/TWAMM/TwammMath.sol b/contracts/libraries/TWAMM/TwammMath.sol deleted file mode 100644 index a5994b51..00000000 --- a/contracts/libraries/TWAMM/TwammMath.sol +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.15; - -import {ABDKMathQuad} from "./ABDKMathQuad.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 { - using ABDKMathQuad for bytes16; - using ABDKMathQuad for uint256; - using ABDKMathQuad for uint160; - using ABDKMathQuad for uint128; - using SafeCast for uint256; - - // ABDKMathQuad FixedPoint96.Q96.fromUInt() - bytes16 internal constant Q96 = 0x405f0000000000000000000000000000; - - bytes16 internal constant ONE = 0x3fff0000000000000000000000000000; - //// @dev The minimum value that a pool price can equal, represented in bytes. - // (TickMath.MIN_SQRT_RATIO + 1).fromUInt() - bytes16 internal constant MIN_SQRT_RATIO_BYTES = 0x401f000276a400000000000000000000; - //// @dev The maximum value that a pool price can equal, represented in bytes. - // (TickMath.MAX_SQRT_RATIO - 1).fromUInt() - bytes16 internal constant MAX_SQRT_RATIO_BYTES = 0x409efffb12c7dfa3f8d4a0c91092bb2a; - - struct PriceParamsBytes16 { - bytes16 sqrtSellRatio; - bytes16 sqrtSellRate; - bytes16 secondsElapsed; - bytes16 sqrtPrice; - bytes16 liquidity; - } - - struct ExecutionUpdateParams { - uint256 secondsElapsedX96; - uint160 sqrtPriceX96; - uint128 liquidity; - uint256 sellRateCurrent0; - uint256 sellRateCurrent1; - } - - function getNewSqrtPriceX96(ExecutionUpdateParams memory params) internal pure returns (uint160 newSqrtPriceX96) { - bytes16 sellRateBytes0 = params.sellRateCurrent0.fromUInt(); - bytes16 sellRateBytes1 = params.sellRateCurrent1.fromUInt(); - bytes16 sqrtSellRateBytes = sellRateBytes0.mul(sellRateBytes1).sqrt(); - bytes16 sqrtSellRatioX96Bytes = sellRateBytes1.div(sellRateBytes0).sqrt().mul(Q96); - - PriceParamsBytes16 memory priceParams = PriceParamsBytes16({ - sqrtSellRatio: sqrtSellRatioX96Bytes.div(Q96), - sqrtSellRate: sqrtSellRateBytes, - secondsElapsed: params.secondsElapsedX96.fromUInt().div(Q96), - sqrtPrice: params.sqrtPriceX96.fromUInt().div(Q96), - liquidity: params.liquidity.fromUInt() - }); - - bytes16 newSqrtPriceBytesX96 = calculateNewSqrtPrice(priceParams).mul(Q96); - bool isOverflow = newSqrtPriceBytesX96.isInfinity() || newSqrtPriceBytesX96.isNaN(); - bytes16 newSqrtPriceX96Bytes = isOverflow ? sqrtSellRatioX96Bytes : newSqrtPriceBytesX96; - - newSqrtPriceX96 = getSqrtPriceWithinBounds( - params.sellRateCurrent0 > params.sellRateCurrent1, newSqrtPriceX96Bytes - ).toUInt().toUint160(); - } - - function getSqrtPriceWithinBounds(bool zeroForOne, bytes16 desiredPriceX96) - internal - pure - returns (bytes16 newSqrtPriceX96) - { - if (zeroForOne) { - newSqrtPriceX96 = MIN_SQRT_RATIO_BYTES.gt(desiredPriceX96) == 1 ? MIN_SQRT_RATIO_BYTES : desiredPriceX96; - } else { - newSqrtPriceX96 = desiredPriceX96.gt(MAX_SQRT_RATIO_BYTES) == 1 ? MAX_SQRT_RATIO_BYTES : desiredPriceX96; - } - } - - function calculateEarningsUpdates(ExecutionUpdateParams memory params, uint160 finalSqrtPriceX96) - internal - pure - returns (uint256 earningsFactorPool0, uint256 earningsFactorPool1) - { - bytes16 sellRateBytes0 = params.sellRateCurrent0.fromUInt(); - bytes16 sellRateBytes1 = params.sellRateCurrent1.fromUInt(); - - bytes16 sellRatio = sellRateBytes1.div(sellRateBytes0); - bytes16 sqrtSellRate = sellRateBytes0.mul(sellRateBytes1).sqrt(); - - EarningsFactorParams memory earningsFactorParams = EarningsFactorParams({ - secondsElapsed: params.secondsElapsedX96.fromUInt().div(Q96), - sellRatio: sellRatio, - sqrtSellRate: sqrtSellRate, - prevSqrtPrice: params.sqrtPriceX96.fromUInt().div(Q96), - newSqrtPrice: finalSqrtPriceX96.fromUInt().div(Q96), - liquidity: params.liquidity.fromUInt() - }); - - // Trade the amm orders. - // If liquidity is 0, it trades the twamm orders against each other for the time duration. - earningsFactorPool0 = getEarningsFactorPool0(earningsFactorParams).mul(Q96).toUInt(); - earningsFactorPool1 = getEarningsFactorPool1(earningsFactorParams).mul(Q96).toUInt(); - } - - struct calculateTimeBetweenTicksParams { - uint256 liquidity; - uint160 sqrtPriceStartX96; - uint160 sqrtPriceEndX96; - uint256 sellRate0; - uint256 sellRate1; - } - - /// @notice Used when crossing an initialized tick. Can extract the amount of seconds it took to cross - /// the tick, and recalibrate the calculation from there to accommodate liquidity changes - function calculateTimeBetweenTicks( - uint256 liquidity, - uint160 sqrtPriceStartX96, - uint160 sqrtPriceEndX96, - uint256 sellRate0, - uint256 sellRate1 - ) internal pure returns (uint256 secondsBetween) { - bytes16 sellRate0Bytes = sellRate0.fromUInt(); - bytes16 sellRate1Bytes = sellRate1.fromUInt(); - bytes16 sqrtPriceStartX96Bytes = sqrtPriceStartX96.fromUInt(); - bytes16 sqrtPriceEndX96Bytes = sqrtPriceEndX96.fromUInt(); - bytes16 sqrtSellRatioX96 = sellRate1Bytes.div(sellRate0Bytes).sqrt().mul(Q96); - bytes16 sqrtSellRate = sellRate0Bytes.mul(sellRate1Bytes).sqrt(); - - bytes16 multiple = getTimeBetweenTicksMultiple(sqrtSellRatioX96, sqrtPriceStartX96Bytes, sqrtPriceEndX96Bytes); - bytes16 numerator = multiple.mul(liquidity.fromUInt()); - bytes16 denominator = uint256(2).fromUInt().mul(sqrtSellRate); - return numerator.mul(Q96).div(denominator).toUInt(); - } - - function getTimeBetweenTicksMultiple(bytes16 sqrtSellRatioX96, bytes16 sqrtPriceStartX96, bytes16 sqrtPriceEndX96) - private - pure - returns (bytes16 multiple) - { - bytes16 multiple1 = sqrtSellRatioX96.add(sqrtPriceEndX96).div(sqrtSellRatioX96.sub(sqrtPriceEndX96)); - bytes16 multiple2 = sqrtSellRatioX96.sub(sqrtPriceStartX96).div(sqrtSellRatioX96.add(sqrtPriceStartX96)); - return multiple1.mul(multiple2).ln(); - } - - struct EarningsFactorParams { - bytes16 secondsElapsed; - bytes16 sellRatio; - bytes16 sqrtSellRate; - bytes16 prevSqrtPrice; - bytes16 newSqrtPrice; - bytes16 liquidity; - } - - function getEarningsFactorPool0(EarningsFactorParams memory params) private pure returns (bytes16 earningsFactor) { - bytes16 minuend = params.sellRatio.mul(params.secondsElapsed); - bytes16 subtrahend = params.liquidity.mul(params.sellRatio.sqrt()).mul( - params.newSqrtPrice.sub(params.prevSqrtPrice) - ).div(params.sqrtSellRate); - return minuend.sub(subtrahend); - } - - function getEarningsFactorPool1(EarningsFactorParams memory params) private pure returns (bytes16 earningsFactor) { - bytes16 minuend = params.secondsElapsed.div(params.sellRatio); - bytes16 subtrahend = params.liquidity.mul(reciprocal(params.sellRatio.sqrt())).mul( - reciprocal(params.newSqrtPrice).sub(reciprocal(params.prevSqrtPrice)) - ).div(params.sqrtSellRate); - return minuend.sub(subtrahend); - } - - function calculateNewSqrtPrice(PriceParamsBytes16 memory params) private pure returns (bytes16 newSqrtPrice) { - bytes16 pow = uint256(2).fromUInt().mul(params.sqrtSellRate).mul(params.secondsElapsed).div(params.liquidity); - bytes16 c = params.sqrtSellRatio.sub(params.sqrtPrice).div(params.sqrtSellRatio.add(params.sqrtPrice)); - newSqrtPrice = params.sqrtSellRatio.mul(pow.exp().sub(c)).div(pow.exp().add(c)); - } - - function reciprocal(bytes16 n) private pure returns (bytes16) { - return ONE.div(n); - } -} diff --git a/contracts/libraries/TransferHelper.sol b/contracts/libraries/TransferHelper.sol deleted file mode 100644 index 9ab40d9e..00000000 --- a/contracts/libraries/TransferHelper.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.15; - -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 -/// @dev implementation from https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol#L63 -library TransferHelper { - /// @notice Transfers tokens from msg.sender to a recipient - /// @dev Calls transfer on token contract, errors with TF if transfer fails - /// @param token The contract address of the token which will be transferred - /// @param to The recipient of the transfer - /// @param value The value of the transfer - function safeTransfer(IERC20Minimal token, address to, uint256 value) internal { - bool success; - - assembly { - // Get a pointer to some free memory. - let freeMemoryPointer := mload(0x40) - - // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. - mstore(add(freeMemoryPointer, 36), value) // Append the "value" argument. - - success := - and( - // Set success to whether the call reverted, if not we check it either - // returned exactly 1 (can't just be non-zero data), or had no return data. - or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), - // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. - // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. - // Counterintuitively, this call must be positioned second to the or() call in the - // surrounding and() call or else returndatasize() will be zero during the computation. - call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) - ) - } - - require(success, "TRANSFER_FAILED"); - } - - /// @notice Transfers tokens from from to a recipient - /// @dev Calls transferFrom on token contract, errors with TF if transfer fails - /// @param token The contract address of the token which will be transferred - /// @param from The origin of the transfer - /// @param to The recipient of the transfer - /// @param value The value of the transfer - function safeTransferFrom(IERC20Minimal token, address from, address to, uint256 value) internal { - (bool success, bytes memory data) = - address(token).call(abi.encodeWithSelector(IERC20Minimal.transferFrom.selector, from, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "STF"); - } -} diff --git a/contracts/libraries/UniswapV4ERC20.sol b/contracts/libraries/UniswapV4ERC20.sol deleted file mode 100644 index fdd93ba4..00000000 --- a/contracts/libraries/UniswapV4ERC20.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity ^0.8.19; - -import {ERC20} from "solmate/tokens/ERC20.sol"; -import {Owned} from "solmate/auth/Owned.sol"; - -contract UniswapV4ERC20 is ERC20, Owned { - constructor(string memory name, string memory symbol) ERC20(name, symbol, 18) Owned(msg.sender) {} - - function mint(address account, uint256 amount) external onlyOwner { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external onlyOwner { - _burn(account, amount); - } -} diff --git a/foundry.toml b/foundry.toml index 7979bfb0..6d64d305 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,4 @@ [profile.default] -src = 'contracts' out = 'foundry-out' solc_version = '0.8.26' optimizer_runs = 1000000 @@ -7,6 +6,7 @@ ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] evm_version = "cancun" gas_limit = "3000000000" +fuzz_runs = 10000 [profile.ci] fuzz_runs = 100000 diff --git a/contracts/base/ImmutableState.sol b/src/base/ImmutableState.sol similarity index 100% rename from contracts/base/ImmutableState.sol rename to src/base/ImmutableState.sol diff --git a/contracts/base/Multicall.sol b/src/base/Multicall.sol similarity index 100% rename from contracts/base/Multicall.sol rename to src/base/Multicall.sol diff --git a/contracts/base/SafeCallback.sol b/src/base/SafeCallback.sol similarity index 100% rename from contracts/base/SafeCallback.sol rename to src/base/SafeCallback.sol diff --git a/contracts/base/SelfPermit.sol b/src/base/SelfPermit.sol similarity index 100% rename from contracts/base/SelfPermit.sol rename to src/base/SelfPermit.sol diff --git a/contracts/BaseHook.sol b/src/base/hooks/BaseHook.sol similarity index 97% rename from contracts/BaseHook.sol rename to src/base/hooks/BaseHook.sol index 01fc4954..e1ed6a06 100644 --- a/contracts/BaseHook.sol +++ b/src/base/hooks/BaseHook.sol @@ -7,8 +7,8 @@ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; -import {SafeCallback} from "./base/SafeCallback.sol"; -import {ImmutableState} from "./base/ImmutableState.sol"; +import {SafeCallback} from "../SafeCallback.sol"; +import {ImmutableState} from "../ImmutableState.sol"; abstract contract BaseHook is IHooks, SafeCallback { error NotSelf(); diff --git a/contracts/interfaces/IMulticall.sol b/src/interfaces/IMulticall.sol similarity index 100% rename from contracts/interfaces/IMulticall.sol rename to src/interfaces/IMulticall.sol diff --git a/contracts/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol similarity index 100% rename from contracts/interfaces/IQuoter.sol rename to src/interfaces/IQuoter.sol diff --git a/contracts/interfaces/ISelfPermit.sol b/src/interfaces/ISelfPermit.sol similarity index 100% rename from contracts/interfaces/ISelfPermit.sol rename to src/interfaces/ISelfPermit.sol diff --git a/contracts/interfaces/external/IERC20PermitAllowed.sol b/src/interfaces/external/IERC20PermitAllowed.sol similarity index 100% rename from contracts/interfaces/external/IERC20PermitAllowed.sol rename to src/interfaces/external/IERC20PermitAllowed.sol diff --git a/contracts/lens/Quoter.sol b/src/lens/Quoter.sol similarity index 100% rename from contracts/lens/Quoter.sol rename to src/lens/Quoter.sol diff --git a/contracts/libraries/PathKey.sol b/src/libraries/PathKey.sol similarity index 100% rename from contracts/libraries/PathKey.sol rename to src/libraries/PathKey.sol diff --git a/contracts/libraries/PoolTicksCounter.sol b/src/libraries/PoolTicksCounter.sol similarity index 99% rename from contracts/libraries/PoolTicksCounter.sol rename to src/libraries/PoolTicksCounter.sol index 60fdbbe5..7420ffd5 100644 --- a/contracts/libraries/PoolTicksCounter.sol +++ b/src/libraries/PoolTicksCounter.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.8.20; -import {PoolGetters} from "./PoolGetters.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol deleted file mode 100644 index 8a35661b..00000000 --- a/test/FullRange.t.sol +++ /dev/null @@ -1,782 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -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/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/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 {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; -import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; -import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; -import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; - -contract TestFullRange is Test, Deployers, GasSnapshot { - using PoolIdLibrary for PoolKey; - using SafeCast for uint256; - using CurrencyLibrary for Currency; - using StateLibrary for IPoolManager; - - event Initialize( - PoolId indexed poolId, - Currency indexed currency0, - Currency indexed currency1, - uint24 fee, - int24 tickSpacing, - IHooks hooks - ); - event ModifyPosition( - PoolId indexed poolId, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta - ); - event Swap( - PoolId indexed id, - address indexed sender, - int128 amount0, - int128 amount1, - uint160 sqrtPriceX96, - uint128 liquidity, - int24 tick, - 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 - int24 internal constant MAX_TICK = -MIN_TICK; - - int24 constant TICK_SPACING = 60; - uint16 constant LOCKED_LIQUIDITY = 1000; - uint256 constant MAX_DEADLINE = 12329839823; - uint256 constant MAX_TICK_LIQUIDITY = 11505069308564788430434325881101412; - uint8 constant DUST = 30; - - MockERC20 token0; - MockERC20 token1; - MockERC20 token2; - - FullRangeImplementation fullRange = FullRangeImplementation( - address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_SWAP_FLAG)) - ); - - PoolId id; - - PoolKey key2; - PoolId id2; - - // For a pool that gets initialized with liquidity in setUp() - PoolKey keyWithLiq; - PoolId idWithLiq; - - function setUp() public { - 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); - - key = createPoolKey(token0, token1); - id = key.toId(); - - key2 = createPoolKey(token1, token2); - id2 = key.toId(); - - keyWithLiq = createPoolKey(token0, token2); - idWithLiq = keyWithLiq.toId(); - - token0.approve(address(fullRange), type(uint256).max); - token1.approve(address(fullRange), type(uint256).max); - token2.approve(address(fullRange), type(uint256).max); - token0.approve(address(router), type(uint256).max); - token1.approve(address(router), type(uint256).max); - token2.approve(address(router), type(uint256).max); - - initPool(keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_PRICE_1_1, ZERO_BYTES); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - keyWithLiq.currency0, - keyWithLiq.currency1, - 3000, - 100 ether, - 100 ether, - 99 ether, - 99 ether, - address(this), - MAX_DEADLINE - ) - ); - } - - function testFullRange_beforeInitialize_AllowsPoolCreation() public { - PoolKey memory testKey = key; - - vm.expectEmit(true, true, true, true); - emit Initialize(id, testKey.currency0, testKey.currency1, testKey.fee, testKey.tickSpacing, testKey.hooks); - - snapStart("FullRangeInitialize"); - manager.initialize(testKey, SQRT_PRICE_1_1, ZERO_BYTES); - snapEnd(); - - (, address liquidityToken) = fullRange.poolInfo(id); - - assertFalse(liquidityToken == address(0)); - } - - function testFullRange_beforeInitialize_RevertsIfWrongSpacing() public { - PoolKey memory wrongKey = PoolKey(key.currency0, key.currency1, 0, TICK_SPACING + 1, fullRange); - - vm.expectRevert( - abi.encodeWithSelector( - Hooks.FailedHookCall.selector, abi.encodeWithSelector(FullRange.TickSpacingNotDefault.selector) - ) - ); - - manager.initialize(wrongKey, SQRT_PRICE_1_1, ZERO_BYTES); - } - - function testFullRange_addLiquidity_InitialAddSucceeds() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - uint256 prevBalance0 = key.currency0.balanceOf(address(this)); - uint256 prevBalance1 = key.currency1.balanceOf(address(this)); - - FullRange.AddLiquidityParams memory addLiquidityParams = FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ); - - snapStart("FullRangeAddInitialLiquidity"); - fullRange.addLiquidity(addLiquidityParams); - snapEnd(); - - (bool hasAccruedFees, address liquidityToken) = fullRange.poolInfo(id); - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY); - - assertEq(key.currency0.balanceOf(address(this)), prevBalance0 - 10 ether); - assertEq(key.currency1.balanceOf(address(this)), prevBalance1 - 10 ether); - - assertEq(liquidityTokenBal, 10 ether - LOCKED_LIQUIDITY); - assertEq(hasAccruedFees, false); - } - - function testFullRange_addLiquidity_InitialAddFuzz(uint256 amount) public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - if (amount <= LOCKED_LIQUIDITY) { - vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, amount, amount, address(this), MAX_DEADLINE - ) - ); - } else if (amount > MAX_TICK_LIQUIDITY) { - vm.expectRevert(); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, amount, amount, address(this), MAX_DEADLINE - ) - ); - } else { - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, 0, 0, address(this), MAX_DEADLINE - ) - ); - - (bool hasAccruedFees, address liquidityToken) = fullRange.poolInfo(id); - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY); - assertEq(hasAccruedFees, false); - } - } - - function testFullRange_addLiquidity_SubsequentAdd() public { - uint256 prevBalance0 = keyWithLiq.currency0.balanceOfSelf(); - uint256 prevBalance1 = keyWithLiq.currency1.balanceOfSelf(); - - (, address liquidityToken) = fullRange.poolInfo(idWithLiq); - uint256 prevLiquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - FullRange.AddLiquidityParams memory addLiquidityParams = FullRange.AddLiquidityParams( - keyWithLiq.currency0, - keyWithLiq.currency1, - 3000, - 10 ether, - 10 ether, - 9 ether, - 9 ether, - address(this), - MAX_DEADLINE - ); - - snapStart("FullRangeAddLiquidity"); - fullRange.addLiquidity(addLiquidityParams); - snapEnd(); - - (bool hasAccruedFees,) = fullRange.poolInfo(idWithLiq); - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(idWithLiq), liquidityTokenBal + LOCKED_LIQUIDITY); - - assertEq(keyWithLiq.currency0.balanceOfSelf(), prevBalance0 - 10 ether); - assertEq(keyWithLiq.currency1.balanceOfSelf(), prevBalance1 - 10 ether); - - assertEq(liquidityTokenBal, prevLiquidityTokenBal + 10 ether); - assertEq(hasAccruedFees, false); - } - - function testFullRange_addLiquidity_FailsIfNoPool() public { - vm.expectRevert(FullRange.PoolNotInitialized.selector); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 0, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ) - ); - } - - function testFullRange_addLiquidity_SwapThenAddSucceeds() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - uint256 prevBalance0 = key.currency0.balanceOf(address(this)); - uint256 prevBalance1 = key.currency1.balanceOf(address(this)); - (, address liquidityToken) = fullRange.poolInfo(id); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ) - ); - - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY); - assertEq(liquidityTokenBal, 10 ether - LOCKED_LIQUIDITY); - assertEq(key.currency0.balanceOf(address(this)), prevBalance0 - 10 ether); - assertEq(key.currency1.balanceOf(address(this)), prevBalance1 - 10 ether); - - vm.expectEmit(true, true, true, true); - emit Swap( - id, address(router), -1 ether, 906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 - ); - - IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: -1 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); - HookEnabledSwapRouter.TestSettings memory settings = - HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); - - snapStart("FullRangeSwap"); - router.swap(key, params, settings, ZERO_BYTES); - snapEnd(); - - (bool hasAccruedFees,) = fullRange.poolInfo(id); - - assertEq(key.currency0.balanceOf(address(this)), prevBalance0 - 10 ether - 1 ether); - assertEq(key.currency1.balanceOf(address(this)), prevBalance1 - 9093389106119850869); - assertEq(hasAccruedFees, true); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 5 ether, 5 ether, 4 ether, 4 ether, address(this), MAX_DEADLINE - ) - ); - - (hasAccruedFees,) = fullRange.poolInfo(id); - liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY); - assertEq(liquidityTokenBal, 14546694553059925434 - LOCKED_LIQUIDITY); - assertEq(hasAccruedFees, true); - } - - function testFullRange_addLiquidity_FailsIfTooMuchSlippage() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 10 ether, 10 ether, address(this), MAX_DEADLINE - ) - ); - - IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); - HookEnabledSwapRouter.TestSettings memory settings = - HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); - - router.swap(key, params, settings, ZERO_BYTES); - - vm.expectRevert(FullRange.TooMuchSlippage.selector); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 10 ether, 10 ether, address(this), MAX_DEADLINE - ) - ); - } - - function testFullRange_swap_TwoSwaps() public { - PoolKey memory testKey = key; - manager.initialize(testKey, SQRT_PRICE_1_1, ZERO_BYTES); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ) - ); - - IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); - HookEnabledSwapRouter.TestSettings memory settings = - HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); - - snapStart("FullRangeFirstSwap"); - router.swap(testKey, params, settings, ZERO_BYTES); - snapEnd(); - - (bool hasAccruedFees,) = fullRange.poolInfo(id); - assertEq(hasAccruedFees, true); - - snapStart("FullRangeSecondSwap"); - router.swap(testKey, params, settings, ZERO_BYTES); - snapEnd(); - - (hasAccruedFees,) = fullRange.poolInfo(id); - assertEq(hasAccruedFees, true); - } - - function testFullRange_swap_TwoPools() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - manager.initialize(key2, SQRT_PRICE_1_1, ZERO_BYTES); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ) - ); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key2.currency0, key2.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ) - ); - - IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10000000, sqrtPriceLimitX96: SQRT_PRICE_1_2}); - - HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); - - router.swap(key, params, testSettings, ZERO_BYTES); - router.swap(key2, params, testSettings, ZERO_BYTES); - - (bool hasAccruedFees,) = fullRange.poolInfo(id); - assertEq(hasAccruedFees, true); - - (hasAccruedFees,) = fullRange.poolInfo(id2); - assertEq(hasAccruedFees, true); - } - - function testFullRange_removeLiquidity_InitialRemoveSucceeds() public { - uint256 prevBalance0 = keyWithLiq.currency0.balanceOfSelf(); - uint256 prevBalance1 = keyWithLiq.currency1.balanceOfSelf(); - - (, address liquidityToken) = fullRange.poolInfo(idWithLiq); - - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - FullRange.RemoveLiquidityParams memory removeLiquidityParams = - FullRange.RemoveLiquidityParams(keyWithLiq.currency0, keyWithLiq.currency1, 3000, 1 ether, MAX_DEADLINE); - - snapStart("FullRangeRemoveLiquidity"); - fullRange.removeLiquidity(removeLiquidityParams); - snapEnd(); - - (bool hasAccruedFees,) = fullRange.poolInfo(idWithLiq); - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(idWithLiq), liquidityTokenBal + LOCKED_LIQUIDITY); - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 99 ether - LOCKED_LIQUIDITY + 5); - assertEq(keyWithLiq.currency0.balanceOfSelf(), prevBalance0 + 1 ether - 1); - assertEq(keyWithLiq.currency1.balanceOfSelf(), prevBalance1 + 1 ether - 1); - assertEq(hasAccruedFees, false); - } - - function testFullRange_removeLiquidity_InitialRemoveFuzz(uint256 amount) public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, - key.currency1, - 3000, - 1000 ether, - 1000 ether, - 999 ether, - 999 ether, - address(this), - MAX_DEADLINE - ) - ); - - (, address liquidityToken) = fullRange.poolInfo(id); - - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - if (amount > UniswapV4ERC20(liquidityToken).balanceOf(address(this))) { - vm.expectRevert(); - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, amount, MAX_DEADLINE) - ); - } else { - uint256 prevLiquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, amount, MAX_DEADLINE) - ); - - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - (bool hasAccruedFees,) = fullRange.poolInfo(id); - - assertEq(prevLiquidityTokenBal - liquidityTokenBal, amount); - assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY); - assertEq(hasAccruedFees, false); - } - } - - function testFullRange_removeLiquidity_FailsIfNoPool() public { - vm.expectRevert(FullRange.PoolNotInitialized.selector); - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 0, 10 ether, MAX_DEADLINE) - ); - } - - function testFullRange_removeLiquidity_FailsIfNoLiquidity() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - (, address liquidityToken) = fullRange.poolInfo(id); - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - vm.expectRevert(); // Insufficient balance error from ERC20 contract - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, 10 ether, MAX_DEADLINE) - ); - } - - function testFullRange_removeLiquidity_SucceedsWithPartial() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - uint256 prevBalance0 = key.currency0.balanceOfSelf(); - uint256 prevBalance1 = key.currency1.balanceOfSelf(); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ) - ); - - (, address liquidityToken) = fullRange.poolInfo(id); - - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - LOCKED_LIQUIDITY); - - assertEq(key.currency0.balanceOfSelf(), prevBalance0 - 10 ether); - assertEq(key.currency1.balanceOfSelf(), prevBalance1 - 10 ether); - - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, 5 ether, MAX_DEADLINE) - ); - - (bool hasAccruedFees,) = fullRange.poolInfo(id); - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY); - assertEq(liquidityTokenBal, 5 ether - LOCKED_LIQUIDITY); - assertEq(key.currency0.balanceOfSelf(), prevBalance0 - 5 ether - 1); - assertEq(key.currency1.balanceOfSelf(), prevBalance1 - 5 ether - 1); - assertEq(hasAccruedFees, false); - } - - function testFullRange_removeLiquidity_DiffRatios() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - uint256 prevBalance0 = key.currency0.balanceOf(address(this)); - uint256 prevBalance1 = key.currency1.balanceOf(address(this)); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 10 ether, 10 ether, 9 ether, 9 ether, address(this), MAX_DEADLINE - ) - ); - - assertEq(key.currency0.balanceOf(address(this)), prevBalance0 - 10 ether); - assertEq(key.currency1.balanceOf(address(this)), prevBalance1 - 10 ether); - - (, address liquidityToken) = fullRange.poolInfo(id); - - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 10 ether - LOCKED_LIQUIDITY); - - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, 5 ether, 2.5 ether, 2 ether, 2 ether, address(this), MAX_DEADLINE - ) - ); - - assertEq(key.currency0.balanceOf(address(this)), prevBalance0 - 12.5 ether); - assertEq(key.currency1.balanceOf(address(this)), prevBalance1 - 12.5 ether); - - assertEq(UniswapV4ERC20(liquidityToken).balanceOf(address(this)), 12.5 ether - LOCKED_LIQUIDITY); - - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, 5 ether, MAX_DEADLINE) - ); - - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - assertEq(manager.getLiquidity(id), liquidityTokenBal + LOCKED_LIQUIDITY); - assertEq(liquidityTokenBal, 7.5 ether - LOCKED_LIQUIDITY); - assertEq(key.currency0.balanceOf(address(this)), prevBalance0 - 7.5 ether - 1); - assertEq(key.currency1.balanceOf(address(this)), prevBalance1 - 7.5 ether - 1); - } - - function testFullRange_removeLiquidity_SwapAndRebalance() public { - (, address liquidityToken) = fullRange.poolInfo(idWithLiq); - - IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_PRICE_1_2}); - - HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); - - router.swap(keyWithLiq, params, testSettings, ZERO_BYTES); - - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - FullRange.RemoveLiquidityParams memory removeLiquidityParams = - FullRange.RemoveLiquidityParams(keyWithLiq.currency0, keyWithLiq.currency1, 3000, 5 ether, MAX_DEADLINE); - - snapStart("FullRangeRemoveLiquidityAndRebalance"); - fullRange.removeLiquidity(removeLiquidityParams); - snapEnd(); - - (bool hasAccruedFees,) = fullRange.poolInfo(idWithLiq); - assertEq(hasAccruedFees, false); - } - - function testFullRange_removeLiquidity_RemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - (, address liquidityToken) = fullRange.poolInfo(id); - - if (amount <= LOCKED_LIQUIDITY) { - vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, amount, amount, address(this), MAX_DEADLINE - ) - ); - } else if (amount >= MAX_TICK_LIQUIDITY) { - vm.expectRevert(); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, amount, amount, address(this), MAX_DEADLINE - ) - ); - } else { - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, 0, 0, address(this), MAX_DEADLINE - ) - ); - - // Test contract removes liquidity, succeeds - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, liquidityTokenBal, MAX_DEADLINE) - ); - - assertEq(manager.getLiquidity(id), LOCKED_LIQUIDITY); - } - } - - function testFullRange_removeLiquidity_ThreeLPsRemovePrincipalAndFees() public { - // Mint tokens for dummy addresses - token0.mint(address(1), 2 ** 128); - token1.mint(address(1), 2 ** 128); - token0.mint(address(2), 2 ** 128); - token1.mint(address(2), 2 ** 128); - - // Approve the hook - vm.prank(address(1)); - token0.approve(address(fullRange), type(uint256).max); - vm.prank(address(1)); - token1.approve(address(fullRange), type(uint256).max); - - vm.prank(address(2)); - token0.approve(address(fullRange), type(uint256).max); - vm.prank(address(2)); - token1.approve(address(fullRange), type(uint256).max); - - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - (, address liquidityToken) = fullRange.poolInfo(id); - - // Test contract adds liquidity - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, - key.currency1, - 3000, - 100 ether, - 100 ether, - 99 ether, - 99 ether, - address(this), - MAX_DEADLINE - ) - ); - - // address(1) adds liquidity - vm.prank(address(1)); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, - key.currency1, - 3000, - 100 ether, - 100 ether, - 99 ether, - 99 ether, - address(this), - MAX_DEADLINE - ) - ); - - // address(2) adds liquidity - vm.prank(address(2)); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, - key.currency1, - 3000, - 100 ether, - 100 ether, - 99 ether, - 99 ether, - address(this), - MAX_DEADLINE - ) - ); - - IPoolManager.SwapParams memory params = - IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100 ether, sqrtPriceLimitX96: SQRT_PRICE_1_4}); - - HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); - - router.swap(key, params, testSettings, ZERO_BYTES); - - (bool hasAccruedFees,) = fullRange.poolInfo(id); - assertEq(hasAccruedFees, true); - - // Test contract removes liquidity, succeeds - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams( - key.currency0, key.currency1, 3000, 300 ether - LOCKED_LIQUIDITY, MAX_DEADLINE - ) - ); - (hasAccruedFees,) = fullRange.poolInfo(id); - - // PoolManager does not have any liquidity left over - assertTrue(manager.getLiquidity(id) >= LOCKED_LIQUIDITY); - assertTrue(manager.getLiquidity(id) < LOCKED_LIQUIDITY + DUST); - - assertEq(hasAccruedFees, false); - } - - function testFullRange_removeLiquidity_SwapRemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - (, address liquidityToken) = fullRange.poolInfo(id); - - if (amount <= LOCKED_LIQUIDITY) { - vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, amount, amount, address(this), MAX_DEADLINE - ) - ); - } else if (amount >= MAX_TICK_LIQUIDITY) { - vm.expectRevert(); - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, amount, amount, address(this), MAX_DEADLINE - ) - ); - } else { - fullRange.addLiquidity( - FullRange.AddLiquidityParams( - key.currency0, key.currency1, 3000, amount, amount, 0, 0, address(this), MAX_DEADLINE - ) - ); - - IPoolManager.SwapParams memory params = IPoolManager.SwapParams({ - zeroForOne: true, - amountSpecified: (FullMath.mulDiv(amount, 1, 4)).toInt256(), - sqrtPriceLimitX96: SQRT_PRICE_1_4 - }); - - HookEnabledSwapRouter.TestSettings memory testSettings = - HookEnabledSwapRouter.TestSettings({takeClaims: false, settleUsingBurn: false}); - - router.swap(key, params, testSettings, ZERO_BYTES); - - // Test contract removes liquidity, succeeds - UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); - - uint256 liquidityTokenBal = UniswapV4ERC20(liquidityToken).balanceOf(address(this)); - - fullRange.removeLiquidity( - FullRange.RemoveLiquidityParams(key.currency0, key.currency1, 3000, liquidityTokenBal, MAX_DEADLINE) - ); - - assertTrue(manager.getLiquidity(id) <= LOCKED_LIQUIDITY + DUST); - } - } - - function testFullRange_BeforeModifyPositionFailsWithWrongMsgSender() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - - vm.expectRevert( - abi.encodeWithSelector( - Hooks.FailedHookCall.selector, abi.encodeWithSelector(FullRange.SenderMustBeHook.selector) - ) - ); - - modifyLiquidityRouter.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({tickLower: MIN_TICK, tickUpper: MAX_TICK, liquidityDelta: 100, salt: 0}), - ZERO_BYTES - ); - } - - function createPoolKey(MockERC20 tokenA, MockERC20 tokenB) internal view returns (PoolKey memory) { - if (address(tokenA) > address(tokenB)) (tokenA, tokenB) = (tokenB, tokenA); - return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, TICK_SPACING, fullRange); - } -} diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol deleted file mode 100644 index 7fbcd995..00000000 --- a/test/GeomeanOracle.t.sol +++ /dev/null @@ -1,237 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {Test} from "forge-std/Test.sol"; -import {GetSender} from "./shared/GetSender.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/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 {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {Oracle} from "../contracts/libraries/Oracle.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; - -contract TestGeomeanOracle is Test, Deployers { - using PoolIdLibrary for PoolKey; - - int24 constant MAX_TICK_SPACING = 32767; - - TestERC20 token0; - TestERC20 token1; - 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 - ) - ) - ); - PoolId id; - - function setUp() public { - deployFreshManagerAndRouters(); - (currency0, currency1) = deployMintAndApprove2Currencies(); - - token0 = TestERC20(Currency.unwrap(currency0)); - token1 = TestERC20(Currency.unwrap(currency1)); - - vm.record(); - GeomeanOracleImplementation impl = new GeomeanOracleImplementation(manager, geomeanOracle); - (, bytes32[] memory writes) = vm.accesses(address(impl)); - vm.etch(address(geomeanOracle), address(impl).code); - // for each storage key that was written during the hook implementation, copy the value over - unchecked { - for (uint256 i = 0; i < writes.length; i++) { - bytes32 slot = writes[i]; - vm.store(address(geomeanOracle), slot, vm.load(address(impl), slot)); - } - } - geomeanOracle.setTime(1); - key = PoolKey(currency0, currency1, 0, MAX_TICK_SPACING, geomeanOracle); - id = key.toId(); - - modifyLiquidityRouter = new PoolModifyLiquidityTest(manager); - - token0.approve(address(geomeanOracle), type(uint256).max); - token1.approve(address(geomeanOracle), type(uint256).max); - token0.approve(address(modifyLiquidityRouter), type(uint256).max); - token1.approve(address(modifyLiquidityRouter), type(uint256).max); - } - - function testBeforeInitializeAllowsPoolCreation() public { - manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES); - } - - function testBeforeInitializeRevertsIfFee() public { - vm.expectRevert( - abi.encodeWithSelector( - Hooks.FailedHookCall.selector, abi.encodeWithSelector(GeomeanOracle.OnlyOneOraclePoolAllowed.selector) - ) - ); - - manager.initialize( - PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 1, MAX_TICK_SPACING, geomeanOracle), - SQRT_PRICE_1_1, - ZERO_BYTES - ); - } - - function testBeforeInitializeRevertsIfNotMaxTickSpacing() public { - vm.expectRevert( - abi.encodeWithSelector( - Hooks.FailedHookCall.selector, abi.encodeWithSelector(GeomeanOracle.OnlyOneOraclePoolAllowed.selector) - ) - ); - - manager.initialize( - PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 0, 60, geomeanOracle), - SQRT_PRICE_1_1, - ZERO_BYTES - ); - } - - function testAfterInitializeState() public { - manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); - GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); - assertEq(observationState.index, 0); - assertEq(observationState.cardinality, 1); - assertEq(observationState.cardinalityNext, 1); - } - - function testAfterInitializeObservation() public { - manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); - Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); - assertTrue(observation.initialized); - assertEq(observation.blockTimestamp, 1); - assertEq(observation.tickCumulative, 0); - assertEq(observation.secondsPerLiquidityCumulativeX128, 0); - } - - function testAfterInitializeObserve0() public { - manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); - uint32[] memory secondsAgo = new uint32[](1); - secondsAgo[0] = 0; - (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = - geomeanOracle.observe(key, secondsAgo); - assertEq(tickCumulatives.length, 1); - assertEq(secondsPerLiquidityCumulativeX128s.length, 1); - assertEq(tickCumulatives[0], 0); - assertEq(secondsPerLiquidityCumulativeX128s[0], 0); - } - - function testBeforeModifyPositionNoObservations() public { - manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); - modifyLiquidityRouter.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 - ), - ZERO_BYTES - ); - - GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); - assertEq(observationState.index, 0); - assertEq(observationState.cardinality, 1); - assertEq(observationState.cardinalityNext, 1); - - Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); - assertTrue(observation.initialized); - assertEq(observation.blockTimestamp, 1); - assertEq(observation.tickCumulative, 0); - assertEq(observation.secondsPerLiquidityCumulativeX128, 0); - } - - function testBeforeModifyPositionObservation() public { - manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); - geomeanOracle.setTime(3); // advance 2 seconds - modifyLiquidityRouter.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 - ), - ZERO_BYTES - ); - - GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); - assertEq(observationState.index, 0); - assertEq(observationState.cardinality, 1); - assertEq(observationState.cardinalityNext, 1); - - Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); - assertTrue(observation.initialized); - assertEq(observation.blockTimestamp, 3); - assertEq(observation.tickCumulative, 13862); - assertEq(observation.secondsPerLiquidityCumulativeX128, 680564733841876926926749214863536422912); - } - - function testBeforeModifyPositionObservationAndCardinality() public { - manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); - geomeanOracle.setTime(3); // advance 2 seconds - geomeanOracle.increaseCardinalityNext(key, 2); - GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); - assertEq(observationState.index, 0); - assertEq(observationState.cardinality, 1); - assertEq(observationState.cardinalityNext, 2); - - modifyLiquidityRouter.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 - ), - ZERO_BYTES - ); - - // cardinality is updated - observationState = geomeanOracle.getState(key); - assertEq(observationState.index, 1); - assertEq(observationState.cardinality, 2); - assertEq(observationState.cardinalityNext, 2); - - // index 0 is untouched - Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); - assertTrue(observation.initialized); - assertEq(observation.blockTimestamp, 1); - assertEq(observation.tickCumulative, 0); - assertEq(observation.secondsPerLiquidityCumulativeX128, 0); - - // index 1 is written - observation = geomeanOracle.getObservation(key, 1); - assertTrue(observation.initialized); - assertEq(observation.blockTimestamp, 3); - assertEq(observation.tickCumulative, 13862); - assertEq(observation.secondsPerLiquidityCumulativeX128, 680564733841876926926749214863536422912); - } - - function testPermanentLiquidity() public { - manager.initialize(key, SQRT_PRICE_2_1, ZERO_BYTES); - geomeanOracle.setTime(3); // advance 2 seconds - modifyLiquidityRouter.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), 1000, 0 - ), - ZERO_BYTES - ); - - vm.expectRevert( - abi.encodeWithSelector( - Hooks.FailedHookCall.selector, - abi.encodeWithSelector(GeomeanOracle.OraclePoolMustLockLiquidity.selector) - ) - ); - - modifyLiquidityRouter.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams( - TickMath.minUsableTick(MAX_TICK_SPACING), TickMath.maxUsableTick(MAX_TICK_SPACING), -1000, 0 - ), - ZERO_BYTES - ); - } -} diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol deleted file mode 100644 index 17f5aecb..00000000 --- a/test/LimitOrder.t.sol +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {Test} from "forge-std/Test.sol"; -import {GetSender} from "./shared/GetSender.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/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"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; - -contract TestLimitOrder is Test, Deployers { - using PoolIdLibrary for PoolKey; - using StateLibrary for IPoolManager; - - uint160 constant SQRT_RATIO_10_1 = 250541448375047931186413801569; - - HookEnabledSwapRouter router; - TestERC20 token0; - TestERC20 token1; - LimitOrder limitOrder = LimitOrder(address(uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_SWAP_FLAG))); - PoolId id; - - function setUp() public { - deployFreshManagerAndRouters(); - (currency0, currency1) = deployMintAndApprove2Currencies(); - - router = new HookEnabledSwapRouter(manager); - token0 = TestERC20(Currency.unwrap(currency0)); - token1 = TestERC20(Currency.unwrap(currency1)); - - vm.record(); - LimitOrderImplementation impl = new LimitOrderImplementation(manager, limitOrder); - (, bytes32[] memory writes) = vm.accesses(address(impl)); - vm.etch(address(limitOrder), address(impl).code); - // for each storage key that was written during the hook implementation, copy the value over - unchecked { - for (uint256 i = 0; i < writes.length; i++) { - bytes32 slot = writes[i]; - vm.store(address(limitOrder), slot, vm.load(address(impl), slot)); - } - } - - // key = PoolKey(currency0, currency1, 3000, 60, limitOrder); - (key, id) = initPoolAndAddLiquidity(currency0, currency1, limitOrder, 3000, SQRT_PRICE_1_1, ZERO_BYTES); - - token0.approve(address(limitOrder), type(uint256).max); - token1.approve(address(limitOrder), type(uint256).max); - token0.approve(address(router), type(uint256).max); - token1.approve(address(router), type(uint256).max); - } - - function testGetTickLowerLast() public { - assertEq(limitOrder.getTickLowerLast(id), 0); - } - - 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); - assertEq(limitOrder.getTickLowerLast(differentKey.toId()), 22997); - } - - function testEpochNext() public { - assertTrue(EpochLibrary.equals(limitOrder.epochNext(), Epoch.wrap(1))); - } - - function testZeroLiquidityRevert() public { - vm.expectRevert(LimitOrder.ZeroLiquidity.selector); - limitOrder.place(key, 0, true, 0); - } - - function testZeroForOneRightBoundaryOfCurrentRange() public { - int24 tickLower = 60; - bool zeroForOne = true; - uint128 liquidity = 1000000; - limitOrder.place(key, tickLower, zeroForOne, liquidity); - assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - - assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity); - } - - function testZeroForOneLeftBoundaryOfCurrentRange() public { - int24 tickLower = 0; - bool zeroForOne = true; - uint128 liquidity = 1000000; - limitOrder.place(key, tickLower, zeroForOne, liquidity); - assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity); - } - - function testZeroForOneCrossedRangeRevert() public { - vm.expectRevert(LimitOrder.CrossedRange.selector); - limitOrder.place(key, -60, true, 1000000); - } - - function testZeroForOneInRangeRevert() public { - // 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_PRICE_1_1 + 1), - HookEnabledSwapRouter.TestSettings(false, false), - ZERO_BYTES - ); - vm.expectRevert(LimitOrder.InRange.selector); - limitOrder.place(key, 0, true, 1000000); - } - - function testNotZeroForOneLeftBoundaryOfCurrentRange() public { - int24 tickLower = -60; - bool zeroForOne = false; - uint128 liquidity = 1000000; - limitOrder.place(key, tickLower, zeroForOne, liquidity); - assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity); - } - - function testNotZeroForOneCrossedRangeRevert() public { - vm.expectRevert(LimitOrder.CrossedRange.selector); - limitOrder.place(key, 0, false, 1000000); - } - - function testNotZeroForOneInRangeRevert() public { - // 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_PRICE_1_1 - 1), - HookEnabledSwapRouter.TestSettings(false, false), - ZERO_BYTES - ); - vm.expectRevert(LimitOrder.InRange.selector); - limitOrder.place(key, -60, false, 1000000); - } - - function testMultipleLPs() public { - int24 tickLower = 60; - bool zeroForOne = true; - uint128 liquidity = 1000000; - limitOrder.place(key, tickLower, zeroForOne, liquidity); - address other = 0x1111111111111111111111111111111111111111; - token0.transfer(other, 1e18); - token1.transfer(other, 1e18); - vm.startPrank(other); - token0.approve(address(limitOrder), type(uint256).max); - token1.approve(address(limitOrder), type(uint256).max); - limitOrder.place(key, tickLower, zeroForOne, liquidity); - vm.stopPrank(); - assertTrue(EpochLibrary.equals(limitOrder.getEpoch(key, tickLower, zeroForOne), Epoch.wrap(1))); - assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, liquidity * 2); - - ( - bool filled, - Currency currency0, - Currency currency1, - uint256 token0Total, - uint256 token1Total, - uint128 liquidityTotal - ) = limitOrder.epochInfos(Epoch.wrap(1)); - assertFalse(filled); - assertTrue(currency0 == Currency.wrap(address(token0))); - assertTrue(currency1 == Currency.wrap(address(token1))); - assertEq(token0Total, 0); - assertEq(token1Total, 0); - assertEq(liquidityTotal, liquidity * 2); - assertEq(limitOrder.getEpochLiquidity(Epoch.wrap(1), new GetSender().sender()), liquidity); - assertEq(limitOrder.getEpochLiquidity(Epoch.wrap(1), other), liquidity); - } - - event Transfer(address indexed from, address indexed to, uint256 value); - - function testKill() public { - int24 tickLower = 0; - bool zeroForOne = true; - uint128 liquidity = 1000000; - limitOrder.place(key, tickLower, zeroForOne, liquidity); - vm.expectEmit(true, true, true, true, address(token0)); - emit Transfer(address(manager), new GetSender().sender(), 2995); - limitOrder.kill(key, tickLower, zeroForOne, new GetSender().sender()); - } - - function testSwapAcrossRange() public { - int24 tickLower = 0; - bool zeroForOne = true; - uint128 liquidity = 1000000; - limitOrder.place(key, tickLower, zeroForOne, liquidity); - - router.swap( - key, - IPoolManager.SwapParams(false, -1e18, TickMath.getSqrtPriceAtTick(60)), - HookEnabledSwapRouter.TestSettings(false, false), - ZERO_BYTES - ); - - assertEq(limitOrder.getTickLowerLast(id), 60); - (, int24 tick,,) = manager.getSlot0(id); - assertEq(tick, 60); - - (bool filled,,, uint256 token0Total, uint256 token1Total,) = limitOrder.epochInfos(Epoch.wrap(1)); - - assertTrue(filled); - assertEq(token0Total, 0); - assertEq(token1Total, 2996 + 17); // 3013, 2 wei of dust - assertEq(manager.getPosition(id, address(limitOrder), tickLower, tickLower + 60, 0).liquidity, 0); - - vm.expectEmit(true, true, true, true, address(token1)); - emit Transfer(address(manager), new GetSender().sender(), 2996 + 17); - limitOrder.withdraw(Epoch.wrap(1), new GetSender().sender()); - - (,,, token0Total, token1Total,) = limitOrder.epochInfos(Epoch.wrap(1)); - - assertEq(token0Total, 0); - assertEq(token1Total, 0); - } -} diff --git a/test/Oracle.t.sol b/test/Oracle.t.sol deleted file mode 100644 index 04157e16..00000000 --- a/test/Oracle.t.sol +++ /dev/null @@ -1,867 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {Test} from "forge-std/Test.sol"; -import {Oracle} from "../contracts/libraries/Oracle.sol"; -import {OracleImplementation} from "./shared/implementation/OracleImplementation.sol"; - -contract TestOracle is Test, GasSnapshot { - OracleImplementation initializedOracle; - OracleImplementation oracle; - - function setUp() public { - oracle = new OracleImplementation(); - initializedOracle = new OracleImplementation(); - initializedOracle.initialize(OracleImplementation.InitializeParams({time: 0, tick: 0, liquidity: 0})); - } - - function testInitialize() public { - snapStart("OracleInitialize"); - oracle.initialize(OracleImplementation.InitializeParams({time: 1, tick: 1, liquidity: 1})); - snapEnd(); - - assertEq(oracle.index(), 0); - assertEq(oracle.cardinality(), 1); - assertEq(oracle.cardinalityNext(), 1); - assertObservation( - oracle, - 0, - Oracle.Observation({ - blockTimestamp: 1, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 0, - initialized: true - }) - ); - } - - function testGrow() public { - initializedOracle.grow(5); - assertEq(initializedOracle.index(), 0); - assertEq(initializedOracle.cardinality(), 1); - assertEq(initializedOracle.cardinalityNext(), 5); - - // does not touch first slot - assertObservation( - initializedOracle, - 0, - Oracle.Observation({ - blockTimestamp: 0, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 0, - initialized: true - }) - ); - - // adds data to all slots - for (uint64 i = 1; i < 5; i++) { - assertObservation( - initializedOracle, - i, - Oracle.Observation({ - blockTimestamp: 1, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 0, - initialized: false - }) - ); - } - - // noop if initializedOracle is already gte size - initializedOracle.grow(3); - assertEq(initializedOracle.index(), 0); - assertEq(initializedOracle.cardinality(), 1); - assertEq(initializedOracle.cardinalityNext(), 5); - } - - function testGrowAfterWrap() public { - initializedOracle.grow(2); - // index is now 1 - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 2, liquidity: 1, tick: 1})); - // index is now 0 again - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 2, liquidity: 1, tick: 1})); - assertEq(initializedOracle.index(), 0); - initializedOracle.grow(3); - - assertEq(initializedOracle.index(), 0); - assertEq(initializedOracle.cardinality(), 2); - assertEq(initializedOracle.cardinalityNext(), 3); - } - - function testGas1Slot() public { - snapStart("OracleGrow1Slot"); - initializedOracle.grow(2); - snapEnd(); - } - - function testGas10Slots() public { - snapStart("OracleGrow10Slots"); - initializedOracle.grow(11); - snapEnd(); - } - - function testGas1SlotCardinalityGreater() public { - initializedOracle.grow(2); - snapStart("OracleGrow1SlotCardinalityGreater"); - initializedOracle.grow(3); - snapEnd(); - } - - function testGas10SlotCardinalityGreater() public { - initializedOracle.grow(2); - snapStart("OracleGrow10SlotsCardinalityGreater"); - initializedOracle.grow(12); - snapEnd(); - } - - function testWrite() public { - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 1, tick: 2, liquidity: 5})); - assertEq(initializedOracle.index(), 0); - assertObservation( - initializedOracle, - 0, - Oracle.Observation({ - blockTimestamp: 1, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 340282366920938463463374607431768211456, - initialized: true - }) - ); - - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 5, tick: -1, liquidity: 8})); - assertEq(initializedOracle.index(), 0); - assertObservation( - initializedOracle, - 0, - Oracle.Observation({ - blockTimestamp: 6, - tickCumulative: 10, - secondsPerLiquidityCumulativeX128: 680564733841876926926749214863536422912, - initialized: true - }) - ); - - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: 2, liquidity: 3})); - assertEq(initializedOracle.index(), 0); - assertObservation( - initializedOracle, - 0, - Oracle.Observation({ - blockTimestamp: 9, - tickCumulative: 7, - secondsPerLiquidityCumulativeX128: 808170621437228850725514692650449502208, - initialized: true - }) - ); - } - - function testWriteAddsNothingIfTimeUnchanged() public { - initializedOracle.grow(2); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 1, tick: 3, liquidity: 2})); - assertEq(initializedOracle.index(), 1); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 0, tick: -5, liquidity: 9})); - assertEq(initializedOracle.index(), 1); - } - - function testWriteTimeChanged() public { - initializedOracle.grow(3); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 6, tick: 3, liquidity: 2})); - assertEq(initializedOracle.index(), 1); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: -5, liquidity: 9})); - assertEq(initializedOracle.index(), 2); - assertObservation( - initializedOracle, - 1, - Oracle.Observation({ - blockTimestamp: 6, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 2041694201525630780780247644590609268736, - initialized: true - }) - ); - } - - function testWriteGrowsCardinalityWritingPast() public { - initializedOracle.grow(2); - initializedOracle.grow(4); - assertEq(initializedOracle.cardinality(), 1); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: 5, liquidity: 6})); - assertEq(initializedOracle.cardinality(), 4); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 6, liquidity: 4})); - assertEq(initializedOracle.cardinality(), 4); - assertEq(initializedOracle.index(), 2); - assertObservation( - initializedOracle, - 2, - Oracle.Observation({ - blockTimestamp: 7, - tickCumulative: 20, - secondsPerLiquidityCumulativeX128: 1247702012043441032699040227249816775338, - initialized: true - }) - ); - } - - function testWriteWrapsAround() public { - initializedOracle.grow(3); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: 1, liquidity: 2})); - - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 2, liquidity: 3})); - - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 5, tick: 3, liquidity: 4})); - - assertEq(initializedOracle.index(), 0); - assertObservation( - initializedOracle, - 0, - Oracle.Observation({ - blockTimestamp: 12, - tickCumulative: 14, - secondsPerLiquidityCumulativeX128: 2268549112806256423089164049545121409706, - initialized: true - }) - ); - } - - function testWriteAccumulatesLiquidity() public { - initializedOracle.grow(4); - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: 3, liquidity: 2})); - - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: -7, liquidity: 6})); - - initializedOracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 5, tick: -2, liquidity: 4})); - - assertEq(initializedOracle.index(), 3); - - assertObservation( - initializedOracle, - 1, - Oracle.Observation({ - blockTimestamp: 3, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 1020847100762815390390123822295304634368, - initialized: true - }) - ); - assertObservation( - initializedOracle, - 2, - Oracle.Observation({ - blockTimestamp: 7, - tickCumulative: 12, - secondsPerLiquidityCumulativeX128: 1701411834604692317316873037158841057280, - initialized: true - }) - ); - assertObservation( - initializedOracle, - 3, - Oracle.Observation({ - blockTimestamp: 12, - tickCumulative: -23, - secondsPerLiquidityCumulativeX128: 1984980473705474370203018543351981233493, - initialized: true - }) - ); - assertObservation( - initializedOracle, - 4, - Oracle.Observation({ - blockTimestamp: 0, - tickCumulative: 0, - secondsPerLiquidityCumulativeX128: 0, - initialized: false - }) - ); - } - - function testObserveFailsBeforeInitialize() public { - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 0; - vm.expectRevert(Oracle.OracleCardinalityCannotBeZero.selector); - oracle.observe(secondsAgos); - } - - function testObserveFailsIfOlderDoesNotExist() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 4, tick: 2, time: 5})); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 1; - vm.expectRevert(abi.encodeWithSelector(Oracle.TargetPredatesOldestObservation.selector, 5, 4)); - oracle.observe(secondsAgos); - } - - function testDoesNotFailAcrossOverflowBoundary() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 4, tick: 2, time: 2 ** 32 - 1})); - oracle.advanceTime(2); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 1); - assertEq(tickCumulative, 2); - assertEq(secondsPerLiquidityCumulativeX128, 85070591730234615865843651857942052864); - } - - function testInterpolationMaxLiquidity() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: type(uint128).max, tick: 0, time: 0})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 13, tick: 0, liquidity: 0})); - (, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(secondsPerLiquidityCumulativeX128, 13); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 6); - assertEq(secondsPerLiquidityCumulativeX128, 7); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 12); - assertEq(secondsPerLiquidityCumulativeX128, 1); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 13); - assertEq(secondsPerLiquidityCumulativeX128, 0); - } - - function testInterpolatesSame0And1Liquidity() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 1, tick: 0, time: 0})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 13, tick: 0, liquidity: type(uint128).max})); - (, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(secondsPerLiquidityCumulativeX128, 13 << 128); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 6); - assertEq(secondsPerLiquidityCumulativeX128, 7 << 128); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 12); - assertEq(secondsPerLiquidityCumulativeX128, 1 << 128); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 13); - assertEq(secondsPerLiquidityCumulativeX128, 0); - } - - function testInterpolatesAcrossChunkBoundaries() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 0, tick: 0, time: 0})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 2 ** 32 - 6, tick: 0, liquidity: 0})); - (, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(secondsPerLiquidityCumulativeX128, (2 ** 32 - 6) << 128); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 13, tick: 0, liquidity: 0})); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(secondsPerLiquidityCumulativeX128, 7 << 128); - - // interpolation checks - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 3); - assertEq(secondsPerLiquidityCumulativeX128, 4 << 128); - (, secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 8); - assertEq(secondsPerLiquidityCumulativeX128, (2 ** 32 - 1) << 128); - } - - function testSingleObservationAtCurrentTime() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 4, tick: 2, time: 5})); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, 0); - assertEq(secondsPerLiquidityCumulativeX128, 0); - } - - function testSingleObservationInRecentPast() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 4, tick: 2, time: 5})); - oracle.advanceTime(3); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 4; - vm.expectRevert(abi.encodeWithSelector(Oracle.TargetPredatesOldestObservation.selector, 5, 4)); - oracle.observe(secondsAgos); - } - - function testSingleObservationSecondsAgo() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 4, tick: 2, time: 5})); - oracle.advanceTime(3); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 3); - assertEq(tickCumulative, 0); - assertEq(secondsPerLiquidityCumulativeX128, 0); - } - - function testSingleObservationInPastCounterfactualInPast() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 4, tick: 2, time: 5})); - oracle.advanceTime(3); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 1); - assertEq(tickCumulative, 4); - assertEq(secondsPerLiquidityCumulativeX128, 170141183460469231731687303715884105728); - } - - function testSingleObservationInPastCounterfactualNow() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 4, tick: 2, time: 5})); - oracle.advanceTime(3); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, 6); - assertEq(secondsPerLiquidityCumulativeX128, 255211775190703847597530955573826158592); - } - - function testTwoObservationsChronologicalZeroSecondsAgoExact() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, -20); - assertEq(secondsPerLiquidityCumulativeX128, 272225893536750770770699685945414569164); - } - - function testTwoObservationsChronologicalZeroSecondsAgoCounterfactual() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - oracle.advanceTime(7); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, -13); - assertEq(secondsPerLiquidityCumulativeX128, 1463214177760035392892510811956603309260); - } - - function testTwoObservationsChronologicalSecondsAgoExactlyFirstObservation() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - oracle.advanceTime(7); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 11); - assertEq(tickCumulative, 0); - assertEq(secondsPerLiquidityCumulativeX128, 0); - } - - function testTwoObservationsChronologicalSecondsAgoBetween() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - oracle.advanceTime(7); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 9); - assertEq(tickCumulative, -10); - assertEq(secondsPerLiquidityCumulativeX128, 136112946768375385385349842972707284582); - } - - function testTwoObservationsReverseOrderZeroSecondsAgoExact() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: -5, liquidity: 4})); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, -17); - assertEq(secondsPerLiquidityCumulativeX128, 782649443918158465965761597093066886348); - } - - function testTwoObservationsReverseOrderZeroSecondsAgoCounterfactual() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: -5, liquidity: 4})); - oracle.advanceTime(7); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, -52); - assertEq(secondsPerLiquidityCumulativeX128, 1378143586029800777026667160098661256396); - } - - function testTwoObservationsReverseOrderSecondsAgoExactlyOnFirstObservation() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: -5, liquidity: 4})); - oracle.advanceTime(7); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 10); - assertEq(tickCumulative, -20); - assertEq(secondsPerLiquidityCumulativeX128, 272225893536750770770699685945414569164); - } - - function testTwoObservationsReverseOrderSecondsAgoBetween() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.grow(2); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: 1, liquidity: 2})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: -5, liquidity: 4})); - oracle.advanceTime(7); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 9); - assertEq(tickCumulative, -19); - assertEq(secondsPerLiquidityCumulativeX128, 442367076997220002502386989661298674892); - } - - function testCanFetchMultipleObservations() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 2 ** 15, tick: 2, time: 5})); - oracle.grow(4); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 13, tick: 6, liquidity: 2 ** 12})); - oracle.advanceTime(5); - uint32[] memory secondsAgos = new uint32[](6); - secondsAgos[0] = 0; - secondsAgos[1] = 3; - secondsAgos[2] = 8; - secondsAgos[3] = 13; - secondsAgos[4] = 15; - secondsAgos[5] = 18; - (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = - oracle.observe(secondsAgos); - assertEq(tickCumulatives.length, 6); - assertEq(tickCumulatives[0], 56); - assertEq(tickCumulatives[1], 38); - assertEq(tickCumulatives[2], 20); - assertEq(tickCumulatives[3], 10); - assertEq(tickCumulatives[4], 6); - assertEq(tickCumulatives[5], 0); - assertEq(secondsPerLiquidityCumulativeX128s.length, 6); - assertEq(secondsPerLiquidityCumulativeX128s[0], 550383467004691728624232610897330176); - assertEq(secondsPerLiquidityCumulativeX128s[1], 301153217795020002454768787094765568); - assertEq(secondsPerLiquidityCumulativeX128s[2], 103845937170696552570609926584401920); - assertEq(secondsPerLiquidityCumulativeX128s[3], 51922968585348276285304963292200960); - assertEq(secondsPerLiquidityCumulativeX128s[4], 31153781151208965771182977975320576); - assertEq(secondsPerLiquidityCumulativeX128s[5], 0); - } - - function testObserveGasSinceMostRecent() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - oracle.advanceTime(2); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 1; - snap("OracleObserveSinceMostRecent", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testObserveGasCurrentTime() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 0; - snap("OracleObserveCurrentTime", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testObserveGasCurrentTimeCounterfactual() public { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: 5})); - initializedOracle.advanceTime(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 0; - snap("OracleObserveCurrentTimeCounterfactual", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testManyObservationsSimpleReads(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - - assertEq(oracle.index(), 1); - assertEq(oracle.cardinality(), 5); - assertEq(oracle.cardinalityNext(), 5); - } - - function testManyObservationsLatestObservationSameTimeAsLatest(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, -21); - assertEq(secondsPerLiquidityCumulativeX128, 2104079302127802832415199655953100107502); - } - - function testManyObservationsLatestObservation5SecondsAfterLatest(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - - // latest observation 5 seconds after latest - oracle.advanceTime(5); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 5); - assertEq(tickCumulative, -21); - assertEq(secondsPerLiquidityCumulativeX128, 2104079302127802832415199655953100107502); - } - - function testManyObservationsCurrentObservation5SecondsAfterLatest(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - - oracle.advanceTime(5); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 0); - assertEq(tickCumulative, 9); - assertEq(secondsPerLiquidityCumulativeX128, 2347138135642758877746181518404363115684); - } - - function testManyObservationsBetweenLatestObservationAtLatest(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 3); - assertEq(tickCumulative, -33); - assertEq(secondsPerLiquidityCumulativeX128, 1593655751746395137220137744805447790318); - } - - function testManyObservationsBetweenLatestObservationAfterLatest(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - - oracle.advanceTime(5); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 8); - assertEq(tickCumulative, -33); - assertEq(secondsPerLiquidityCumulativeX128, 1593655751746395137220137744805447790318); - } - - function testManyObservationsOlderThanOldestReverts(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - - (uint32 oldestTimestamp,,,) = oracle.observations(oracle.index() + 1); - uint32 secondsAgo = 15; - // overflow desired here - uint32 target; - unchecked { - target = oracle.time() - secondsAgo; - } - - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = secondsAgo; - vm.expectRevert( - abi.encodeWithSelector( - Oracle.TargetPredatesOldestObservation.selector, oldestTimestamp, uint32(int32(target)) - ) - ); - oracle.observe(secondsAgos); - - oracle.advanceTime(5); - - secondsAgos[0] = 20; - vm.expectRevert( - abi.encodeWithSelector( - Oracle.TargetPredatesOldestObservation.selector, oldestTimestamp, uint32(int32(target)) - ) - ); - oracle.observe(secondsAgos); - } - - function testManyObservationsOldest(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 14); - assertEq(tickCumulative, -13); - assertEq(secondsPerLiquidityCumulativeX128, 544451787073501541541399371890829138329); - } - - function testManyObservationsOldestAfterTime(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - oracle.advanceTime(6); - (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observeSingle(oracle, 20); - assertEq(tickCumulative, -13); - assertEq(secondsPerLiquidityCumulativeX128, 544451787073501541541399371890829138329); - } - - function testManyObservationsFetchManyValues(uint32 startingTime) public { - setupOracleWithManyObservations(startingTime); - oracle.advanceTime(6); - uint32[] memory secondsAgos = new uint32[](7); - secondsAgos[0] = 20; - secondsAgos[1] = 17; - secondsAgos[2] = 13; - secondsAgos[3] = 10; - secondsAgos[4] = 5; - secondsAgos[5] = 1; - secondsAgos[6] = 0; - (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = - oracle.observe(secondsAgos); - assertEq(tickCumulatives[0], -13); - assertEq(secondsPerLiquidityCumulativeX128s[0], 544451787073501541541399371890829138329); - assertEq(tickCumulatives[1], -31); - assertEq(secondsPerLiquidityCumulativeX128s[1], 799663562264205389138930327464655296921); - assertEq(tickCumulatives[2], -43); - assertEq(secondsPerLiquidityCumulativeX128s[2], 1045423049484883168306923099498710116305); - assertEq(tickCumulatives[3], -37); - assertEq(secondsPerLiquidityCumulativeX128s[3], 1423514568285925905488450441089563684590); - assertEq(tickCumulatives[4], -15); - assertEq(secondsPerLiquidityCumulativeX128s[4], 2152691068830794041481396028443352709138); - assertEq(tickCumulatives[5], 9); - assertEq(secondsPerLiquidityCumulativeX128s[5], 2347138135642758877746181518404363115684); - assertEq(tickCumulatives[6], 15); - assertEq(secondsPerLiquidityCumulativeX128s[6], 2395749902345750086812377890894615717321); - } - - function testGasAllOfLast20Seconds() public { - setupOracleWithManyObservations(5); - oracle.advanceTime(6); - uint32[] memory secondsAgos = new uint32[](20); - for (uint32 i = 0; i < 20; i++) { - secondsAgos[i] = 20 - i; - } - snap("OracleObserveLast20Seconds", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testGasLatestEqual() public { - setupOracleWithManyObservations(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 0; - snap("OracleObserveLatestEqual", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testGasLatestTransform() public { - setupOracleWithManyObservations(5); - oracle.advanceTime(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 0; - snap("OracleObserveLatestTransform", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testGasOldest() public { - setupOracleWithManyObservations(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 14; - snap("OracleObserveOldest", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testGasBetweenOldestAndOldestPlusOne() public { - setupOracleWithManyObservations(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 13; - snap("OracleObserveBetweenOldestAndOldestPlusOne", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testGasMiddle() public { - setupOracleWithManyObservations(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 5; - snap("OracleObserveMiddle", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testFullOracle() public { - setupFullOracle(); - - assertEq(oracle.cardinalityNext(), 65535); - assertEq(oracle.cardinality(), 65535); - assertEq(oracle.index(), 165); - - // can observe into the ordered portion with exact seconds ago - (int56 tickCumulative, uint160 secondsPerLiquidityCumulative) = observeSingle(oracle, 100 * 13); - assertEq(tickCumulative, -27970560813); - assertEq(secondsPerLiquidityCumulative, 60465049086512033878831623038233202591033); - - // can observe into the ordered portion with unexact seconds ago - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 100 * 13 + 5); - assertEq(tickCumulative, -27970232823); - assertEq(secondsPerLiquidityCumulative, 60465023149565257990964350912969670793706); - - // can observe at exactly the latest observation - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 0); - assertEq(tickCumulative, -28055903863); - assertEq(secondsPerLiquidityCumulative, 60471787506468701386237800669810720099776); - - // can observe into the unordered portion of array at exact seconds ago - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 200 * 13); - assertEq(tickCumulative, -27885347763); - assertEq(secondsPerLiquidityCumulative, 60458300386499273141628780395875293027404); - - // can observe into the unordered portion of array at seconds ago between observations - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 200 * 13 + 5); - assertEq(tickCumulative, -27885020273); - assertEq(secondsPerLiquidityCumulative, 60458274409952896081377821330361274907140); - - // can observe the oldest observation - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 13 * 65534); - assertEq(tickCumulative, -175890); - assertEq(secondsPerLiquidityCumulative, 33974356747348039873972993881117400879779); - - // can observe at exactly the latest observation after some time passes - oracle.advanceTime(5); - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 5); - assertEq(tickCumulative, -28055903863); - assertEq(secondsPerLiquidityCumulative, 60471787506468701386237800669810720099776); - - // can observe after the latest observation counterfactual - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 3); - assertEq(tickCumulative, -28056035261); - assertEq(secondsPerLiquidityCumulative, 60471797865298117996489508104462919730461); - - // can observe the oldest observation after time passes - (tickCumulative, secondsPerLiquidityCumulative) = observeSingle(oracle, 13 * 65534 + 5); - assertEq(tickCumulative, -175890); - assertEq(secondsPerLiquidityCumulative, 33974356747348039873972993881117400879779); - } - - function testFullOracleGasCostObserveZero() public { - setupFullOracle(); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 0; - snap("FullOracleObserveZero", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testFullOracleGasCostObserve200By13() public { - setupFullOracle(); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 200 * 13; - snap("FullOracleObserve200By13", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testFullOracleGasCostObserve200By13Plus5() public { - setupFullOracle(); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 200 * 13 + 5; - snap("FullOracleObserve200By13Plus5", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testFullOracleGasCostObserve0After5Seconds() public { - setupFullOracle(); - oracle.advanceTime(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 0; - snap("FullOracleObserve0After5Seconds", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testFullOracleGasCostObserve5After5Seconds() public { - setupFullOracle(); - oracle.advanceTime(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 5; - snap("FullOracleObserve5After5Seconds", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testFullOracleGasCostObserveOldest() public { - setupFullOracle(); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 13 * 65534; - snap("FullOracleObserveOldest", oracle.getGasCostOfObserve(secondsAgos)); - } - - function testFullOracleGasCostObserveOldestAfter5Seconds() public { - setupFullOracle(); - oracle.advanceTime(5); - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = 13 * 65534; - snap("FullOracleObserveOldestAfter5Seconds", oracle.getGasCostOfObserve(secondsAgos)); - } - - // fixtures and helpers - - function observeSingle(OracleImplementation _initializedOracle, uint32 secondsAgo) - internal - view - returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulative) - { - uint32[] memory secondsAgos = new uint32[](1); - secondsAgos[0] = secondsAgo; - (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulatives) = - _initializedOracle.observe(secondsAgos); - return (tickCumulatives[0], secondsPerLiquidityCumulatives[0]); - } - - function assertObservation(OracleImplementation _initializedOracle, uint64 idx, Oracle.Observation memory expected) - internal - { - (uint32 blockTimestamp, int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128, bool initialized) = - _initializedOracle.observations(idx); - assertEq(blockTimestamp, expected.blockTimestamp); - assertEq(tickCumulative, expected.tickCumulative); - assertEq(secondsPerLiquidityCumulativeX128, expected.secondsPerLiquidityCumulativeX128); - assertEq(initialized, expected.initialized); - } - - function setupOracleWithManyObservations(uint32 startingTime) internal { - oracle.initialize(OracleImplementation.InitializeParams({liquidity: 5, tick: -5, time: startingTime})); - oracle.grow(5); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: 1, liquidity: 2})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 2, tick: -6, liquidity: 4})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 4, tick: -2, liquidity: 4})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 1, tick: -2, liquidity: 9})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 3, tick: 4, liquidity: 2})); - oracle.update(OracleImplementation.UpdateParams({advanceTimeBy: 6, tick: 6, liquidity: 7})); - } - - function setupFullOracle() internal { - uint16 BATCH_SIZE = 300; - oracle.initialize( - OracleImplementation.InitializeParams({ - liquidity: 0, - tick: 0, - // Monday, October 5, 2020 9:00:00 AM GMT-05:00 - time: 1601906400 - }) - ); - - uint16 cardinalityNext = oracle.cardinalityNext(); - while (cardinalityNext < 65535) { - uint16 growTo = cardinalityNext + BATCH_SIZE < 65535 ? 65535 : cardinalityNext + BATCH_SIZE; - oracle.grow(growTo); - cardinalityNext = growTo; - } - - for (int24 i = 0; i < 65535; i += int24(uint24(BATCH_SIZE))) { - OracleImplementation.UpdateParams[] memory batch = new OracleImplementation.UpdateParams[](BATCH_SIZE); - for (int24 j = 0; j < int24(uint24(BATCH_SIZE)); j++) { - batch[uint24(j)] = OracleImplementation.UpdateParams({ - advanceTimeBy: 13, - tick: -i - j, - liquidity: uint128(int128(i) + int128(j)) - }); - } - oracle.batchUpdate(batch); - } - } -} diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 0767cadd..c0fa9497 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -3,24 +3,26 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; -import {PathKey} from "../contracts/libraries/PathKey.sol"; -import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; -import {Quoter} from "../contracts/lens/Quoter.sol"; -import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; -import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PathKey} from "../src/libraries/PathKey.sol"; +import {IQuoter} from "../src/interfaces/IQuoter.sol"; +import {Quoter} from "../src/lens/Quoter.sol"; + +// v4-core +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +// solmate +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; + contract QuoterTest is Test, Deployers { using SafeCast for *; using PoolIdLibrary for PoolKey; diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol deleted file mode 100644 index 0f2f82e0..00000000 --- a/test/TWAMM.t.sol +++ /dev/null @@ -1,432 +0,0 @@ -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 "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/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 {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; -import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; -import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -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/src/types/PoolKey.sol"; - -contract TWAMMTest is Test, Deployers, GasSnapshot { - using PoolIdLibrary for PoolKey; - using CurrencyLibrary for Currency; - - event SubmitOrder( - PoolId indexed poolId, - address indexed owner, - uint160 expiration, - bool zeroForOne, - uint256 sellRate, - uint256 earningsFactorLast - ); - - event UpdateOrder( - PoolId indexed poolId, - address indexed owner, - uint160 expiration, - bool zeroForOne, - uint256 sellRate, - uint256 earningsFactorLast - ); - - TWAMM twamm = - TWAMM(address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG))); - address hookAddress; - MockERC20 token0; - MockERC20 token1; - PoolKey poolKey; - PoolId poolId; - - function setUp() public { - deployFreshManagerAndRouters(); - (currency0, currency1) = deployMintAndApprove2Currencies(); - - token0 = MockERC20(Currency.unwrap(currency0)); - token1 = MockERC20(Currency.unwrap(currency1)); - - TWAMMImplementation impl = new TWAMMImplementation(manager, 10_000, twamm); - (, bytes32[] memory writes) = vm.accesses(address(impl)); - vm.etch(address(twamm), address(impl).code); - // for each storage key that was written during the hook implementation, copy the value over - unchecked { - for (uint256 i = 0; i < writes.length; i++) { - bytes32 slot = writes[i]; - vm.store(address(twamm), slot, vm.load(address(impl), slot)); - } - } - - (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_PRICE_1_1, ZERO_BYTES); - - token0.approve(address(modifyLiquidityRouter), 100 ether); - token1.approve(address(modifyLiquidityRouter), 100 ether); - token0.mint(address(this), 100 ether); - token1.mint(address(this), 100 ether); - modifyLiquidityRouter.modifyLiquidity( - poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether, 0), ZERO_BYTES - ); - modifyLiquidityRouter.modifyLiquidity( - poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0), ZERO_BYTES - ); - modifyLiquidityRouter.modifyLiquidity( - poolKey, - IPoolManager.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether, 0), - ZERO_BYTES - ); - } - - function testTWAMM_beforeInitialize_SetsLastVirtualOrderTimestamp() public { - (PoolKey memory initKey, PoolId initId) = newPoolKeyWithTWAMM(twamm); - assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); - vm.warp(10000); - - manager.initialize(initKey, SQRT_PRICE_1_1, ZERO_BYTES); - assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); - } - - function testTWAMM_submitOrder_StoresOrderWithCorrectPoolAndOrderPoolInfo() public { - uint160 expiration = 30000; - uint160 submitTimestamp = 10000; - uint160 duration = expiration - submitTimestamp; - - ITWAMM.OrderKey memory orderKey = ITWAMM.OrderKey(address(this), expiration, true); - - ITWAMM.Order memory nullOrder = twamm.getOrder(poolKey, orderKey); - assertEq(nullOrder.sellRate, 0); - assertEq(nullOrder.earningsFactorLast, 0); - - vm.warp(10000); - token0.approve(address(twamm), 100 ether); - snapStart("TWAMMSubmitOrder"); - twamm.submitOrder(poolKey, orderKey, 1 ether); - snapEnd(); - - ITWAMM.Order memory submittedOrder = twamm.getOrder(poolKey, orderKey); - (uint256 sellRateCurrent0For1, uint256 earningsFactorCurrent0For1) = twamm.getOrderPool(poolKey, true); - (uint256 sellRateCurrent1For0, uint256 earningsFactorCurrent1For0) = twamm.getOrderPool(poolKey, false); - - assertEq(submittedOrder.sellRate, 1 ether / duration); - assertEq(submittedOrder.earningsFactorLast, 0); - assertEq(sellRateCurrent0For1, 1 ether / duration); - assertEq(sellRateCurrent1For0, 0); - assertEq(earningsFactorCurrent0For1, 0); - assertEq(earningsFactorCurrent1For0, 0); - } - - function TWAMMSingleSell0For1SellRateAndEarningsFactorGetsUpdatedProperly() public { - // TODO: fails with a bug for single pool sell, swap amount 3 wei above balance. - - ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), 30000, true); - ITWAMM.OrderKey memory orderKey2 = ITWAMM.OrderKey(address(this), 40000, true); - - token0.approve(address(twamm), 100e18); - token1.approve(address(twamm), 100e18); - vm.warp(10000); - twamm.submitOrder(poolKey, orderKey1, 1e18); - vm.warp(30000); - twamm.submitOrder(poolKey, orderKey2, 1e18); - vm.warp(40000); - - ITWAMM.Order memory submittedOrder = twamm.getOrder(poolKey, orderKey2); - (, uint256 earningsFactorCurrent) = twamm.getOrderPool(poolKey, true); - assertEq(submittedOrder.sellRate, 1 ether / 10000); - assertEq(submittedOrder.earningsFactorLast, earningsFactorCurrent); - } - - function testTWAMM_submitOrder_StoresSellRatesEarningsFactorsProperly() public { - uint160 expiration1 = 30000; - uint160 expiration2 = 40000; - uint256 submitTimestamp1 = 10000; - uint256 submitTimestamp2 = 30000; - uint256 earningsFactor0For1; - uint256 earningsFactor1For0; - uint256 sellRate0For1; - uint256 sellRate1For0; - - ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), expiration1, true); - ITWAMM.OrderKey memory orderKey2 = ITWAMM.OrderKey(address(this), expiration2, true); - ITWAMM.OrderKey memory orderKey3 = ITWAMM.OrderKey(address(this), expiration2, false); - - token0.approve(address(twamm), 100e18); - token1.approve(address(twamm), 100e18); - - // Submit 2 TWAMM orders and test all information gets updated - vm.warp(submitTimestamp1); - twamm.submitOrder(poolKey, orderKey1, 1e18); - twamm.submitOrder(poolKey, orderKey3, 3e18); - - (sellRate0For1, earningsFactor0For1) = twamm.getOrderPool(poolKey, true); - (sellRate1For0, earningsFactor1For0) = twamm.getOrderPool(poolKey, false); - assertEq(sellRate0For1, 1e18 / (expiration1 - submitTimestamp1)); - assertEq(sellRate1For0, 3e18 / (expiration2 - submitTimestamp1)); - assertEq(earningsFactor0For1, 0); - assertEq(earningsFactor1For0, 0); - - // Warp time and submit 1 TWAMM order. Test that pool information is updated properly as one order expires and - // another order is added to the pool - vm.warp(submitTimestamp2); - twamm.submitOrder(poolKey, orderKey2, 2e18); - - (sellRate0For1, earningsFactor0For1) = twamm.getOrderPool(poolKey, true); - (sellRate1For0, earningsFactor1For0) = twamm.getOrderPool(poolKey, false); - - assertEq(sellRate0For1, 2e18 / (expiration2 - submitTimestamp2)); - assertEq(sellRate1For0, 3e18 / (expiration2 - submitTimestamp1)); - assertEq(earningsFactor0For1, 1712020976636017581269515821040000); - assertEq(earningsFactor1For0, 1470157410324350030712806974476955); - } - - function testTWAMM_submitOrder_EmitsEvent() public { - ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), 30000, true); - - token0.approve(address(twamm), 100e18); - vm.warp(10000); - - vm.expectEmit(false, false, false, true); - emit SubmitOrder(poolId, address(this), 30000, true, 1 ether / 20000, 0); - twamm.submitOrder(poolKey, orderKey1, 1e18); - } - - function testTWAMM_updateOrder_EmitsEvent() public { - ITWAMM.OrderKey memory orderKey1; - ITWAMM.OrderKey memory orderKey2; - uint256 orderAmount; - (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); - // decrease order amount by 10% - int256 amountDelta = -1; - - // set timestamp to halfway through the order - vm.warp(20000); - - vm.expectEmit(true, true, true, true); - emit UpdateOrder(poolId, address(this), 30000, true, 0, 10000 << 96); - twamm.updateOrder(poolKey, orderKey1, amountDelta); - } - - function testTWAMM_updateOrder_ZeroForOne_DecreasesSellrateUpdatesSellTokensOwed() public { - ITWAMM.OrderKey memory orderKey1; - ITWAMM.OrderKey memory orderKey2; - uint256 orderAmount; - (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); - // decrease order amount by 10% - int256 amountDelta = -int256(orderAmount) / 10; - - // set timestamp to halfway through the order - vm.warp(20000); - - (uint256 originalSellRate,) = twamm.getOrderPool(poolKey, true); - twamm.updateOrder(poolKey, orderKey1, amountDelta); - (uint256 updatedSellRate,) = twamm.getOrderPool(poolKey, true); - - uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); - 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(token0Owed, uint256(-amountDelta)); - assertEq(token1Owed, orderAmount / 2); - } - - function testTWAMM_updateOrder_OneForZero_DecreasesSellrateUpdatesSellTokensOwed() public { - ITWAMM.OrderKey memory orderKey1; - ITWAMM.OrderKey memory orderKey2; - uint256 orderAmount; - (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); - - // decrease order amount by 10% - int256 amountDelta = -int256(orderAmount) / 10; - - // set timestamp to halfway through the order - vm.warp(20000); - - (uint256 originalSellRate,) = twamm.getOrderPool(poolKey, false); - twamm.updateOrder(poolKey, orderKey2, amountDelta); - (uint256 updatedSellRate,) = twamm.getOrderPool(poolKey, false); - - uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); - 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(token0Owed, orderAmount / 2); - assertEq(token1Owed, uint256(-amountDelta)); - } - - function testTWAMM_updatedOrder_ZeroForOne_ClosesOrderIfEliminatingPosition() public { - ITWAMM.OrderKey memory orderKey1; - ITWAMM.OrderKey memory orderKey2; - uint256 orderAmount; - (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); - - // set timestamp to halfway through the order - vm.warp(20000); - - twamm.updateOrder(poolKey, orderKey1, -1); - ITWAMM.Order memory deletedOrder = twamm.getOrder(poolKey, orderKey1); - uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); - uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); - - assertEq(deletedOrder.sellRate, 0); - assertEq(deletedOrder.earningsFactorLast, 0); - assertEq(token0Owed, orderAmount / 2); - assertEq(token1Owed, orderAmount / 2); - } - - function testTWAMM_updatedOrder_OneForZero_ClosesOrderIfEliminatingPosition() public { - ITWAMM.OrderKey memory orderKey1; - ITWAMM.OrderKey memory orderKey2; - uint256 orderAmount; - (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); - - // set timestamp to halfway through the order - vm.warp(20000); - - twamm.updateOrder(poolKey, orderKey2, -1); - ITWAMM.Order memory deletedOrder = twamm.getOrder(poolKey, orderKey2); - uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey2.owner); - uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey2.owner); - - assertEq(deletedOrder.sellRate, 0); - assertEq(deletedOrder.earningsFactorLast, 0); - assertEq(token0Owed, orderAmount / 2); - assertEq(token1Owed, orderAmount / 2); - } - - function testTWAMM_updatedOrder_ZeroForOne_IncreaseOrderAmount() public { - int256 amountDelta = 1 ether; - ITWAMM.OrderKey memory orderKey1; - ITWAMM.OrderKey memory orderKey2; - uint256 orderAmount; - (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); - - // set timestamp to halfway through the order - vm.warp(20000); - - uint256 balance0TWAMMBefore = token0.balanceOf(address(twamm)); - token0.approve(address(twamm), uint256(amountDelta)); - twamm.updateOrder(poolKey, orderKey1, amountDelta); - uint256 balance0TWAMMAfter = token0.balanceOf(address(twamm)); - - ITWAMM.Order memory updatedOrder = twamm.getOrder(poolKey, orderKey1); - uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); - uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); - - assertEq(balance0TWAMMAfter - balance0TWAMMBefore, uint256(amountDelta)); - assertEq(updatedOrder.sellRate, 150000000000000); - assertEq(token0Owed, 0); - assertEq(token1Owed, orderAmount / 2); - } - - function testTWAMM_updatedOrder_OneForZero_IncreaseOrderAmount() public { - int256 amountDelta = 1 ether; - ITWAMM.OrderKey memory orderKey1; - ITWAMM.OrderKey memory orderKey2; - uint256 orderAmount; - (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); - - // set timestamp to halfway through the order - vm.warp(20000); - - uint256 balance0TWAMMBefore = token1.balanceOf(address(twamm)); - token1.approve(address(twamm), uint256(amountDelta)); - twamm.updateOrder(poolKey, orderKey2, amountDelta); - uint256 balance0TWAMMAfter = token1.balanceOf(address(twamm)); - - ITWAMM.Order memory updatedOrder = twamm.getOrder(poolKey, orderKey2); - uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey2.owner); - uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey2.owner); - - assertEq(balance0TWAMMAfter - balance0TWAMMBefore, uint256(amountDelta)); - assertEq(updatedOrder.sellRate, 150000000000000); - assertEq(token0Owed, orderAmount / 2); - assertEq(token1Owed, 0); - } - - function testTWAMMEndToEndSimSymmetricalOrderPools() public { - uint256 orderAmount = 1e18; - ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), 30000, true); - ITWAMM.OrderKey memory orderKey2 = ITWAMM.OrderKey(address(this), 30000, false); - - token0.approve(address(twamm), 100e18); - token1.approve(address(twamm), 100e18); - modifyLiquidityRouter.modifyLiquidity( - poolKey, IPoolManager.ModifyLiquidityParams(-2400, 2400, 10 ether, 0), ZERO_BYTES - ); - - vm.warp(10000); - twamm.submitOrder(poolKey, orderKey1, orderAmount); - twamm.submitOrder(poolKey, orderKey2, orderAmount); - vm.warp(20000); - twamm.executeTWAMMOrders(poolKey); - twamm.updateOrder(poolKey, orderKey1, 0); - twamm.updateOrder(poolKey, orderKey2, 0); - - uint256 earningsToken0 = twamm.tokensOwed(poolKey.currency0, address(this)); - uint256 earningsToken1 = twamm.tokensOwed(poolKey.currency1, address(this)); - - assertEq(earningsToken0, orderAmount / 2); - assertEq(earningsToken1, orderAmount / 2); - - uint256 balance0BeforeTWAMM = MockERC20(Currency.unwrap(poolKey.currency0)).balanceOf(address(twamm)); - uint256 balance1BeforeTWAMM = MockERC20(Currency.unwrap(poolKey.currency1)).balanceOf(address(twamm)); - uint256 balance0BeforeThis = poolKey.currency0.balanceOfSelf(); - uint256 balance1BeforeThis = poolKey.currency1.balanceOfSelf(); - - vm.warp(30000); - twamm.executeTWAMMOrders(poolKey); - twamm.updateOrder(poolKey, orderKey1, 0); - twamm.updateOrder(poolKey, orderKey2, 0); - twamm.claimTokens(poolKey.currency0, address(this), 0); - twamm.claimTokens(poolKey.currency1, address(this), 0); - - assertEq(twamm.tokensOwed(poolKey.currency0, address(this)), 0); - assertEq(twamm.tokensOwed(poolKey.currency1, address(this)), 0); - - uint256 balance0AfterTWAMM = MockERC20(Currency.unwrap(poolKey.currency0)).balanceOf(address(twamm)); - uint256 balance1AfterTWAMM = MockERC20(Currency.unwrap(poolKey.currency1)).balanceOf(address(twamm)); - uint256 balance0AfterThis = poolKey.currency0.balanceOfSelf(); - uint256 balance1AfterThis = poolKey.currency1.balanceOfSelf(); - - assertEq(balance1AfterTWAMM, 0); - assertEq(balance0AfterTWAMM, 0); - assertEq(balance0BeforeTWAMM - balance0AfterTWAMM, orderAmount); - assertEq(balance0AfterThis - balance0BeforeThis, orderAmount); - assertEq(balance1BeforeTWAMM - balance1AfterTWAMM, orderAmount); - assertEq(balance1AfterThis - balance1BeforeThis, orderAmount); - } - - function newPoolKeyWithTWAMM(IHooks hooks) public returns (PoolKey memory, PoolId) { - (Currency _token0, Currency _token1) = deployMintAndApprove2Currencies(); - PoolKey memory key = PoolKey(_token0, _token1, 0, 60, hooks); - return (key, key.toId()); - } - - function submitOrdersBothDirections() - internal - returns (ITWAMM.OrderKey memory key1, ITWAMM.OrderKey memory key2, uint256 amount) - { - key1 = ITWAMM.OrderKey(address(this), 30000, true); - key2 = ITWAMM.OrderKey(address(this), 30000, false); - amount = 1 ether; - - token0.approve(address(twamm), amount); - token1.approve(address(twamm), amount); - - vm.warp(10000); - twamm.submitOrder(poolKey, key1, amount); - twamm.submitOrder(poolKey, key2, amount); - } -} diff --git a/test/shared/GetSender.sol b/test/shared/GetSender.sol deleted file mode 100644 index d1709219..00000000 --- a/test/shared/GetSender.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -contract GetSender { - function sender() external view returns (address) { - return msg.sender; - } -} diff --git a/test/shared/implementation/FullRangeImplementation.sol b/test/shared/implementation/FullRangeImplementation.sol deleted file mode 100644 index 2d4ce3cc..00000000 --- a/test/shared/implementation/FullRangeImplementation.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -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/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.validateHookPermissions(addressToEtch, getHookPermissions()); - } - - // make this a no-op in testing - function validateHookAddress(BaseHook _this) internal pure override {} -} diff --git a/test/shared/implementation/GeomeanOracleImplementation.sol b/test/shared/implementation/GeomeanOracleImplementation.sol deleted file mode 100644 index b953a3b6..00000000 --- a/test/shared/implementation/GeomeanOracleImplementation.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -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/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.validateHookPermissions(addressToEtch, getHookPermissions()); - } - - // make this a no-op in testing - function validateHookAddress(BaseHook _this) internal pure override {} - - function setTime(uint32 _time) external { - time = _time; - } - - function _blockTimestamp() internal view override returns (uint32) { - return time; - } -} diff --git a/test/shared/implementation/LimitOrderImplementation.sol b/test/shared/implementation/LimitOrderImplementation.sol deleted file mode 100644 index 11625771..00000000 --- a/test/shared/implementation/LimitOrderImplementation.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -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/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.validateHookPermissions(addressToEtch, getHookPermissions()); - } - - // make this a no-op in testing - function validateHookAddress(BaseHook _this) internal pure override {} -} diff --git a/test/shared/implementation/OracleImplementation.sol b/test/shared/implementation/OracleImplementation.sol deleted file mode 100644 index 7eefe3d3..00000000 --- a/test/shared/implementation/OracleImplementation.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; - -import {Oracle} from "../../../contracts/libraries/Oracle.sol"; - -contract OracleImplementation { - using Oracle for Oracle.Observation[65535]; - - Oracle.Observation[65535] public observations; - - uint32 public time; - int24 public tick; - uint128 public liquidity; - uint16 public index; - uint16 public cardinality; - uint16 public cardinalityNext; - - struct InitializeParams { - uint32 time; - int24 tick; - uint128 liquidity; - } - - function initialize(InitializeParams calldata params) external { - require(cardinality == 0, "already initialized"); - time = params.time; - tick = params.tick; - liquidity = params.liquidity; - (cardinality, cardinalityNext) = observations.initialize(params.time); - } - - function advanceTime(uint32 by) public { - unchecked { - time += by; - } - } - - struct UpdateParams { - uint32 advanceTimeBy; - int24 tick; - uint128 liquidity; - } - - // write an observation, then change tick and liquidity - function update(UpdateParams calldata params) external { - advanceTime(params.advanceTimeBy); - (index, cardinality) = observations.write(index, time, tick, liquidity, cardinality, cardinalityNext); - tick = params.tick; - liquidity = params.liquidity; - } - - function batchUpdate(UpdateParams[] calldata params) external { - // sload everything - int24 _tick = tick; - uint128 _liquidity = liquidity; - uint16 _index = index; - uint16 _cardinality = cardinality; - uint16 _cardinalityNext = cardinalityNext; - uint32 _time = time; - - for (uint256 i = 0; i < params.length; i++) { - _time += params[i].advanceTimeBy; - (_index, _cardinality) = - observations.write(_index, _time, _tick, _liquidity, _cardinality, _cardinalityNext); - _tick = params[i].tick; - _liquidity = params[i].liquidity; - } - - // sstore everything - tick = _tick; - liquidity = _liquidity; - index = _index; - cardinality = _cardinality; - time = _time; - } - - function grow(uint16 _cardinalityNext) external { - cardinalityNext = observations.grow(cardinalityNext, _cardinalityNext); - } - - function observe(uint32[] calldata secondsAgos) - external - view - returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) - { - return observations.observe(time, secondsAgos, tick, index, liquidity, cardinality); - } - - function getGasCostOfObserve(uint32[] calldata secondsAgos) external view returns (uint256) { - (uint32 _time, int24 _tick, uint128 _liquidity, uint16 _index) = (time, tick, liquidity, index); - uint256 gasBefore = gasleft(); - observations.observe(_time, secondsAgos, _tick, _index, _liquidity, cardinality); - return gasBefore - gasleft(); - } -} diff --git a/test/shared/implementation/TWAMMImplementation.sol b/test/shared/implementation/TWAMMImplementation.sol deleted file mode 100644 index f217db8c..00000000 --- a/test/shared/implementation/TWAMMImplementation.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -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/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.validateHookPermissions(addressToEtch, getHookPermissions()); - } - - // make this a no-op in testing - function validateHookAddress(BaseHook _this) internal pure override {} -} diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol deleted file mode 100644 index 4021f453..00000000 --- a/test/utils/HookEnabledSwapRouter.sol +++ /dev/null @@ -1,80 +0,0 @@ -// 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"; -import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; - -contract HookEnabledSwapRouter is PoolTestBase { - using CurrencyLibrary for Currency; - using CurrencySettler 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 takeClaims; - bool settleUsingBurn; - } - - function swap( - PoolKey memory key, - IPoolManager.SwapParams memory params, - TestSettings memory testSettings, - bytes memory hookData - ) external payable returns (BalanceDelta delta) { - delta = abi.decode( - manager.unlock(abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), (BalanceDelta) - ); - - uint256 ethBalance = address(this).balance; - if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); - } - - function unlockCallback(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) { - data.key.currency0.settle( - manager, data.sender, uint256(int256(-delta.amount0())), data.testSettings.settleUsingBurn - ); - if (delta.amount1() > 0) { - data.key.currency1.take( - manager, data.sender, uint256(int256(delta.amount1())), data.testSettings.takeClaims - ); - } - } else { - data.key.currency1.settle( - manager, data.sender, uint256(int256(-delta.amount1())), data.testSettings.settleUsingBurn - ); - if (delta.amount0() > 0) { - data.key.currency0.take( - manager, data.sender, uint256(int256(delta.amount0())), data.testSettings.takeClaims - ); - } - } - - return abi.encode(delta); - } -}