diff --git a/contracts/SimpleCollections.sol b/contracts/SimpleCollections.sol index 49d6bab7..9e6ddc52 100644 --- a/contracts/SimpleCollections.sol +++ b/contracts/SimpleCollections.sol @@ -6,6 +6,7 @@ import "@devprotocol/i-s-tokens/contracts/interfaces/ISTokensManagerStruct.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "./interfaces/IProperty.sol"; import "./interfaces/ISwapAndStake.sol"; +import "./utils/uniswapV3/libraries/OracleLibrary.sol"; contract SimpleCollections is ITokenURIDescriptor, OwnableUpgradeable { struct Image { @@ -21,6 +22,9 @@ contract SimpleCollections is ITokenURIDescriptor, OwnableUpgradeable { mapping(address => mapping(bytes32 => Image)) public propertyImages; mapping(address => mapping(uint256 => uint256)) public stakedAmountAtMinted; + uint32 public lookbackSec = 300; // 5 minutes + address public devEthUniV3Pool; + function initialize(address _contract) external initializer { __Ownable_init(); swapAndStake = ISwapAndStake(_contract); @@ -36,6 +40,14 @@ contract SimpleCollections is ITokenURIDescriptor, OwnableUpgradeable { swapAndStake = ISwapAndStake(_contract); } + function setDevEthUniV3Pool(address _pool) external onlyOwner { + devEthUniV3Pool = _pool; + } + + function setLookbackSec(uint32 _lookbackSec) external onlyOwner { + lookbackSec = _lookbackSec; + } + function image( uint256 id, address, @@ -117,14 +129,41 @@ contract SimpleCollections is ITokenURIDescriptor, OwnableUpgradeable { return false; } - // Always only allow staking via the SwapAndStake contract. - ISwapAndStake.Amounts memory stakeVia = swapAndStake.gatewayOf( - img.gateway - ); - // Validate the staking position. - bool valid = img.requiredETHAmount <= stakeVia.input && - img.requiredETHFee <= stakeVia.fee; + bool valid = false; + + // `requiredETHFee` will be 0 when using direct `DEV` otherwise it's assumed + // that input currency is `ETH`. + if (img.requiredETHFee > 0) { + // This condition validates input user ETH and fee. + // Always only allow staking via the SwapAndStake contract. + ISwapAndStake.Amounts memory stakeVia = swapAndStake.gatewayOf( + img.gateway + ); + + valid = + img.requiredETHAmount <= stakeVia.input && + img.requiredETHFee <= stakeVia.fee; + } else { + // This condition validates input `DEV` equivalent `ETH` with required. + + // Get the average price tick first + (int24 arithmeticMeanTick, ) = OracleLibrary.consult( + devEthUniV3Pool, + lookbackSec + ); + + // Get the quote for selling `DEV staked`. Assumes 1e18 for both. + // Fetch the `amount of ETH` the `DEV staked` is equal to. + uint256 equivalentETHStaked = OracleLibrary.getQuoteAtTick( + arithmeticMeanTick, + uint128(_positions.amount), + swapAndStake.devAddress(), // Fetch the `DEV token address` for this chain and setting from swapAndStake. + swapAndStake.wethAddress() // Fetch the `WETH token address` for this chain and setting from swapAndStake. + ); + + valid = img.requiredETHAmount <= equivalentETHStaked; + } if (valid) { stakedAmountAtMinted[_positions.property][id] = _positions.amount; diff --git a/contracts/interfaces/ISwapAndStake.sol b/contracts/interfaces/ISwapAndStake.sol index 1f8b3fe4..7a470bbd 100644 --- a/contracts/interfaces/ISwapAndStake.sol +++ b/contracts/interfaces/ISwapAndStake.sol @@ -8,5 +8,9 @@ interface ISwapAndStake { uint256 fee; } + function wethAddress() external view returns (address); + + function devAddress() external view returns (address); + function gatewayOf(address _addr) external view returns (Amounts memory); } diff --git a/contracts/utils/uniswapV3/interfaces/IUniswapV3Pool.sol b/contracts/utils/uniswapV3/interfaces/IUniswapV3Pool.sol new file mode 100644 index 00000000..78474d7e --- /dev/null +++ b/contracts/utils/uniswapV3/interfaces/IUniswapV3Pool.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +import "./pool/IUniswapV3PoolImmutables.sol"; +import "./pool/IUniswapV3PoolState.sol"; +import "./pool/IUniswapV3PoolDerivedState.sol"; +import "./pool/IUniswapV3PoolActions.sol"; +import "./pool/IUniswapV3PoolOwnerActions.sol"; +import "./pool/IUniswapV3PoolEvents.sol"; + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolEvents +{ + +} diff --git a/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolActions.sol b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolActions.sol new file mode 100644 index 00000000..b805b61a --- /dev/null +++ b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolActions.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +/// @title Permissionless pool actions +/// @notice Contains pool methods that can be called by anyone +interface IUniswapV3PoolActions { + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 + function initialize(uint160 sqrtPriceX96) external; + + /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position + /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback + /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + /// on tickLower, tickUpper, the amount of liquidity, and the current price. + /// @param recipient The address for which the liquidity will be created + /// @param tickLower The lower tick of the position in which to add liquidity + /// @param tickUpper The upper tick of the position in which to add liquidity + /// @param amount The amount of liquidity to mint + /// @param data Any data that should be passed through to the callback + /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback + /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Collects tokens owed to a position + /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. + /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or + /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the + /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. + /// @param recipient The address which should receive the fees collected + /// @param tickLower The lower tick of the position for which to collect fees + /// @param tickUpper The upper tick of the position for which to collect fees + /// @param amount0Requested How much token0 should be withdrawn from the fees owed + /// @param amount1Requested How much token1 should be withdrawn from the fees owed + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); + + /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position + /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 + /// @dev Fees must be collected separately via a call to #collect + /// @param tickLower The lower tick of the position for which to burn liquidity + /// @param tickUpper The upper tick of the position for which to burn liquidity + /// @param amount How much liquidity to burn + /// @return amount0 The amount of token0 sent to the recipient + /// @return amount1 The amount of token1 sent to the recipient + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback + /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback + /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling + /// with 0 amount{0,1} and sending the donation amount(s) from the callback + /// @param recipient The address which will receive the token0 and token1 amounts + /// @param amount0 The amount of token0 to send + /// @param amount1 The amount of token1 to send + /// @param data Any data to be passed through to the callback + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; + + /// @notice Increase the maximum number of price and liquidity observations that this pool will store + /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to + /// the input observationCardinalityNext. + /// @param observationCardinalityNext The desired minimum number of observations for the pool to store + function increaseObservationCardinalityNext( + uint16 observationCardinalityNext + ) external; +} diff --git a/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolDerivedState.sol b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolDerivedState.sol new file mode 100644 index 00000000..8ae79285 --- /dev/null +++ b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolDerivedState.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe( + uint32[] calldata secondsAgos + ) + external + view + returns ( + int56[] memory tickCumulatives, + uint160[] memory secondsPerLiquidityCumulativeX128s + ); + + /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range + /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. + /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first + /// snapshot is taken and the second snapshot is taken. + /// @param tickLower The lower tick of the range + /// @param tickUpper The upper tick of the range + /// @return tickCumulativeInside The snapshot of the tick accumulator for the range + /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range + /// @return secondsInside The snapshot of seconds per liquidity for the range + function snapshotCumulativesInside( + int24 tickLower, + int24 tickUpper + ) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} diff --git a/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolEvents.sol b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolEvents.sol new file mode 100644 index 00000000..d7dd9ab7 --- /dev/null +++ b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolEvents.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol( + uint8 feeProtocol0Old, + uint8 feeProtocol1Old, + uint8 feeProtocol0New, + uint8 feeProtocol1New + ); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol( + address indexed sender, + address indexed recipient, + uint128 amount0, + uint128 amount1 + ); +} diff --git a/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolImmutables.sol b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolImmutables.sol new file mode 100644 index 00000000..dceb8dce --- /dev/null +++ b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolImmutables.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +/// @title Pool state that never changes +/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values +interface IUniswapV3PoolImmutables { + /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface + /// @return The contract address + function factory() external view returns (address); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); + + /// @notice The pool tick spacing + /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive + /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... + /// This value is an int24 to avoid casting even though it is always positive. + /// @return The tick spacing + function tickSpacing() external view returns (int24); + + /// @notice The maximum amount of position liquidity that can use any tick in the range + /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and + /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool + /// @return The max amount of liquidity per tick + function maxLiquidityPerTick() external view returns (uint128); +} diff --git a/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolOwnerActions.sol b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolOwnerActions.sol new file mode 100644 index 00000000..7303c451 --- /dev/null +++ b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolOwnerActions.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} diff --git a/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolState.sol b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolState.sol new file mode 100644 index 00000000..e429631a --- /dev/null +++ b/contracts/utils/uniswapV3/interfaces/pool/IUniswapV3PoolState.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() + external + view + returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks( + int24 tick + ) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions( + bytes32 key + ) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations( + uint256 index + ) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} diff --git a/contracts/utils/uniswapV3/libraries/FullMath.sol b/contracts/utils/uniswapV3/libraries/FullMath.sol new file mode 100644 index 00000000..f33f23b5 --- /dev/null +++ b/contracts/utils/uniswapV3/libraries/FullMath.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +/// @title Contains 512-bit math functions +/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision +/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = (type(uint256).max - denominator + 1) & denominator; + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } +} diff --git a/contracts/utils/uniswapV3/libraries/OracleLibrary.sol b/contracts/utils/uniswapV3/libraries/OracleLibrary.sol new file mode 100644 index 00000000..9c041569 --- /dev/null +++ b/contracts/utils/uniswapV3/libraries/OracleLibrary.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +import "./FullMath.sol"; +import "./TickMath.sol"; +import "../interfaces/IUniswapV3Pool.sol"; + +/// @title Oracle library +/// @notice Provides functions to integrate with V3 pool oracle +library OracleLibrary { + /// @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool + /// @param pool Address of the pool that we want to observe + /// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means + /// @return arithmeticMeanTick The arithmetic mean tick from (block.timestamp - secondsAgo) to block.timestamp + /// @return harmonicMeanLiquidity The harmonic mean liquidity from (block.timestamp - secondsAgo) to block.timestamp + function consult( + address pool, + uint32 secondsAgo + ) + internal + view + returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity) + { + require(secondsAgo != 0, "BP"); + + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo; + secondsAgos[1] = 0; + + ( + int56[] memory tickCumulatives, + uint160[] memory secondsPerLiquidityCumulativeX128s + ) = IUniswapV3Pool(pool).observe(secondsAgos); + + int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0]; + uint160 secondsPerLiquidityCumulativesDelta = secondsPerLiquidityCumulativeX128s[ + 1 + ] - secondsPerLiquidityCumulativeX128s[0]; + + arithmeticMeanTick = int24(tickCumulativesDelta / int32(secondsAgo)); + // Always round to negative infinity + if ( + tickCumulativesDelta < 0 && + (tickCumulativesDelta % int32(secondsAgo) != 0) + ) arithmeticMeanTick--; + + // We are multiplying here instead of shifting to ensure that harmonicMeanLiquidity doesn't overflow uint128 + uint192 secondsAgoX160 = uint192(secondsAgo) * type(uint160).max; + harmonicMeanLiquidity = uint128( + secondsAgoX160 / + (uint192(secondsPerLiquidityCumulativesDelta) << 32) + ); + } + + /// @notice Given a tick and a token amount, calculates the amount of token received in exchange + /// @param tick Tick value used to calculate the quote + /// @param baseAmount Amount of token to be converted + /// @param baseToken Address of an ERC20 token contract used as the baseAmount denomination + /// @param quoteToken Address of an ERC20 token contract used as the quoteAmount denomination + /// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken + function getQuoteAtTick( + int24 tick, + uint128 baseAmount, + address baseToken, + address quoteToken + ) internal pure returns (uint256 quoteAmount) { + uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick); + + // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself + if (sqrtRatioX96 <= type(uint128).max) { + uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96; + quoteAmount = baseToken < quoteToken + ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192) + : FullMath.mulDiv(1 << 192, baseAmount, ratioX192); + } else { + uint256 ratioX128 = FullMath.mulDiv( + sqrtRatioX96, + sqrtRatioX96, + 1 << 64 + ); + quoteAmount = baseToken < quoteToken + ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128) + : FullMath.mulDiv(1 << 128, baseAmount, ratioX128); + } + } + + /// @notice Given a pool, it returns the number of seconds ago of the oldest stored observation + /// @param pool Address of Uniswap V3 pool that we want to observe + /// @return secondsAgo The number of seconds ago of the oldest observation stored for the pool + function getOldestObservationSecondsAgo( + address pool + ) internal view returns (uint32 secondsAgo) { + ( + , + , + uint16 observationIndex, + uint16 observationCardinality, + , + , + + ) = IUniswapV3Pool(pool).slot0(); + require(observationCardinality > 0, "NI"); + + (uint32 observationTimestamp, , , bool initialized) = IUniswapV3Pool( + pool + ).observations((observationIndex + 1) % observationCardinality); + + // The next index might not be initialized if the cardinality is in the process of increasing + // In this case the oldest observation is always in index 0 + if (!initialized) { + (observationTimestamp, , , ) = IUniswapV3Pool(pool).observations(0); + } + + secondsAgo = uint32(block.timestamp) - observationTimestamp; + } + + /// @notice Given a pool, it returns the tick value as of the start of the current block + /// @param pool Address of Uniswap V3 pool + /// @return The tick that the pool was in at the start of the current block + function getBlockStartingTickAndLiquidity( + address pool + ) internal view returns (int24, uint128) { + ( + , + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + , + , + + ) = IUniswapV3Pool(pool).slot0(); + + // 2 observations are needed to reliably calculate the block starting tick + require(observationCardinality > 1, "NEO"); + + // If the latest observation occurred in the past, then no tick-changing trades have happened in this block + // therefore the tick in `slot0` is the same as at the beginning of the current block. + // We don't need to check if this observation is initialized - it is guaranteed to be. + ( + uint32 observationTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + + ) = IUniswapV3Pool(pool).observations(observationIndex); + if (observationTimestamp != uint32(block.timestamp)) { + return (tick, IUniswapV3Pool(pool).liquidity()); + } + + uint256 prevIndex = (uint256(observationIndex) + + observationCardinality - + 1) % observationCardinality; + ( + uint32 prevObservationTimestamp, + int56 prevTickCumulative, + uint160 prevSecondsPerLiquidityCumulativeX128, + bool prevInitialized + ) = IUniswapV3Pool(pool).observations(prevIndex); + + require(prevInitialized, "ONI"); + + uint32 delta = observationTimestamp - prevObservationTimestamp; + tick = int24((tickCumulative - prevTickCumulative) / int32(delta)); + uint128 liquidity = uint128( + (uint192(delta) * type(uint160).max) / + (uint192( + secondsPerLiquidityCumulativeX128 - + prevSecondsPerLiquidityCumulativeX128 + ) << 32) + ); + return (tick, liquidity); + } + + /// @notice Information for calculating a weighted arithmetic mean tick + struct WeightedTickData { + int24 tick; + uint128 weight; + } + + /// @notice Given an array of ticks and weights, calculates the weighted arithmetic mean tick + /// @param weightedTickData An array of ticks and weights + /// @return weightedArithmeticMeanTick The weighted arithmetic mean tick + /// @dev Each entry of `weightedTickData` should represents ticks from pools with the same underlying pool tokens. If they do not, + /// extreme care must be taken to ensure that ticks are comparable (including decimal differences). + /// @dev Note that the weighted arithmetic mean tick corresponds to the weighted geometric mean price. + function getWeightedArithmeticMeanTick( + WeightedTickData[] memory weightedTickData + ) internal pure returns (int24 weightedArithmeticMeanTick) { + // Accumulates the sum of products between each tick and its weight + int256 numerator; + + // Accumulates the sum of the weights + uint256 denominator; + + // Products fit in 152 bits, so it would take an array of length ~2**104 to overflow this logic + for (uint256 i; i < weightedTickData.length; i++) { + numerator += + weightedTickData[i].tick * + int256(uint256(weightedTickData[i].weight)); + denominator += weightedTickData[i].weight; + } + + weightedArithmeticMeanTick = int24(numerator / int256(denominator)); + // Always round to negative infinity + if (numerator < 0 && (numerator % int256(denominator) != 0)) + weightedArithmeticMeanTick--; + } + + /// @notice Returns the "synthetic" tick which represents the price of the first entry in `tokens` in terms of the last + /// @dev Useful for calculating relative prices along routes. + /// @dev There must be one tick for each pairwise set of tokens. + /// @param tokens The token contract addresses + /// @param ticks The ticks, representing the price of each token pair in `tokens` + /// @return syntheticTick The synthetic tick, representing the relative price of the outermost tokens in `tokens` + function getChainedPrice( + address[] memory tokens, + int24[] memory ticks + ) internal pure returns (int256 syntheticTick) { + require(tokens.length - 1 == ticks.length, "DL"); + for (uint256 i = 1; i <= ticks.length; i++) { + // check the tokens for address sort order, then accumulate the + // ticks into the running synthetic tick, ensuring that intermediate tokens "cancel out" + tokens[i - 1] < tokens[i] + ? syntheticTick += ticks[i - 1] + : syntheticTick -= ticks[i - 1]; + } + } +} diff --git a/contracts/utils/uniswapV3/libraries/TickMath.sol b/contracts/utils/uniswapV3/libraries/TickMath.sol new file mode 100644 index 00000000..7f276ca8 --- /dev/null +++ b/contracts/utils/uniswapV3/libraries/TickMath.sol @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.9; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = + 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick( + int24 tick + ) internal pure returns (uint160 sqrtPriceX96) { + uint256 absTick = tick < 0 + ? uint256(-int256(tick)) + : uint256(int256(tick)); + require(absTick <= uint256(int256(MAX_TICK)), "T"); + + uint256 ratio = absTick & 0x1 != 0 + ? 0xfffcb933bd6fad37aa2d162d1a594001 + : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) + ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) + ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) + ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) + ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) + ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) + ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) + ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) + ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) + ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) + ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) + ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) + ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) + ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) + ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) + ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) + ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) + ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) + ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) + ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint256).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160( + (ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1) + ); + } + + /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio + function getTickAtSqrtRatio( + uint160 sqrtPriceX96 + ) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require( + sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, + "R" + ); + uint256 ratio = uint256(sqrtPriceX96) << 32; + + uint256 r = ratio; + uint256 msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24( + (log_sqrt10001 - 3402992956809132418596140100660247210) >> 128 + ); + int24 tickHi = int24( + (log_sqrt10001 + 291339464771989622907027621153398088495) >> 128 + ); + + tick = tickLow == tickHi + ? tickLow + : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 + ? tickHi + : tickLow; + } +} diff --git a/package.json b/package.json index c2ed3191..d583a991 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@openzeppelin/hardhat-upgrades": "^1.22.1", "@typechain/ethers-v5": "^10.0.0", "@typechain/hardhat": "^6.0.0", + "@uniswap/v3-periphery": "^1.4.3", "solhint": "^3.3.2", "typechain": "^8.0.0" } diff --git a/yarn.lock b/yarn.lock index 1a5252a0..659ab3b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -989,6 +989,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.3.tgz#6b076a7b751811b90fe3a172a7faeaa603e13a3f" integrity sha512-SXDRl7HKpl2WDoJpn7CK/M9U4Z8gNXDHHChAKh0Iz+Wew3wu6CmFYBeie3je8V0GSXZAIYYwUktSrnW/kwVPtg== +"@openzeppelin/contracts@3.4.2-solc-0.7": + version "3.4.2-solc-0.7" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" + integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA== + "@openzeppelin/contracts@4.8.2": version "4.8.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.2.tgz#d815ade0027b50beb9bcca67143c6bcc3e3923d6" @@ -1423,6 +1428,32 @@ "@typescript-eslint/types" "5.59.5" eslint-visitor-keys "^3.3.0" +"@uniswap/lib@^4.0.1-alpha": + version "4.0.1-alpha" + resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" + integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== + +"@uniswap/v2-core@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + +"@uniswap/v3-core@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" + integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA== + +"@uniswap/v3-periphery@^1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.3.tgz#a6da4632dbd46b139cc13a410e4ec09ad22bd19f" + integrity sha512-80c+wtVzl5JJT8UQskxVYYG3oZb4pkhY0zDe0ab/RX4+8f9+W5d8wI4BT0wLB0wFQTSnbW+QdBSpkHA/vRyGBA== + dependencies: + "@openzeppelin/contracts" "3.4.2-solc-0.7" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + base64-sol "1.0.1" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -1732,6 +1763,11 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64-sol@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6" + integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"