From 180e9ac104270f4a590fcf1e156491087bc87192 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Thu, 5 May 2022 17:22:07 -0700 Subject: [PATCH 1/6] uniswap v3 pair --- contracts/interfaces/uniswap/BitMath.sol | 94 ++++++++ contracts/interfaces/uniswap/FixedPoint96.sol | 10 + contracts/interfaces/uniswap/FullMath.sol | 124 ++++++++++ .../interfaces/uniswap/IUniswapV3Pool.sol | 17 ++ .../uniswap/IUniswapV3PoolActions.sol | 103 ++++++++ .../uniswap/IUniswapV3PoolImmutables.sol | 35 +++ .../uniswap/IUniswapV3PoolState.sol | 116 +++++++++ .../interfaces/uniswap/LiquidityMath.sol | 17 ++ .../interfaces/uniswap/LowGasSafeMath.sol | 46 ++++ contracts/interfaces/uniswap/Quoter.sol | 226 +++++++++++++++++ contracts/interfaces/uniswap/SafeCast.sol | 28 +++ .../interfaces/uniswap/SqrtPriceMath.sol | 227 ++++++++++++++++++ contracts/interfaces/uniswap/SwapMath.sol | 98 ++++++++ contracts/interfaces/uniswap/TickBitmap.sol | 79 ++++++ contracts/interfaces/uniswap/TickMath.sol | 205 ++++++++++++++++ contracts/interfaces/uniswap/UnsafeMath.sol | 17 ++ contracts/swappa/PairUniswapV3.sol | 61 +++++ 17 files changed, 1503 insertions(+) create mode 100644 contracts/interfaces/uniswap/BitMath.sol create mode 100644 contracts/interfaces/uniswap/FixedPoint96.sol create mode 100644 contracts/interfaces/uniswap/FullMath.sol create mode 100644 contracts/interfaces/uniswap/IUniswapV3Pool.sol create mode 100644 contracts/interfaces/uniswap/IUniswapV3PoolActions.sol create mode 100644 contracts/interfaces/uniswap/IUniswapV3PoolImmutables.sol create mode 100644 contracts/interfaces/uniswap/IUniswapV3PoolState.sol create mode 100644 contracts/interfaces/uniswap/LiquidityMath.sol create mode 100644 contracts/interfaces/uniswap/LowGasSafeMath.sol create mode 100644 contracts/interfaces/uniswap/Quoter.sol create mode 100644 contracts/interfaces/uniswap/SafeCast.sol create mode 100644 contracts/interfaces/uniswap/SqrtPriceMath.sol create mode 100644 contracts/interfaces/uniswap/SwapMath.sol create mode 100644 contracts/interfaces/uniswap/TickBitmap.sol create mode 100644 contracts/interfaces/uniswap/TickMath.sol create mode 100644 contracts/interfaces/uniswap/UnsafeMath.sol create mode 100644 contracts/swappa/PairUniswapV3.sol diff --git a/contracts/interfaces/uniswap/BitMath.sol b/contracts/interfaces/uniswap/BitMath.sol new file mode 100644 index 0000000..3a7216c --- /dev/null +++ b/contracts/interfaces/uniswap/BitMath.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title BitMath +/// @dev This library provides functionality for computing bit properties of an unsigned integer +library BitMath { + /// @notice Returns the index of the most significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @dev The function satisfies the property: + /// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1) + /// @param x the value for which to compute the most significant bit, must be greater than 0 + /// @return r the index of the most significant bit + function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + if (x >= 0x100000000000000000000000000000000) { + x >>= 128; + r += 128; + } + if (x >= 0x10000000000000000) { + x >>= 64; + r += 64; + } + if (x >= 0x100000000) { + x >>= 32; + r += 32; + } + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 0x4) { + x >>= 2; + r += 2; + } + if (x >= 0x2) r += 1; + } + + /// @notice Returns the index of the least significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @dev The function satisfies the property: + /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) + /// @param x the value for which to compute the least significant bit, must be greater than 0 + /// @return r the index of the least significant bit + function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + r = 255; + if (x & type(uint128).max > 0) { + r -= 128; + } else { + x >>= 128; + } + if (x & type(uint64).max > 0) { + r -= 64; + } else { + x >>= 64; + } + if (x & type(uint32).max > 0) { + r -= 32; + } else { + x >>= 32; + } + if (x & type(uint16).max > 0) { + r -= 16; + } else { + x >>= 16; + } + if (x & type(uint8).max > 0) { + r -= 8; + } else { + x >>= 8; + } + if (x & 0xf > 0) { + r -= 4; + } else { + x >>= 4; + } + if (x & 0x3 > 0) { + r -= 2; + } else { + x >>= 2; + } + if (x & 0x1 > 0) r -= 1; + } +} diff --git a/contracts/interfaces/uniswap/FixedPoint96.sol b/contracts/interfaces/uniswap/FixedPoint96.sol new file mode 100644 index 0000000..63b42c2 --- /dev/null +++ b/contracts/interfaces/uniswap/FixedPoint96.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.4.0; + +/// @title FixedPoint96 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +/// @dev Used in SqrtPriceMath.sol +library FixedPoint96 { + uint8 internal constant RESOLUTION = 96; + uint256 internal constant Q96 = 0x1000000000000000000000000; +} diff --git a/contracts/interfaces/uniswap/FullMath.sol b/contracts/interfaces/uniswap/FullMath.sol new file mode 100644 index 0000000..9985059 --- /dev/null +++ b/contracts/interfaces/uniswap/FullMath.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.0 <0.8.0; + +/// @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 = -denominator & 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/interfaces/uniswap/IUniswapV3Pool.sol b/contracts/interfaces/uniswap/IUniswapV3Pool.sol new file mode 100644 index 0000000..84ced41 --- /dev/null +++ b/contracts/interfaces/uniswap/IUniswapV3Pool.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import './IUniswapV3PoolImmutables.sol'; +import './IUniswapV3PoolState.sol'; +import './IUniswapV3PoolActions.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, + IUniswapV3PoolActions +{ +} diff --git a/contracts/interfaces/uniswap/IUniswapV3PoolActions.sol b/contracts/interfaces/uniswap/IUniswapV3PoolActions.sol new file mode 100644 index 0000000..44fb61c --- /dev/null +++ b/contracts/interfaces/uniswap/IUniswapV3PoolActions.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @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/interfaces/uniswap/IUniswapV3PoolImmutables.sol b/contracts/interfaces/uniswap/IUniswapV3PoolImmutables.sol new file mode 100644 index 0000000..c9beb15 --- /dev/null +++ b/contracts/interfaces/uniswap/IUniswapV3PoolImmutables.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @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/interfaces/uniswap/IUniswapV3PoolState.sol b/contracts/interfaces/uniswap/IUniswapV3PoolState.sol new file mode 100644 index 0000000..620256c --- /dev/null +++ b/contracts/interfaces/uniswap/IUniswapV3PoolState.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @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/interfaces/uniswap/LiquidityMath.sol b/contracts/interfaces/uniswap/LiquidityMath.sol new file mode 100644 index 0000000..d5e2303 --- /dev/null +++ b/contracts/interfaces/uniswap/LiquidityMath.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Math library for liquidity +library LiquidityMath { + /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows + /// @param x The liquidity before change + /// @param y The delta by which liquidity should be changed + /// @return z The liquidity delta + function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { + if (y < 0) { + require((z = x - uint128(-y)) < x, 'LS'); + } else { + require((z = x + uint128(y)) >= x, 'LA'); + } + } +} diff --git a/contracts/interfaces/uniswap/LowGasSafeMath.sol b/contracts/interfaces/uniswap/LowGasSafeMath.sol new file mode 100644 index 0000000..cc51a5a --- /dev/null +++ b/contracts/interfaces/uniswap/LowGasSafeMath.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.6.0; + +/// @title Optimized overflow and underflow safe math operations +/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost +library LowGasSafeMath { + /// @notice Returns x + y, reverts if sum overflows uint256 + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x); + } + + /// @notice Returns x - y, reverts if underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x); + } + + /// @notice Returns x * y, reverts if overflows + /// @param x The multiplicand + /// @param y The multiplier + /// @return z The product of x and y + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + require(x == 0 || (z = x * y) / x == y); + } + + /// @notice Returns x + y, reverts if overflows or underflows + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x + y) >= x == (y >= 0)); + } + + /// @notice Returns x - y, reverts if overflows or underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x - y) <= x == (y >= 0)); + } +} diff --git a/contracts/interfaces/uniswap/Quoter.sol b/contracts/interfaces/uniswap/Quoter.sol new file mode 100644 index 0000000..7d8cf77 --- /dev/null +++ b/contracts/interfaces/uniswap/Quoter.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.0 <0.8.0; + +import "./IUniswapV3Pool.sol"; +import "./LiquidityMath.sol"; +import "./LowGasSafeMath.sol"; +import "./SafeCast.sol"; +import "./SwapMath.sol"; +import "./TickBitmap.sol"; +import "./TickMath.sol"; + +library Quoter { + using LowGasSafeMath for uint256; + using LowGasSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + + struct SwapCache { + // the protocol fee for the input token + uint8 feeProtocol; + // liquidity at the beginning of the swap + uint128 liquidityStart; + } + + // the top level state of the swap, the results of which are recorded in storage at the end + struct SwapState { + // the amount remaining to be swapped in/out of the input/output asset + int256 amountSpecifiedRemaining; + // the amount already swapped out/in of the output/input asset + int256 amountCalculated; + // current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // amount of input token paid as protocol fee + uint128 protocolFee; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + } + + struct Slot0 { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // the current protocol fee as a percentage of the swap fee taken on withdrawal + // represented as an integer denominator (1/x)% + uint8 feeProtocol; + } + + function quote( + IUniswapV3Pool pool, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96 + ) internal view returns (int256 amount0, int256 amount1) { + Slot0 memory slot0Start; + ( + slot0Start.sqrtPriceX96, + slot0Start.tick, + , // uint16 observationIndex + , // uint16 observationCardinality + , // uint16 observationCardinalityNext + slot0Start.feeProtocol, + // bool unlocked + + ) = pool.slot0(); + + require( + zeroForOne + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && + sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && + sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + "SPL" + ); + + SwapCache memory cache = SwapCache({ + liquidityStart: pool.liquidity(), + feeProtocol: zeroForOne + ? (slot0Start.feeProtocol % 16) + : (slot0Start.feeProtocol >> 4) + }); + + bool exactInput = amountSpecified > 0; + int24 poolTickSpacing = pool.tickSpacing(); + uint24 poolFee = pool.fee(); + + SwapState memory state = SwapState({ + amountSpecifiedRemaining: amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + protocolFee: 0, + liquidity: cache.liquidityStart + }); + + // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit + while ( + state.amountSpecifiedRemaining != 0 && + state.sqrtPriceX96 != sqrtPriceLimitX96 + ) { + StepComputations memory step; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + (step.tickNext, step.initialized) = TickBitmap + .nextInitializedTickWithinOneWord( + pool, + state.tick, + poolTickSpacing, + zeroForOne + ); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + + // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted + ( + state.sqrtPriceX96, + step.amountIn, + step.amountOut, + step.feeAmount + ) = SwapMath.computeSwapStep( + state.sqrtPriceX96, + ( + zeroForOne + ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 + : step.sqrtPriceNextX96 > sqrtPriceLimitX96 + ) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + poolFee + ); + + if (exactInput) { + state.amountSpecifiedRemaining -= (step.amountIn + + step.feeAmount).toInt256(); + state.amountCalculated = state.amountCalculated.sub( + step.amountOut.toInt256() + ); + } else { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + state.amountCalculated = state.amountCalculated.add( + (step.amountIn + step.feeAmount).toInt256() + ); + } + + // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee + if (cache.feeProtocol > 0) { + uint256 delta = step.feeAmount / cache.feeProtocol; + step.feeAmount -= delta; + state.protocolFee += uint128(delta); + } + + // shift tick if we reached the next price + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + ( + , + // uint128 liquidityGross, + int128 liquidityNet, + , // uint256 feeGrowthOutside0X128 + , // uint256 feeGrowthOutside1X128 + , // int56 tickCumulativeOutside + , // uint160 secondsPerLiquidityOutsideX128 + , // uint32 secondsOutside + // bool initialized + ) = + pool.ticks(step.tickNext); + + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta( + state.liquidity, + liquidityNet + ); + } + + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + } + } + + (amount0, amount1) = zeroForOne == exactInput + ? ( + amountSpecified - state.amountSpecifiedRemaining, + state.amountCalculated + ) + : ( + state.amountCalculated, + amountSpecified - state.amountSpecifiedRemaining + ); + } +} diff --git a/contracts/interfaces/uniswap/SafeCast.sol b/contracts/interfaces/uniswap/SafeCast.sol new file mode 100644 index 0000000..a8ea229 --- /dev/null +++ b/contracts/interfaces/uniswap/SafeCast.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Safe casting methods +/// @notice Contains methods for safely casting between types +library SafeCast { + /// @notice Cast a uint256 to a uint160, revert on overflow + /// @param y The uint256 to be downcasted + /// @return z The downcasted integer, now type uint160 + function toUint160(uint256 y) internal pure returns (uint160 z) { + require((z = uint160(y)) == y); + } + + /// @notice Cast a int256 to a int128, revert on overflow or underflow + /// @param y The int256 to be downcasted + /// @return z The downcasted integer, now type int128 + function toInt128(int256 y) internal pure returns (int128 z) { + require((z = int128(y)) == y); + } + + /// @notice Cast a uint256 to a int256, revert on overflow + /// @param y The uint256 to be casted + /// @return z The casted integer, now type int256 + function toInt256(uint256 y) internal pure returns (int256 z) { + require(y < 2**255); + z = int256(y); + } +} diff --git a/contracts/interfaces/uniswap/SqrtPriceMath.sol b/contracts/interfaces/uniswap/SqrtPriceMath.sol new file mode 100644 index 0000000..685f485 --- /dev/null +++ b/contracts/interfaces/uniswap/SqrtPriceMath.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import './LowGasSafeMath.sol'; +import './SafeCast.sol'; + +import './FullMath.sol'; +import './UnsafeMath.sol'; +import './FixedPoint96.sol'; + +/// @title Functions based on Q64.96 sqrt price and liquidity +/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas +library SqrtPriceMath { + using LowGasSafeMath for uint256; + using SafeCast for uint256; + + /// @notice Gets the next sqrt price given a delta of token0 + /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the + /// price less in order to not send too much output. + /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), + /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). + /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token0 to add or remove from virtual reserves + /// @param add Whether to add or remove the amount of token0 + /// @return The price after adding or removing amount, depending on add + function getNextSqrtPriceFromAmount0RoundingUp( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + + if (add) { + uint256 product; + if ((product = amount * sqrtPX96) / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) + // always fits in 160 bits + return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + } + + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))); + } else { + uint256 product; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + + /// @notice Gets the next sqrt price given a delta of token1 + /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the + /// price less in order to not send too much output. + /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity + /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token1 to add, or remove, from virtual reserves + /// @param add Whether to add, or remove, the amount of token1 + /// @return The price after adding or removing `amount` + function getNextSqrtPriceFromAmount1RoundingDown( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if (add) { + uint256 quotient = + ( + amount <= type(uint160).max + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ); + + return uint256(sqrtPX96).add(quotient).toUint160(); + } else { + uint256 quotient = + ( + amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ); + + require(sqrtPX96 > quotient); + // always fits 160 bits + return uint160(sqrtPX96 - quotient); + } + } + + /// @notice Gets the next sqrt price given an input amount of token0 or token1 + /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds + /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount + /// @param liquidity The amount of usable liquidity + /// @param amountIn How much of token0, or token1, is being swapped in + /// @param zeroForOne Whether the amount in is token0 or token1 + /// @return sqrtQX96 The price after adding the input amount to token0 or token1 + function getNextSqrtPriceFromInput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we don't pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + /// @notice Gets the next sqrt price given an output amount of token0 or token1 + /// @dev Throws if price or liquidity are 0 or the next price is out of bounds + /// @param sqrtPX96 The starting price before accounting for the output amount + /// @param liquidity The amount of usable liquidity + /// @param amountOut How much of token0, or token1, is being swapped out + /// @param zeroForOne Whether the amount out is token0 or token1 + /// @return sqrtQX96 The price after removing the output amount of token0 or token1 + function getNextSqrtPriceFromOutput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + /// @notice Gets the amount0 delta between two prices + /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), + /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up or down + /// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0); + + return + roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + sqrtRatioAX96 + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Helper that gets signed token0 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount0) { + return + liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + + /// @notice Helper that gets signed token1 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount1) { + return + liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } +} diff --git a/contracts/interfaces/uniswap/SwapMath.sol b/contracts/interfaces/uniswap/SwapMath.sol new file mode 100644 index 0000000..ee176fb --- /dev/null +++ b/contracts/interfaces/uniswap/SwapMath.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import './FullMath.sol'; +import './SqrtPriceMath.sol'; + +/// @title Computes the result of a swap within ticks +/// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. +library SwapMath { + /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap + /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive + /// @param sqrtRatioCurrentX96 The current sqrt price of the pool + /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred + /// @param liquidity The usable liquidity + /// @param amountRemaining How much input or output amount is remaining to be swapped in/out + /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip + /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target + /// @return amountIn The amount to be swapped in, of either token0 or token1, based on the direction of the swap + /// @return amountOut The amount to be received, of either token0 or token1, based on the direction of the swap + /// @return feeAmount The amount of input that will be taken as a fee + function computeSwapStep( + uint160 sqrtRatioCurrentX96, + uint160 sqrtRatioTargetX96, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + internal + pure + returns ( + uint160 sqrtRatioNextX96, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; + bool exactIn = amountRemaining >= 0; + + if (exactIn) { + uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6); + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true); + if (amountRemainingLessFee >= amountIn) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtRatioCurrentX96, + liquidity, + amountRemainingLessFee, + zeroForOne + ); + } else { + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false); + if (uint256(-amountRemaining) >= amountOut) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtRatioCurrentX96, + liquidity, + uint256(-amountRemaining), + zeroForOne + ); + } + + bool max = sqrtRatioTargetX96 == sqrtRatioNextX96; + + // get the input/output amounts + if (zeroForOne) { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false); + } else { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false); + } + + // cap the output amount to not exceed the remaining output amount + if (!exactIn && amountOut > uint256(-amountRemaining)) { + amountOut = uint256(-amountRemaining); + } + + if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { + // we didn't reach the target, so take the remainder of the maximum input as fee + feeAmount = uint256(amountRemaining) - amountIn; + } else { + feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); + } + } +} diff --git a/contracts/interfaces/uniswap/TickBitmap.sol b/contracts/interfaces/uniswap/TickBitmap.sol new file mode 100644 index 0000000..62f754c --- /dev/null +++ b/contracts/interfaces/uniswap/TickBitmap.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import './BitMath.sol'; +import './IUniswapV3PoolState.sol'; + +/// @title Packed tick initialized state library +/// @notice Stores a packed mapping of tick index to its initialized state +/// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word. +library TickBitmap { + /// @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) { + wordPos = int16(tick >> 8); + bitPos = uint8(tick % 256); + } + + /// @notice Flips the initialized state for a given tick from false to true, or vice versa + /// @param self The mapping in which to flip the tick + /// @param tick The tick to flip + /// @param tickSpacing The spacing between usable ticks + function flipTick( + mapping(int16 => uint256) storage self, + int24 tick, + int24 tickSpacing + ) internal { + require(tick % tickSpacing == 0); // ensure that the tick is spaced + (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); + uint256 mask = 1 << bitPos; + self[wordPos] ^= mask; + } + + /// @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 pool 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 nextInitializedTickWithinOneWord( + IUniswapV3PoolState pool, + int24 tick, + int24 tickSpacing, + bool lte + ) internal view returns (int24 next, bool initialized) { + 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 = pool.tickBitmap(wordPos) & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(bitPos - BitMath.mostSignificantBit(masked))) * tickSpacing + : (compressed - int24(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 masked = pool.tickBitmap(wordPos) & 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(BitMath.leastSignificantBit(masked) - bitPos)) * tickSpacing + : (compressed + 1 + int24(type(uint8).max - bitPos)) * tickSpacing; + } + } +} diff --git a/contracts/interfaces/uniswap/TickMath.sol b/contracts/interfaces/uniswap/TickMath.sol new file mode 100644 index 0000000..ee48fee --- /dev/null +++ b/contracts/interfaces/uniswap/TickMath.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0 <0.8.0; + +/// @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(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/contracts/interfaces/uniswap/UnsafeMath.sol b/contracts/interfaces/uniswap/UnsafeMath.sol new file mode 100644 index 0000000..f62f846 --- /dev/null +++ b/contracts/interfaces/uniswap/UnsafeMath.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Math functions that do not check inputs or outputs +/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks +library UnsafeMath { + /// @notice Returns ceil(x / y) + /// @dev division by 0 has unspecified behavior, and must be checked externally + /// @param x The dividend + /// @param y The divisor + /// @return z The quotient, ceil(x / y) + function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := add(div(x, y), gt(mod(x, y), 0)) + } + } +} diff --git a/contracts/swappa/PairUniswapV3.sol b/contracts/swappa/PairUniswapV3.sol new file mode 100644 index 0000000..7755717 --- /dev/null +++ b/contracts/swappa/PairUniswapV3.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.0 <0.8.0; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../interfaces/uniswap/IUniswapV3Pool.sol"; +import "../interfaces/uniswap/Quoter.sol"; +import "../interfaces/uniswap/SafeCast.sol"; +import "../interfaces/uniswap/TickMath.sol"; +import "./ISwappaPairV1.sol"; + +contract PairUniswapV3 is ISwappaPairV1 { + using SafeMath for uint; + using SafeCast for uint; + + function swap( + address input, + address output, + address to, + bytes calldata data + ) external override { + address pairAddr = parseData(data); + uint inputAmount = ERC20(input).balanceOf(address(this)); + require( + ERC20(input).transfer(pairAddr, inputAmount), + "PairUniswapV3: transfer failed!"); + IUniswapV3Pool pair = IUniswapV3Pool(pairAddr); + bool zeroForOne = pair.token0() == input; + pair.swap( + to, + zeroForOne, + inputAmount.toInt256(), + zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + new bytes(0)); + } + + function parseData(bytes memory data) private pure returns (address pairAddr) { + require(data.length == 20, "PairUniswapV3: invalid data!"); + assembly { + pairAddr := mload(add(data, 20)) + } + } + + function getOutputAmount( + address input, + address output, + uint amountIn, + bytes calldata data + ) external view override returns (uint amountOut) { + address pairAddr = parseData(data); + IUniswapV3Pool pair = IUniswapV3Pool(pairAddr); + bool zeroForOne = pair.token0() == input; + // amount0, amount1 are delta of the pair reserves + (int256 amount0, int256 amount1) = Quoter.quote( + pair, + zeroForOne, + amountIn.toInt256(), + zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1); + return zeroForOne ? uint256(-amount1) : uint256(-amount0); + } +} From 861000516cdc8b716fb481c61ef548e33fc4e4d0 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Sun, 8 May 2022 13:09:21 -0700 Subject: [PATCH 2/6] implement get amount in --- contracts/swappa/PairUniswapV3.sol | 56 +++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/contracts/swappa/PairUniswapV3.sol b/contracts/swappa/PairUniswapV3.sol index 7755717..a0006c8 100644 --- a/contracts/swappa/PairUniswapV3.sol +++ b/contracts/swappa/PairUniswapV3.sol @@ -10,8 +10,8 @@ import "../interfaces/uniswap/TickMath.sol"; import "./ISwappaPairV1.sol"; contract PairUniswapV3 is ISwappaPairV1 { - using SafeMath for uint; - using SafeCast for uint; + using SafeMath for uint256; + using SafeCast for uint256; function swap( address input, @@ -20,33 +20,41 @@ contract PairUniswapV3 is ISwappaPairV1 { bytes calldata data ) external override { address pairAddr = parseData(data); - uint inputAmount = ERC20(input).balanceOf(address(this)); + uint256 inputAmount = ERC20(input).balanceOf(address(this)); require( ERC20(input).transfer(pairAddr, inputAmount), - "PairUniswapV3: transfer failed!"); + "PairUniswapV3: transfer failed!" + ); IUniswapV3Pool pair = IUniswapV3Pool(pairAddr); bool zeroForOne = pair.token0() == input; pair.swap( to, zeroForOne, inputAmount.toInt256(), - zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, - new bytes(0)); + zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1, + new bytes(0) + ); } - function parseData(bytes memory data) private pure returns (address pairAddr) { + function parseData(bytes memory data) + private + pure + returns (address pairAddr) + { require(data.length == 20, "PairUniswapV3: invalid data!"); assembly { - pairAddr := mload(add(data, 20)) + pairAddr := mload(add(data, 20)) } } function getOutputAmount( address input, address output, - uint amountIn, + uint256 amountIn, bytes calldata data - ) external view override returns (uint amountOut) { + ) external view override returns (uint256 amountOut) { address pairAddr = parseData(data); IUniswapV3Pool pair = IUniswapV3Pool(pairAddr); bool zeroForOne = pair.token0() == input; @@ -55,7 +63,31 @@ contract PairUniswapV3 is ISwappaPairV1 { pair, zeroForOne, amountIn.toInt256(), - zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1); - return zeroForOne ? uint256(-amount1) : uint256(-amount0); + zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1 + ); + return uint256(-(zeroForOne ? amount1 : amount0)); + } + + function getInputAmount( + address input, + address output, + uint256 amountOut, + bytes calldata data + ) external view returns (uint256 amountIn) { + address pairAddr = parseData(data); + IUniswapV3Pool pair = IUniswapV3Pool(pairAddr); + bool zeroForOne = pair.token0() == input; + // amount0, amount1 are delta of the pair reserves + (int256 amount0, int256 amount1) = Quoter.quote( + pair, + zeroForOne, + -amountOut.toInt256(), + zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1 + ); + return uint256(zeroForOne ? amount0 : amount1); } } From 049df9494261c87bb900d9d4ffbb87c1384f0dc2 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Fri, 10 Jun 2022 22:35:47 -0700 Subject: [PATCH 3/6] add tick lens library functions --- contracts/interfaces/uniswap/TickLens.sol | 88 +++++++++++++++++++++++ contracts/swappa/PairUniswapV3.sol | 25 +++++++ 2 files changed, 113 insertions(+) create mode 100644 contracts/interfaces/uniswap/TickLens.sol diff --git a/contracts/interfaces/uniswap/TickLens.sol b/contracts/interfaces/uniswap/TickLens.sol new file mode 100644 index 0000000..750340e --- /dev/null +++ b/contracts/interfaces/uniswap/TickLens.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.0 <0.8.0; +pragma experimental ABIEncoderV2; + +import "./IUniswapV3Pool.sol"; + +library TickLens { + + struct PopulatedTick { + int24 tick; + int128 liquidityNet; + uint128 liquidityGross; + } + + function getSpotTicks(IUniswapV3Pool pool) + internal + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + int16 tickBitmapIndex, + PopulatedTick[] memory populatedTicksAbove, + PopulatedTick[] memory populatedTicksSpot, + PopulatedTick[] memory populatedTicksBelow + ) + { + // get the populated ticks above and below the current spot tick + ( + sqrtPriceX96, + tick, + , // uint16 observationIndex + , // uint16 observationCardinality + , // uint16 observationCardinalityNext + , // uint8 feeProtocol + // bool unlocked + ) = pool.slot0(); + + int24 tickSpacing = pool.tickSpacing(); + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + // current word position within bitmap + tickBitmapIndex = int16(compressed >> 8); + + // get the populated ticks at, above, and below the current word + populatedTicksSpot = getPopulatedTicksInWord(pool, tickBitmapIndex); + populatedTicksAbove = getPopulatedTicksInWord( + pool, + tickBitmapIndex + 1 + ); + populatedTicksBelow = getPopulatedTicksInWord( + pool, + tickBitmapIndex - 1 + ); + } + + function getPopulatedTicksInWord(IUniswapV3Pool pool, int16 tickBitmapIndex) + internal + view + returns (PopulatedTick[] memory populatedTicks) + { + // fetch bitmap + uint256 bitmap = pool.tickBitmap(tickBitmapIndex); + + // calculate the number of populated ticks + uint256 numberOfPopulatedTicks; + for (uint256 i = 0; i < 256; i++) { + if (bitmap & (1 << i) > 0) numberOfPopulatedTicks++; + } + + // fetch populated tick data + int24 tickSpacing = pool.tickSpacing(); + populatedTicks = new PopulatedTick[](numberOfPopulatedTicks); + for (uint256 i = 0; i < 256; i++) { + if (bitmap & (1 << i) > 0) { + int24 populatedTick = ((int24(tickBitmapIndex) << 8) + + int24(i)) * tickSpacing; + (uint128 liquidityGross, int128 liquidityNet, , , , , , ) = pool + .ticks(populatedTick); + populatedTicks[--numberOfPopulatedTicks] = PopulatedTick({ + tick: populatedTick, + liquidityNet: liquidityNet, + liquidityGross: liquidityGross + }); + } + } + } +} diff --git a/contracts/swappa/PairUniswapV3.sol b/contracts/swappa/PairUniswapV3.sol index a0006c8..779190d 100644 --- a/contracts/swappa/PairUniswapV3.sol +++ b/contracts/swappa/PairUniswapV3.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.4.0 <0.8.0; +pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "../interfaces/uniswap/IUniswapV3Pool.sol"; import "../interfaces/uniswap/Quoter.sol"; import "../interfaces/uniswap/SafeCast.sol"; +import "../interfaces/uniswap/TickLens.sol"; import "../interfaces/uniswap/TickMath.sol"; import "./ISwappaPairV1.sol"; @@ -90,4 +92,27 @@ contract PairUniswapV3 is ISwappaPairV1 { ); return uint256(zeroForOne ? amount0 : amount1); } + + function getSpotTicks(IUniswapV3Pool pool) + public + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + int16 tickBitmapIndex, + TickLens.PopulatedTick[] memory populatedTicksAbove, + TickLens.PopulatedTick[] memory populatedTicksSpot, + TickLens.PopulatedTick[] memory populatedTicksBelow + ) + { + return TickLens.getSpotTicks(pool); + } + + function getPopulatedTicksInWord(IUniswapV3Pool pool, int16 tickBitmapIndex) + public + view + returns (TickLens.PopulatedTick[] memory populatedTicks) + { + return TickLens.getPopulatedTicksInWord(pool, tickBitmapIndex); + } } From 539eeb037b7dd8b073bb29577e88f7641f436061 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Fri, 10 Jun 2022 23:09:10 -0700 Subject: [PATCH 4/6] factoru --- .../interfaces/uniswap/IuniswapV3Factory.sol | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 contracts/interfaces/uniswap/IuniswapV3Factory.sol diff --git a/contracts/interfaces/uniswap/IuniswapV3Factory.sol b/contracts/interfaces/uniswap/IuniswapV3Factory.sol new file mode 100644 index 0000000..540cfdc --- /dev/null +++ b/contracts/interfaces/uniswap/IuniswapV3Factory.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title The interface for the Uniswap V3 Factory +/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees +interface IUniswapV3Factory { + /// @notice Emitted when the owner of the factory is changed + /// @param oldOwner The owner before the owner was changed + /// @param newOwner The owner after the owner was changed + event OwnerChanged(address indexed oldOwner, address indexed newOwner); + + /// @notice Emitted when a pool is created + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param pool The address of the created pool + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed fee, + int24 tickSpacing, + address pool + ); + + /// @notice Emitted when a new fee amount is enabled for pool creation via the factory + /// @param fee The enabled fee, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee + event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); + + /// @notice Returns the current owner of the factory + /// @dev Can be changed by the current owner via setOwner + /// @return The address of the factory owner + function owner() external view returns (address); + + /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled + /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context + /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee + /// @return The tick spacing + function feeAmountTickSpacing(uint24 fee) external view returns (int24); + + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The pool address + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + /// @notice Creates a pool for the given two tokens and fee + /// @param tokenA One of the two tokens in the desired pool + /// @param tokenB The other of the two tokens in the desired pool + /// @param fee The desired fee for the pool + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved + /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments + /// are invalid. + /// @return pool The address of the newly created pool + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) external returns (address pool); + + /// @notice Updates the owner of the factory + /// @dev Must be called by the current owner + /// @param _owner The new owner of the factory + function setOwner(address _owner) external; + + /// @notice Enables a fee amount with the given tickSpacing + /// @dev Fee amounts may never be removed once enabled + /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) + /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount + function enableFeeAmount(uint24 fee, int24 tickSpacing) external; +} From e5029339f13785259ef24cc88d7a42c1f1d19602 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Mon, 13 Jun 2022 18:29:41 -0700 Subject: [PATCH 5/6] update --- contracts/interfaces/uniswap/IQuoter.sol | 51 +++++++++++++++++++ contracts/interfaces/uniswap/ITickLens.sol | 25 +++++++++ .../uniswap/IUniswapV3SwapCallback.sol | 21 ++++++++ contracts/interfaces/uniswap/TickLens.sol | 3 +- contracts/swappa/PairUniswapV3.sol | 33 +++++++++--- 5 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 contracts/interfaces/uniswap/IQuoter.sol create mode 100644 contracts/interfaces/uniswap/ITickLens.sol create mode 100644 contracts/interfaces/uniswap/IUniswapV3SwapCallback.sol diff --git a/contracts/interfaces/uniswap/IQuoter.sol b/contracts/interfaces/uniswap/IQuoter.sol new file mode 100644 index 0000000..cb4ffc5 --- /dev/null +++ b/contracts/interfaces/uniswap/IQuoter.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.4.0 <0.8.0; +pragma experimental ABIEncoderV2; + +/// @title Quoter Interface +/// @notice Supports quoting the calculated amounts from exact input or exact output swaps +/// @dev These functions are not marked view because they rely on calling non-view functions and reverting +/// to compute the result. They are also not gas efficient and should not be called on-chain. +interface IQuoter { + /// @notice Returns the amount out received for a given exact input swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee + /// @param amountIn The amount of the first token to swap + /// @return amountOut The amount of the last token that would be received + function quoteExactInput(bytes calldata path, uint256 amountIn) external returns (uint256 amountOut); + + /// @notice Returns the amount out received for a given exact input but for a swap of a single pool + /// @param tokenIn The token being swapped in + /// @param tokenOut The token being swapped out + /// @param fee The fee of the token pool to consider for the pair + /// @param amountIn The desired input amount + /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountOut The amount of `tokenOut` that would be received + function quoteExactInputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountIn, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountOut); + + /// @notice Returns the amount in required for a given exact output swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee + /// @param amountOut The amount of the last token to receive + /// @return amountIn The amount of first token required to be paid + function quoteExactOutput(bytes calldata path, uint256 amountOut) external returns (uint256 amountIn); + + /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool + /// @param tokenIn The token being swapped in + /// @param tokenOut The token being swapped out + /// @param fee The fee of the token pool to consider for the pair + /// @param amountOut The desired output amount + /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountIn The amount required as the input for the swap in order to receive `amountOut` + function quoteExactOutputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountOut, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountIn); +} diff --git a/contracts/interfaces/uniswap/ITickLens.sol b/contracts/interfaces/uniswap/ITickLens.sol new file mode 100644 index 0000000..9cc6926 --- /dev/null +++ b/contracts/interfaces/uniswap/ITickLens.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.4.0 <0.8.0; +pragma experimental ABIEncoderV2; + +/// @title Tick Lens +/// @notice Provides functions for fetching chunks of tick data for a pool +/// @dev This avoids the waterfall of fetching the tick bitmap, parsing the bitmap to know which ticks to fetch, and +/// then sending additional multicalls to fetch the tick data +interface ITickLens { + struct PopulatedTick { + int24 tick; + int128 liquidityNet; + uint128 liquidityGross; + } + + /// @notice Get all the tick data for the populated ticks from a word of the tick bitmap of a pool + /// @param pool The address of the pool for which to fetch populated tick data + /// @param tickBitmapIndex The index of the word in the tick bitmap for which to parse the bitmap and + /// fetch all the populated ticks + /// @return populatedTicks An array of tick data for the given word in the tick bitmap + function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex) + external + view + returns (PopulatedTick[] memory populatedTicks); +} diff --git a/contracts/interfaces/uniswap/IUniswapV3SwapCallback.sol b/contracts/interfaces/uniswap/IUniswapV3SwapCallback.sol new file mode 100644 index 0000000..9f183b2 --- /dev/null +++ b/contracts/interfaces/uniswap/IUniswapV3SwapCallback.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} diff --git a/contracts/interfaces/uniswap/TickLens.sol b/contracts/interfaces/uniswap/TickLens.sol index 750340e..77f5d8b 100644 --- a/contracts/interfaces/uniswap/TickLens.sol +++ b/contracts/interfaces/uniswap/TickLens.sol @@ -18,7 +18,6 @@ library TickLens { returns ( uint160 sqrtPriceX96, int24 tick, - int16 tickBitmapIndex, PopulatedTick[] memory populatedTicksAbove, PopulatedTick[] memory populatedTicksSpot, PopulatedTick[] memory populatedTicksBelow @@ -40,7 +39,7 @@ library TickLens { if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity // current word position within bitmap - tickBitmapIndex = int16(compressed >> 8); + int16 tickBitmapIndex = int16(compressed >> 8); // get the populated ticks at, above, and below the current word populatedTicksSpot = getPopulatedTicksInWord(pool, tickBitmapIndex); diff --git a/contracts/swappa/PairUniswapV3.sol b/contracts/swappa/PairUniswapV3.sol index 779190d..39f459c 100644 --- a/contracts/swappa/PairUniswapV3.sol +++ b/contracts/swappa/PairUniswapV3.sol @@ -5,13 +5,14 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "../interfaces/uniswap/IUniswapV3Pool.sol"; +import "../interfaces/uniswap/IUniswapV3SwapCallback.sol"; import "../interfaces/uniswap/Quoter.sol"; import "../interfaces/uniswap/SafeCast.sol"; import "../interfaces/uniswap/TickLens.sol"; import "../interfaces/uniswap/TickMath.sol"; import "./ISwappaPairV1.sol"; -contract PairUniswapV3 is ISwappaPairV1 { +contract PairUniswapV3 is ISwappaPairV1, IUniswapV3SwapCallback { using SafeMath for uint256; using SafeCast for uint256; @@ -23,12 +24,9 @@ contract PairUniswapV3 is ISwappaPairV1 { ) external override { address pairAddr = parseData(data); uint256 inputAmount = ERC20(input).balanceOf(address(this)); - require( - ERC20(input).transfer(pairAddr, inputAmount), - "PairUniswapV3: transfer failed!" - ); IUniswapV3Pool pair = IUniswapV3Pool(pairAddr); bool zeroForOne = pair.token0() == input; + // calling swap will trigger the uniswapV3SwapCallback pair.swap( to, zeroForOne, @@ -40,6 +38,26 @@ contract PairUniswapV3 is ISwappaPairV1 { ); } + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + ERC20 token; + uint256 amount; + if (amount0Delta > 0) { + amount = uint256(amount0Delta); + token = ERC20(IUniswapV3Pool(msg.sender).token0()); + } else if (amount1Delta > 0) { + amount = uint256(amount1Delta); + token = ERC20(IUniswapV3Pool(msg.sender).token1()); + } + require( + token.transfer(msg.sender, amount), + "PairUniswapV3: transfer failed!" + ); + } + function parseData(bytes memory data) private pure @@ -99,7 +117,6 @@ contract PairUniswapV3 is ISwappaPairV1 { returns ( uint160 sqrtPriceX96, int24 tick, - int16 tickBitmapIndex, TickLens.PopulatedTick[] memory populatedTicksAbove, TickLens.PopulatedTick[] memory populatedTicksSpot, TickLens.PopulatedTick[] memory populatedTicksBelow @@ -115,4 +132,8 @@ contract PairUniswapV3 is ISwappaPairV1 { { return TickLens.getPopulatedTicksInWord(pool, tickBitmapIndex); } + + function recoverERC20(ERC20 token) public { + token.transfer(msg.sender, token.balanceOf(address(this))); + } } From aeb490d2de8029e53346156acdb0338c7a85067a Mon Sep 17 00:00:00 2001 From: Di Wu Date: Mon, 13 Jun 2022 19:05:43 -0700 Subject: [PATCH 6/6] quoter --- contracts/interfaces/uniswap/Quoter.sol | 31 ++----------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/contracts/interfaces/uniswap/Quoter.sol b/contracts/interfaces/uniswap/Quoter.sol index 7d8cf77..a3530e3 100644 --- a/contracts/interfaces/uniswap/Quoter.sol +++ b/contracts/interfaces/uniswap/Quoter.sol @@ -15,13 +15,6 @@ library Quoter { using SafeCast for uint256; using SafeCast for int256; - struct SwapCache { - // the protocol fee for the input token - uint8 feeProtocol; - // liquidity at the beginning of the swap - uint128 liquidityStart; - } - // the top level state of the swap, the results of which are recorded in storage at the end struct SwapState { // the amount remaining to be swapped in/out of the input/output asset @@ -32,8 +25,6 @@ library Quoter { uint160 sqrtPriceX96; // the tick associated with the current price int24 tick; - // amount of input token paid as protocol fee - uint128 protocolFee; // the current liquidity in range uint128 liquidity; } @@ -60,9 +51,6 @@ library Quoter { uint160 sqrtPriceX96; // the current tick int24 tick; - // the current protocol fee as a percentage of the swap fee taken on withdrawal - // represented as an integer denominator (1/x)% - uint8 feeProtocol; } function quote( @@ -78,7 +66,7 @@ library Quoter { , // uint16 observationIndex , // uint16 observationCardinality , // uint16 observationCardinalityNext - slot0Start.feeProtocol, + , // slot0Start.feeProtocol // bool unlocked ) = pool.slot0(); @@ -92,13 +80,6 @@ library Quoter { "SPL" ); - SwapCache memory cache = SwapCache({ - liquidityStart: pool.liquidity(), - feeProtocol: zeroForOne - ? (slot0Start.feeProtocol % 16) - : (slot0Start.feeProtocol >> 4) - }); - bool exactInput = amountSpecified > 0; int24 poolTickSpacing = pool.tickSpacing(); uint24 poolFee = pool.fee(); @@ -108,8 +89,7 @@ library Quoter { amountCalculated: 0, sqrtPriceX96: slot0Start.sqrtPriceX96, tick: slot0Start.tick, - protocolFee: 0, - liquidity: cache.liquidityStart + liquidity: pool.liquidity() }); // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit @@ -172,13 +152,6 @@ library Quoter { ); } - // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee - if (cache.feeProtocol > 0) { - uint256 delta = step.feeAmount / cache.feeProtocol; - step.feeAmount -= delta; - state.protocolFee += uint128(delta); - } - // shift tick if we reached the next price if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { // if the tick is initialized, run the tick transition