From cfca04c3f1a999e030e5c39ba3d7b97e34e52135 Mon Sep 17 00:00:00 2001 From: Raphael Nembhard Date: Thu, 22 Aug 2024 20:24:07 -0400 Subject: [PATCH] is compiling and test are passing --- foundry.toml | 8 + src/TWAMM.sol | 654 +++++++++ src/implementation/TWAMMImplementation.sol | 16 + src/interfaces/ITWAMM.sol | 136 ++ src/libraries/PoolGetters.sol | 106 ++ src/libraries/TWAMM/ABDKMathQuad.sol | 1546 ++++++++++++++++++++ src/libraries/TWAMM/OrderPool.sol | 34 + src/libraries/TWAMM/TwammMath.sol | 179 +++ src/libraries/TransferHelper.sol | 54 + src/mocks/MockERC20.sol | 20 + test/TWAMM.t.sol | 436 ++++++ 11 files changed, 3189 insertions(+) create mode 100644 src/TWAMM.sol create mode 100644 src/implementation/TWAMMImplementation.sol create mode 100644 src/interfaces/ITWAMM.sol create mode 100644 src/libraries/PoolGetters.sol create mode 100644 src/libraries/TWAMM/ABDKMathQuad.sol create mode 100644 src/libraries/TWAMM/OrderPool.sol create mode 100644 src/libraries/TWAMM/TwammMath.sol create mode 100644 src/libraries/TransferHelper.sol create mode 100644 src/mocks/MockERC20.sol create mode 100644 test/TWAMM.t.sol diff --git a/foundry.toml b/foundry.toml index 25b918f..0c3a90c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,3 +4,11 @@ out = "out" libs = ["lib"] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +# foundry.toml + +solc_version = '0.8.26' +evm_version = "cancun" +optimizer_runs = 800 +via_ir = false +ffi = true \ No newline at end of file diff --git a/src/TWAMM.sol b/src/TWAMM.sol new file mode 100644 index 0000000..5dfc7dc --- /dev/null +++ b/src/TWAMM.sol @@ -0,0 +1,654 @@ +// 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 "v4-periphery/src/base/hooks/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 + onlyByPoolManager + 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 onlyByPoolManager returns (bytes4) { + executeTWAMMOrders(key); + return BaseHook.beforeAddLiquidity.selector; + } + + function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) + external + override + onlyByPoolManager + 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,,,) = poolManager.getSlot0(poolId); + State storage twamm = twammStates[poolId]; + + (bool zeroForOne, uint160 sqrtPriceLimitX96) = + _executeTWAMMOrders(twamm, poolManager, key, PoolParamsOnExecute(sqrtPriceX96, poolManager.getLiquidity(poolId))); + + if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { + poolManager.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 = poolManager.swap(key, swapParams, ZERO_BYTES); + + if (swapParams.zeroForOne) { + if (delta.amount0() < 0) { + key.currency0.settle(poolManager, address(this), uint256(uint128(-delta.amount0())), false); + } + if (delta.amount1() > 0) { + key.currency1.take(poolManager, address(this), uint256(uint128(delta.amount1())), false); + } + } else { + if (delta.amount1() < 0) { + key.currency1.settle(poolManager, address(this), uint256(uint128(-delta.amount1())), false); + } + if (delta.amount0() > 0) { + key.currency0.take(poolManager, 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/src/implementation/TWAMMImplementation.sol b/src/implementation/TWAMMImplementation.sol new file mode 100644 index 0000000..b66c5c0 --- /dev/null +++ b/src/implementation/TWAMMImplementation.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; +import {TWAMM} from "../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/src/interfaces/ITWAMM.sol b/src/interfaces/ITWAMM.sol new file mode 100644 index 0000000..3b932d3 --- /dev/null +++ b/src/interfaces/ITWAMM.sol @@ -0,0 +1,136 @@ +// 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/src/libraries/PoolGetters.sol b/src/libraries/PoolGetters.sol new file mode 100644 index 0000000..df31f3c --- /dev/null +++ b/src/libraries/PoolGetters.sol @@ -0,0 +1,106 @@ +// 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/src/libraries/TWAMM/ABDKMathQuad.sol b/src/libraries/TWAMM/ABDKMathQuad.sol new file mode 100644 index 0000000..00fa5a0 --- /dev/null +++ b/src/libraries/TWAMM/ABDKMathQuad.sol @@ -0,0 +1,1546 @@ +// 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/src/libraries/TWAMM/OrderPool.sol b/src/libraries/TWAMM/OrderPool.sol new file mode 100644 index 0000000..4822dbf --- /dev/null +++ b/src/libraries/TWAMM/OrderPool.sol @@ -0,0 +1,34 @@ +// 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/src/libraries/TWAMM/TwammMath.sol b/src/libraries/TWAMM/TwammMath.sol new file mode 100644 index 0000000..a5994b5 --- /dev/null +++ b/src/libraries/TWAMM/TwammMath.sol @@ -0,0 +1,179 @@ +// 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/src/libraries/TransferHelper.sol b/src/libraries/TransferHelper.sol new file mode 100644 index 0000000..9ab40d9 --- /dev/null +++ b/src/libraries/TransferHelper.sol @@ -0,0 +1,54 @@ +// 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/src/mocks/MockERC20.sol b/src/mocks/MockERC20.sol new file mode 100644 index 0000000..4dab3db --- /dev/null +++ b/src/mocks/MockERC20.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals + ) ERC20(_name, _symbol, _decimals) {} + + function mint(address to, uint256 value) public virtual { + _mint(to, value); + } + + function burn(address from, uint256 value) public virtual { + _burn(from, value); + } +} diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol new file mode 100644 index 0000000..fa1abd4 --- /dev/null +++ b/test/TWAMM.t.sol @@ -0,0 +1,436 @@ +pragma solidity ^0.8.15; + +import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {MockERC20} from "../src/mocks/MockERC20.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {TWAMMImplementation} from "../src/implementation/TWAMMImplementation.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TWAMM} from "../src/TWAMM.sol"; +import {ITWAMM} from "../src//interfaces/ITWAMM.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +contract TWAMMTest is Test, Deployers, GasSnapshot { + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + + event SubmitOrder( + PoolId indexed poolId, + address indexed owner, + uint160 expiration, + bool zeroForOne, + uint256 sellRate, + uint256 earningsFactorLast + ); + + event UpdateOrder( + PoolId indexed poolId, + address indexed owner, + uint160 expiration, + bool zeroForOne, + uint256 sellRate, + uint256 earningsFactorLast + ); + + // + TWAMM twamm = + TWAMM(address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG))); + address hookAddress; + MockERC20 token0; + MockERC20 token1; + PoolKey poolKey; + PoolId poolId; + + function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + + token0 = MockERC20(Currency.unwrap(currency0)); + token1 = MockERC20(Currency.unwrap(currency1)); + + TWAMMImplementation impl = new TWAMMImplementation(manager, 10_000, twamm); + (, bytes32[] memory writes) = vm.accesses(address(impl)); + vm.etch(address(twamm), address(impl).code); + // for each storage key that was written during the hook implementation, copy the value over + unchecked { + for (uint256 i = 0; i < writes.length; i++) { + bytes32 slot = writes[i]; + vm.store(address(twamm), slot, vm.load(address(impl), slot)); + } + } + + (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_PRICE_1_1, ZERO_BYTES); + + token0.approve(address(modifyLiquidityRouter), 100 ether); + token1.approve(address(modifyLiquidityRouter), 100 ether); + token0.mint(address(this), 100 ether); + token1.mint(address(this), 100 ether); + modifyLiquidityRouter.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams(-60, 60, 10 ether, 0), ZERO_BYTES + ); + modifyLiquidityRouter.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0), ZERO_BYTES + ); + modifyLiquidityRouter.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 10 ether, 0), + ZERO_BYTES + ); + } + + // This checking the last order that took place? + function testTWAMM_beforeInitialize_SetsLastVirtualOrderTimestamp() public { + (PoolKey memory initKey, PoolId initId) = newPoolKeyWithTWAMM(twamm); + assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); + vm.warp(10000); + + manager.initialize(initKey, SQRT_PRICE_1_1, ZERO_BYTES); + assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); + } + + // @note need to figure out what snapStart is. + // When it is comment out it is panicing for an arithmetic underflow or overflow. + // function testTWAMM_submitOrder_StoresOrderWithCorrectPoolAndOrderPoolInfo() public { + // uint160 expiration = 30000; + // uint160 submitTimestamp = 10000; + // uint160 duration = expiration - submitTimestamp; + + // ITWAMM.OrderKey memory orderKey = ITWAMM.OrderKey(address(this), expiration, true); + + // ITWAMM.Order memory nullOrder = twamm.getOrder(poolKey, orderKey); + // assertEq(nullOrder.sellRate, 0); + // assertEq(nullOrder.earningsFactorLast, 0); + + // vm.warp(10000); + // token0.approve(address(twamm), 100 ether); + // // @note snapStart("TWAMMSubmitOrder"); + // twamm.submitOrder(poolKey, orderKey, 1 ether); + // snapEnd(); + + // ITWAMM.Order memory submittedOrder = twamm.getOrder(poolKey, orderKey); + // (uint256 sellRateCurrent0For1, uint256 earningsFactorCurrent0For1) = twamm.getOrderPool(poolKey, true); + // (uint256 sellRateCurrent1For0, uint256 earningsFactorCurrent1For0) = twamm.getOrderPool(poolKey, false); + + // assertEq(submittedOrder.sellRate, 1 ether / duration); + // assertEq(submittedOrder.earningsFactorLast, 0); + // assertEq(sellRateCurrent0For1, 1 ether / duration); + // assertEq(sellRateCurrent1For0, 0); + // assertEq(earningsFactorCurrent0For1, 0); + // assertEq(earningsFactorCurrent1For0, 0); + // } + + function TWAMMSingleSell0For1SellRateAndEarningsFactorGetsUpdatedProperly() public { + // TODO: fails with a bug for single pool sell, swap amount 3 wei above balance. + + ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), 30000, true); + ITWAMM.OrderKey memory orderKey2 = ITWAMM.OrderKey(address(this), 40000, true); + + token0.approve(address(twamm), 100e18); + token1.approve(address(twamm), 100e18); + vm.warp(10000); + twamm.submitOrder(poolKey, orderKey1, 1e18); + vm.warp(30000); + twamm.submitOrder(poolKey, orderKey2, 1e18); + vm.warp(40000); + + ITWAMM.Order memory submittedOrder = twamm.getOrder(poolKey, orderKey2); + (, uint256 earningsFactorCurrent) = twamm.getOrderPool(poolKey, true); + assertEq(submittedOrder.sellRate, 1 ether / 10000); + assertEq(submittedOrder.earningsFactorLast, earningsFactorCurrent); + } + + function testTWAMM_submitOrder_StoresSellRatesEarningsFactorsProperly() public { + uint160 expiration1 = 30000; + uint160 expiration2 = 40000; + uint256 submitTimestamp1 = 10000; + uint256 submitTimestamp2 = 30000; + uint256 earningsFactor0For1; + uint256 earningsFactor1For0; + uint256 sellRate0For1; + uint256 sellRate1For0; + + ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), expiration1, true); + ITWAMM.OrderKey memory orderKey2 = ITWAMM.OrderKey(address(this), expiration2, true); + ITWAMM.OrderKey memory orderKey3 = ITWAMM.OrderKey(address(this), expiration2, false); + + token0.approve(address(twamm), 100e18); + token1.approve(address(twamm), 100e18); + + // Submit 2 TWAMM orders and test all information gets updated + vm.warp(submitTimestamp1); + twamm.submitOrder(poolKey, orderKey1, 1e18); + twamm.submitOrder(poolKey, orderKey3, 3e18); + + (sellRate0For1, earningsFactor0For1) = twamm.getOrderPool(poolKey, true); + (sellRate1For0, earningsFactor1For0) = twamm.getOrderPool(poolKey, false); + assertEq(sellRate0For1, 1e18 / (expiration1 - submitTimestamp1)); + assertEq(sellRate1For0, 3e18 / (expiration2 - submitTimestamp1)); + assertEq(earningsFactor0For1, 0); + assertEq(earningsFactor1For0, 0); + + // Warp time and submit 1 TWAMM order. Test that pool information is updated properly as one order expires and + // another order is added to the pool + vm.warp(submitTimestamp2); + twamm.submitOrder(poolKey, orderKey2, 2e18); + + (sellRate0For1, earningsFactor0For1) = twamm.getOrderPool(poolKey, true); + (sellRate1For0, earningsFactor1For0) = twamm.getOrderPool(poolKey, false); + + assertEq(sellRate0For1, 2e18 / (expiration2 - submitTimestamp2)); + assertEq(sellRate1For0, 3e18 / (expiration2 - submitTimestamp1)); + assertEq(earningsFactor0For1, 1712020976636017581269515821040000); + assertEq(earningsFactor1For0, 1470157410324350030712806974476955); + } + + function testTWAMM_submitOrder_EmitsEvent() public { + ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), 30000, true); + + token0.approve(address(twamm), 100e18); + vm.warp(10000); + + vm.expectEmit(false, false, false, true); + emit SubmitOrder(poolId, address(this), 30000, true, 1 ether / 20000, 0); + twamm.submitOrder(poolKey, orderKey1, 1e18); + } + + function testTWAMM_updateOrder_EmitsEvent() public { + ITWAMM.OrderKey memory orderKey1; + ITWAMM.OrderKey memory orderKey2; + uint256 orderAmount; + (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); + // decrease order amount by 10% + int256 amountDelta = -1; + + // set timestamp to halfway through the order + vm.warp(20000); + + vm.expectEmit(true, true, true, true); + emit UpdateOrder(poolId, address(this), 30000, true, 0, 10000 << 96); + twamm.updateOrder(poolKey, orderKey1, amountDelta); + } + + function testTWAMM_updateOrder_ZeroForOne_DecreasesSellrateUpdatesSellTokensOwed() public { + ITWAMM.OrderKey memory orderKey1; + ITWAMM.OrderKey memory orderKey2; + uint256 orderAmount; + (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); + // decrease order amount by 10% + int256 amountDelta = -int256(orderAmount) / 10; + + // set timestamp to halfway through the order + vm.warp(20000); + + (uint256 originalSellRate,) = twamm.getOrderPool(poolKey, true); + twamm.updateOrder(poolKey, orderKey1, amountDelta); + (uint256 updatedSellRate,) = twamm.getOrderPool(poolKey, true); + + uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); + uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); + + // takes 10% off the remaining half (so 80% of original sellrate) + assertEq(updatedSellRate, (originalSellRate * 80) / 100); + assertEq(token0Owed, uint256(-amountDelta)); + assertEq(token1Owed, orderAmount / 2); + } + + function testTWAMM_updateOrder_OneForZero_DecreasesSellrateUpdatesSellTokensOwed() public { + ITWAMM.OrderKey memory orderKey1; + ITWAMM.OrderKey memory orderKey2; + uint256 orderAmount; + (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); + + // decrease order amount by 10% + int256 amountDelta = -int256(orderAmount) / 10; + + // set timestamp to halfway through the order + vm.warp(20000); + + (uint256 originalSellRate,) = twamm.getOrderPool(poolKey, false); + twamm.updateOrder(poolKey, orderKey2, amountDelta); + (uint256 updatedSellRate,) = twamm.getOrderPool(poolKey, false); + + uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); + uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); + + // takes 10% off the remaining half (so 80% of original sellrate) + assertEq(updatedSellRate, (originalSellRate * 80) / 100); + assertEq(token0Owed, orderAmount / 2); + assertEq(token1Owed, uint256(-amountDelta)); + } + + function testTWAMM_updatedOrder_ZeroForOne_ClosesOrderIfEliminatingPosition() public { + ITWAMM.OrderKey memory orderKey1; + ITWAMM.OrderKey memory orderKey2; + uint256 orderAmount; + (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); + + // set timestamp to halfway through the order + vm.warp(20000); + + twamm.updateOrder(poolKey, orderKey1, -1); + ITWAMM.Order memory deletedOrder = twamm.getOrder(poolKey, orderKey1); + uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); + uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); + + assertEq(deletedOrder.sellRate, 0); + assertEq(deletedOrder.earningsFactorLast, 0); + assertEq(token0Owed, orderAmount / 2); + assertEq(token1Owed, orderAmount / 2); + } + + function testTWAMM_updatedOrder_OneForZero_ClosesOrderIfEliminatingPosition() public { + ITWAMM.OrderKey memory orderKey1; + ITWAMM.OrderKey memory orderKey2; + uint256 orderAmount; + (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); + + // set timestamp to halfway through the order + vm.warp(20000); + + twamm.updateOrder(poolKey, orderKey2, -1); + ITWAMM.Order memory deletedOrder = twamm.getOrder(poolKey, orderKey2); + uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey2.owner); + uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey2.owner); + + assertEq(deletedOrder.sellRate, 0); + assertEq(deletedOrder.earningsFactorLast, 0); + assertEq(token0Owed, orderAmount / 2); + assertEq(token1Owed, orderAmount / 2); + } + + function testTWAMM_updatedOrder_ZeroForOne_IncreaseOrderAmount() public { + int256 amountDelta = 1 ether; + ITWAMM.OrderKey memory orderKey1; + ITWAMM.OrderKey memory orderKey2; + uint256 orderAmount; + (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); + + // set timestamp to halfway through the order + vm.warp(20000); + + uint256 balance0TWAMMBefore = token0.balanceOf(address(twamm)); + token0.approve(address(twamm), uint256(amountDelta)); + twamm.updateOrder(poolKey, orderKey1, amountDelta); + uint256 balance0TWAMMAfter = token0.balanceOf(address(twamm)); + + ITWAMM.Order memory updatedOrder = twamm.getOrder(poolKey, orderKey1); + uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey1.owner); + uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); + + assertEq(balance0TWAMMAfter - balance0TWAMMBefore, uint256(amountDelta)); + assertEq(updatedOrder.sellRate, 150000000000000); + assertEq(token0Owed, 0); + assertEq(token1Owed, orderAmount / 2); + } + + function testTWAMM_updatedOrder_OneForZero_IncreaseOrderAmount() public { + int256 amountDelta = 1 ether; + ITWAMM.OrderKey memory orderKey1; + ITWAMM.OrderKey memory orderKey2; + uint256 orderAmount; + (orderKey1, orderKey2, orderAmount) = submitOrdersBothDirections(); + + // set timestamp to halfway through the order + vm.warp(20000); + + uint256 balance0TWAMMBefore = token1.balanceOf(address(twamm)); + token1.approve(address(twamm), uint256(amountDelta)); + twamm.updateOrder(poolKey, orderKey2, amountDelta); + uint256 balance0TWAMMAfter = token1.balanceOf(address(twamm)); + + ITWAMM.Order memory updatedOrder = twamm.getOrder(poolKey, orderKey2); + uint256 token0Owed = twamm.tokensOwed(poolKey.currency0, orderKey2.owner); + uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey2.owner); + + assertEq(balance0TWAMMAfter - balance0TWAMMBefore, uint256(amountDelta)); + assertEq(updatedOrder.sellRate, 150000000000000); + assertEq(token0Owed, orderAmount / 2); + assertEq(token1Owed, 0); + } + + function testTWAMMEndToEndSimSymmetricalOrderPools() public { + uint256 orderAmount = 1e18; + ITWAMM.OrderKey memory orderKey1 = ITWAMM.OrderKey(address(this), 30000, true); + ITWAMM.OrderKey memory orderKey2 = ITWAMM.OrderKey(address(this), 30000, false); + + token0.approve(address(twamm), 100e18); + token1.approve(address(twamm), 100e18); + modifyLiquidityRouter.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams(-2400, 2400, 10 ether, 0), ZERO_BYTES + ); + + vm.warp(10000); + twamm.submitOrder(poolKey, orderKey1, orderAmount); + twamm.submitOrder(poolKey, orderKey2, orderAmount); + vm.warp(20000); + twamm.executeTWAMMOrders(poolKey); + twamm.updateOrder(poolKey, orderKey1, 0); + twamm.updateOrder(poolKey, orderKey2, 0); + + uint256 earningsToken0 = twamm.tokensOwed(poolKey.currency0, address(this)); + uint256 earningsToken1 = twamm.tokensOwed(poolKey.currency1, address(this)); + + assertEq(earningsToken0, orderAmount / 2); + assertEq(earningsToken1, orderAmount / 2); + + uint256 balance0BeforeTWAMM = MockERC20(Currency.unwrap(poolKey.currency0)).balanceOf(address(twamm)); + uint256 balance1BeforeTWAMM = MockERC20(Currency.unwrap(poolKey.currency1)).balanceOf(address(twamm)); + uint256 balance0BeforeThis = poolKey.currency0.balanceOfSelf(); + uint256 balance1BeforeThis = poolKey.currency1.balanceOfSelf(); + + vm.warp(30000); + twamm.executeTWAMMOrders(poolKey); + twamm.updateOrder(poolKey, orderKey1, 0); + twamm.updateOrder(poolKey, orderKey2, 0); + twamm.claimTokens(poolKey.currency0, address(this), 0); + twamm.claimTokens(poolKey.currency1, address(this), 0); + + assertEq(twamm.tokensOwed(poolKey.currency0, address(this)), 0); + assertEq(twamm.tokensOwed(poolKey.currency1, address(this)), 0); + + uint256 balance0AfterTWAMM = MockERC20(Currency.unwrap(poolKey.currency0)).balanceOf(address(twamm)); + uint256 balance1AfterTWAMM = MockERC20(Currency.unwrap(poolKey.currency1)).balanceOf(address(twamm)); + uint256 balance0AfterThis = poolKey.currency0.balanceOfSelf(); + uint256 balance1AfterThis = poolKey.currency1.balanceOfSelf(); + + assertEq(balance1AfterTWAMM, 0); + assertEq(balance0AfterTWAMM, 0); + assertEq(balance0BeforeTWAMM - balance0AfterTWAMM, orderAmount); + assertEq(balance0AfterThis - balance0BeforeThis, orderAmount); + assertEq(balance1BeforeTWAMM - balance1AfterTWAMM, orderAmount); + assertEq(balance1AfterThis - balance1BeforeThis, orderAmount); + } + + function newPoolKeyWithTWAMM(IHooks hooks) public returns (PoolKey memory, PoolId) { + (Currency _token0, Currency _token1) = deployMintAndApprove2Currencies(); + PoolKey memory key = PoolKey(_token0, _token1, 0, 60, hooks); + return (key, key.toId()); + } + + function submitOrdersBothDirections() + internal + returns (ITWAMM.OrderKey memory key1, ITWAMM.OrderKey memory key2, uint256 amount) + { + key1 = ITWAMM.OrderKey(address(this), 30000, true); + key2 = ITWAMM.OrderKey(address(this), 30000, false); + amount = 1 ether; + + token0.approve(address(twamm), amount); + token1.approve(address(twamm), amount); + + vm.warp(10000); + twamm.submitOrder(poolKey, key1, amount); + twamm.submitOrder(poolKey, key2, amount); + } +}