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/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 11b50b48..cc1e28ba 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -351599 \ No newline at end of file +351599 diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index 57bb2528..7e1193b2 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -160908 \ No newline at end of file +160908 diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index adbd3641..0ff0d2a4 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -140744 \ No newline at end of file +140744 diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 0cc24bab..cd8b876f 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -1032313 \ No newline at end of file +1032313 diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index f8dbe13d..1e3fe500 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -145773 \ No newline at end of file +145773 diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 7c72c23e..63dd7ec3 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -281063 \ No newline at end of file +281063 diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 57992011..ee18e52f 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -114959 \ No newline at end of file +114959 diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index f2e4301d..de0e9e85 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -141889 \ No newline at end of file +141889 diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 2437153b..971218cf 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -155811 \ No newline at end of file +155811 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/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/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol deleted file mode 100644 index e6ff1695..00000000 --- a/test/GeomeanOracle.t.sol +++ /dev/null @@ -1,221 +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(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(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(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/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/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); - } -}