diff --git a/contracts/interfaces/algebra/IAlgebraFactory.sol b/contracts/interfaces/algebra/IAlgebraFactory.sol new file mode 100644 index 0000000..55a639f --- /dev/null +++ b/contracts/interfaces/algebra/IAlgebraFactory.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/** + * @title The interface for the Algebra Factory + * @dev Credit to Uniswap Labs under GPL-2.0-or-later license: + * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces + */ +interface IAlgebraFactory { + /** + * @notice Emitted when the owner of the factory is changed + * @param newOwner The owner after the owner was changed + */ + event Owner(address indexed newOwner); + + /** + * @notice Emitted when the vault address is changed + * @param newVaultAddress The vault address after the address was changed + */ + event VaultAddress(address indexed newVaultAddress); + + /** + * @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 pool The address of the created pool + */ + event Pool(address indexed token0, address indexed token1, address pool); + + /** + * @notice Emitted when the farming address is changed + * @param newFarmingAddress The farming address after the address was changed + */ + event FarmingAddress(address indexed newFarmingAddress); + + event FeeConfiguration( + uint16 alpha1, + uint16 alpha2, + uint32 beta1, + uint32 beta2, + uint16 gamma1, + uint16 gamma2, + uint32 volumeBeta, + uint16 volumeGamma, + uint16 baseFee + ); + + /** + * @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 current poolDeployerAddress + * @return The address of the poolDeployer + */ + function poolDeployer() external view returns (address); + + /** + * @dev Is retrieved from the pools to restrict calling + * certain functions not by a tokenomics contract + * @return The tokenomics contract address + */ + function farmingAddress() external view returns (address); + + function vaultAddress() external view returns (address); + + /** + * @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 + * @return pool The pool address + */ + function poolByPair(address tokenA, address tokenB) 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 + * @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) 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; + + /** + * @dev updates tokenomics address on the factory + * @param _farmingAddress The new tokenomics contract address + */ + function setFarmingAddress(address _farmingAddress) external; + + /** + * @dev updates vault address on the factory + * @param _vaultAddress The new vault contract address + */ + function setVaultAddress(address _vaultAddress) external; + + /** + * @notice Changes initial fee configuration for new pools + * @dev changes coefficients for sigmoids: α / (1 + e^( (β-x) / γ)) + * alpha1 + alpha2 + baseFee (max possible fee) must be <= type(uint16).max + * gammas must be > 0 + * @param alpha1 max value of the first sigmoid + * @param alpha2 max value of the second sigmoid + * @param beta1 shift along the x-axis for the first sigmoid + * @param beta2 shift along the x-axis for the second sigmoid + * @param gamma1 horizontal stretch factor for the first sigmoid + * @param gamma2 horizontal stretch factor for the second sigmoid + * @param volumeBeta shift along the x-axis for the outer volume-sigmoid + * @param volumeGamma horizontal stretch factor the outer volume-sigmoid + * @param baseFee minimum possible fee + */ + function setBaseFeeConfiguration( + uint16 alpha1, + uint16 alpha2, + uint32 beta1, + uint32 beta2, + uint16 gamma1, + uint16 gamma2, + uint32 volumeBeta, + uint16 volumeGamma, + uint16 baseFee + ) external; +} diff --git a/contracts/interfaces/algebra/IAlgebraPool.sol b/contracts/interfaces/algebra/IAlgebraPool.sol new file mode 100644 index 0000000..a34e986 --- /dev/null +++ b/contracts/interfaces/algebra/IAlgebraPool.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import './pool/IAlgebraPoolImmutables.sol'; +import './pool/IAlgebraPoolState.sol'; +import './pool/IAlgebraPoolDerivedState.sol'; +import './pool/IAlgebraPoolActions.sol'; +import './pool/IAlgebraPoolPermissionedActions.sol'; +import './pool/IAlgebraPoolEvents.sol'; + +/** + * @title The interface for a Algebra Pool + * @dev The pool interface is broken up into many smaller pieces. + * Credit to Uniswap Labs under GPL-2.0-or-later license: + * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces + */ +interface IAlgebraPool is + IAlgebraPoolImmutables, + IAlgebraPoolState, + IAlgebraPoolDerivedState, + IAlgebraPoolActions, + IAlgebraPoolPermissionedActions, + IAlgebraPoolEvents +{ + // used only for combining interfaces +} diff --git a/contracts/interfaces/algebra/IAlgebraPoolDeployer.sol b/contracts/interfaces/algebra/IAlgebraPoolDeployer.sol new file mode 100644 index 0000000..5c795bf --- /dev/null +++ b/contracts/interfaces/algebra/IAlgebraPoolDeployer.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/** + * @title An interface for a contract that is capable of deploying Algebra Pools + * @notice A contract that constructs a pool must implement this to pass arguments to the pool + * @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash + * of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain. + * Credit to Uniswap Labs under GPL-2.0-or-later license: + * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces + */ +interface IAlgebraPoolDeployer { + /** + * @notice Emitted when the factory address is changed + * @param factory The factory address after the address was changed + */ + event Factory(address indexed factory); + + /** + * @notice Get the parameters to be used in constructing the pool, set transiently during pool creation. + * @dev Called by the pool constructor to fetch the parameters of the pool + * Returns dataStorage The pools associated dataStorage + * Returns factory The factory address + * Returns token0 The first token of the pool by address sort order + * Returns token1 The second token of the pool by address sort order + */ + function parameters() + external + view + returns ( + address dataStorage, + address factory, + address token0, + address token1 + ); + + /** + * @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then + * clearing it after deploying the pool. + * @param dataStorage The pools associated dataStorage + * @param factory The contract address of the Algebra factory + * @param token0 The first token of the pool by address sort order + * @param token1 The second token of the pool by address sort order + * @return pool The deployed pool's address + */ + function deploy( + address dataStorage, + address factory, + address token0, + address token1 + ) external returns (address pool); + + /** + * @dev Sets the factory address to the poolDeployer for permissioned actions + * @param factory The address of the Algebra factory + */ + function setFactory(address factory) external; +} diff --git a/contracts/interfaces/algebra/IAlgebraVirtualPool.sol b/contracts/interfaces/algebra/IAlgebraVirtualPool.sol new file mode 100644 index 0000000..dcf7a08 --- /dev/null +++ b/contracts/interfaces/algebra/IAlgebraVirtualPool.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +interface IAlgebraVirtualPool { + enum Status { + NOT_EXIST, + ACTIVE, + NOT_STARTED + } + + /** + * @dev This function is called by the main pool when an initialized tick is crossed there. + * If the tick is also initialized in a virtual pool it should be crossed too + * @param nextTick The crossed tick + * @param zeroToOne The direction + */ + function cross(int24 nextTick, bool zeroToOne) external; + + /** + * @dev This function is called from the main pool before every swap To increase seconds per liquidity + * cumulative considering previous timestamp and liquidity. The liquidity is stored in a virtual pool + * @param currentTimestamp The timestamp of the current swap + * @return Status The status of virtual pool + */ + function increaseCumulative(uint32 currentTimestamp) external returns (Status); +} diff --git a/contracts/interfaces/algebra/callback/IAlgebraFlashCallback.sol b/contracts/interfaces/algebra/callback/IAlgebraFlashCallback.sol new file mode 100644 index 0000000..f72356e --- /dev/null +++ b/contracts/interfaces/algebra/callback/IAlgebraFlashCallback.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/** + * @title Callback for IAlgebraPoolActions#flash + * @notice Any contract that calls IAlgebraPoolActions#flash must implement this interface + * @dev Credit to Uniswap Labs under GPL-2.0-or-later license: + * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces + */ +interface IAlgebraFlashCallback { + /** + * @notice Called to `msg.sender` after transferring to the recipient from IAlgebraPool#flash. + * @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. + * The caller of this method must be checked to be a AlgebraPool deployed by the canonical AlgebraFactory. + * @param fee0 The fee amount in token0 due to the pool by the end of the flash + * @param fee1 The fee amount in token1 due to the pool by the end of the flash + * @param data Any data passed through by the caller via the IAlgebraPoolActions#flash call + */ + function algebraFlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} diff --git a/contracts/interfaces/algebra/callback/IAlgebraMintCallback.sol b/contracts/interfaces/algebra/callback/IAlgebraMintCallback.sol new file mode 100644 index 0000000..319af5d --- /dev/null +++ b/contracts/interfaces/algebra/callback/IAlgebraMintCallback.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Callback for IAlgebraPoolActions#mint +/// @notice Any contract that calls IAlgebraPoolActions#mint must implement this interface +/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: +/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces +interface IAlgebraMintCallback { + /// @notice Called to `msg.sender` after minting liquidity to a position from IAlgebraPool#mint. + /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity. + /// The caller of this method must be checked to be a AlgebraPool deployed by the canonical AlgebraFactory. + /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity + /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity + /// @param data Any data passed through by the caller via the IAlgebraPoolActions#mint call + function algebraMintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external; +} diff --git a/contracts/interfaces/algebra/callback/IAlgebraSwapCallback.sol b/contracts/interfaces/algebra/callback/IAlgebraSwapCallback.sol new file mode 100644 index 0000000..4fa3b7d --- /dev/null +++ b/contracts/interfaces/algebra/callback/IAlgebraSwapCallback.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Callback for IAlgebraPoolActions#swap +/// @notice Any contract that calls IAlgebraPoolActions#swap must implement this interface +/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: +/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces +interface IAlgebraSwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IAlgebraPool#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 AlgebraPool deployed by the canonical AlgebraFactory. + /// 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 IAlgebraPoolActions#swap call + function algebraSwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} diff --git a/contracts/interfaces/algebra/pool/IAlgebraPoolActions.sol b/contracts/interfaces/algebra/pool/IAlgebraPoolActions.sol new file mode 100644 index 0000000..778bc15 --- /dev/null +++ b/contracts/interfaces/algebra/pool/IAlgebraPoolActions.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Permissionless pool actions +/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: +/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces +interface IAlgebraPoolActions { + /** + * @notice Sets the initial price for the pool + * @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + * @param price the initial sqrt price of the pool as a Q64.96 + */ + function initialize(uint160 price) external; + + /** + * @notice Adds liquidity for the given recipient/bottomTick/topTick position + * @dev The caller of this method receives a callback in the form of IAlgebraMintCallback# AlgebraMintCallback + * in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + * on bottomTick, topTick, the amount of liquidity, and the current price. + * @param sender The address which will receive potential surplus of paid tokens + * @param recipient The address for which the liquidity will be created + * @param bottomTick The lower tick of the position in which to add liquidity + * @param topTick The upper tick of the position in which to add liquidity + * @param amount The desired 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 + * @return liquidityActual The actual minted amount of liquidity + */ + function mint( + address sender, + address recipient, + int24 bottomTick, + int24 topTick, + uint128 amount, + bytes calldata data + ) + external + returns ( + uint256 amount0, + uint256 amount1, + uint128 liquidityActual + ); + + /** + * @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 bottomTick The lower tick of the position for which to collect fees + * @param topTick 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 bottomTick, + int24 topTick, + 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 bottomTick The lower tick of the position for which to burn liquidity + * @param topTick 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 bottomTick, + int24 topTick, + 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 IAlgebraSwapCallback# AlgebraSwapCallback + * @param recipient The address to receive the output of the swap + * @param zeroToOne 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 limitSqrtPrice 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. If using the Router it should contain + * SwapRouter#SwapCallbackData + * @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 zeroToOne, + int256 amountSpecified, + uint160 limitSqrtPrice, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /** + * @notice Swap token0 for token1, or token1 for token0 (tokens that have fee on transfer) + * @dev The caller of this method receives a callback in the form of I AlgebraSwapCallback# AlgebraSwapCallback + * @param sender The address called this function (Comes from the Router) + * @param recipient The address to receive the output of the swap + * @param zeroToOne 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 limitSqrtPrice 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. If using the Router it should contain + * SwapRouter#SwapCallbackData + * @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 swapSupportingFeeOnInputTokens( + address sender, + address recipient, + bool zeroToOne, + int256 amountSpecified, + uint160 limitSqrtPrice, + 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 IAlgebraFlashCallback# AlgebraFlashCallback + * @dev All excess tokens paid in the callback are distributed to liquidity providers as an additional fee. So this method can be used + * to donate underlying tokens 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; +} diff --git a/contracts/interfaces/algebra/pool/IAlgebraPoolDerivedState.sol b/contracts/interfaces/algebra/pool/IAlgebraPoolDerivedState.sol new file mode 100644 index 0000000..7be18db --- /dev/null +++ b/contracts/interfaces/algebra/pool/IAlgebraPoolDerivedState.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/** + * @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. + * @dev Credit to Uniswap Labs under GPL-2.0-or-later license: + * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces + */ +interface IAlgebraPoolDerivedState { + /** + * @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 secondsPerLiquidityCumulatives Cumulative seconds per liquidity-in-range value as of each `secondsAgos` + * from the current block timestamp + * @return volatilityCumulatives Cumulative standard deviation as of each `secondsAgos` + * @return volumePerAvgLiquiditys Cumulative swap volume per liquidity as of each `secondsAgos` + */ + function getTimepoints(uint32[] calldata secondsAgos) + external + view + returns ( + int56[] memory tickCumulatives, + uint160[] memory secondsPerLiquidityCumulatives, + uint112[] memory volatilityCumulatives, + uint256[] memory volumePerAvgLiquiditys + ); + + /** + * @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 bottomTick The lower tick of the range + * @param topTick The upper tick of the range + * @return innerTickCumulative The snapshot of the tick accumulator for the range + * @return innerSecondsSpentPerLiquidity The snapshot of seconds per liquidity for the range + * @return innerSecondsSpent The snapshot of the number of seconds during which the price was in this range + */ + function getInnerCumulatives(int24 bottomTick, int24 topTick) + external + view + returns ( + int56 innerTickCumulative, + uint160 innerSecondsSpentPerLiquidity, + uint32 innerSecondsSpent + ); +} diff --git a/contracts/interfaces/algebra/pool/IAlgebraPoolEvents.sol b/contracts/interfaces/algebra/pool/IAlgebraPoolEvents.sol new file mode 100644 index 0000000..b2a9c60 --- /dev/null +++ b/contracts/interfaces/algebra/pool/IAlgebraPoolEvents.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Events emitted by a pool +/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: +/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces +interface IAlgebraPoolEvents { + /** + * @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 price 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 price, 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 bottomTick The lower tick of the position + * @param topTick The upper tick of the position + * @param liquidityAmount 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 bottomTick, + int24 indexed topTick, + uint128 liquidityAmount, + 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 recipient The address that received fees + * @param bottomTick The lower tick of the position + * @param topTick 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 bottomTick, int24 indexed topTick, 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 bottomTick The lower tick of the position + * @param topTick The upper tick of the position + * @param liquidityAmount 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 bottomTick, int24 indexed topTick, uint128 liquidityAmount, 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 price 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 price, 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 when the community fee is changed by the pool + * @param communityFee0New The updated value of the token0 community fee percent + * @param communityFee1New The updated value of the token1 community fee percent + */ + event CommunityFee(uint8 communityFee0New, uint8 communityFee1New); + + /** + * @notice Emitted when new activeIncentive is set + * @param virtualPoolAddress The address of a virtual pool associated with the current active incentive + */ + event Incentive(address indexed virtualPoolAddress); + + /** + * @notice Emitted when the fee changes + * @param fee The value of the token fee + */ + event Fee(uint16 fee); + + /** + * @notice Emitted when the LiquidityCooldown changes + * @param liquidityCooldown The value of locktime for added liquidity + */ + event LiquidityCooldown(uint32 liquidityCooldown); +} diff --git a/contracts/interfaces/algebra/pool/IAlgebraPoolImmutables.sol b/contracts/interfaces/algebra/pool/IAlgebraPoolImmutables.sol new file mode 100644 index 0000000..d7cf69a --- /dev/null +++ b/contracts/interfaces/algebra/pool/IAlgebraPoolImmutables.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + + +/// @title Pool state that never changes +/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: +/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces +interface IAlgebraPoolImmutables { + /** + * @notice The contract that stores all the timepoints and can perform actions with them + * @return The operator address + */ + function dataStorageOperator() external view returns (address); + + /** + * @notice The contract that deployed the pool, which must adhere to the IAlgebraFactory 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 tick spacing + * @dev Ticks can only be used at multiples of this value + * e.g.: a tickSpacing of 60 means ticks can be initialized every 60th tick, i.e., ..., -120, -60, 0, 60, 120, ... + * 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/algebra/pool/IAlgebraPoolPermissionedActions.sol b/contracts/interfaces/algebra/pool/IAlgebraPoolPermissionedActions.sol new file mode 100644 index 0000000..fb4f33a --- /dev/null +++ b/contracts/interfaces/algebra/pool/IAlgebraPoolPermissionedActions.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/** + * @title Permissioned pool actions + * @notice Contains pool methods that may only be called by the factory owner or tokenomics + * @dev Credit to Uniswap Labs under GPL-2.0-or-later license: + * https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces + */ +interface IAlgebraPoolPermissionedActions { + /** + * @notice Set the community's % share of the fees. Cannot exceed 25% (250) + * @param communityFee0 new community fee percent for token0 of the pool in thousandths (1e-3) + * @param communityFee1 new community fee percent for token1 of the pool in thousandths (1e-3) + */ + function setCommunityFee(uint8 communityFee0, uint8 communityFee1) external; + + /** + * @notice Sets an active incentive + * @param virtualPoolAddress The address of a virtual pool associated with the incentive + */ + function setIncentive(address virtualPoolAddress) external; + + /** + * @notice Sets new lock time for added liquidity + * @param newLiquidityCooldown The time in seconds + */ + function setLiquidityCooldown(uint32 newLiquidityCooldown) external; +} diff --git a/contracts/interfaces/algebra/pool/IAlgebraPoolState.sol b/contracts/interfaces/algebra/pool/IAlgebraPoolState.sol new file mode 100644 index 0000000..dd13b54 --- /dev/null +++ b/contracts/interfaces/algebra/pool/IAlgebraPoolState.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Pool state that can change +/// @dev Credit to Uniswap Labs under GPL-2.0-or-later license: +/// https://github.com/Uniswap/v3-core/tree/main/contracts/interfaces +interface IAlgebraPoolState { + /** + * @notice The globalState structure in the pool stores many values but requires only one slot + * and is exposed as a single method to save gas when accessed externally. + * @return price The current price of the pool as a sqrt(token1/token0) Q64.96 value; + * Returns tick The current tick of the pool, i.e. according to the last tick transition that was run; + * Returns This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(price) if the price is on a tick + * boundary; + * Returns fee The last pool fee value in hundredths of a bip, i.e. 1e-6; + * Returns timepointIndex The index of the last written timepoint; + * Returns communityFeeToken0 The community fee percentage of the swap fee in thousandths (1e-3) for token0; + * Returns communityFeeToken1 The community fee percentage of the swap fee in thousandths (1e-3) for token1; + * Returns unlocked Whether the pool is currently locked to reentrancy; + */ + function globalState() + external + view + returns ( + uint160 price, + int24 tick, + uint16 fee, + uint16 timepointIndex, + uint8 communityFeeToken0, + uint8 communityFeeToken1, + 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 totalFeeGrowth0Token() 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 totalFeeGrowth1Token() external view returns (uint256); + + /** + * @notice The currently in range liquidity available to the pool + * @dev This value has no relationship to the total liquidity across all ticks. + * Returned value cannot exceed type(uint128).max + */ + function liquidity() external view returns (uint128); + + /** + * @notice Look up information about a specific tick in the pool + * @dev This is a public structure, so the `return` natspec tags are omitted. + * @param tick The tick to look up + * @return liquidityTotal the total amount of position liquidity that uses the pool either as tick lower or + * tick upper + * @return liquidityDelta how much liquidity changes when the pool price crosses the tick; + * Returns outerFeeGrowth0Token the fee growth on the other side of the tick from the current tick in token0; + * Returns outerFeeGrowth1Token the fee growth on the other side of the tick from the current tick in token1; + * Returns outerTickCumulative the cumulative tick value on the other side of the tick from the current tick; + * Returns outerSecondsPerLiquidity the seconds spent per liquidity on the other side of the tick from the current tick; + * Returns outerSecondsSpent the seconds spent on the other side of the tick from the current tick; + * Returns initialized Set to true if the tick is initialized, i.e. liquidityTotal is greater than 0 + * otherwise equal to false. Outside values can only be used if the tick is initialized. + * 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 liquidityTotal, + int128 liquidityDelta, + uint256 outerFeeGrowth0Token, + uint256 outerFeeGrowth1Token, + int56 outerTickCumulative, + uint160 outerSecondsPerLiquidity, + uint32 outerSecondsSpent, + bool initialized + ); + + /** @notice Returns 256 packed tick initialized boolean values. See TickTable for more information */ + function tickTable(int16 wordPosition) external view returns (uint256); + + /** + * @notice Returns the information about a position by the position's key + * @dev This is a public mapping of structures, so the `return` natspec tags are omitted. + * @param key The position's key is a hash of a preimage composed by the owner, bottomTick and topTick + * @return liquidityAmount The amount of liquidity in the position; + * Returns lastLiquidityAddTimestamp Timestamp of last adding of liquidity; + * Returns innerFeeGrowth0Token Fee growth of token0 inside the tick range as of the last mint/burn/poke; + * Returns innerFeeGrowth1Token Fee growth of token1 inside the tick range as of the last mint/burn/poke; + * Returns fees0 The computed amount of token0 owed to the position as of the last mint/burn/poke; + * Returns fees1 The computed amount of token1 owed to the position as of the last mint/burn/poke + */ + function positions(bytes32 key) + external + view + returns ( + uint128 liquidityAmount, + uint32 lastLiquidityAddTimestamp, + uint256 innerFeeGrowth0Token, + uint256 innerFeeGrowth1Token, + uint128 fees0, + uint128 fees1 + ); + + /** + * @notice Returns data about a specific timepoint index + * @param index The element of the timepoints array to fetch + * @dev You most likely want to use #getTimepoints() instead of this method to get an timepoint as of some amount of time + * ago, rather than at a specific index in the array. + * This is a public mapping of structures, so the `return` natspec tags are omitted. + * @return initialized whether the timepoint has been initialized and the values are safe to use; + * Returns blockTimestamp The timestamp of the timepoint; + * Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the timepoint timestamp; + * Returns secondsPerLiquidityCumulative the seconds per in range liquidity for the life of the pool as of the timepoint timestamp; + * Returns volatilityCumulative Cumulative standard deviation for the life of the pool as of the timepoint timestamp; + * Returns averageTick Time-weighted average tick; + * Returns volumePerLiquidityCumulative Cumulative swap volume per liquidity for the life of the pool as of the timepoint timestamp; + */ + function timepoints(uint256 index) + external + view + returns ( + bool initialized, + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulative, + uint88 volatilityCumulative, + int24 averageTick, + uint144 volumePerLiquidityCumulative + ); + + /** + * @notice Returns the information about active incentive + * @dev if there is no active incentive at the moment, virtualPool,endTimestamp,startTimestamp would be equal to 0 + * @return virtualPool The address of a virtual pool associated with the current active incentive + */ + function activeIncentive() external view returns (address virtualPool); + + /** + * @notice Returns the lock time for added liquidity + */ + function liquidityCooldown() external view returns (uint32 cooldownInSeconds); +} diff --git a/contracts/interfaces/algebra/utils/AlgQuoter.sol b/contracts/interfaces/algebra/utils/AlgQuoter.sol new file mode 100644 index 0000000..2cbaa40 --- /dev/null +++ b/contracts/interfaces/algebra/utils/AlgQuoter.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.0 <0.8.0; + +import "../IAlgebraPool.sol"; +import "../../uniswap/LiquidityMath.sol"; +import "../../uniswap/LowGasSafeMath.sol"; +import "../../uniswap/SafeCast.sol"; +import "../../uniswap/SwapMath.sol"; +import "./TickTable.sol"; +import "../../uniswap/TickMath.sol"; + +library AlgQuoter { + using LowGasSafeMath for uint256; + using LowGasSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + + // 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; + // 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 + uint16 fee; + } + + function quote( + IAlgebraPool pool, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96 + ) internal view returns (int256 amount0, int256 amount1) { + Slot0 memory slot0Start; + ( + slot0Start.sqrtPriceX96, + slot0Start.tick, + slot0Start.fee, + , // uint16 observationCardinality + , // uint16 observationCardinalityNext + , // slot0Start.feeProtocol + // bool unlocked + + ) = pool.globalState(); + + require( + zeroForOne + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && + sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && + sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + "SPL" + ); + + bool exactInput = amountSpecified > 0; + int24 poolTickSpacing = pool.tickSpacing(); + uint24 poolFee = slot0Start.fee; + + SwapState memory state = SwapState({ + amountSpecifiedRemaining: amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + liquidity: pool.liquidity() + }); + + // 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) = TickTable + .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() + ); + } + + // 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 + ); + } +} \ No newline at end of file diff --git a/contracts/interfaces/algebra/utils/AlgTickLens.sol b/contracts/interfaces/algebra/utils/AlgTickLens.sol new file mode 100644 index 0000000..342f797 --- /dev/null +++ b/contracts/interfaces/algebra/utils/AlgTickLens.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.0 <0.8.0; +pragma experimental ABIEncoderV2; + +import "../IAlgebraPool.sol"; + +library AlgTickLens { + + struct PopulatedTick { + int24 tick; + int128 liquidityNet; + uint128 liquidityGross; + } + + function getSpotTicks(IAlgebraPool pool) + internal + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + PopulatedTick[] memory populatedTicksTwiceAbove, + PopulatedTick[] memory populatedTicksAbove, + PopulatedTick[] memory populatedTicksSpot, + PopulatedTick[] memory populatedTicksBelow, + PopulatedTick[] memory populatedTicksTwiceBelow + ) + { + // get the populated ticks above and below the current spot tick + ( + sqrtPriceX96, + tick, + , // uint16 observationIndex + , // uint16 observationCardinality + , // uint16 observationCardinalityNext + , // uint8 feeProtocol + // bool unlocked + ) = pool.globalState(); + + int24 tickSpacing = pool.tickSpacing(); + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + // current word position within bitmap + int16 tickBitmapIndex = int16(compressed >> 8); + + // get the populated ticks at, above, and below the current word + populatedTicksSpot = getPopulatedTicksInWord(pool, tickBitmapIndex); + populatedTicksTwiceAbove = getPopulatedTicksInWord( + pool, + tickBitmapIndex + 2 + ); + populatedTicksTwiceBelow = getPopulatedTicksInWord( + pool, + tickBitmapIndex - 2 + ); + populatedTicksAbove = getPopulatedTicksInWord( + pool, + tickBitmapIndex + 1 + ); + populatedTicksBelow = getPopulatedTicksInWord( + pool, + tickBitmapIndex - 1 + ); + } + + function getPopulatedTicksInWord(IAlgebraPool pool, int16 tickBitmapIndex) + internal + view + returns (PopulatedTick[] memory populatedTicks) + { + // fetch bitmap + uint256 bitmap = pool.tickTable(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 + }); + } + } + } +} \ No newline at end of file diff --git a/contracts/interfaces/algebra/utils/Constants.sol b/contracts/interfaces/algebra/utils/Constants.sol new file mode 100644 index 0000000..ec29535 --- /dev/null +++ b/contracts/interfaces/algebra/utils/Constants.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.6.8; + +library Constants { + uint8 internal constant RESOLUTION = 96; + uint256 internal constant Q96 = 0x1000000000000000000000000; + uint256 internal constant Q128 = 0x100000000000000000000000000000000; + // fee value in hundredths of a bip, i.e. 1e-6 + uint16 internal constant BASE_FEE = 100; + int24 internal constant TICK_SPACING = 60; + + // max(uint128) / ( (MAX_TICK - MIN_TICK) / TICK_SPACING ) + uint128 internal constant MAX_LIQUIDITY_PER_TICK = 11505743598341114571880798222544994; + + uint32 internal constant MAX_LIQUIDITY_COOLDOWN = 1 days; + uint8 internal constant MAX_COMMUNITY_FEE = 250; + uint256 internal constant COMMUNITY_FEE_DENOMINATOR = 1000; +} \ No newline at end of file diff --git a/contracts/interfaces/algebra/utils/TickTable.sol b/contracts/interfaces/algebra/utils/TickTable.sol new file mode 100644 index 0000000..97a1581 --- /dev/null +++ b/contracts/interfaces/algebra/utils/TickTable.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.6.8; + +import './Constants.sol'; +import '../../uniswap/TickMath.sol'; +import "../IAlgebraPool.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 TickTable { + /// @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 + /// @notice get position of single 1-bit + /// @dev it is assumed that word contains exactly one 1-bit, otherwise the result will be incorrect + /// @param word The word containing only one 1-bit + function getSingleSignificantBit(uint256 word) internal pure returns (uint8 singleBitPos) { + assembly { + singleBitPos := iszero(and(word, 0x5555555555555555555555555555555555555555555555555555555555555555)) + singleBitPos := or(singleBitPos, shl(7, iszero(and(word, 0x00000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)))) + singleBitPos := or(singleBitPos, shl(6, iszero(and(word, 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF)))) + singleBitPos := or(singleBitPos, shl(5, iszero(and(word, 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF)))) + singleBitPos := or(singleBitPos, shl(4, iszero(and(word, 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF)))) + singleBitPos := or(singleBitPos, shl(3, iszero(and(word, 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF)))) + singleBitPos := or(singleBitPos, shl(2, iszero(and(word, 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F)))) + singleBitPos := or(singleBitPos, shl(1, iszero(and(word, 0x3333333333333333333333333333333333333333333333333333333333333333)))) + } + } + + /// @notice get position of most significant 1-bit (leftmost) + /// @dev it is assumed that before the call, a check will be made that the argument (word) is not equal to zero + /// @param word The word containing at least one 1-bit + function getMostSignificantBit(uint256 word) internal pure returns (uint8 mostBitPos) { + assembly { + word := or(word, shr(1, word)) + word := or(word, shr(2, word)) + word := or(word, shr(4, word)) + word := or(word, shr(8, word)) + word := or(word, shr(16, word)) + word := or(word, shr(32, word)) + word := or(word, shr(64, word)) + word := or(word, shr(128, word)) + word := sub(word, shr(1, word)) + } + return (getSingleSignificantBit(word)); + } + + function getLeastSignificantBit(uint256 word) internal pure returns (uint8 leastSignificantBitPos) { + assembly { + word := sub(word, and(word, sub(word, 1))) + } + return (getSingleSignificantBit(word)); + } + + /// @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 self The mapping in which to compute the next initialized tick + /// @param tick The starting tick + /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) + /// @return nextTick 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 nextTickInTheSameRow( + mapping(int16 => uint256) storage self, + int24 tick, + bool lte + ) internal view returns (int24 nextTick, bool initialized) { + { + int24 tickSpacing = Constants.TICK_SPACING; + // compress and round towards negative infinity if negative + assembly { + tick := sub(sdiv(tick, tickSpacing), and(slt(tick, 0), not(iszero(smod(tick, tickSpacing))))) + } + } + + if (lte) { + // unpacking not made into a separate function for gas and contract size savings + int16 rowNumber; + uint8 bitNumber; + assembly { + bitNumber := and(tick, 0xFF) + rowNumber := shr(8, tick) + } + uint256 _row = self[rowNumber] << (255 - bitNumber); // all the 1s at or to the right of the current bitNumber + + if (_row != 0) { + tick -= int24(255 - getMostSignificantBit(_row)); + return (uncompressAndBoundTick(tick), true); + } else { + tick -= int24(bitNumber); + return (uncompressAndBoundTick(tick), false); + } + } else { + // start from the word of the next tick, since the current tick state doesn't matter + tick += 1; + int16 rowNumber; + uint8 bitNumber; + assembly { + bitNumber := and(tick, 0xFF) + rowNumber := shr(8, tick) + } + + // all the 1s at or to the left of the bitNumber + uint256 _row = self[rowNumber] >> (bitNumber); + + if (_row != 0) { + tick += int24(getSingleSignificantBit(-_row & _row)); // least significant bit + return (uncompressAndBoundTick(tick), true); + } else { + tick += int24(255 - bitNumber); + return (uncompressAndBoundTick(tick), false); + } + } + } + + function uncompressAndBoundTick(int24 tick) private pure returns (int24 boundedTick) { + boundedTick = tick * Constants.TICK_SPACING; + if (boundedTick < TickMath.MIN_TICK) { + boundedTick = TickMath.MIN_TICK; + } else if (boundedTick > TickMath.MAX_TICK) { + boundedTick = TickMath.MAX_TICK; + } + } + + /// @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( + IAlgebraPool 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 = (uint256(1) << bitPos) - 1 + (uint256(1) << bitPos); + uint256 masked = pool.tickTable(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 - getMostSignificantBit(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 = ~((uint256(1) << bitPos) - 1); + uint256 masked = pool.tickTable(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(getLeastSignificantBit(masked) - bitPos)) * tickSpacing + : (compressed + 1 + int24(type(uint8).max - bitPos)) * tickSpacing; + } + } +} \ No newline at end of file diff --git a/contracts/interfaces/stCelo/IAccount.sol b/contracts/interfaces/stCelo/IAccount.sol new file mode 100644 index 0000000..e7f8ff0 --- /dev/null +++ b/contracts/interfaces/stCelo/IAccount.sol @@ -0,0 +1,5 @@ +pragma solidity 0.6.8; + +interface IAccount { + function getTotalCelo() external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/interfaces/stCelo/IManager.sol b/contracts/interfaces/stCelo/IManager.sol new file mode 100644 index 0000000..2a78408 --- /dev/null +++ b/contracts/interfaces/stCelo/IManager.sol @@ -0,0 +1,8 @@ +pragma solidity 0.6.8; + +interface IManager { + function deposit() external payable; + + function toStakedCelo(uint256 celoAmount) external view returns (uint256); + function toCelo(uint256 stCeloAmount) external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/interfaces/stCelo/IRebasedStakedCelo.sol b/contracts/interfaces/stCelo/IRebasedStakedCelo.sol new file mode 100644 index 0000000..5bd11e9 --- /dev/null +++ b/contracts/interfaces/stCelo/IRebasedStakedCelo.sol @@ -0,0 +1,9 @@ +pragma solidity 0.6.8; + +interface IRebasedStakedCelo { + function deposit(uint256 stCeloAmount) external; + function withdraw(uint256 stCeloAmount) external; + + function toStakedCelo(uint256 rstCeloAmount) external view returns (uint256); + function toRebasedStakedCelo(uint256 stCeloAmount) external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/interfaces/stCelo/IStakedCelo.sol b/contracts/interfaces/stCelo/IStakedCelo.sol new file mode 100644 index 0000000..a6137e8 --- /dev/null +++ b/contracts/interfaces/stCelo/IStakedCelo.sol @@ -0,0 +1,5 @@ +pragma solidity 0.6.8; + +interface IStakedCelo { + function totalSupply() external view returns (uint256); +} \ No newline at end of file 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/IUniswapV2Router.sol b/contracts/interfaces/uniswap/IUniswapV2Router.sol new file mode 100644 index 0000000..c4fbd7a --- /dev/null +++ b/contracts/interfaces/uniswap/IUniswapV2Router.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.8; + +interface IUniswapV2Router { +function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); +} \ No newline at end of file diff --git a/contracts/interfaces/uniswap/IUniswapV3Factory.sol b/contracts/interfaces/uniswap/IUniswapV3Factory.sol new file mode 100644 index 0000000..de9e244 --- /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; +} \ No newline at end of file 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/IUniswapV3SwapCallback.sol b/contracts/interfaces/uniswap/IUniswapV3SwapCallback.sol new file mode 100644 index 0000000..f72ec96 --- /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; +} \ No newline at end of file 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..3dc85dc --- /dev/null +++ b/contracts/interfaces/uniswap/Quoter.sol @@ -0,0 +1,199 @@ +// 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; + + // 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; + // 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; + } + + 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" + ); + + 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, + liquidity: pool.liquidity() + }); + + // 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() + ); + } + + // 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 + ); + } +} \ No newline at end of file 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/TickLens.sol b/contracts/interfaces/uniswap/TickLens.sol new file mode 100644 index 0000000..a96304c --- /dev/null +++ b/contracts/interfaces/uniswap/TickLens.sol @@ -0,0 +1,92 @@ +// 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, + PopulatedTick[] memory populatedTicksTwiceAbove, + PopulatedTick[] memory populatedTicksAbove, + PopulatedTick[] memory populatedTicksSpot, + PopulatedTick[] memory populatedTicksBelow, + PopulatedTick[] memory populatedTicksTwiceBelow + ) + { + // 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 + int16 tickBitmapIndex = int16(compressed >> 8); + + // get the populated ticks at, above, and below the current word + populatedTicksTwiceAbove = new PopulatedTick[](0); + populatedTicksSpot = getPopulatedTicksInWord(pool, tickBitmapIndex); + populatedTicksTwiceBelow = getPopulatedTicksInWord( + pool, + tickBitmapIndex - 2 + ); + populatedTicksAbove = getPopulatedTicksInWord( + pool, + tickBitmapIndex + 1 + ); + populatedTicksTwiceBelow = new PopulatedTick[](0); + + } + + 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 + }); + } + } + } +} \ No newline at end of file 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/PairAlgebra.sol b/contracts/swappa/PairAlgebra.sol new file mode 100644 index 0000000..0bc5f35 --- /dev/null +++ b/contracts/swappa/PairAlgebra.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.8; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../interfaces/algebra/IAlgebraPool.sol"; +import "../interfaces/algebra/callback/IAlgebraSwapCallback.sol"; +import "../interfaces/algebra/utils/AlgQuoter.sol"; +import "../interfaces/uniswap/SafeCast.sol"; +import "../interfaces/algebra/utils/AlgTickLens.sol"; +import "../interfaces/uniswap/TickMath.sol"; +import "./ISwappaPairV1.sol"; + +// Algebra is the outsourced Ubeswap V3 + +contract PairAlgebra is ISwappaPairV1, IAlgebraSwapCallback { + using SafeMath for uint256; + using SafeCast for uint256; + + function swap( + address input, + address, + address to, + bytes calldata data + ) external override { + address pairAddr = parseData(data); + uint256 inputAmount = ERC20(input).balanceOf(address(this)); + IAlgebraPool pair = IAlgebraPool(pairAddr); + bool zeroForOne = pair.token0() == input; + // calling swap will trigger the uniswapV3SwapCallback + pair.swap( + to, + zeroForOne, + inputAmount.toInt256(), + zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1, + new bytes(0) + ); + } + + function algebraSwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata + ) external override { + ERC20 token; + uint256 amount; + if (amount0Delta > 0) { + amount = uint256(amount0Delta); + token = ERC20(IAlgebraPool(msg.sender).token0()); + } else if (amount1Delta > 0) { + amount = uint256(amount1Delta); + token = ERC20(IAlgebraPool(msg.sender).token1()); + } + require( + token.transfer(msg.sender, amount), + "PairAlgebra: transfer failed!" + ); + } + + function parseData(bytes memory data) + private + pure + returns (address pairAddr) + { + require(data.length == 20, "PairAlgebra: invalid data!"); + assembly { + pairAddr := mload(add(data, 20)) + } + } + + function getOutputAmount( + address input, + address, + uint256 amountIn, + bytes calldata data + ) external view override returns (uint256 amountOut) { + address pairAddr = parseData(data); + IAlgebraPool pair = IAlgebraPool(pairAddr); + bool zeroForOne = pair.token0() == input; + // amount0, amount1 are delta of the pair reserves + (int256 amount0, int256 amount1) = AlgQuoter.quote( + pair, + zeroForOne, + amountIn.toInt256(), + zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1 + ); + return uint256(-(zeroForOne ? amount1 : amount0)); + } + + function getInputAmount( + address input, + address, + uint256 amountOut, + bytes calldata data + ) external view returns (uint256 amountIn) { + address pairAddr = parseData(data); + IAlgebraPool pair = IAlgebraPool(pairAddr); + bool zeroForOne = pair.token0() == input; + // amount0, amount1 are delta of the pair reserves + (int256 amount0, int256 amount1) = AlgQuoter.quote( + pair, + zeroForOne, + -amountOut.toInt256(), + zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1 + ); + return uint256(zeroForOne ? amount0 : amount1); + } + + function getSpotTicks(IAlgebraPool pool) + public + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + AlgTickLens.PopulatedTick[] memory populatedTicksTwiceAbove, + AlgTickLens.PopulatedTick[] memory populatedTicksAbove, + AlgTickLens.PopulatedTick[] memory populatedTicksSpot, + AlgTickLens.PopulatedTick[] memory populatedTicksBelow, + AlgTickLens.PopulatedTick[] memory populatedTicksTwiceBelow + ) + { + return AlgTickLens.getSpotTicks(pool); + } + + function getPopulatedTicksInWord(IAlgebraPool pool, int16 tickBitmapIndex) + public + view + returns (AlgTickLens.PopulatedTick[] memory populatedTicks) + { + return AlgTickLens.getPopulatedTicksInWord(pool, tickBitmapIndex); + } + + function recoverERC20(ERC20 token) public { + token.transfer(msg.sender, token.balanceOf(address(this))); + } +} \ No newline at end of file diff --git a/contracts/swappa/PairRStCelo.sol b/contracts/swappa/PairRStCelo.sol new file mode 100644 index 0000000..4c70d67 --- /dev/null +++ b/contracts/swappa/PairRStCelo.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../interfaces/stCelo/IRebasedStakedCelo.sol"; +import "./ISwappaPairV1.sol"; + +contract PairRStCelo is ISwappaPairV1 { + using SafeMath for uint; + + function swap( + address input, + address output, + address to, + bytes calldata data + ) external override { + (address rebaseAddr, uint8 inputType) = parseData(data); + uint inputAmount = ERC20(input).balanceOf(address(this)); + + if (inputType == 1) { + // rstCelo -> stCelo + uint stCeloAmount = IRebasedStakedCelo(rebaseAddr).toStakedCelo(inputAmount); + IRebasedStakedCelo(rebaseAddr).withdraw(stCeloAmount); + } else if (inputType == 2) { + // stCelo -> rstCelo + require(ERC20(input).approve(rebaseAddr, inputAmount)); + IRebasedStakedCelo(rebaseAddr).deposit(inputAmount); + } + uint outputAmount = ERC20(output).balanceOf(address(this)); + require(ERC20(output).transfer(to, outputAmount), "PairRStCelo: Transfer Failed"); + } + + function parseData(bytes memory data) private pure returns (address rebaseAddr, uint8 inputType) { + require(data.length == 21, "PairRStCelo: invalid data!"); + inputType = uint8(data[20]); + assembly { + rebaseAddr := mload(add(data, 20)) + } + } + + function getOutputAmount( + address, + address, + uint amountIn, + bytes calldata + ) external view override returns (uint amountOut) { + return amountIn; + } + + receive() external payable {} +} \ No newline at end of file diff --git a/contracts/swappa/PairStCelo.sol b/contracts/swappa/PairStCelo.sol new file mode 100644 index 0000000..d3c9ae1 --- /dev/null +++ b/contracts/swappa/PairStCelo.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "../interfaces/stCelo/IManager.sol"; +import "./ISwappaPairV1.sol"; + +contract PairStCelo is ISwappaPairV1 { + using SafeMath for uint; + + address constant CELO_ADDRESS = 0x471EcE3750Da237f93B8E339c536989b8978a438; + + function swap( + address input, + address output, + address to, + bytes calldata data + ) external override { + require(input == CELO_ADDRESS, "PairStCelo: Incorrect Input"); + address payable managerAddr = parseData(data); + uint inputAmount = ERC20(input).balanceOf(address(this)); + IManager(managerAddr).deposit{value: inputAmount}(); + uint outputAmount = ERC20(output).balanceOf(address(this)); + require(ERC20(output).transfer(to, outputAmount), "PairStCelo: Transfer Failed"); + } + + function parseData(bytes memory data) private pure returns (address payable managerAddr) { + require(data.length == 20, "PairStCelo: invalid data!"); + assembly { + managerAddr := mload(add(data, 20)) + } + } + + function getOutputAmount( + address, + address, + uint amountIn, + bytes calldata + ) external view override returns (uint amountOut) { + return amountIn; + } + + receive() external payable {} +} \ No newline at end of file diff --git a/contracts/swappa/PairUniswapV3.sol b/contracts/swappa/PairUniswapV3.sol new file mode 100644 index 0000000..56e2232 --- /dev/null +++ b/contracts/swappa/PairUniswapV3.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.8; +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, IUniswapV3SwapCallback { + using SafeMath for uint256; + using SafeCast for uint256; + + function swap( + address input, + address, + address to, + bytes calldata data + ) external override { + address pairAddr = parseData(data); + uint256 inputAmount = ERC20(input).balanceOf(address(this)); + IUniswapV3Pool pair = IUniswapV3Pool(pairAddr); + bool zeroForOne = pair.token0() == input; + // calling swap will trigger the uniswapV3SwapCallback + pair.swap( + to, + zeroForOne, + inputAmount.toInt256(), + zeroForOne + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1, + new bytes(0) + ); + } + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata + ) 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 + returns (address pairAddr) + { + require(data.length == 20, "PairUniswapV3: invalid data!"); + assembly { + pairAddr := mload(add(data, 20)) + } + } + + function getOutputAmount( + address input, + address, + uint256 amountIn, + bytes calldata data + ) external view override returns (uint256 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 uint256(-(zeroForOne ? amount1 : amount0)); + } + + function getInputAmount( + address input, + address, + 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); + } + + function getSpotTicks(IUniswapV3Pool pool) + public + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + TickLens.PopulatedTick[] memory populatedTicksTwiceAbove, + TickLens.PopulatedTick[] memory populatedTicksAbove, + TickLens.PopulatedTick[] memory populatedTicksSpot, + TickLens.PopulatedTick[] memory populatedTicksBelow, + TickLens.PopulatedTick[] memory populatedTicksTwiceBelow + ) + { + return TickLens.getSpotTicks(pool); + } + + function getPopulatedTicksInWord(IUniswapV3Pool pool, int16 tickBitmapIndex) + public + view + returns (TickLens.PopulatedTick[] memory populatedTicks) + { + return TickLens.getPopulatedTicksInWord(pool, tickBitmapIndex); + } + + function recoverERC20(ERC20 token) public { + token.transfer(msg.sender, token.balanceOf(address(this))); + } +} \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..b0e51c9 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,45 @@ +// constants used internally but not expected to be used externally +export const NEGATIVE_ONE = BigInt(-1); +export const ZERO = BigInt(0); +export const ONE = BigInt(1); + +// used in liquidity amount math +export const Q96 = BigInt(2) ** BigInt(96); +export const Q192 = Q96 ** BigInt(2); + +/** + * The default factory enabled fee amounts, denominated in hundredths of bips. + */ +export enum UniV3FeeAmount { + LOWEST = 100, + LOW = 500, + MEDIUM = 3000, + HIGH = 10000, +} + +/** + * The default factory tick spacings by fee amount. + */ +export const TICK_SPACINGS: { [amount in UniV3FeeAmount]: number } = { + [UniV3FeeAmount.LOWEST]: 1, + [UniV3FeeAmount.LOW]: 10, + [UniV3FeeAmount.MEDIUM]: 60, + [UniV3FeeAmount.HIGH]: 200, +}; + +export const MaxUint256 = BigInt( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +); + +export const KNOWN_FEE_ON_TRANSFER_TOKENS: Partial<{ + [chain: number]: string[]; +}> = { + [42220]: [ + "0x22401536505dd5d85F7d57f8B37172feDa8f499d", + "0xD90BBdf5904cb7d275c41f897495109B9A5adA58", + "0x918146359264C492BD6934071c6Bd31C854EDBc3", + "0xE273Ad7ee11dCfAA87383aD5977EE1504aC07568", + "0x7D00cd74FF385c955EA3d79e47BF06bD7386387D", + "0x9802d866fdE4563d088a6619F7CeF82C0B991A55", + ].map((s) => s.toLowerCase()), +}; diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..c2acbd4 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,42 @@ +export const SwappaErrorType = { + MathError: "MathError", + HydrationError: "HydrationError", +} as const + +export type SwappaErrorTypeValue = typeof SwappaErrorType[keyof typeof SwappaErrorType]; + +export class SwappaError extends Error { + + constructor(private readonly _type: EType, message: string) { + super(`Swappa${_type}: ${message}`); + } + + public get type(): EType { + return this._type; + } + + public static is(e: unknown): e is SwappaError { + if (!e || !(typeof e === 'object') || Array.isArray(e)) return false; + return 'type' in e && typeof e.type === 'string' && e.type in SwappaErrorType; + } +} + +export class SwappaMathError extends SwappaError { + constructor(message: string) { + super(SwappaErrorType.MathError, message); + } + + public static is(e: unknown): e is SwappaMathError { + return SwappaError.is(e) && e.type === SwappaErrorType.MathError; + } +} + +export class SwappaHydrationError extends SwappaError { + constructor(message: string) { + super(SwappaErrorType.HydrationError, message); + } + + public static is(e: unknown): e is SwappaHydrationError { + return SwappaError.is(e) && e.type === SwappaErrorType.HydrationError; + } +} \ No newline at end of file diff --git a/src/pairs/concentrated-liquidity/algebra.ts b/src/pairs/concentrated-liquidity/algebra.ts new file mode 100644 index 0000000..2cd52e3 --- /dev/null +++ b/src/pairs/concentrated-liquidity/algebra.ts @@ -0,0 +1,90 @@ +import Web3 from "web3"; +import { + PairAlgebra as PairAlgebraContract, + ABI as PairAlgebraABI, +} from "../../../types/web3-v1-contracts/PairAlgebra"; + +import { Address } from "../../pair"; + +import { UniV3FeeAmount } from "../../constants"; +import { ABI as V3PoolABI } from "../../../types/web3-v1-contracts/IUniswapV3Pool"; +import { PairContentratedLiquidity } from "./pair-concentrated-liquidity"; +import { IAlgebraPool } from "../../../types/web3-v1-contracts/IAlgebraPool"; +import { address as pairAlgebraAddress } from "../../../tools/deployed/mainnet.PairAlgebra.addr.json"; +import { selectAddress } from "../../utils"; + +export class PairAlgebra extends PairContentratedLiquidity { + allowRepeats = false; + private swappaPool: PairAlgebraContract; + private swapPool: IAlgebraPool; + + constructor(chainId: number, private web3: Web3, private pairAddr: Address) { + const pairAddress = selectAddress(chainId, { + mainnet: pairAlgebraAddress, + }); + super(pairAddress); + this.pairKey = pairAddr; + this.swapPool = new this.web3.eth.Contract( + V3PoolABI, + pairAddr + ) as unknown as IAlgebraPool; + this.swappaPool = new this.web3.eth.Contract( + PairAlgebraABI, + pairAddress + ) as unknown as PairAlgebraContract; + } + + protected async _init() { + const [tokenA, tokenB, { fee }] = await Promise.all([ + this.swapPool.methods.token0().call(), + this.swapPool.methods.token1().call(), + this.swapPool.methods.globalState().call(), + ]); + + this.swapFee = parseInt(fee.toString()) as UniV3FeeAmount; + return { + pairKey: this.pairAddr, + tokenA, + tokenB, + }; + } + + public async outputAmountAsync( + inputToken: Address, + inputAmount: bigint, + outputToken: string + ): Promise { + const res = await this.swappaPool.methods + .getOutputAmount( + inputToken, + outputToken, + inputAmount.toString(), + this.swapData(inputToken).extra + ) + .call(); + return BigInt(res); + } + + public async refresh() { + // Algebra pool fees are dynamic - so we do have to feth them on each refresh + const [info, liquidity, { fee }] = await Promise.all([ + this.swappaPool.methods.getSpotTicks(this.pairAddr).call(), + this.swapPool.methods.liquidity().call(), + this.swapPool.methods.globalState().call(), + ]); + const { ticks, tickToIndex, sqrtPriceX96, tick } = + PairAlgebra.transformGetSpotTicksPayload(info); + + this.tick = tick; + this.tickToIndex = tickToIndex; + this.tickIndex = this.tickToIndex[tick]; + this.ticks = ticks; + this.sqrtRatioX96 = sqrtPriceX96; + this.liquidity = BigInt(liquidity.toString()); + this.swapFee = parseInt(fee.toString()) as UniV3FeeAmount; + } + + protected swapExtraData() { + return this.pairAddr; + } +} diff --git a/src/pairs/concentrated-liquidity/pair-concentrated-liquidity.ts b/src/pairs/concentrated-liquidity/pair-concentrated-liquidity.ts new file mode 100644 index 0000000..a6cc30c --- /dev/null +++ b/src/pairs/concentrated-liquidity/pair-concentrated-liquidity.ts @@ -0,0 +1,311 @@ +import { Address, BigNumberString, Pair, Snapshot } from "../../pair"; + +import { TickMath } from "../../utils/concentrated-liquidity/tickMath"; +import { SwapMath } from "../../utils/concentrated-liquidity/swapMath"; +import { LiquidityMath } from "../../utils/concentrated-liquidity/liquidityMath"; + +import { NEGATIVE_ONE, ONE, UniV3FeeAmount, ZERO } from "../../constants"; +import { JSBI_BN } from "../../utils/JSBI_BN"; +import BigNumber from "bignumber.js"; +import { SwappaMathError } from "../../errors"; + +export interface SpotTicksPayload { + sqrtPriceX96: string; + tick: string; + populatedTicksTwiceAbove: { + tick: string; + liquidityNet: string; + liquidityGross: string; + }[]; + populatedTicksAbove: { + tick: string; + liquidityNet: string; + liquidityGross: string; + }[]; + populatedTicksSpot: { + tick: string; + liquidityNet: string; + liquidityGross: string; + }[]; + populatedTicksBelow: { + tick: string; + liquidityNet: string; + liquidityGross: string; + }[]; + populatedTicksTwiceBelow: { + tick: string; + liquidityNet: string; + liquidityGross: string; + }[]; +} + +interface PairConcentratedLiquiditySnapshot extends Snapshot { + tick: number; + liquidity: BigNumberString; + ticks: (Omit & { + liquidityNet: BigNumberString; + liquidityGross: BigNumberString; + })[]; + sqrtRatioX96: BigNumberString; + tickIndex: number; + swapFee: UniV3FeeAmount; +} + +interface PopulatedTick { + bitPos: number; + wordPos: number; + tick: number; + liquidityNet: bigint; + liquidityGross: bigint; +} + +interface StepComputations { + sqrtPriceStartX96: bigint; + tickIndexNext: number; + tickNext: number; + initialized: boolean; + sqrtPriceNextX96: bigint; + amountIn: bigint; + amountOut: bigint; + feeAmount: bigint; +} + +export abstract class PairContentratedLiquidity extends Pair { + protected swapFee: UniV3FeeAmount = UniV3FeeAmount.LOW; // in 100 bps + protected tick?: number; + protected liquidity?: bigint; + + // Ticks is sorted primarily by wordPosition, and then by bitPosition. + // In this way, decreasing the index by 1 shifts to the left tick and increasing shifts to the right + // Without having to worry about stepping up between words + protected ticks: PopulatedTick[] = []; + protected tickToIndex: { [tick: number]: number } = {}; + protected sqrtRatioX96?: bigint; + + // Tick index is calculated based on the positon of the current tick within the tick's wordPosition + protected tickIndex?: number; + + public abstract outputAmountAsync( + inputToken: Address, + inputAmount: bigint, + outputToken: string + ): Promise; + + public static transformGetSpotTicksPayload({ + sqrtPriceX96, + tick, + populatedTicksTwiceAbove, + populatedTicksAbove, + populatedTicksBelow, + populatedTicksSpot, + populatedTicksTwiceBelow, + }: SpotTicksPayload) { + const tickToIndex: { [tick: number]: number } = {}; + const foundTick: Set = new Set(); + const ticks: PopulatedTick[] = [ + ...populatedTicksTwiceBelow, + ...populatedTicksBelow, + ...populatedTicksSpot, + ...populatedTicksAbove, + ...populatedTicksTwiceAbove, + { + tick, + liquidityNet: 0, + liquidityGross: 0, + }, + ] + .map(({ tick: tick_bn, liquidityNet, liquidityGross }) => { + const tick = parseInt(tick_bn.toString()); + const { wordPos, bitPos } = TickMath.position(tick); + return { + wordPos, + bitPos, + tick, + liquidityNet: BigInt(liquidityNet.toString()), + liquidityGross: BigInt(liquidityGross.toString()), + }; + }) + .filter(({ tick }) => { + if (foundTick.has(tick)) return false; + foundTick.add(tick); + return true; + }) + .sort((t1, t2) => { + if (t1.wordPos < t2.wordPos) return -1; + else if (t1.wordPos > t2.wordPos) return 1; + if (t1.bitPos < t2.bitPos) return -1; + return 1; + }); + for (let i = 0; i < ticks.length; i++) { + tickToIndex[ticks[i].tick] = i; + } + + return { + ticks, + tickToIndex, + tick: parseInt(tick.toString()), + sqrtPriceX96: BigInt(sqrtPriceX96.toString()), + }; + } + + // TODO: change from native BigInt to BigNumber, so that this wrapper is not needed + public outputAmount(inputToken: string, inputAmount: BigNumber): BigNumber { + const bnOutput = this._outputAmount( + inputToken, + BigInt(inputAmount.toFixed(0)) + ); + + return new BigNumber(bnOutput.toString()); + } + + // ref: https://github.com/Uniswap/v3-sdk/blob/main/src/entities/pool.ts + private _outputAmount(inputToken: Address, inputAmount: bigint): bigint { + const zeroForOne = inputToken === this.tokenA; + const sqrtPriceLimitX96 = zeroForOne + ? TickMath.MIN_SQRT_RATIO + ONE + : TickMath.MAX_SQRT_RATIO - ONE; + + if (zeroForOne && sqrtPriceLimitX96 >= (this.sqrtRatioX96 ?? -1)) + throw new SwappaMathError("RATIO_CURRENT"); + else if (sqrtPriceLimitX96 <= (this.sqrtRatioX96 ?? -1)) + throw new SwappaMathError("RATIO_CURRENT"); + + const state = { + tickIndex: this.tickIndex ?? 1, + amountSpecifiedRemaining: inputAmount, + amountCalculated: ZERO, + sqrtPriceX96: this.sqrtRatioX96, + tick: this.tick, + liquidity: this.liquidity, + }; + + while ( + state.amountSpecifiedRemaining != ZERO && + state.sqrtPriceX96 != sqrtPriceLimitX96 + ) { + let step: Partial = {}; + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + step.tickIndexNext = zeroForOne + ? state.tickIndex - 1 + : state.tickIndex + 1; + + // If we have exceeded the ticks then we cannot reasonably calculate any more. Output will be atleast what we have calculated so far + // This indicates that the pool is not deep enough to handle the swap within the tick space we retrieved + // In most cases, this will not happen, but it is possible if the pool is very shallow + if (step.tickIndexNext < 0 || step.tickIndexNext >= this.ticks.length) + return state.amountCalculated; + + step.tickNext = this.ticks[step.tickIndexNext].tick; + + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + [state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount] = + SwapMath.computeSwapStep( + state.sqrtPriceX96 as bigint, + ( + zeroForOne + ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 + : step.sqrtPriceNextX96 > sqrtPriceLimitX96 + ) + ? sqrtPriceLimitX96 + : (step.sqrtPriceNextX96 as bigint), + state.liquidity as bigint, + state.amountSpecifiedRemaining, + this.swapFee as UniV3FeeAmount + ); + state.amountSpecifiedRemaining = + state.amountSpecifiedRemaining - (step.amountIn + step.feeAmount); + state.amountCalculated = state.amountCalculated + step.amountOut; + + if (JSBI_BN.equal(state.sqrtPriceX96, step.sqrtPriceNextX96 ?? 0)) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + let liquidityNet = BigInt( + this.ticks[step.tickIndexNext].liquidityNet + ); + + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (zeroForOne) + liquidityNet = JSBI_BN.multiply(liquidityNet, NEGATIVE_ONE); + + state.liquidity = LiquidityMath.addDelta( + state.liquidity ?? BigInt(0), + liquidityNet + ); + } + + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + state.tickIndex = this.tickToIndex[step.tickNext]; + } else if ( + JSBI_BN.notEqual( + state.sqrtPriceX96, + step.sqrtPriceStartX96 ?? BigInt(0) + ) + ) { + // updated comparison function + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + state.tickIndex = this.tickToIndex[state.tick]; + } + } + + return state.amountCalculated; + } + + public getSwapFee() { + return this.swapFee; + } + + public restore(snapshot: PairConcentratedLiquiditySnapshot): void { + this.swapFee = snapshot.swapFee; + this.tick = snapshot.tick; + this.liquidity = BigInt(snapshot.liquidity); + this.sqrtRatioX96 = BigInt(snapshot.sqrtRatioX96); + this.tickIndex = snapshot.tickIndex; + + const ticks: PopulatedTick[] = new Array(snapshot.ticks.length); + const tickToIndex: Record = {}; + + for (let i = 0; i < snapshot.ticks.length; i++) { + const { liquidityGross, liquidityNet, tick, ...rest } = snapshot.ticks[i]; + const populatedTick: PopulatedTick = { + ...rest, + liquidityGross: BigInt(liquidityGross), + liquidityNet: BigInt(liquidityNet), + tick, + }; + + ticks[i] = populatedTick; + tickToIndex[populatedTick.tick] = i; + } + } + + public snapshot(): PairConcentratedLiquiditySnapshot { + if ( + !this.tick || + !this.sqrtRatioX96 || + !this.liquidity || + this.tickIndex === undefined + ) + throw new Error("Pair not initialized"); + return { + swapFee: this.swapFee, + tick: this.tick, + liquidity: this.liquidity.toString(), + sqrtRatioX96: this.sqrtRatioX96.toString(), + ticks: this.ticks.map(({ liquidityGross, liquidityNet, ...rest }) => ({ + ...rest, + liquidityGross: liquidityGross.toString(), + liquidityNet: liquidityNet.toString(), + })), + tickIndex: this.tickIndex, + }; + } +} diff --git a/src/pairs/concentrated-liquidity/uniswapv3.ts b/src/pairs/concentrated-liquidity/uniswapv3.ts new file mode 100644 index 0000000..8f7a9a6 --- /dev/null +++ b/src/pairs/concentrated-liquidity/uniswapv3.ts @@ -0,0 +1,92 @@ +import Web3 from "web3"; +import { + PairUniswapV3 as PairUniswapV3Contract, + ABI as PairUniswapV3ABI, +} from "../../../types/web3-v1-contracts/PairUniswapV3"; + +import { Address, Snapshot } from "../../pair"; +import { UniV3FeeAmount } from "../../constants"; +import { + IUniswapV3Pool, + ABI as V3PoolABI, +} from "../../../types/web3-v1-contracts/IUniswapV3Pool"; +import { PairContentratedLiquidity } from "./pair-concentrated-liquidity"; +import { address as pairUniV3Address } from "../../../tools/deployed/mainnet.PairUniswapV3.addr.json"; +import { selectAddress } from "../../utils"; + +export class PairUniswapV3 extends PairContentratedLiquidity { + allowRepeats = false; + private swappaPool: PairUniswapV3Contract; + private swapPool: IUniswapV3Pool; + + constructor(chainId: number, private web3: Web3, private pairAddr: Address) { + const pairAddress = selectAddress(chainId, { + mainnet: pairUniV3Address, + }); + super(pairAddress); + + this.pairKey = pairAddr; + + this.swapPool = new this.web3.eth.Contract( + V3PoolABI, + pairAddr + ) as unknown as IUniswapV3Pool; + this.swappaPool = new this.web3.eth.Contract( + PairUniswapV3ABI, + pairAddress + ) as unknown as PairUniswapV3Contract; + } + + protected async _init() { + const [tokenA, tokenB, fee] = await Promise.all([ + this.swapPool.methods.token0().call(), + this.swapPool.methods.token1().call(), + this.swapPool.methods.fee().call(), + ]); + + // this.swapPool.methods. + this.swapFee = parseInt(fee.toString()) as UniV3FeeAmount; + return { + pairKey: this.pairAddr, + tokenA, + tokenB, + }; + } + + public async outputAmountAsync( + inputToken: Address, + inputAmount: bigint, + outputToken: string + ): Promise { + const res = await this.swappaPool.methods + .getOutputAmount( + inputToken, + outputToken, + inputAmount.toString(), + this.swapData(inputToken).extra + ) + .call(); + return BigInt(res); + } + + public async refresh() { + const [info, liquidity] = await Promise.all([ + this.swappaPool.methods.getSpotTicks(this.pairAddr).call(), + this.swapPool.methods.liquidity().call(), + ]); + + const { ticks, tickToIndex, sqrtPriceX96, tick } = + PairUniswapV3.transformGetSpotTicksPayload(info); + + this.tick = tick; + this.tickToIndex = tickToIndex; + this.tickIndex = this.tickToIndex[tick]; + this.ticks = ticks; + this.sqrtRatioX96 = sqrtPriceX96; + this.liquidity = BigInt(liquidity.toString()); + } + + protected swapExtraData() { + return this.pairAddr; + } +} diff --git a/src/pairs/stCelo.ts b/src/pairs/stCelo.ts new file mode 100644 index 0000000..036fbff --- /dev/null +++ b/src/pairs/stCelo.ts @@ -0,0 +1,168 @@ +import Web3 from "web3"; +import { Address, BigNumberString, Pair, Snapshot } from "../pair"; +import { + IStakedCelo, + ABI as StakedCeloABI, +} from "../../types/web3-v1-contracts/IStakedCelo"; +import { + IAccount, + ABI as AccountABI, +} from "../../types/web3-v1-contracts/IAccount"; +import { address as pairStCeloAddress } from "../../tools/deployed/mainnet.PairStCelo.addr.json"; +import { address as pairRstCeloAddress } from "../../tools/deployed/mainnet.PairRstCelo.addr.json"; + +import { selectAddress } from "../utils"; +import BigNumber from "bignumber.js"; + +interface PairStakedCeloSnapshot extends Snapshot { + stCeloSupply: BigNumberString; + celoBalance: BigNumberString; +} + +abstract class PairStakedCelo extends Pair { + allowRepeats = true; + + private stCeloContract: IStakedCelo; + private accountContract: IAccount; + private stCeloSupply: bigint = BigInt(0); + private celoBalance: bigint = BigInt(0); + + constructor( + private web3: Web3, + swappaPairAddress: Address, + private stakedCeloAddress: Address, + private accountAddress: Address + ) { + super(swappaPairAddress); + this.stCeloContract = new this.web3.eth.Contract( + StakedCeloABI, + this.stakedCeloAddress + ) as unknown as IStakedCelo; + this.accountContract = new this.web3.eth.Contract( + AccountABI, + this.accountAddress + ) as unknown as IAccount; + } + + protected async _fetchSupplies() { + const [stCeloSupply, celoBalace] = await Promise.all([ + this.stCeloContract.methods.totalSupply().call(), + this.accountContract.methods.getTotalCelo().call(), + ]); + this.stCeloSupply = BigInt(stCeloSupply.toString()); + this.celoBalance = BigInt(celoBalace.toString()); + } + public async refresh(): Promise { + await this._fetchSupplies(); + } + + protected toStakedCelo(celoAmount: bigint): bigint { + return (celoAmount * this.stCeloSupply) / this.celoBalance; + } + + protected toCelo(stCeloAmount: bigint): bigint { + return (stCeloAmount * this.celoBalance) / this.celoBalance; + } + + public snapshot(): PairStakedCeloSnapshot { + return { + stCeloSupply: this.stCeloSupply.toString(), + celoBalance: this.celoBalance.toString(), + }; + } + + public restore({ stCeloSupply, celoBalance }: PairStakedCeloSnapshot): void { + this.stCeloSupply = BigInt(stCeloSupply); + this.celoBalance = BigInt(celoBalance); + } +} + +export class PairStCelo extends PairStakedCelo { + constructor( + chainId: number, + web3: Web3, + accountAddress: Address, + private managerAddress: Address, + private celoAddr: Address, + private stCeloAddr: Address + ) { + super( + web3, + selectAddress(chainId, { mainnet: pairStCeloAddress }), + stCeloAddr, + accountAddress + ); + } + + protected async _init() { + return { + pairKey: this.managerAddress, + tokenA: this.celoAddr, + tokenB: this.stCeloAddr, + }; + } + + public outputAmount(inputToken: string, inputAmount: BigNumber): BigNumber { + const bnOutput = this._outputAmount( + inputToken, + BigInt(inputAmount.toFixed(0)) + ); + + return new BigNumber(bnOutput.toString()); + } + + private _outputAmount(inputToken: string, inputAmount: bigint): bigint { + if (inputToken === this.tokenB) return BigInt(0); + return this.toStakedCelo(inputAmount); + } + + protected swapExtraData(): string { + return this.managerAddress; + } +} + +export class PairRebasedStCelo extends PairStakedCelo { + constructor( + chainId: number, + web3: Web3, + accountAddress: Address, + private rstCeloAddr: Address, + private stCeloAddr: Address + ) { + super( + web3, + selectAddress(chainId, { mainnet: pairRstCeloAddress }), + stCeloAddr, + accountAddress + ); + } + + protected async _init() { + await this._fetchSupplies(); + return { + pairKey: this.rstCeloAddr, + tokenA: this.rstCeloAddr, + tokenB: this.stCeloAddr, + }; + } + + public outputAmount(inputToken: string, inputAmount: BigNumber): BigNumber { + const bnOutput = this._outputAmount( + inputToken, + BigInt(inputAmount.toFixed(0)) + ); + + return new BigNumber(bnOutput.toString()); + } + + private _outputAmount(inputToken: string, inputAmount: bigint): bigint { + if (inputToken === this.tokenB) return this.toCelo(inputAmount); + return this.toStakedCelo(inputAmount); + } + + protected swapExtraData(inputToken: string): string { + const swapType = inputToken === this.tokenA ? "01" : "02"; + + return `${this.rstCeloAddr}${swapType}`; + } +} diff --git a/src/registries/algebra.ts b/src/registries/algebra.ts new file mode 100644 index 0000000..4913d46 --- /dev/null +++ b/src/registries/algebra.ts @@ -0,0 +1,85 @@ +import Web3 from "web3"; +import { concurrentMap } from "@celo/utils/lib/async"; + +import { Address, Pair } from "../pair"; +import { Registry } from "../registry"; +import { + IAlgebraFactory, + ABI as FactoryABI, +} from "../../types/web3-v1-contracts/IAlgebraFactory"; +import { PairAlgebra } from "../pairs/concentrated-liquidity/algebra"; + +export class RegistryAlgebra extends Registry { + private factory: IAlgebraFactory; + + constructor( + name: string, + private web3: Web3, + factoryAddr: Address, + private opts?: { + fixedFee?: number; + fetchUsingAllPairs?: boolean; + } + ) { + super(name); + this.factory = new web3.eth.Contract( + FactoryABI, + factoryAddr + ) as unknown as IAlgebraFactory; + } + + findNumberOfPools = async (): Promise => { + const poolEvents = await this.factory.getPastEvents("Pool"); + return poolEvents.length; + }; + + findPairs = async (tokenWhitelist: Address[] = []): Promise => { + const chainId = await this.web3.eth.getChainId(); + + if (!this.opts?.fetchUsingAllPairs) { + const pairsToFetch: { tokenA: Address; tokenB: Address }[] = []; + const nPairs = tokenWhitelist.length; + for (let i = 0; i < nPairs - 1; i += 1) { + for (let j = i + 1; j < nPairs; j += 1) { + pairsToFetch.push({ + tokenA: tokenWhitelist[i], + tokenB: tokenWhitelist[j], + }); + } + } + + const fetched = await concurrentMap( + 10, + pairsToFetch, + async (pairInfo) => { + const pairAddr = await this.factory.methods + .poolByPair(pairInfo.tokenA, pairInfo.tokenB) + .call(); + + if (pairAddr === "0x0000000000000000000000000000000000000000") + return null; + const pair = new PairAlgebra(chainId, this.web3, pairAddr); + pair.pairKey = pairAddr; + + return pair; + } + ); + const pairs = fetched.filter((p) => p !== null) as Pair[]; + + return pairs; + } else { + const pools = (await this.factory.getPastEvents("Pool")).map( + (v) => v.returnValues.pool + ); + const pairMap = new Map(); + await concurrentMap(5, pools, async (pairAddr) => { + const pair = new PairAlgebra(chainId, this.web3, pairAddr); + if (pair !== null) { + pairMap.set(pairAddr, pair); + } + return pair; + }); + return Array.from(pairMap.values()); + } + }; +} diff --git a/src/registry-cfg.ts b/src/registry-cfg.ts index 9cd8775..db92eb9 100644 --- a/src/registry-cfg.ts +++ b/src/registry-cfg.ts @@ -1,116 +1,304 @@ -import BigNumber from "bignumber.js" -import Web3 from "web3" -import { ContractKit } from "@celo/contractkit" -import { SavingsCELOAddressMainnet } from "@terminal-fi/savingscelo" -import { PairSavingsCELO } from "./pairs/savingscelo" -import { PairStableSwap } from "./pairs/stableswap" -import { PairOpenSumSwap } from "./pairs/opensumswap" -import { PairSymmetricSwap } from "./pairs/symmetricswap" -import { RegistryAave } from "./registries/aave" -import { RegistryAaveV2 } from "./registries/aave-v2" -import { RegistryMento } from "./registries/mento" -import { RegistryStatic } from "./registries/static" -import { RegistryUniswapV2 } from "./registries/uniswapv2" -import { RegistryBalancer } from "./registries/balancer" -import { address as registryHelperUniswapV2 } from "../tools/deployed/mainnet.RegistryHelperUniswapV2.addr.json" -import { createCurvePairs } from "./pairs/curve" +import BigNumber from "bignumber.js"; +import Web3 from "web3"; +import { ContractKit } from "@celo/contractkit"; +import { SavingsCELOAddressMainnet } from "@terminal-fi/savingscelo"; +import { PairSavingsCELO } from "./pairs/savingscelo"; +import { PairStableSwap } from "./pairs/stableswap"; +import { PairOpenSumSwap } from "./pairs/opensumswap"; +import { PairSymmetricSwap } from "./pairs/symmetricswap"; +import { RegistryAave } from "./registries/aave"; +import { RegistryAaveV2 } from "./registries/aave-v2"; +import { RegistryMento } from "./registries/mento"; +import { RegistryStatic } from "./registries/static"; +import { RegistryUniswapV2 } from "./registries/uniswapv2"; +import { RegistryBalancer } from "./registries/balancer"; +import { address as registryHelperUniswapV2 } from "../tools/deployed/mainnet.RegistryHelperUniswapV2.addr.json"; +import { createCurvePairs } from "./pairs/curve"; +import { RegistryAlgebra } from "./registries/algebra"; +import { PairRebasedStCelo, PairStCelo } from "./pairs/stCelo"; -export const mainnetRegistryMoola = - (kit: ContractKit) => new RegistryAave("moola", kit, "0x7AAaD5a5fa74Aec83b74C2a098FBC86E17Ce4aEA") -export const mainnetRegistryUbeswap = - (kit: ContractKit) => new RegistryUniswapV2( - "ubeswap", kit.web3 as unknown as Web3, - "0x62d5b84bE28a183aBB507E125B384122D2C25fAE", - { registryHelperAddr: registryHelperUniswapV2 }) -export const mainnetRegistrySushiswap = - (kit: ContractKit) => new RegistryUniswapV2( - "sushiswap", - kit.web3 as unknown as Web3, - "0xc35DADB65012eC5796536bD9864eD8773aBc74C4", - { registryHelperAddr: registryHelperUniswapV2 }) -export const mainnetRegistryMobius = - (kit: ContractKit) => { - const web3 = kit.web3 as unknown as Web3 - return new RegistryStatic("mobius", web3.eth.getChainId().then(chainId => [ - // Source: https://github.com/mobiusAMM/mobius-interface/blob/main/src/constants/StablePools.ts - new PairStableSwap(chainId, web3, "0xC0BA93D4aaf90d39924402162EE4a213300d1d60"), // cUSD <-> wUSDC - new PairStableSwap(chainId, web3, "0x9F4AdBD0af281C69a582eB2E6fa2A594D4204CAe"), // cUSD <-> atUST - new PairStableSwap(chainId, web3, "0x9906589Ea8fd27504974b7e8201DF5bBdE986b03"), // cUSD <-> USDCv2 - new PairStableSwap(chainId, web3, "0xF3f65dFe0c8c8f2986da0FEc159ABE6fd4E700B4"), // cUSD <-> DAIv2 - new PairStableSwap(chainId, web3, "0x74ef28D635c6C5800DD3Cd62d4c4f8752DaACB09"), // cETH <-> WETHv2 - new PairStableSwap(chainId, web3, "0xaEFc4e8cF655a182E8346B24c8AbcE45616eE0d2"), // cBTC <-> WBTCv2 - new PairStableSwap(chainId, web3, "0xcCe0d62Ce14FB3e4363Eb92Db37Ff3630836c252"), // cUSD <-> pUSDCv2 - new PairStableSwap(chainId, web3, "0xA5037661989789d0310aC2B796fa78F1B01F195D"), // cUSD <-> USDCv1 - new PairStableSwap(chainId, web3, "0x0986B42F5f9C42FeEef66fC23eba9ea1164C916D"), // cUSD <-> aaUSDC - new PairStableSwap(chainId, web3, "0xa2F0E57d4cEAcF025E81C76f28b9Ad6E9Fbe8735"), // cUSD <-> pUSDv2 - new PairStableSwap(chainId, web3, "0xFc9e2C63370D8deb3521922a7B2b60f4Cff7e75a"), // CELO <-> pCELOv2 - new PairStableSwap(chainId, web3, "0x23C95678862a229fAC088bd9705622d78130bC3e"), // cEUR <-> pEURv2 - new PairStableSwap(chainId, web3, "0x02Db089fb09Fda92e05e92aFcd41D9AAfE9C7C7C"), // cUSD <-> pUSD - new PairStableSwap(chainId, web3, "0x63C1914bf00A9b395A2bF89aaDa55A5615A3656e"), // cUSD <-> asUSDC - new PairStableSwap(chainId, web3, "0x2080AAa167e2225e1FC9923250bA60E19a180Fb2"), // cUSD <-> pUSDC - new PairStableSwap(chainId, web3, "0x19260b9b573569dDB105780176547875fE9fedA3"), // BTC <-> WBTC - new PairStableSwap(chainId, web3, "0xE0F2cc70E52f05eDb383313393d88Df2937DA55a"), // cETH <-> WETH - new PairStableSwap(chainId, web3, "0xdBF27fD2a702Cc02ac7aCF0aea376db780D53247"), // cUSD <-> cUSDT - new PairStableSwap(chainId, web3, "0x0ff04189Ef135b6541E56f7C638489De92E9c778"), // cUSD <-> bUSDC - new PairStableSwap(chainId, web3, "0x413FfCc28e6cDDE7e93625Ef4742810fE9738578"), // CELO <-> pCELO - new PairStableSwap(chainId, web3, "0x382Ed834c6b7dBD10E4798B08889eaEd1455E820"), // cEUR <-> pEUR - ])) - } -export const mainnetRegistryMisc = - (kit: ContractKit) => { - const web3 = kit.web3 as unknown as Web3 - return new RegistryStatic("misc", web3.eth.getChainId().then(chainId => [ - // Optics V1 <-> V2 migration - new PairOpenSumSwap(chainId, web3, "0xb1a0BDe36341065cA916c9f5619aCA82A43659A3"), // wETH <-> wETHv2 - new PairOpenSumSwap(chainId, web3, "0xd5ab1BA8b2Ec70752068d1d728e728eAd0E19CBA"), // wBTC <-> wBTCv2 - new PairOpenSumSwap(chainId, web3, "0x70bfA1C8Ab4e42B9BE74f65941EFb6e5308148c7"), // USDC <-> USDCv2 - // Symmetric V1 <-> V2 migration - new PairSymmetricSwap(chainId, web3, - "0xF21150EC57c360dA61cE7900dbaFdE9884198026", "0x7c64aD5F9804458B8c9F93f7300c15D55956Ac2a", "0x8427bD503dd3169cCC9aFF7326c15258Bc305478") - ])) - } -export const mainnetRegistrySavingsCELO = - (kit: ContractKit) => new RegistryStatic("savingscelo", kit.web3.eth.getChainId().then(chainId => [ - new PairSavingsCELO(chainId, kit, SavingsCELOAddressMainnet), - ])) -export const mainnetRegistryMoolaV2 = - (kit: ContractKit) => new RegistryAaveV2("moola-v2", kit.web3 as unknown as Web3, "0xD1088091A174d33412a968Fa34Cb67131188B332") -export const mainnetRegistryCeloDex = - (kit: ContractKit) => new RegistryUniswapV2( - "celodex", - kit.web3 as unknown as Web3, - "0x31bD38d982ccDf3C2D95aF45a3456d319f0Ee1b6", - { registryHelperAddr: registryHelperUniswapV2 }) -export const mainnetRegistrySymmetric = - (kit: ContractKit) => new RegistryBalancer("symmetric", kit.web3 as unknown as Web3, "0x3E30b138ecc85cD89210e1A19a8603544A917372") -export const mainnetRegistryCurve = - (kit: ContractKit) => { - const web3 = kit.web3 as unknown as Web3 - const pairs = web3.eth.getChainId().then(chainId => - Promise.all([ - createCurvePairs(chainId, web3, "0xf4cab10dC19695AaCe14b7A16d7705b600ad5F73", 2), // cUSD <-> USDC - createCurvePairs(chainId, web3, "0x32fD7e563c6521Ab4D59CE3277bcfBe3317CFd63", 3), // tripool: cUSD, USDT, USDC - createCurvePairs(chainId, web3, "0xAF7Ee5Ba02dC9879D24cb16597cd854e13f3aDa8", 2), // agEUR <-> cEUR - createCurvePairs(chainId, web3, "0x9Be5da31c7A42d7e045189ac1822D1fA5838e635", 2), // axlUSDC <-> cUSD - ]).then(r => r.flat())) - return new RegistryStatic("curve", pairs) - } +export const mainnetRegistryMoola = (kit: ContractKit) => + new RegistryAave("moola", kit, "0x7AAaD5a5fa74Aec83b74C2a098FBC86E17Ce4aEA"); +export const mainnetRegistryUbeswap = (kit: ContractKit) => + new RegistryUniswapV2( + "ubeswap", + kit.web3 as unknown as Web3, + "0x62d5b84bE28a183aBB507E125B384122D2C25fAE", + { registryHelperAddr: registryHelperUniswapV2 } + ); +export const mainnetRegistrySushiswap = (kit: ContractKit) => + new RegistryUniswapV2( + "sushiswap", + kit.web3 as unknown as Web3, + "0xc35DADB65012eC5796536bD9864eD8773aBc74C4", + { registryHelperAddr: registryHelperUniswapV2 } + ); +export const mainnetRegistryMobius = (kit: ContractKit) => { + const web3 = kit.web3 as unknown as Web3; + return new RegistryStatic( + "mobius", + web3.eth.getChainId().then((chainId) => [ + // Source: https://github.com/mobiusAMM/mobius-interface/blob/main/src/constants/StablePools.ts + new PairStableSwap( + chainId, + web3, + "0xC0BA93D4aaf90d39924402162EE4a213300d1d60" + ), // cUSD <-> wUSDC + new PairStableSwap( + chainId, + web3, + "0x9F4AdBD0af281C69a582eB2E6fa2A594D4204CAe" + ), // cUSD <-> atUST + new PairStableSwap( + chainId, + web3, + "0x9906589Ea8fd27504974b7e8201DF5bBdE986b03" + ), // cUSD <-> USDCv2 + new PairStableSwap( + chainId, + web3, + "0xF3f65dFe0c8c8f2986da0FEc159ABE6fd4E700B4" + ), // cUSD <-> DAIv2 + new PairStableSwap( + chainId, + web3, + "0x74ef28D635c6C5800DD3Cd62d4c4f8752DaACB09" + ), // cETH <-> WETHv2 + new PairStableSwap( + chainId, + web3, + "0xaEFc4e8cF655a182E8346B24c8AbcE45616eE0d2" + ), // cBTC <-> WBTCv2 + new PairStableSwap( + chainId, + web3, + "0xcCe0d62Ce14FB3e4363Eb92Db37Ff3630836c252" + ), // cUSD <-> pUSDCv2 + new PairStableSwap( + chainId, + web3, + "0xA5037661989789d0310aC2B796fa78F1B01F195D" + ), // cUSD <-> USDCv1 + new PairStableSwap( + chainId, + web3, + "0x0986B42F5f9C42FeEef66fC23eba9ea1164C916D" + ), // cUSD <-> aaUSDC + new PairStableSwap( + chainId, + web3, + "0xa2F0E57d4cEAcF025E81C76f28b9Ad6E9Fbe8735" + ), // cUSD <-> pUSDv2 + new PairStableSwap( + chainId, + web3, + "0xFc9e2C63370D8deb3521922a7B2b60f4Cff7e75a" + ), // CELO <-> pCELOv2 + new PairStableSwap( + chainId, + web3, + "0x23C95678862a229fAC088bd9705622d78130bC3e" + ), // cEUR <-> pEURv2 + new PairStableSwap( + chainId, + web3, + "0x02Db089fb09Fda92e05e92aFcd41D9AAfE9C7C7C" + ), // cUSD <-> pUSD + new PairStableSwap( + chainId, + web3, + "0x63C1914bf00A9b395A2bF89aaDa55A5615A3656e" + ), // cUSD <-> asUSDC + new PairStableSwap( + chainId, + web3, + "0x2080AAa167e2225e1FC9923250bA60E19a180Fb2" + ), // cUSD <-> pUSDC + new PairStableSwap( + chainId, + web3, + "0x19260b9b573569dDB105780176547875fE9fedA3" + ), // BTC <-> WBTC + new PairStableSwap( + chainId, + web3, + "0xE0F2cc70E52f05eDb383313393d88Df2937DA55a" + ), // cETH <-> WETH + new PairStableSwap( + chainId, + web3, + "0xdBF27fD2a702Cc02ac7aCF0aea376db780D53247" + ), // cUSD <-> cUSDT + new PairStableSwap( + chainId, + web3, + "0x0ff04189Ef135b6541E56f7C638489De92E9c778" + ), // cUSD <-> bUSDC + new PairStableSwap( + chainId, + web3, + "0x413FfCc28e6cDDE7e93625Ef4742810fE9738578" + ), // CELO <-> pCELO + new PairStableSwap( + chainId, + web3, + "0x382Ed834c6b7dBD10E4798B08889eaEd1455E820" + ), // cEUR <-> pEUR + ]) + ); +}; +export const mainnetRegistryMisc = (kit: ContractKit) => { + const web3 = kit.web3 as unknown as Web3; + return new RegistryStatic( + "misc", + web3.eth.getChainId().then((chainId) => [ + // Optics V1 <-> V2 migration + new PairOpenSumSwap( + chainId, + web3, + "0xb1a0BDe36341065cA916c9f5619aCA82A43659A3" + ), // wETH <-> wETHv2 + new PairOpenSumSwap( + chainId, + web3, + "0xd5ab1BA8b2Ec70752068d1d728e728eAd0E19CBA" + ), // wBTC <-> wBTCv2 + new PairOpenSumSwap( + chainId, + web3, + "0x70bfA1C8Ab4e42B9BE74f65941EFb6e5308148c7" + ), // USDC <-> USDCv2 + // Symmetric V1 <-> V2 migration + new PairSymmetricSwap( + chainId, + web3, + "0xF21150EC57c360dA61cE7900dbaFdE9884198026", + "0x7c64aD5F9804458B8c9F93f7300c15D55956Ac2a", + "0x8427bD503dd3169cCC9aFF7326c15258Bc305478" + ), + ]) + ); +}; +export const mainnetRegistrySavingsCELO = (kit: ContractKit) => + new RegistryStatic( + "savingscelo", + kit.web3.eth + .getChainId() + .then((chainId) => [ + new PairSavingsCELO(chainId, kit, SavingsCELOAddressMainnet), + ]) + ); +export const mainnetRegistryMoolaV2 = (kit: ContractKit) => + new RegistryAaveV2( + "moola-v2", + kit.web3 as unknown as Web3, + "0xD1088091A174d33412a968Fa34Cb67131188B332" + ); +export const mainnetRegistryCeloDex = (kit: ContractKit) => + new RegistryUniswapV2( + "celodex", + kit.web3 as unknown as Web3, + "0x31bD38d982ccDf3C2D95aF45a3456d319f0Ee1b6", + { registryHelperAddr: registryHelperUniswapV2 } + ); +export const mainnetRegistrySymmetric = (kit: ContractKit) => + new RegistryBalancer( + "symmetric", + kit.web3 as unknown as Web3, + "0x3E30b138ecc85cD89210e1A19a8603544A917372" + ); +export const mainnetRegistryCurve = (kit: ContractKit) => { + const web3 = kit.web3 as unknown as Web3; + const pairs = web3.eth.getChainId().then((chainId) => + Promise.all([ + createCurvePairs( + chainId, + web3, + "0xf4cab10dC19695AaCe14b7A16d7705b600ad5F73", + 2 + ), // cUSD <-> USDC + createCurvePairs( + chainId, + web3, + "0x32fD7e563c6521Ab4D59CE3277bcfBe3317CFd63", + 3 + ), // tripool: cUSD, USDT, USDC + createCurvePairs( + chainId, + web3, + "0xAF7Ee5Ba02dC9879D24cb16597cd854e13f3aDa8", + 2 + ), // agEUR <-> cEUR + createCurvePairs( + chainId, + web3, + "0x9Be5da31c7A42d7e045189ac1822D1fA5838e635", + 2 + ), // axlUSDC <-> cUSD + ]).then((r) => r.flat()) + ); + return new RegistryStatic("curve", pairs); +}; + +export const mainnetRegistryUbeswapV3 = (kit: ContractKit) => + new RegistryAlgebra( + "ubeswap-v3", + kit.web3 as unknown as Web3, + "0x9e7C3C558173B08B3050863dde8104657e80a1c6" + ); + +export const mainnetStCeloRegistry = (kit: ContractKit) => { + const CELO = "0x471EcE3750Da237f93B8E339c536989b8978a438"; + const ACCOUNT_PROXY = "0x4aAD04D41FD7fd495503731C5a2579e19054C432"; + const MANAGER_PROXY = "0x0239b96D10a434a56CC9E09383077A0490cF9398"; + const REBASED_PROXY = "0xDc5762753043327d74e0a538199c1488FC1F44cf"; + const STAKED_PROXY = "0xC668583dcbDc9ae6FA3CE46462758188adfdfC24"; + + const web3 = kit.web3 as unknown as Web3; + const pairs = web3.eth + .getChainId() + .then((chainId) => [ + new PairStCelo( + chainId, + web3, + ACCOUNT_PROXY, + MANAGER_PROXY, + CELO, + STAKED_PROXY + ), + new PairRebasedStCelo( + chainId, + web3, + ACCOUNT_PROXY, + REBASED_PROXY, + STAKED_PROXY + ), + ]); + + return new RegistryStatic("stcelo", pairs); +}; // mainnetRegistriesWhitelist contains list of more established protocols with // overall higher TVL. -export const mainnetRegistriesWhitelist = (kit: ContractKit) => ([ - new RegistryMento(kit), - // Uniswap forks: - mainnetRegistryUbeswap(kit), - mainnetRegistrySushiswap(kit), - // Curve forks: - mainnetRegistryCurve(kit), - // Stableswap forks: - mainnetRegistryMobius(kit), - // Balancer forks: - mainnetRegistrySymmetric(kit), - // Direct conversion protocols: - mainnetRegistryMoola(kit), - mainnetRegistryMoolaV2(kit), - mainnetRegistryMisc(kit), -]) +export const mainnetRegistriesWhitelist = (kit: ContractKit) => [ + new RegistryMento(kit), + // Uniswap forks: + mainnetRegistryUbeswap(kit), + mainnetRegistrySushiswap(kit), + // Curve forks: + mainnetRegistryCurve(kit), + // Stableswap forks: + mainnetRegistryMobius(kit), + // Balancer forks: + mainnetRegistrySymmetric(kit), + // Direct conversion protocols: + mainnetRegistryMoola(kit), + mainnetRegistryMoolaV2(kit), + mainnetRegistryMisc(kit), + + // Liquid staking + mainnetStCeloRegistry(kit), + + // Unswap V3 Forks + mainnetRegistryUbeswapV3(kit), +]; diff --git a/src/utils/JSBI_BN.ts b/src/utils/JSBI_BN.ts new file mode 100644 index 0000000..1320c34 --- /dev/null +++ b/src/utils/JSBI_BN.ts @@ -0,0 +1,57 @@ + +// Helper class makes it easier to switch underlying bigint lib for univ3 math +export class JSBI_BN { + private constructor() {} + + public static exponentiate = (n1: bigint, n2: bigint) => + n1 ** n2; + + public static toNumber = (num: bigint) => Math.floor(Number(num)); + + public static greaterThanOrEqual = (n1: bigint, n2: bigint) => + n1 >= n2; + + public static divide = (numerator: bigint, denominator: bigint) => + numerator / denominator; + + public static remainder = (numerator: bigint, denominator: bigint) => + numerator % denominator; + + public static multiply = (left: bigint, right: bigint) => + left * right; + + public static subtract = (left: bigint, right: bigint) => + left - right; + + public static add = (left: bigint, right: bigint) => + left + right; + + public static greaterThan = (left: bigint, right: bigint) => + left > right; + + public static notEqual = (n1: bigint, n2: bigint) => n1 != n2; + + public static lessThanOrEqual = (left: bigint, right: bigint) => + left <= right; + + public static lessThan = (left: bigint, right: bigint) => + left < right; + + public static equal = (n1: bigint, n2: bigint) => n1 == n2; + + public static leftShift = (n: bigint, shiftBy: bigint) => { + return n << shiftBy; + }; + + public static signedRightShift = (n: bigint, shiftBy: bigint) => { + return n >> shiftBy; + }; + + public static bitwiseAnd = (n1: bigint, n2: bigint) => { + return n1 & n2; + }; + + public static bitwiseOr = (n1: bigint, n2: bigint) => { + return n1 | n2; + }; +} diff --git a/src/utils/concentrated-liquidity/fullMath.ts b/src/utils/concentrated-liquidity/fullMath.ts new file mode 100644 index 0000000..c911caf --- /dev/null +++ b/src/utils/concentrated-liquidity/fullMath.ts @@ -0,0 +1,19 @@ +import { ONE, ZERO } from "../../constants"; + +export abstract class FullMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static mulDivRoundingUp( + a: bigint, + b: bigint, + denominator: bigint + ): bigint { + const product = a * b; + let result = product / denominator; + if ( product % denominator != ZERO) result = result + ONE; + return result; + } +} diff --git a/src/utils/concentrated-liquidity/liquidityMath.ts b/src/utils/concentrated-liquidity/liquidityMath.ts new file mode 100644 index 0000000..1facd7d --- /dev/null +++ b/src/utils/concentrated-liquidity/liquidityMath.ts @@ -0,0 +1,16 @@ +import { NEGATIVE_ONE, ZERO } from "../../constants"; + +export abstract class LiquidityMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static addDelta(x: bigint, y: bigint): bigint { + if (y < ZERO) { + return x - (y * NEGATIVE_ONE); + } else { + return x + y; + } + } +} diff --git a/src/utils/concentrated-liquidity/mostSignificantBit.ts b/src/utils/concentrated-liquidity/mostSignificantBit.ts new file mode 100644 index 0000000..06094c1 --- /dev/null +++ b/src/utils/concentrated-liquidity/mostSignificantBit.ts @@ -0,0 +1,25 @@ +import { ZERO, MaxUint256 } from "../../constants"; +import { JSBI_BN } from "../JSBI_BN"; +import { SwappaMathError } from "../../errors"; + +const TWO = BigInt(2); +const POWERS_OF_2 = [128, 64, 32, 16, 8, 4, 2, 1].map( + (pow: number): [number, bigint] => [ + pow, + JSBI_BN.exponentiate(TWO, BigInt(pow)), + ] +); + +export function mostSignificantBit(x: bigint): number { + if (JSBI_BN.lessThanOrEqual(x, ZERO)) throw new SwappaMathError("mostSignificantBit must be gt 0") + if (JSBI_BN.greaterThan(x, MaxUint256)) throw new SwappaMathError("mostSignificantBit must be lt MaxUint256") + + let msb: number = 0; + for (const [power, min] of POWERS_OF_2) { + if (JSBI_BN.greaterThanOrEqual(x, min)) { + x = JSBI_BN.signedRightShift(x, BigInt(power)); + msb += power; + } + } + return msb; +} diff --git a/src/utils/concentrated-liquidity/sqrtPriceMath.ts b/src/utils/concentrated-liquidity/sqrtPriceMath.ts new file mode 100644 index 0000000..8a0bafb --- /dev/null +++ b/src/utils/concentrated-liquidity/sqrtPriceMath.ts @@ -0,0 +1,184 @@ +import { ONE, ZERO, Q96, MaxUint256 } from "../../constants"; +import { FullMath } from "./fullMath"; +import { JSBI_BN } from "../JSBI_BN"; +import { SwappaMathError } from "../../errors"; + +const MaxUint160 = JSBI_BN.subtract( + JSBI_BN.exponentiate(BigInt(2), BigInt(160)), + ONE +); + +function multiplyIn256(x: bigint, y: bigint): bigint { + const product = JSBI_BN.multiply(x, y); + return JSBI_BN.bitwiseAnd(product, MaxUint256); +} + +function addIn256(x: bigint, y: bigint): bigint { + const sum = JSBI_BN.add(x, y); + return JSBI_BN.bitwiseAnd(sum, MaxUint256); +} + +export abstract class SqrtPriceMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static getAmount0Delta( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + roundUp: boolean + ): bigint { + if (JSBI_BN.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; + } + + const numerator1 = JSBI_BN.leftShift(liquidity, BigInt(96)); + const numerator2 = JSBI_BN.subtract(sqrtRatioBX96, sqrtRatioAX96); + + return roundUp + ? FullMath.mulDivRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + ONE, + sqrtRatioAX96 + ) + : JSBI_BN.divide( + JSBI_BN.divide( + JSBI_BN.multiply(numerator1, numerator2), + sqrtRatioBX96 + ), + sqrtRatioAX96 + ); + } + + public static getAmount1Delta( + sqrtRatioAX96: bigint, + sqrtRatioBX96: bigint, + liquidity: bigint, + roundUp: boolean + ): bigint { + if (JSBI_BN.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { + [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96]; + } + + return roundUp + ? FullMath.mulDivRoundingUp( + liquidity, + JSBI_BN.subtract(sqrtRatioBX96, sqrtRatioAX96), + Q96 + ) + : JSBI_BN.divide( + JSBI_BN.multiply( + liquidity, + JSBI_BN.subtract(sqrtRatioBX96, sqrtRatioAX96) + ), + Q96 + ); + } + + public static getNextSqrtPriceFromInput( + sqrtPX96: bigint, + liquidity: bigint, + amountIn: bigint, + zeroForOne: boolean + ): bigint { + if (JSBI_BN.lessThanOrEqual(sqrtPX96, ZERO)) throw new SwappaMathError('SQRT_PRICE must be greater than 0'); + if (JSBI_BN.lessThanOrEqual(liquidity, ZERO)) throw new SwappaMathError('LIQUIDITY must be greater than 0'); + + return zeroForOne + ? this.getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amountIn, + true + ) + : this.getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96, + liquidity, + amountIn, + true + ); + } + + public static getNextSqrtPriceFromOutput( + sqrtPX96: bigint, + liquidity: bigint, + amountOut: bigint, + zeroForOne: boolean + ): bigint { + if (JSBI_BN.lessThanOrEqual(sqrtPX96, ZERO)) throw new SwappaMathError('SQRT_PRICE must be greater than 0'); + if (JSBI_BN.lessThanOrEqual(liquidity, ZERO)) throw new SwappaMathError('LIQUIDITY must be greater than 0'); + + return zeroForOne + ? this.getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96, + liquidity, + amountOut, + false + ) + : this.getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amountOut, + false + ); + } + + private static getNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96: bigint, + liquidity: bigint, + amount: bigint, + add: boolean + ): bigint { + if (JSBI_BN.equal(amount, ZERO)) return sqrtPX96; + const numerator1 = JSBI_BN.leftShift(liquidity, BigInt(96)); + + if (add) { + let product = multiplyIn256(amount, sqrtPX96); + if (JSBI_BN.equal(JSBI_BN.divide(product, amount), sqrtPX96)) { + const denominator = addIn256(numerator1, product); + if (JSBI_BN.greaterThanOrEqual(denominator, numerator1)) { + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator); + } + } + + return FullMath.mulDivRoundingUp( + numerator1, + ONE, + JSBI_BN.add(JSBI_BN.divide(numerator1, sqrtPX96), amount) + ); + } else { + let product = multiplyIn256(amount, sqrtPX96); + + if (!JSBI_BN.equal(JSBI_BN.divide(product, amount), sqrtPX96)) throw new SwappaMathError('INVALID_PRODUCT'); + if (JSBI_BN.lessThanOrEqual(product, numerator1)) throw new SwappaMathError('INSUFFICIENT_LIQUIDITY'); + + const denominator = JSBI_BN.subtract(numerator1, product); + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator); + } + } + + private static getNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96: bigint, + liquidity: bigint, + amount: bigint, + add: boolean + ): bigint { + if (add) { + const quotient = JSBI_BN.lessThanOrEqual(amount, MaxUint160) + ? JSBI_BN.divide( + JSBI_BN.leftShift(amount, BigInt(96)), + liquidity + ) + : JSBI_BN.divide(JSBI_BN.multiply(amount, Q96), liquidity); + + return JSBI_BN.add(sqrtPX96, quotient); + } else { + const quotient = FullMath.mulDivRoundingUp(amount, Q96, liquidity); + + if (JSBI_BN.lessThanOrEqual(sqrtPX96, quotient)) throw new SwappaMathError('INSUFFICIENT_LIQUIDITY'); + return JSBI_BN.subtract(sqrtPX96, quotient); + } + } +} diff --git a/src/utils/concentrated-liquidity/swapMath.ts b/src/utils/concentrated-liquidity/swapMath.ts new file mode 100644 index 0000000..f6603a3 --- /dev/null +++ b/src/utils/concentrated-liquidity/swapMath.ts @@ -0,0 +1,187 @@ +import { UniV3FeeAmount, NEGATIVE_ONE, ZERO } from "../../constants"; +import { FullMath } from "./fullMath"; +import { SqrtPriceMath } from "./sqrtPriceMath"; +import { JSBI_BN } from "../JSBI_BN"; + +const MAX_FEE = JSBI_BN.exponentiate(BigInt(10), BigInt(6)); + +export abstract class SwapMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + public static computeSwapStep( + sqrtRatioCurrentX96: bigint, + sqrtRatioTargetX96: bigint, + liquidity: bigint, + amountRemaining: bigint, + feePips: UniV3FeeAmount + ): [bigint, bigint, bigint, bigint] { + // sqrtRatioCurrentX96, + // sqrtRatioTargetX96, + // liquidity, + // amountRemaining, + // feePips, + // }); + const returnValues: Partial<{ + sqrtRatioNextX96: bigint; + amountIn: bigint; + amountOut: bigint; + UniV3FeeAmount: bigint; + }> = {}; + + const zeroForOne = JSBI_BN.greaterThanOrEqual( + sqrtRatioCurrentX96, + sqrtRatioTargetX96 + ); + const exactIn = JSBI_BN.greaterThanOrEqual(amountRemaining, ZERO); + + if (exactIn) { + const amountRemainingLessFee = JSBI_BN.divide( + JSBI_BN.multiply( + amountRemaining, + JSBI_BN.subtract(MAX_FEE, BigInt(feePips)) + ), + MAX_FEE + ); + returnValues.amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta( + sqrtRatioTargetX96, + sqrtRatioCurrentX96, + liquidity, + true + ) + : SqrtPriceMath.getAmount1Delta( + sqrtRatioCurrentX96, + sqrtRatioTargetX96, + liquidity, + true + ); + if ( + JSBI_BN.greaterThanOrEqual( + amountRemainingLessFee, + returnValues.amountIn! + ) + ) { + returnValues.sqrtRatioNextX96 = sqrtRatioTargetX96; + } else { + returnValues.sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtRatioCurrentX96, + liquidity, + amountRemainingLessFee, + zeroForOne + ); + } + } else { + returnValues.amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta( + sqrtRatioTargetX96, + sqrtRatioCurrentX96, + liquidity, + false + ) + : SqrtPriceMath.getAmount0Delta( + sqrtRatioCurrentX96, + sqrtRatioTargetX96, + liquidity, + false + ); + if ( + JSBI_BN.greaterThanOrEqual( + JSBI_BN.multiply(amountRemaining, NEGATIVE_ONE), + returnValues.amountOut ?? 0 + ) + ) { + returnValues.sqrtRatioNextX96 = sqrtRatioTargetX96; + } else { + returnValues.sqrtRatioNextX96 = + SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtRatioCurrentX96, + liquidity, + JSBI_BN.multiply(amountRemaining, NEGATIVE_ONE), + zeroForOne + ); + } + } + + const max = JSBI_BN.equal( + sqrtRatioTargetX96, + returnValues.sqrtRatioNextX96 ?? 0 + ); + + if (zeroForOne) { + returnValues.amountIn = + max && exactIn + ? returnValues.amountIn + : SqrtPriceMath.getAmount0Delta( + returnValues.sqrtRatioNextX96, + sqrtRatioCurrentX96, + liquidity, + true + ); + returnValues.amountOut = + max && !exactIn + ? returnValues.amountOut + : SqrtPriceMath.getAmount1Delta( + returnValues.sqrtRatioNextX96, + sqrtRatioCurrentX96, + liquidity, + false + ); + } else { + returnValues.amountIn = + max && exactIn + ? returnValues.amountIn + : SqrtPriceMath.getAmount1Delta( + sqrtRatioCurrentX96, + returnValues.sqrtRatioNextX96, + liquidity, + true + ); + returnValues.amountOut = + max && !exactIn + ? returnValues.amountOut + : SqrtPriceMath.getAmount0Delta( + sqrtRatioCurrentX96, + returnValues.sqrtRatioNextX96, + liquidity, + false + ); + } + + if ( + !exactIn && + JSBI_BN.greaterThan( + returnValues.amountOut!, + JSBI_BN.multiply(amountRemaining, NEGATIVE_ONE) + ) + ) { + returnValues.amountOut = JSBI_BN.multiply(amountRemaining, NEGATIVE_ONE); + } + + if ( + exactIn && + JSBI_BN.notEqual(sqrtRatioTargetX96, returnValues.sqrtRatioNextX96) + ) { + // we didn't reach the target, so take the remainder of the maximum input as fee + returnValues.UniV3FeeAmount = JSBI_BN.subtract( + amountRemaining, + returnValues.amountIn! + ); + } else { + returnValues.UniV3FeeAmount = FullMath.mulDivRoundingUp( + returnValues.amountIn!, + BigInt(feePips), + JSBI_BN.subtract(MAX_FEE, BigInt(feePips)) + ); + } + + return [ + returnValues.sqrtRatioNextX96!, + returnValues.amountIn!, + returnValues.amountOut!, + returnValues.UniV3FeeAmount!, + ]; + } +} diff --git a/src/utils/concentrated-liquidity/tickMath.ts b/src/utils/concentrated-liquidity/tickMath.ts new file mode 100644 index 0000000..f86880a --- /dev/null +++ b/src/utils/concentrated-liquidity/tickMath.ts @@ -0,0 +1,184 @@ +import { ONE, ZERO, MaxUint256 } from "../../constants"; +import { mostSignificantBit } from "./mostSignificantBit"; +import { JSBI_BN } from "../JSBI_BN"; +import { SwappaMathError } from "../../errors"; + +function mulShift(val: bigint, mulBy: string): bigint { + return JSBI_BN.signedRightShift( + JSBI_BN.multiply(val, BigInt(mulBy)), + BigInt(128) + ); +} + +const Q32 = BigInt(2) ** BigInt(32); + +export abstract class TickMath { + /** + * Cannot be constructed. + */ + private constructor() {} + + /** + * The minimum tick that can be used on any pool. + */ + public static MIN_TICK: number = -887272; + /** + * The maximum tick that can be used on any pool. + */ + public static MAX_TICK: number = -TickMath.MIN_TICK; + + /** + * The sqrt ratio corresponding to the minimum tick that could be used on any pool. + */ + public static MIN_SQRT_RATIO: bigint = BigInt("4295128739"); + /** + * The sqrt ratio corresponding to the maximum tick that could be used on any pool. + */ + public static MAX_SQRT_RATIO: bigint = BigInt( + "1461446703485210103287273052203988822378723970342" + ); + + /** + * Returns the sqrt ratio as a Q64.96 for the given tick. The sqrt ratio is computed as sqrt(1.0001)^tick + * @param tick the tick for which to compute the sqrt ratio + */ + public static getSqrtRatioAtTick(tick: number): bigint { + if (!Number.isInteger(tick)) throw new SwappaMathError("Tick is not an integer") + if (tick < TickMath.MIN_TICK) throw new SwappaMathError("Tick less than min tick") + if (tick > TickMath.MAX_TICK) throw new SwappaMathError("Tick greater than max tick") + + const absTick: number = tick < 0 ? tick * -1 : tick; + + let ratio: bigint = + (absTick & 0x1) != 0 + ? BigInt("0xfffcb933bd6fad37aa2d162d1a594001") + : BigInt("0x100000000000000000000000000000000"); + if ((absTick & 0x2) != 0) + ratio = mulShift(ratio, "0xfff97272373d413259a46990580e213a"); + if ((absTick & 0x4) != 0) + ratio = mulShift(ratio, "0xfff2e50f5f656932ef12357cf3c7fdcc"); + if ((absTick & 0x8) != 0) + ratio = mulShift(ratio, "0xffe5caca7e10e4e61c3624eaa0941cd0"); + if ((absTick & 0x10) != 0) + ratio = mulShift(ratio, "0xffcb9843d60f6159c9db58835c926644"); + if ((absTick & 0x20) != 0) + ratio = mulShift(ratio, "0xff973b41fa98c081472e6896dfb254c0"); + if ((absTick & 0x40) != 0) + ratio = mulShift(ratio, "0xff2ea16466c96a3843ec78b326b52861"); + if ((absTick & 0x80) != 0) + ratio = mulShift(ratio, "0xfe5dee046a99a2a811c461f1969c3053"); + if ((absTick & 0x100) != 0) + ratio = mulShift(ratio, "0xfcbe86c7900a88aedcffc83b479aa3a4"); + if ((absTick & 0x200) != 0) + ratio = mulShift(ratio, "0xf987a7253ac413176f2b074cf7815e54"); + if ((absTick & 0x400) != 0) + ratio = mulShift(ratio, "0xf3392b0822b70005940c7a398e4b70f3"); + if ((absTick & 0x800) != 0) + ratio = mulShift(ratio, "0xe7159475a2c29b7443b29c7fa6e889d9"); + if ((absTick & 0x1000) != 0) + ratio = mulShift(ratio, "0xd097f3bdfd2022b8845ad8f792aa5825"); + if ((absTick & 0x2000) != 0) + ratio = mulShift(ratio, "0xa9f746462d870fdf8a65dc1f90e061e5"); + if ((absTick & 0x4000) != 0) + ratio = mulShift(ratio, "0x70d869a156d2a1b890bb3df62baf32f7"); + if ((absTick & 0x8000) != 0) + ratio = mulShift(ratio, "0x31be135f97d08fd981231505542fcfa6"); + if ((absTick & 0x10000) != 0) + ratio = mulShift(ratio, "0x9aa508b5b7a84e1c677de54f3e99bc9"); + if ((absTick & 0x20000) != 0) + ratio = mulShift(ratio, "0x5d6af8dedb81196699c329225ee604"); + if ((absTick & 0x40000) != 0) + ratio = mulShift(ratio, "0x2216e584f5fa1ea926041bedfe98"); + if ((absTick & 0x80000) != 0) + ratio = mulShift(ratio, "0x48a170391f7dc42444e8fa2"); + + if (tick > 0) ratio = JSBI_BN.divide(MaxUint256, ratio); + + // back to Q96 + return JSBI_BN.greaterThan(JSBI_BN.remainder(ratio, Q32), ZERO) + ? JSBI_BN.add(JSBI_BN.divide(ratio, Q32), ONE) + : JSBI_BN.divide(ratio, Q32); + } + + /** + * Computes the position in the mapping where the initialized bit lives + * @param tick Current tick + */ + public static position = ( + tick: number + ): { wordPos: number; bitPos: number } => ({ + wordPos: tick >> 8, + bitPos: tick % 256, + }); + + /** + * Returns the tick corresponding to a given sqrt ratio, s.t. #getSqrtRatioAtTick(tick) <= sqrtRatioX96 + * and #getSqrtRatioAtTick(tick + 1) > sqrtRatioX96 + * @param sqrtRatioX96 the sqrt ratio as a Q64.96 for which to compute the tick + */ + public static getTickAtSqrtRatio(sqrtRatioX96: bigint): number { + + // TODO: Make this boolean expression more readable - just use de morgans + if (!(JSBI_BN.greaterThanOrEqual(sqrtRatioX96, TickMath.MIN_SQRT_RATIO) && + JSBI_BN.lessThan(sqrtRatioX96, TickMath.MAX_SQRT_RATIO))) throw new SwappaMathError("SQRT_RATIO"); + + const sqrtRatioX128 = JSBI_BN.leftShift(sqrtRatioX96, BigInt(32)); + + const msb = mostSignificantBit(sqrtRatioX128); + + let r: bigint; + if (JSBI_BN.greaterThanOrEqual(BigInt(msb), BigInt(128))) { + r = JSBI_BN.signedRightShift(sqrtRatioX128, BigInt(msb - 127)); + } else { + r = JSBI_BN.leftShift(sqrtRatioX128, BigInt(127 - msb)); + } + + let log_2: bigint = JSBI_BN.leftShift( + JSBI_BN.subtract(BigInt(msb), BigInt(128)), + BigInt(64) + ); + + for (let i = 0; i < 14; i++) { + r = JSBI_BN.signedRightShift(JSBI_BN.multiply(r, r), BigInt(127)); + const f = JSBI_BN.signedRightShift(r, BigInt(128)); + log_2 = JSBI_BN.bitwiseOr( + log_2, + JSBI_BN.leftShift(f, BigInt(63 - i)) + ); + r = JSBI_BN.signedRightShift(r, f); + } + + const log_sqrt10001 = JSBI_BN.multiply( + log_2, + BigInt("255738958999603826347141") + ); + + const tickLow = JSBI_BN.toNumber( + JSBI_BN.signedRightShift( + JSBI_BN.subtract( + log_sqrt10001, + BigInt("3402992956809132418596140100660247210") + ), + BigInt(128) + ) + ); + const tickHigh = JSBI_BN.toNumber( + JSBI_BN.signedRightShift( + JSBI_BN.add( + log_sqrt10001, + BigInt("291339464771989622907027621153398088495") + ), + BigInt(128) + ) + ); + + return tickLow === tickHigh + ? tickLow + : JSBI_BN.lessThanOrEqual( + TickMath.getSqrtRatioAtTick(tickHigh), + sqrtRatioX96 + ) + ? tickHigh + : tickLow; + } +} diff --git a/src/utils.ts b/src/utils/index.ts similarity index 96% rename from src/utils.ts rename to src/utils/index.ts index 85e6c60..880a77b 100644 --- a/src/utils.ts +++ b/src/utils/index.ts @@ -1,7 +1,7 @@ import Web3 from "web3" import { concurrentMap } from '@celo/utils/lib/async' -import { Address, Pair } from "./pair" +import { Address, Pair } from "../pair" export const initPairsAndFilterByWhitelist = async (pairs: Pair[], tokenWhitelist: Address[]) => { await concurrentMap(10, pairs, (p) => p.init()) diff --git a/tools/deployed/mainnet.PairAlgebra.addr.json b/tools/deployed/mainnet.PairAlgebra.addr.json new file mode 100644 index 0000000..ff8c4ae --- /dev/null +++ b/tools/deployed/mainnet.PairAlgebra.addr.json @@ -0,0 +1 @@ +{ "address": "0x899C200699D05fba84987554294E3dc9cf8E48A8" } diff --git a/tools/deployed/mainnet.PairRstCelo.addr.json b/tools/deployed/mainnet.PairRstCelo.addr.json new file mode 100644 index 0000000..2ff3d46 --- /dev/null +++ b/tools/deployed/mainnet.PairRstCelo.addr.json @@ -0,0 +1 @@ +{ "address": "0xDC0017777Aed1374172C92BB74f1137C892d29A7" } diff --git a/tools/deployed/mainnet.PairStCelo.addr.json b/tools/deployed/mainnet.PairStCelo.addr.json new file mode 100644 index 0000000..b8d682f --- /dev/null +++ b/tools/deployed/mainnet.PairStCelo.addr.json @@ -0,0 +1 @@ +{ "address": "0xcc41ead573b9B36B4c75955a8F78a2D5Aa0BaB00" } diff --git a/tools/deployed/mainnet.PairUniswapV3.addr.json b/tools/deployed/mainnet.PairUniswapV3.addr.json new file mode 100644 index 0000000..3a34e8b --- /dev/null +++ b/tools/deployed/mainnet.PairUniswapV3.addr.json @@ -0,0 +1 @@ +{ "address": "0xEdd8491663054bE2763959a737A62395fBeAb472" }