From 93395a65dd285012d300fb33fac7b745d4d5029f Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Fri, 20 Jan 2023 01:11:31 -0800 Subject: [PATCH 01/11] feat: adds uniswapv3 auto-comound --- contracts/interfaces/INTokenUniswapV3.sol | 6 + contracts/interfaces/IPoolCore.sol | 7 + .../protocol/libraries/logic/SupplyLogic.sol | 46 ++++++ .../protocol/libraries/types/DataTypes.sol | 9 ++ contracts/protocol/pool/PoolCore.sol | 24 +++ .../protocol/tokenization/NTokenUniswapV3.sol | 140 ++++++++++++++++++ 6 files changed, 232 insertions(+) diff --git a/contracts/interfaces/INTokenUniswapV3.sol b/contracts/interfaces/INTokenUniswapV3.sol index ddb633ad7..6578bf4a3 100644 --- a/contracts/interfaces/INTokenUniswapV3.sol +++ b/contracts/interfaces/INTokenUniswapV3.sol @@ -20,4 +20,10 @@ interface INTokenUniswapV3 { uint256 amount1Min, bool receiveEthAsWeth ) external; + + function collectSupplyUniswapV3Fees( + address user, + uint256 tokenId, + address incentiveReceiver + ) external; } diff --git a/contracts/interfaces/IPoolCore.sol b/contracts/interfaces/IPoolCore.sol index 0ad6c1f7e..d78c5f435 100644 --- a/contracts/interfaces/IPoolCore.sol +++ b/contracts/interfaces/IPoolCore.sol @@ -318,6 +318,13 @@ interface IPoolCore { bool receiveEthAsWeth ) external; + function collectSupplyUniswapV3Fees( + address asset, + uint256 tokenId, + uint256 amount0Min, + uint256 amount1Min + ) external; + /** * @notice Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower * already supplied enough collateral, or he was given enough allowance by a credit delegator on the diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index c062ba058..a115f08af 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -17,6 +17,7 @@ import {WadRayMath} from "../math/WadRayMath.sol"; import {PercentageMath} from "../math/PercentageMath.sol"; import {ValidationLogic} from "./ValidationLogic.sol"; import {ReserveLogic} from "./ReserveLogic.sol"; +import {GenericLogic} from "./GenericLogic.sol"; import {XTokenType} from "../../../interfaces/IXTokenType.sol"; import {INTokenUniswapV3} from "../../../interfaces/INTokenUniswapV3.sol"; @@ -462,6 +463,51 @@ library SupplyLogic { } } + function executeCollectSupplyUniswapV3Fees( + mapping(address => DataTypes.ReserveData) storage reservesData, + mapping(uint256 => address) storage reservesList, + DataTypes.UserConfigurationMap storage userConfig, + DataTypes.ExecuteCollectAndSupplyUniswapV3FeesParams memory params + ) external { + DataTypes.ReserveData storage reserve = reservesData[params.asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); + + INToken nToken = INToken(reserveCache.xTokenAddress); + require( + nToken.getXTokenType() == XTokenType.NTokenUniswapV3, + Errors.ONLY_UNIV3_ALLOWED + ); + + address positionOwner = nToken.ownerOf(params.tokenId); + address incentiveReceiver = address(0); + + if (msg.sender != positionOwner) { + (, , , , , , , uint256 healthFactor, , ) = GenericLogic + .calculateUserAccountData( + reservesData, + reservesList, + DataTypes.CalculateUserAccountDataParams({ + userConfig: userConfig, + reservesCount: params.reservesCount, + user: positionOwner, + oracle: params.oracle + }) + ); + + require( + healthFactor < DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD + ); + incentiveReceiver = msg.sender; + } + + INTokenUniswapV3(reserveCache.xTokenAddress).collectSupplyUniswapV3Fees( + positionOwner, + params.tokenId, + incentiveReceiver + ); + } + /** * @notice Validates a transfer of PTokens. The sender is subjected to health factor validation to avoid * collateralization constraints violation. diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index fb0035470..7ba80c6bc 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -188,6 +188,15 @@ library DataTypes { address oracle; } + struct ExecuteCollectAndSupplyUniswapV3FeesParams { + address asset; + uint256 tokenId; + uint256 reservesCount; + uint256 amount0; + uint256 amount1; + address oracle; + } + struct FinalizeTransferParams { address asset; address from; diff --git a/contracts/protocol/pool/PoolCore.sol b/contracts/protocol/pool/PoolCore.sol index fb1e9391f..dcc44d62a 100644 --- a/contracts/protocol/pool/PoolCore.sol +++ b/contracts/protocol/pool/PoolCore.sol @@ -234,6 +234,30 @@ contract PoolCore is ); } + function collectSupplyUniswapV3Fees( + address asset, + uint256 tokenId, + uint256 amount0Min, + uint256 amount1Min + ) external virtual override { + DataTypes.PoolStorage storage ps = poolStorage(); + + return + SupplyLogic.executeCollectSupplyUniswapV3Fees( + ps._reserves, + ps._reservesList, + ps._usersConfig[msg.sender], + DataTypes.ExecuteCollectAndSupplyUniswapV3FeesParams({ + asset: asset, + tokenId: tokenId, + reservesCount: ps._reservesCount, + amount0: amount0Min, + amount1: amount1Min, + oracle: ADDRESSES_PROVIDER.getPriceOracle() + }) + ); + } + function decreaseUniswapV3Liquidity( address asset, uint256 tokenId, diff --git a/contracts/protocol/tokenization/NTokenUniswapV3.sol b/contracts/protocol/tokenization/NTokenUniswapV3.sol index cd4f52dad..13223f071 100644 --- a/contracts/protocol/tokenization/NTokenUniswapV3.sol +++ b/contracts/protocol/tokenization/NTokenUniswapV3.sol @@ -9,6 +9,7 @@ import {Address} from "../../dependencies/openzeppelin/contracts/Address.sol"; import {SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; import {SafeCast} from "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; +import {PercentageMath} from "../libraries/math/PercentageMath.sol"; import {WadRayMath} from "../libraries/math/WadRayMath.sol"; import {IPool} from "../../interfaces/IPool.sol"; import {NToken} from "./NToken.sol"; @@ -17,6 +18,7 @@ import {INonfungiblePositionManager} from "../../dependencies/uniswap/INonfungib import {IWETH} from "../../misc/interfaces/IWETH.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {INTokenUniswapV3} from "../../interfaces/INTokenUniswapV3.sol"; +import {IUniswapV3OracleWrapper} from "../../interfaces/IUniswapV3OracleWrapper.sol"; /** * @title UniswapV3 NToken @@ -25,6 +27,12 @@ import {INTokenUniswapV3} from "../../interfaces/INTokenUniswapV3.sol"; */ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { using SafeERC20 for IERC20; + using PercentageMath for uint256; + + bytes32 constant UNISWAPV3_DATA_STORAGE_POSITION = + bytes32( + uint256(keccak256("paraspace.proxy.ntoken.uniswapv3.storage")) - 1 + ); /** * @dev Constructor. @@ -34,6 +42,10 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { _ERC721Data.balanceLimit = 30; } + struct UniswapV3Storage { + uint256 compoundIncentive; + } + function getXTokenType() external pure override returns (XTokenType) { return XTokenType.NTokenUniswapV3; } @@ -141,6 +153,134 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { ); } + function setCompoundIncentive(uint256 incentive) external onlyPoolAdmin { + UniswapV3Storage storage uniV3Storage = uinswapV3DataStorage(); + + uniV3Storage.compoundIncentive = incentive; + } + + function uinswapV3DataStorage() + internal + pure + returns (UniswapV3Storage storage rgs) + { + bytes32 position = UNISWAPV3_DATA_STORAGE_POSITION; + assembly { + rgs.slot := position + } + } + + function collectSupplyUniswapV3Fees( + address user, + uint256 tokenId, + address incentiveReceiver + ) external onlyPool nonReentrant { + require(user == ownerOf(tokenId), Errors.NOT_THE_OWNER); + + ( + , + , + address token0, + address token1, + , + , + , + , + , + , + , + + ) = INonfungiblePositionManager(_underlyingAsset).positions(tokenId); + + uint256 token0BalanceBefore = IERC20(token0).balanceOf(address(this)); + uint256 token1BalanceBefore = IERC20(token1).balanceOf(address(this)); + + INonfungiblePositionManager.CollectParams + memory collectParams = INonfungiblePositionManager.CollectParams({ + tokenId: tokenId, + recipient: address(this), + amount0Max: type(uint128).max, + amount1Max: type(uint128).max + }); + + (uint256 amount0, uint256 amount1) = INonfungiblePositionManager( + _underlyingAsset + ).collect(collectParams); + + if (incentiveReceiver != address(0)) { + UniswapV3Storage storage uniV3Storage = uinswapV3DataStorage(); + + uint256 compoundIncentive = uniV3Storage.compoundIncentive; + + if (compoundIncentive > 0) { + if (amount0 > 0) { + uint256 incentiveAmount0 = amount0.percentMul( + compoundIncentive + ); + + IERC20(token0).safeTransfer( + incentiveReceiver, + incentiveAmount0 + ); + amount0 = amount0 - incentiveAmount0; + } + + if (amount1 > 0) { + uint256 incentiveAmount1 = amount1.percentMul( + compoundIncentive + ); + + IERC20(token1).safeTransfer( + incentiveReceiver, + incentiveAmount1 + ); + amount1 = amount1 - incentiveAmount1; + } + } + } + + INonfungiblePositionManager.IncreaseLiquidityParams + memory params = INonfungiblePositionManager + .IncreaseLiquidityParams({ + tokenId: tokenId, + amount0Desired: amount0, + amount1Desired: amount1, + amount0Min: 0, + amount1Min: 0, + deadline: block.timestamp + }); + + INonfungiblePositionManager(_underlyingAsset).increaseLiquidity(params); + + if (amount0 > 0) { + uint256 token0BalanceAfter = IERC20(token0).balanceOf( + address(this) + ); + if (token0BalanceAfter > token0BalanceBefore) { + POOL.supply( + token0, + token0BalanceAfter - token0BalanceBefore, + user, + 0x0 + ); + } + } + + if (amount1 > 0) { + uint256 token1BalanceAfter = IERC20(token1).balanceOf( + address(this) + ); + if (token1BalanceAfter > token1BalanceBefore) { + POOL.supply( + token1, + token1BalanceAfter - token1BalanceBefore, + user, + 0x0 + ); + } + } + } + function _safeTransferETH(address to, uint256 value) internal { (bool success, ) = to.call{value: value}(new bytes(0)); require(success, "ETH_TRANSFER_FAILED"); From 64fdaab896ce25edf6e7b29e3a38a7fd516bf2ef Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Fri, 20 Jan 2023 16:16:49 -0800 Subject: [PATCH 02/11] chore: adds erc20 approvals for uniswap token manager --- .../protocol/tokenization/NTokenUniswapV3.sol | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/contracts/protocol/tokenization/NTokenUniswapV3.sol b/contracts/protocol/tokenization/NTokenUniswapV3.sol index 13223f071..110aefc18 100644 --- a/contracts/protocol/tokenization/NTokenUniswapV3.sol +++ b/contracts/protocol/tokenization/NTokenUniswapV3.sol @@ -19,6 +19,8 @@ import {IWETH} from "../../misc/interfaces/IWETH.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {INTokenUniswapV3} from "../../interfaces/INTokenUniswapV3.sol"; import {IUniswapV3OracleWrapper} from "../../interfaces/IUniswapV3OracleWrapper.sol"; +import {IRewardController} from "../../interfaces/IRewardController.sol"; +import {ReserveConfiguration} from "../libraries/configuration/ReserveConfiguration.sol"; /** * @title UniswapV3 NToken @@ -28,6 +30,7 @@ import {IUniswapV3OracleWrapper} from "../../interfaces/IUniswapV3OracleWrapper. contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { using SafeERC20 for IERC20; using PercentageMath for uint256; + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; bytes32 constant UNISWAPV3_DATA_STORAGE_POSITION = bytes32( @@ -42,6 +45,24 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { _ERC721Data.balanceLimit = 30; } + function initialize( + IPool initializingPool, + address underlyingAsset, + IRewardController incentivesController, + string calldata nTokenName, + string calldata nTokenSymbol, + bytes calldata params + ) public virtual override initializer { + super.initialize(initializingPool, underlyingAsset, incentivesController, nTokenName, nTokenSymbol, params); + address[] memory reserveList = POOL.getReservesList(); + for (uint256 index = 0; index < reserveList.length; index++) { + DataTypes.ReserveConfigurationMap memory poolConfiguration = POOL.getReserveData(reserveList[index]).configuration; + if (poolConfiguration.getAssetType() == DataTypes.AssetType.ERC20) { + IERC20(reserveList[index]).approve(_underlyingAsset, type(uint128).max); + } + } + } + struct UniswapV3Storage { uint256 compoundIncentive; } From 05a7833ce64437d287e1c5d30d3c491249ba4ce0 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Fri, 20 Jan 2023 16:24:40 -0800 Subject: [PATCH 03/11] chore: refactor interface --- contracts/interfaces/IPoolCore.sol | 6 ++---- contracts/protocol/libraries/types/DataTypes.sol | 2 -- contracts/protocol/pool/PoolCore.sol | 8 ++------ 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/IPoolCore.sol b/contracts/interfaces/IPoolCore.sol index d78c5f435..047011be8 100644 --- a/contracts/interfaces/IPoolCore.sol +++ b/contracts/interfaces/IPoolCore.sol @@ -318,11 +318,9 @@ interface IPoolCore { bool receiveEthAsWeth ) external; - function collectSupplyUniswapV3Fees( + function collectCompoundAndSupplyUniswapV3Fees( address asset, - uint256 tokenId, - uint256 amount0Min, - uint256 amount1Min + uint256 tokenId ) external; /** diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index 7ba80c6bc..3cc55a53c 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -192,8 +192,6 @@ library DataTypes { address asset; uint256 tokenId; uint256 reservesCount; - uint256 amount0; - uint256 amount1; address oracle; } diff --git a/contracts/protocol/pool/PoolCore.sol b/contracts/protocol/pool/PoolCore.sol index dcc44d62a..e5c586120 100644 --- a/contracts/protocol/pool/PoolCore.sol +++ b/contracts/protocol/pool/PoolCore.sol @@ -234,11 +234,9 @@ contract PoolCore is ); } - function collectSupplyUniswapV3Fees( + function collectCompoundAndSupplyUniswapV3Fees( address asset, - uint256 tokenId, - uint256 amount0Min, - uint256 amount1Min + uint256 tokenId ) external virtual override { DataTypes.PoolStorage storage ps = poolStorage(); @@ -251,8 +249,6 @@ contract PoolCore is asset: asset, tokenId: tokenId, reservesCount: ps._reservesCount, - amount0: amount0Min, - amount1: amount1Min, oracle: ADDRESSES_PROVIDER.getPriceOracle() }) ); From 5e32045eb7ebf1fbc871edb0153f63134028bdc9 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Fri, 20 Jan 2023 16:35:31 -0800 Subject: [PATCH 04/11] chore: lint --- contracts/protocol/pool/PoolCore.sol | 1 + .../protocol/tokenization/NTokenUniswapV3.sol | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/contracts/protocol/pool/PoolCore.sol b/contracts/protocol/pool/PoolCore.sol index e5c586120..3c3ee6d01 100644 --- a/contracts/protocol/pool/PoolCore.sol +++ b/contracts/protocol/pool/PoolCore.sol @@ -238,6 +238,7 @@ contract PoolCore is address asset, uint256 tokenId ) external virtual override { + // we need re-entrancy here. might need to create a special supplyERC20 from NToken as a workaround DataTypes.PoolStorage storage ps = poolStorage(); return diff --git a/contracts/protocol/tokenization/NTokenUniswapV3.sol b/contracts/protocol/tokenization/NTokenUniswapV3.sol index 110aefc18..bb0de4fb3 100644 --- a/contracts/protocol/tokenization/NTokenUniswapV3.sol +++ b/contracts/protocol/tokenization/NTokenUniswapV3.sol @@ -53,12 +53,24 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { string calldata nTokenSymbol, bytes calldata params ) public virtual override initializer { - super.initialize(initializingPool, underlyingAsset, incentivesController, nTokenName, nTokenSymbol, params); + super.initialize( + initializingPool, + underlyingAsset, + incentivesController, + nTokenName, + nTokenSymbol, + params + ); address[] memory reserveList = POOL.getReservesList(); for (uint256 index = 0; index < reserveList.length; index++) { - DataTypes.ReserveConfigurationMap memory poolConfiguration = POOL.getReserveData(reserveList[index]).configuration; + DataTypes.ReserveConfigurationMap memory poolConfiguration = POOL + .getReserveData(reserveList[index]) + .configuration; if (poolConfiguration.getAssetType() == DataTypes.AssetType.ERC20) { - IERC20(reserveList[index]).approve(_underlyingAsset, type(uint128).max); + IERC20(reserveList[index]).approve( + _underlyingAsset, + type(uint128).max + ); } } } @@ -196,8 +208,6 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { uint256 tokenId, address incentiveReceiver ) external onlyPool nonReentrant { - require(user == ownerOf(tokenId), Errors.NOT_THE_OWNER); - ( , , From 035b4215d13b8f74e6f7dcfa048e93033dc74ec5 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Fri, 20 Jan 2023 20:48:23 -0800 Subject: [PATCH 05/11] chore: adds a test --- .../protocol/tokenization/NTokenUniswapV3.sol | 4 ++ test/_uniswapv3_position_control.spec.ts | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/contracts/protocol/tokenization/NTokenUniswapV3.sol b/contracts/protocol/tokenization/NTokenUniswapV3.sol index bb0de4fb3..15a08f1f9 100644 --- a/contracts/protocol/tokenization/NTokenUniswapV3.sol +++ b/contracts/protocol/tokenization/NTokenUniswapV3.sol @@ -71,6 +71,10 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { _underlyingAsset, type(uint128).max ); + IERC20(reserveList[index]).approve( + address(POOL), + type(uint128).max + ); } } } diff --git a/test/_uniswapv3_position_control.spec.ts b/test/_uniswapv3_position_control.spec.ts index b84badfae..af98ad5a3 100644 --- a/test/_uniswapv3_position_control.spec.ts +++ b/test/_uniswapv3_position_control.spec.ts @@ -274,6 +274,61 @@ describe("Uniswap V3 NFT position control", () => { almostEqual(afterLiquidity, beforeLiquidity.div(2)); }); + it("collect fee and autocompound[ @skip-on-coverage ]", async () => { + const { + users: [user1, trader], + dai, + weth, + nftPositionManager, + pool, + } = testEnv; + + const traderDaiAmount = await convertToCurrencyDecimals( + dai.address, + "10000" + ); + await fund({token: dai, user: trader, amount: traderDaiAmount}); + const userWethAmount = await convertToCurrencyDecimals(weth.address, "1"); + + await fund({token: weth, user: user1, amount: userWethAmount}); + await approveSwapRouter({token: weth, user: trader}); + + await approveSwapRouter({token: dai, user: trader}); + + const fee = 3000; + await swapToken({ + tokenIn: dai, + tokenOut: weth, + fee, + amountIn: traderDaiAmount, + trader, + zeroForOne: true, + }); + + await swapToken({ + tokenIn: weth, + tokenOut: dai, + fee, + amountIn: userWethAmount.div(1000), + trader, + zeroForOne: false, + }); + + const beforeLiquidity = (await nftPositionManager.positions(1)).liquidity; + + await waitForTx( + await pool + .connect(user1.signer) + .collectCompoundAndSupplyUniswapV3Fees(nftPositionManager.address, 1, { + gasLimit: 12_450_000, + }) + ); + + const afterLiquidity = (await nftPositionManager.positions(1)).liquidity; + + expect(afterLiquidity).to.be.gt(beforeLiquidity); + }); + it("collect fee by decreaseLiquidity by NTokenUniswapV3 [ @skip-on-coverage ]", async () => { const { users: [user1, trader], From d32c5de3002f3b99fd02d62e84bc24fc177fbd4c Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 23 Jan 2023 16:54:04 -0800 Subject: [PATCH 06/11] chore: adds uniswap dependencies --- .../NoDelegateCall.sol | 2 +- .../UniswapV3Factory.sol | 24 +- .../UniswapV3Pool.sol | 665 +++++++----------- .../UniswapV3PoolDeployer.sol | 20 +- .../interfaces/IERC20Minimal.sol | 52 ++ .../interfaces/IUniswapV3Factory.sol | 78 ++ .../interfaces/IUniswapV3Pool.sol | 26 + .../interfaces/IUniswapV3PoolDeployer.sol | 26 + .../uniswapv3-core/interfaces/LICENSE | 445 ++++++++++++ .../callback/IUniswapV3FlashCallback.sol | 18 + .../callback/IUniswapV3MintCallback.sol | 18 + .../callback/IUniswapV3SwapCallback.sol | 21 + .../interfaces/pool/IUniswapV3PoolActions.sol | 103 +++ .../pool/IUniswapV3PoolDerivedState.sol | 40 ++ .../interfaces/pool/IUniswapV3PoolErrors.sol | 19 + .../interfaces/pool/IUniswapV3PoolEvents.sol | 121 ++++ .../pool/IUniswapV3PoolImmutables.sol | 35 + .../pool/IUniswapV3PoolOwnerActions.sol | 23 + .../interfaces/pool/IUniswapV3PoolState.sol | 117 +++ .../uniswapv3-core/libraries/BitMath.sol | 98 +++ .../libraries/FixedPoint128.sol | 8 + .../uniswapv3-core/libraries/FixedPoint96.sol | 10 + .../uniswapv3-core/libraries/FullMath.sol | 128 ++++ .../uniswapv3-core/libraries/Oracle.sol | 352 +++++++++ .../uniswapv3-core/libraries/Position.sol | 93 +++ .../uniswapv3-core/libraries/SafeCast.sol | 28 + .../libraries/SqrtPriceMath.sol | 237 +++++++ .../uniswapv3-core/libraries/SwapMath.sol | 100 +++ .../uniswapv3-core/libraries/Tick.sol | 192 +++++ .../uniswapv3-core/libraries/TickBitmap.sol | 84 +++ .../uniswapv3-core/libraries/TickMath.sol | 214 ++++++ .../libraries/TransferHelper.sol | 26 + .../uniswapv3-core/libraries/UnsafeMath.sol | 17 + .../NonfungiblePositionManager.sol | 399 +++++++++++ .../uniswapv3-periphery/SwapRouter.sol | 241 +++++++ .../base/BlockTimestamp.sol | 12 + .../uniswapv3-periphery/base/ERC721Permit.sol | 85 +++ .../base/LiquidityManagement.sol | 99 +++ .../uniswapv3-periphery/base/Multicall.sol | 28 + .../base/PeripheryImmutableState.sol | 18 + .../base/PeripheryPayments.sol | 70 ++ .../base/PeripheryPaymentsWithFee.sol | 52 ++ .../base/PeripheryValidation.sol | 11 + .../base/PoolInitializer.sol | 36 + .../uniswapv3-periphery/base/SelfPermit.sol | 63 ++ .../interfaces/IERC20Metadata.sol | 18 + .../interfaces/IERC721Permit.sol | 32 + .../interfaces}/IMulticall.sol | 0 .../INonfungiblePositionManager.sol | 180 +++++ .../INonfungibleTokenPositionDescriptor.sol | 17 + .../interfaces/IPeripheryImmutableState.sol | 12 + .../interfaces/IPeripheryPayments.sol | 28 + .../interfaces/IPeripheryPaymentsWithFee.sol | 29 + .../interfaces/IPoolInitializer.sol | 22 + .../interfaces/IQuoter.sol | 51 ++ .../interfaces/IQuoterV2.sol | 98 +++ .../interfaces/ISelfPermit.sol | 76 ++ .../interfaces/ISwapRouter.sol | 67 ++ .../interfaces/ITickLens.sol | 25 + .../interfaces/IV3Migrator.sol | 34 + .../interfaces/external/IERC1271.sol | 16 + .../external/IERC20PermitAllowed.sol | 27 + .../interfaces/external/IWETH9.sol | 13 + .../libraries/BytesLib.sol | 99 +++ .../libraries/CallbackValidation.sol | 36 + .../uniswapv3-periphery/libraries/ChainId.sol | 13 + .../libraries/LiquidityAmounts.sol | 145 ++++ .../uniswapv3-periphery/libraries/Path.sol | 69 ++ .../libraries/PoolAddress.sol | 38 + .../libraries/PositionKey.sol | 13 + .../libraries/TransferHelper.sol | 61 ++ .../protocol/libraries/logic/SupplyLogic.sol | 100 --- .../libraries/logic/UniswapV3Logic.sol | 125 ++++ contracts/protocol/pool/PoolCore.sol | 5 +- helpers/contracts-deployments.ts | 31 +- helpers/types.ts | 1 + 76 files changed, 5476 insertions(+), 559 deletions(-) rename contracts/dependencies/{univ3 => uniswapv3-core}/NoDelegateCall.sol (97%) rename contracts/dependencies/{univ3 => uniswapv3-core}/UniswapV3Factory.sol (83%) rename contracts/dependencies/{univ3 => uniswapv3-core}/UniswapV3Pool.sol (62%) rename contracts/dependencies/{univ3 => uniswapv3-core}/UniswapV3PoolDeployer.sol (71%) create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/IERC20Minimal.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Factory.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Pool.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3PoolDeployer.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/LICENSE create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3FlashCallback.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3MintCallback.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3SwapCallback.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolActions.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolDerivedState.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolErrors.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolEvents.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolImmutables.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolOwnerActions.sol create mode 100644 contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolState.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/BitMath.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/FixedPoint128.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/FixedPoint96.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/FullMath.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/Oracle.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/Position.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/SafeCast.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/SqrtPriceMath.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/SwapMath.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/Tick.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/TickBitmap.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/TickMath.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/TransferHelper.sol create mode 100644 contracts/dependencies/uniswapv3-core/libraries/UnsafeMath.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/NonfungiblePositionManager.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/SwapRouter.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/BlockTimestamp.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/ERC721Permit.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/Multicall.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/PeripheryImmutableState.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/PeripheryPayments.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/PeripheryPaymentsWithFee.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/PeripheryValidation.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/PoolInitializer.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/base/SelfPermit.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IERC20Metadata.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IERC721Permit.sol rename contracts/dependencies/{uniswap => uniswapv3-periphery/interfaces}/IMulticall.sol (100%) create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/INonfungiblePositionManager.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/INonfungibleTokenPositionDescriptor.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryImmutableState.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPayments.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPaymentsWithFee.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IPoolInitializer.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IQuoter.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IQuoterV2.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/ISelfPermit.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/ITickLens.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/IV3Migrator.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC1271.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC20PermitAllowed.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/interfaces/external/IWETH9.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/BytesLib.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/CallbackValidation.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/ChainId.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/LiquidityAmounts.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/Path.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/PoolAddress.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/PositionKey.sol create mode 100644 contracts/dependencies/uniswapv3-periphery/libraries/TransferHelper.sol create mode 100644 contracts/protocol/libraries/logic/UniswapV3Logic.sol diff --git a/contracts/dependencies/univ3/NoDelegateCall.sol b/contracts/dependencies/uniswapv3-core/NoDelegateCall.sol similarity index 97% rename from contracts/dependencies/univ3/NoDelegateCall.sol rename to contracts/dependencies/uniswapv3-core/NoDelegateCall.sol index 5411979dc..a6602aa3c 100644 --- a/contracts/dependencies/univ3/NoDelegateCall.sol +++ b/contracts/dependencies/uniswapv3-core/NoDelegateCall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.7.6; +pragma solidity 0.8.10; /// @title Prevents delegatecall to a contract /// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract diff --git a/contracts/dependencies/univ3/UniswapV3Factory.sol b/contracts/dependencies/uniswapv3-core/UniswapV3Factory.sol similarity index 83% rename from contracts/dependencies/univ3/UniswapV3Factory.sol rename to contracts/dependencies/uniswapv3-core/UniswapV3Factory.sol index 0ea0b9b7f..15163de4d 100644 --- a/contracts/dependencies/univ3/UniswapV3Factory.sol +++ b/contracts/dependencies/uniswapv3-core/UniswapV3Factory.sol @@ -1,29 +1,23 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.7.6; +pragma solidity 0.8.10; -import "./interfaces/IUniswapV3Factory.sol"; +import {IUniswapV3Factory} from './interfaces/IUniswapV3Factory.sol'; -import "./UniswapV3PoolDeployer.sol"; -import "./NoDelegateCall.sol"; +import {UniswapV3PoolDeployer} from './UniswapV3PoolDeployer.sol'; +import {NoDelegateCall} from './NoDelegateCall.sol'; -import "./UniswapV3Pool.sol"; +import {UniswapV3Pool} from './UniswapV3Pool.sol'; /// @title Canonical Uniswap V3 factory /// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees -contract UniswapV3Factory is - IUniswapV3Factory, - UniswapV3PoolDeployer, - NoDelegateCall -{ +contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall { /// @inheritdoc IUniswapV3Factory address public override owner; /// @inheritdoc IUniswapV3Factory mapping(uint24 => int24) public override feeAmountTickSpacing; /// @inheritdoc IUniswapV3Factory - mapping(address => mapping(address => mapping(uint24 => address))) - public - override getPool; + mapping(address => mapping(address => mapping(uint24 => address))) public override getPool; constructor() { owner = msg.sender; @@ -44,9 +38,7 @@ contract UniswapV3Factory is uint24 fee ) external override noDelegateCall returns (address pool) { require(tokenA != tokenB); - (address token0, address token1) = tokenA < tokenB - ? (tokenA, tokenB) - : (tokenB, tokenA); + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0)); int24 tickSpacing = feeAmountTickSpacing[fee]; require(tickSpacing != 0); diff --git a/contracts/dependencies/univ3/UniswapV3Pool.sol b/contracts/dependencies/uniswapv3-core/UniswapV3Pool.sol similarity index 62% rename from contracts/dependencies/univ3/UniswapV3Pool.sol rename to contracts/dependencies/uniswapv3-core/UniswapV3Pool.sol index 8b61e89ae..ec3100130 100644 --- a/contracts/dependencies/univ3/UniswapV3Pool.sol +++ b/contracts/dependencies/uniswapv3-core/UniswapV3Pool.sol @@ -1,35 +1,31 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.7.6; - -import "./interfaces/IUniswapV3Pool.sol"; - -import "./NoDelegateCall.sol"; - -import "./libraries/LowGasSafeMath.sol"; -import "./libraries/SafeCast.sol"; -import "./libraries/Tick.sol"; -import "./libraries/TickBitmap.sol"; -import "./libraries/Position.sol"; -import "./libraries/Oracle.sol"; - -import "./libraries/FullMath.sol"; -import "./libraries/FixedPoint128.sol"; -import "./libraries/TransferHelper.sol"; -import "./libraries/TickMath.sol"; -import "./libraries/LiquidityMath.sol"; -import "./libraries/SqrtPriceMath.sol"; -import "./libraries/SwapMath.sol"; - -import "./interfaces/IUniswapV3PoolDeployer.sol"; -import "./interfaces/IUniswapV3Factory.sol"; -import "./interfaces/IERC20Minimal.sol"; -import "./interfaces/callback/IUniswapV3MintCallback.sol"; -import "./interfaces/callback/IUniswapV3SwapCallback.sol"; -import "./interfaces/callback/IUniswapV3FlashCallback.sol"; +pragma solidity 0.8.10; + +import {IUniswapV3PoolImmutables, IUniswapV3PoolState, IUniswapV3PoolActions, IUniswapV3PoolDerivedState, IUniswapV3PoolOwnerActions, IUniswapV3Pool} from './interfaces/IUniswapV3Pool.sol'; + +import {NoDelegateCall} from './NoDelegateCall.sol'; + +import {SafeCast} from './libraries/SafeCast.sol'; +import {Tick} from './libraries/Tick.sol'; +import {TickBitmap} from './libraries/TickBitmap.sol'; +import {Position} from './libraries/Position.sol'; +import {Oracle} from './libraries/Oracle.sol'; + +import {FullMath} from './libraries/FullMath.sol'; +import {FixedPoint128} from './libraries/FixedPoint128.sol'; +import {TransferHelper} from './libraries/TransferHelper.sol'; +import {TickMath} from './libraries/TickMath.sol'; +import {SqrtPriceMath} from './libraries/SqrtPriceMath.sol'; +import {SwapMath} from './libraries/SwapMath.sol'; + +import {IUniswapV3PoolDeployer} from './interfaces/IUniswapV3PoolDeployer.sol'; +import {IUniswapV3Factory} from './interfaces/IUniswapV3Factory.sol'; +import {IERC20Minimal} from './interfaces/IERC20Minimal.sol'; +import {IUniswapV3MintCallback} from './interfaces/callback/IUniswapV3MintCallback.sol'; +import {IUniswapV3SwapCallback} from './interfaces/callback/IUniswapV3SwapCallback.sol'; +import {IUniswapV3FlashCallback} from './interfaces/callback/IUniswapV3FlashCallback.sol'; contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { - using LowGasSafeMath for uint256; - using LowGasSafeMath for int256; using SafeCast for uint256; using SafeCast for int256; using Tick for mapping(int24 => Tick.Info); @@ -102,7 +98,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { /// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because /// we use balance checks to determine the payment status of interactions such as mint, swap and flash. modifier lock() { - require(slot0.unlocked, "LOK"); + if (!slot0.unlocked) revert LOK(); slot0.unlocked = false; _; slot0.unlocked = true; @@ -116,21 +112,17 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { constructor() { int24 _tickSpacing; - (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer( - msg.sender - ).parameters(); + (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters(); tickSpacing = _tickSpacing; - maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick( - _tickSpacing - ); + maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing); } /// @dev Common checks for valid tick inputs. function checkTicks(int24 tickLower, int24 tickUpper) private pure { - require(tickLower < tickUpper, "TLU"); - require(tickLower >= TickMath.MIN_TICK, "TLM"); - require(tickUpper <= TickMath.MAX_TICK, "TUM"); + if (tickLower >= tickUpper) revert TLU(); + if (tickLower < TickMath.MIN_TICK) revert TLM(); + if (tickUpper > TickMath.MAX_TICK) revert TUM(); } /// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests. @@ -143,10 +135,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { /// check function balance0() private view returns (uint256) { (bool success, bytes memory data) = token0.staticcall( - abi.encodeWithSelector( - IERC20Minimal.balanceOf.selector, - address(this) - ) + abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)) ); require(success && data.length >= 32); return abi.decode(data, (uint256)); @@ -157,10 +146,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { /// check function balance1() private view returns (uint256) { (bool success, bytes memory data) = token1.staticcall( - abi.encodeWithSelector( - IERC20Minimal.balanceOf.selector, - address(this) - ) + abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)) ); require(success && data.length >= 32); return abi.decode(data, (uint256)); @@ -191,12 +177,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { Tick.Info storage lower = ticks[tickLower]; Tick.Info storage upper = ticks[tickUpper]; bool initializedLower; - ( - tickCumulativeLower, - secondsPerLiquidityOutsideLowerX128, - secondsOutsideLower, - initializedLower - ) = ( + (tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = ( lower.tickCumulativeOutside, lower.secondsPerLiquidityOutsideX128, lower.secondsOutside, @@ -205,12 +186,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { require(initializedLower); bool initializedUpper; - ( - tickCumulativeUpper, - secondsPerLiquidityOutsideUpperX128, - secondsOutsideUpper, - initializedUpper - ) = ( + (tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = ( upper.tickCumulativeOutside, upper.secondsPerLiquidityOutsideX128, upper.secondsOutside, @@ -221,19 +197,16 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { Slot0 memory _slot0 = slot0; - if (_slot0.tick < tickLower) { - return ( - tickCumulativeLower - tickCumulativeUpper, - secondsPerLiquidityOutsideLowerX128 - - secondsPerLiquidityOutsideUpperX128, - secondsOutsideLower - secondsOutsideUpper - ); - } else if (_slot0.tick < tickUpper) { - uint32 time = _blockTimestamp(); - ( - int56 tickCumulative, - uint160 secondsPerLiquidityCumulativeX128 - ) = observations.observeSingle( + unchecked { + if (_slot0.tick < tickLower) { + return ( + tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128, + secondsOutsideLower - secondsOutsideUpper + ); + } else if (_slot0.tick < tickUpper) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observations.observeSingle( time, 0, _slot0.tick, @@ -241,20 +214,20 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { liquidity, _slot0.observationCardinality ); - return ( - tickCumulative - tickCumulativeLower - tickCumulativeUpper, - secondsPerLiquidityCumulativeX128 - - secondsPerLiquidityOutsideLowerX128 - - secondsPerLiquidityOutsideUpperX128, - time - secondsOutsideLower - secondsOutsideUpper - ); - } else { - return ( - tickCumulativeUpper - tickCumulativeLower, - secondsPerLiquidityOutsideUpperX128 - - secondsPerLiquidityOutsideLowerX128, - secondsOutsideUpper - secondsOutsideLower - ); + return ( + tickCumulative - tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityCumulativeX128 - + secondsPerLiquidityOutsideLowerX128 - + secondsPerLiquidityOutsideUpperX128, + time - secondsOutsideLower - secondsOutsideUpper + ); + } else { + return ( + tickCumulativeUpper - tickCumulativeLower, + secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128, + secondsOutsideUpper - secondsOutsideLower + ); + } } } @@ -264,10 +237,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { view override noDelegateCall - returns ( - int56[] memory tickCumulatives, - uint160[] memory secondsPerLiquidityCumulativeX128s - ) + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) { return observations.observe( @@ -281,9 +251,12 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { } /// @inheritdoc IUniswapV3PoolActions - function increaseObservationCardinalityNext( - uint16 observationCardinalityNext - ) external override lock noDelegateCall { + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) + external + override + lock + noDelegateCall + { uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event uint16 observationCardinalityNextNew = observations.grow( observationCardinalityNextOld, @@ -291,22 +264,17 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { ); slot0.observationCardinalityNext = observationCardinalityNextNew; if (observationCardinalityNextOld != observationCardinalityNextNew) - emit IncreaseObservationCardinalityNext( - observationCardinalityNextOld, - observationCardinalityNextNew - ); + emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew); } /// @inheritdoc IUniswapV3PoolActions /// @dev not locked because it initializes unlocked function initialize(uint160 sqrtPriceX96) external override { - require(slot0.sqrtPriceX96 == 0, "AI"); + if (slot0.sqrtPriceX96 != 0) revert AI(); int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); - (uint16 cardinality, uint16 cardinalityNext) = observations.initialize( - _blockTimestamp() - ); + (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp()); slot0 = Slot0({ sqrtPriceX96: sqrtPriceX96, @@ -371,10 +339,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { uint128 liquidityBefore = liquidity; // SLOAD for gas optimization // write an oracle entry - ( - slot0.observationIndex, - slot0.observationCardinality - ) = observations.write( + (slot0.observationIndex, slot0.observationCardinality) = observations.write( _slot0.observationIndex, _blockTimestamp(), _slot0.tick, @@ -394,10 +359,9 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { params.liquidityDelta ); - liquidity = LiquidityMath.addDelta( - liquidityBefore, - params.liquidityDelta - ); + liquidity = params.liquidityDelta < 0 + ? liquidityBefore - uint128(-params.liquidityDelta) + : liquidityBefore + uint128(params.liquidityDelta); } else { // current tick is above the passed range; liquidity can only become in range by crossing from right to // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it @@ -432,17 +396,14 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { bool flippedUpper; if (liquidityDelta != 0) { uint32 time = _blockTimestamp(); - ( - int56 tickCumulative, - uint160 secondsPerLiquidityCumulativeX128 - ) = observations.observeSingle( - time, - 0, - slot0.tick, - slot0.observationIndex, - liquidity, - slot0.observationCardinality - ); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = observations.observeSingle( + time, + 0, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); flippedLower = ticks.update( tickLower, @@ -477,21 +438,16 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { } } - (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = ticks - .getFeeGrowthInside( - tickLower, - tickUpper, - tick, - _feeGrowthGlobal0X128, - _feeGrowthGlobal1X128 - ); - - position.update( - liquidityDelta, - feeGrowthInside0X128, - feeGrowthInside1X128 + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = ticks.getFeeGrowthInside( + tickLower, + tickUpper, + tick, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128 ); + position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128); + // clear any tick data that is no longer needed if (liquidityDelta < 0) { if (flippedLower) { @@ -518,7 +474,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { owner: recipient, tickLower: tickLower, tickUpper: tickUpper, - liquidityDelta: int256(amount).toInt128() + liquidityDelta: int256(uint256(amount)).toInt128() }) ); @@ -529,25 +485,11 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { uint256 balance1Before; if (amount0 > 0) balance0Before = balance0(); if (amount1 > 0) balance1Before = balance1(); - IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback( - amount0, - amount1, - data - ); - if (amount0 > 0) - require(balance0Before.add(amount0) <= balance0(), "M0"); - if (amount1 > 0) - require(balance1Before.add(amount1) <= balance1(), "M1"); - - emit Mint( - msg.sender, - recipient, - tickLower, - tickUpper, - amount, - amount0, - amount1 - ); + IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data); + if (amount0 > 0 && balance0Before + amount0 > balance0()) revert M0(); + if (amount1 > 0 && balance1Before + amount1 > balance1()) revert M1(); + + emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1); } /// @inheritdoc IUniswapV3PoolActions @@ -559,36 +501,23 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { uint128 amount1Requested ) external override lock returns (uint128 amount0, uint128 amount1) { // we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1} - Position.Info storage position = positions.get( - msg.sender, - tickLower, - tickUpper - ); + Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper); - amount0 = amount0Requested > position.tokensOwed0 - ? position.tokensOwed0 - : amount0Requested; - amount1 = amount1Requested > position.tokensOwed1 - ? position.tokensOwed1 - : amount1Requested; + amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested; + amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested; - if (amount0 > 0) { - position.tokensOwed0 -= amount0; - TransferHelper.safeTransfer(token0, recipient, amount0); - } - if (amount1 > 0) { - position.tokensOwed1 -= amount1; - TransferHelper.safeTransfer(token1, recipient, amount1); + unchecked { + if (amount0 > 0) { + position.tokensOwed0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + position.tokensOwed1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } } - emit Collect( - msg.sender, - recipient, - tickLower, - tickUpper, - amount0, - amount1 - ); + emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1); } /// @inheritdoc IUniswapV3PoolActions @@ -598,30 +527,28 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { int24 tickUpper, uint128 amount ) external override lock returns (uint256 amount0, uint256 amount1) { - ( - Position.Info storage position, - int256 amount0Int, - int256 amount1Int - ) = _modifyPosition( + unchecked { + (Position.Info storage position, int256 amount0Int, int256 amount1Int) = _modifyPosition( ModifyPositionParams({ owner: msg.sender, tickLower: tickLower, tickUpper: tickUpper, - liquidityDelta: -int256(amount).toInt128() + liquidityDelta: -int256(uint256(amount)).toInt128() }) ); - amount0 = uint256(-amount0Int); - amount1 = uint256(-amount1Int); + amount0 = uint256(-amount0Int); + amount1 = uint256(-amount1Int); - if (amount0 > 0 || amount1 > 0) { - (position.tokensOwed0, position.tokensOwed1) = ( - position.tokensOwed0 + uint128(amount0), - position.tokensOwed1 + uint128(amount1) - ); - } + if (amount0 > 0 || amount1 > 0) { + (position.tokensOwed0, position.tokensOwed1) = ( + position.tokensOwed0 + uint128(amount0), + position.tokensOwed1 + uint128(amount1) + ); + } - emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1); + emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1); + } } struct SwapCache { @@ -681,24 +608,17 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata data - ) - external - override - noDelegateCall - returns (int256 amount0, int256 amount1) - { - require(amountSpecified != 0, "AS"); + ) external override noDelegateCall returns (int256 amount0, int256 amount1) { + if (amountSpecified == 0) revert AS(); Slot0 memory slot0Start = slot0; - require(slot0Start.unlocked, "LOK"); + if (!slot0Start.unlocked) revert LOK(); require( zeroForOne - ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && - sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO - : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && - sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, - "SPL" + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + 'SPL' ); slot0.unlocked = false; @@ -706,9 +626,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { SwapCache memory cache = SwapCache({ liquidityStart: liquidity, blockTimestamp: _blockTimestamp(), - feeProtocol: zeroForOne - ? (slot0Start.feeProtocol % 16) - : (slot0Start.feeProtocol >> 4), + feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4), secondsPerLiquidityCumulativeX128: 0, tickCumulative: 0, computedLatestObservation: false @@ -721,28 +639,22 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { amountCalculated: 0, sqrtPriceX96: slot0Start.sqrtPriceX96, tick: slot0Start.tick, - feeGrowthGlobalX128: zeroForOne - ? feeGrowthGlobal0X128 - : feeGrowthGlobal1X128, + feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128, protocolFee: 0, liquidity: cache.liquidityStart }); // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit - while ( - state.amountSpecifiedRemaining != 0 && - state.sqrtPriceX96 != sqrtPriceLimitX96 - ) { + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) { StepComputations memory step; step.sqrtPriceStartX96 = state.sqrtPriceX96; - (step.tickNext, step.initialized) = tickBitmap - .nextInitializedTickWithinOneWord( - state.tick, - tickSpacing, - zeroForOne - ); + (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord( + state.tick, + tickSpacing, + 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) { @@ -755,18 +667,9 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { 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, - step.amountIn, - step.amountOut, - step.feeAmount - ) = SwapMath.computeSwapStep( - state.sqrtPriceX96, - ( - zeroForOne - ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 - : step.sqrtPriceNextX96 > sqrtPriceLimitX96 - ) + (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96) ? sqrtPriceLimitX96 : step.sqrtPriceNextX96, state.liquidity, @@ -775,32 +678,33 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { ); if (exactInput) { - state.amountSpecifiedRemaining -= (step.amountIn + - step.feeAmount).toInt256(); - state.amountCalculated = state.amountCalculated.sub( - step.amountOut.toInt256() - ); + // safe because we test that amountSpecified > amountIn + feeAmount in SwapMath + unchecked { + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); + } + state.amountCalculated -= step.amountOut.toInt256(); } else { - state.amountSpecifiedRemaining += step.amountOut.toInt256(); - state.amountCalculated = state.amountCalculated.add( - (step.amountIn + step.feeAmount).toInt256() - ); + unchecked { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + } + state.amountCalculated += (step.amountIn + step.feeAmount).toInt256(); } // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee if (cache.feeProtocol > 0) { - uint256 delta = step.feeAmount / cache.feeProtocol; - step.feeAmount -= delta; - state.protocolFee += uint128(delta); + unchecked { + uint256 delta = step.feeAmount / cache.feeProtocol; + step.feeAmount -= delta; + state.protocolFee += uint128(delta); + } } // update global fee tracker - if (state.liquidity > 0) - state.feeGrowthGlobalX128 += FullMath.mulDiv( - step.feeAmount, - FixedPoint128.Q128, - state.liquidity - ); + if (state.liquidity > 0) { + unchecked { + state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity); + } + } // shift tick if we reached the next price if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { @@ -809,10 +713,7 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { // check for the placeholder value, which we replace with the actual value the first time the swap // crosses an initialized tick if (!cache.computedLatestObservation) { - ( - cache.tickCumulative, - cache.secondsPerLiquidityCumulativeX128 - ) = observations.observeSingle( + (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle( cache.blockTimestamp, 0, slot0Start.tick, @@ -824,31 +725,26 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { } int128 liquidityNet = ticks.cross( step.tickNext, - ( - zeroForOne - ? state.feeGrowthGlobalX128 - : feeGrowthGlobal0X128 - ), - ( - zeroForOne - ? feeGrowthGlobal1X128 - : state.feeGrowthGlobalX128 - ), + (zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128), + (zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128), cache.secondsPerLiquidityCumulativeX128, cache.tickCumulative, cache.blockTimestamp ); // if we're moving leftward, we interpret liquidityNet as the opposite sign // safe because liquidityNet cannot be type(int128).min - if (zeroForOne) liquidityNet = -liquidityNet; + unchecked { + if (zeroForOne) liquidityNet = -liquidityNet; + } - state.liquidity = LiquidityMath.addDelta( - state.liquidity, - liquidityNet - ); + state.liquidity = liquidityNet < 0 + ? state.liquidity - uint128(-liquidityNet) + : state.liquidity + uint128(liquidityNet); } - state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + unchecked { + 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); @@ -857,23 +753,15 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { // update tick and write an oracle entry if the tick change if (state.tick != slot0Start.tick) { - ( - uint16 observationIndex, - uint16 observationCardinality - ) = observations.write( - slot0Start.observationIndex, - cache.blockTimestamp, - slot0Start.tick, - cache.liquidityStart, - slot0Start.observationCardinality, - slot0Start.observationCardinalityNext - ); - ( - slot0.sqrtPriceX96, - slot0.tick, - slot0.observationIndex, - slot0.observationCardinality - ) = ( + (uint16 observationIndex, uint16 observationCardinality) = observations.write( + slot0Start.observationIndex, + cache.blockTimestamp, + slot0Start.tick, + cache.liquidityStart, + slot0Start.observationCardinality, + slot0Start.observationCardinalityNext + ); + (slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = ( state.sqrtPriceX96, state.tick, observationIndex, @@ -885,71 +773,48 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { } // update liquidity if it changed - if (cache.liquidityStart != state.liquidity) - liquidity = state.liquidity; + if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity; // update fee growth global and, if necessary, protocol fees // overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees if (zeroForOne) { feeGrowthGlobal0X128 = state.feeGrowthGlobalX128; - if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee; + unchecked { + if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee; + } } else { feeGrowthGlobal1X128 = state.feeGrowthGlobalX128; - if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee; + unchecked { + if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee; + } } - (amount0, amount1) = zeroForOne == exactInput - ? ( - amountSpecified - state.amountSpecifiedRemaining, - state.amountCalculated - ) - : ( - state.amountCalculated, - amountSpecified - state.amountSpecifiedRemaining - ); + unchecked { + (amount0, amount1) = zeroForOne == exactInput + ? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated) + : (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining); + } // do the transfers and collect payment if (zeroForOne) { - if (amount1 < 0) - TransferHelper.safeTransfer( - token1, - recipient, - uint256(-amount1) - ); + unchecked { + if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1)); + } uint256 balance0Before = balance0(); - IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback( - amount0, - amount1, - data - ); - require(balance0Before.add(uint256(amount0)) <= balance0(), "IIA"); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + if (balance0Before + uint256(amount0) > balance0()) revert IIA(); } else { - if (amount0 < 0) - TransferHelper.safeTransfer( - token0, - recipient, - uint256(-amount0) - ); + unchecked { + if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0)); + } uint256 balance1Before = balance1(); - IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback( - amount0, - amount1, - data - ); - require(balance1Before.add(uint256(amount1)) <= balance1(), "IIA"); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + if (balance1Before + uint256(amount1) > balance1()) revert IIA(); } - emit Swap( - msg.sender, - recipient, - amount0, - amount1, - state.sqrtPriceX96, - state.liquidity, - state.tick - ); + emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick); slot0.unlocked = true; } @@ -961,77 +826,57 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { bytes calldata data ) external override lock noDelegateCall { uint128 _liquidity = liquidity; - require(_liquidity > 0, "L"); + if (_liquidity <= 0) revert L(); uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6); uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6); uint256 balance0Before = balance0(); uint256 balance1Before = balance1(); - if (amount0 > 0) - TransferHelper.safeTransfer(token0, recipient, amount0); - if (amount1 > 0) - TransferHelper.safeTransfer(token1, recipient, amount1); + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); - IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback( - fee0, - fee1, - data - ); + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); uint256 balance0After = balance0(); uint256 balance1After = balance1(); - require(balance0Before.add(fee0) <= balance0After, "F0"); - require(balance1Before.add(fee1) <= balance1After, "F1"); - - // sub is safe because we know balanceAfter is gt balanceBefore by at least fee - uint256 paid0 = balance0After - balance0Before; - uint256 paid1 = balance1After - balance1Before; - - if (paid0 > 0) { - uint8 feeProtocol0 = slot0.feeProtocol % 16; - uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0; - if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0); - feeGrowthGlobal0X128 += FullMath.mulDiv( - paid0 - fees0, - FixedPoint128.Q128, - _liquidity - ); - } - if (paid1 > 0) { - uint8 feeProtocol1 = slot0.feeProtocol >> 4; - uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1; - if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1); - feeGrowthGlobal1X128 += FullMath.mulDiv( - paid1 - fees1, - FixedPoint128.Q128, - _liquidity - ); - } + if (balance0Before + fee0 > balance0After) revert F0(); + if (balance1Before + fee1 > balance1After) revert F1(); + + unchecked { + // sub is safe because we know balanceAfter is gt balanceBefore by at least fee + uint256 paid0 = balance0After - balance0Before; + uint256 paid1 = balance1After - balance1Before; - emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1); + if (paid0 > 0) { + uint8 feeProtocol0 = slot0.feeProtocol % 16; + uint256 pFees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0; + if (uint128(pFees0) > 0) protocolFees.token0 += uint128(pFees0); + feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - pFees0, FixedPoint128.Q128, _liquidity); + } + if (paid1 > 0) { + uint8 feeProtocol1 = slot0.feeProtocol >> 4; + uint256 pFees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1; + if (uint128(pFees1) > 0) protocolFees.token1 += uint128(pFees1); + feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - pFees1, FixedPoint128.Q128, _liquidity); + } + + emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1); + } } /// @inheritdoc IUniswapV3PoolOwnerActions - function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) - external - override - lock - onlyFactoryOwner - { - require( - (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) && - (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)) - ); - uint8 feeProtocolOld = slot0.feeProtocol; - slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4); - emit SetFeeProtocol( - feeProtocolOld % 16, - feeProtocolOld >> 4, - feeProtocol0, - feeProtocol1 - ); + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner { + unchecked { + require( + (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) && + (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)) + ); + uint8 feeProtocolOld = slot0.feeProtocol; + slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4); + emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1); + } } /// @inheritdoc IUniswapV3PoolOwnerActions @@ -1039,29 +884,21 @@ contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { address recipient, uint128 amount0Requested, uint128 amount1Requested - ) - external - override - lock - onlyFactoryOwner - returns (uint128 amount0, uint128 amount1) - { - amount0 = amount0Requested > protocolFees.token0 - ? protocolFees.token0 - : amount0Requested; - amount1 = amount1Requested > protocolFees.token1 - ? protocolFees.token1 - : amount1Requested; - - if (amount0 > 0) { - if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings - protocolFees.token0 -= amount0; - TransferHelper.safeTransfer(token0, recipient, amount0); - } - if (amount1 > 0) { - if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings - protocolFees.token1 -= amount1; - TransferHelper.safeTransfer(token1, recipient, amount1); + ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) { + amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested; + amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested; + + unchecked { + if (amount0 > 0) { + if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings + protocolFees.token0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings + protocolFees.token1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } } emit CollectProtocol(msg.sender, recipient, amount0, amount1); diff --git a/contracts/dependencies/univ3/UniswapV3PoolDeployer.sol b/contracts/dependencies/uniswapv3-core/UniswapV3PoolDeployer.sol similarity index 71% rename from contracts/dependencies/univ3/UniswapV3PoolDeployer.sol rename to contracts/dependencies/uniswapv3-core/UniswapV3PoolDeployer.sol index 8af4b6d27..13ed06595 100644 --- a/contracts/dependencies/univ3/UniswapV3PoolDeployer.sol +++ b/contracts/dependencies/uniswapv3-core/UniswapV3PoolDeployer.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.7.6; +pragma solidity 0.8.10; -import "./interfaces/IUniswapV3PoolDeployer.sol"; +import {IUniswapV3PoolDeployer} from './interfaces/IUniswapV3PoolDeployer.sol'; -import "./UniswapV3Pool.sol"; +import {UniswapV3Pool} from './UniswapV3Pool.sol'; contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer { struct Parameters { @@ -31,18 +31,8 @@ contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer { uint24 fee, int24 tickSpacing ) internal returns (address pool) { - parameters = Parameters({ - factory: factory, - token0: token0, - token1: token1, - fee: fee, - tickSpacing: tickSpacing - }); - pool = address( - new UniswapV3Pool{ - salt: keccak256(abi.encode(token0, token1, fee)) - }() - ); + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); delete parameters; } } diff --git a/contracts/dependencies/uniswapv3-core/interfaces/IERC20Minimal.sol b/contracts/dependencies/uniswapv3-core/interfaces/IERC20Minimal.sol new file mode 100644 index 000000000..5785071d9 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/IERC20Minimal.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IERC20Minimal { + /// @notice Returns the balance of a token + /// @param account The account for which to look up the number of tokens it has, i.e. its balance + /// @return The number of tokens held by the account + function balanceOf(address account) external view returns (uint256); + + /// @notice Transfers the amount of token from the `msg.sender` to the recipient + /// @param recipient The account that will receive the amount transferred + /// @param amount The number of tokens to send from the sender to the recipient + /// @return Returns true for a successful transfer, false for an unsuccessful transfer + function transfer(address recipient, uint256 amount) external returns (bool); + + /// @notice Returns the current allowance given to a spender by an owner + /// @param owner The account of the token owner + /// @param spender The account of the token spender + /// @return The current allowance granted by `owner` to `spender` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` + /// @param spender The account which will be allowed to spend a given amount of the owners tokens + /// @param amount The amount of tokens allowed to be used by `spender` + /// @return Returns true for a successful approval, false for unsuccessful + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` + /// @param sender The account from which the transfer will be initiated + /// @param recipient The recipient of the transfer + /// @param amount The amount of the transfer + /// @return Returns true for a successful transfer, false for unsuccessful + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. + /// @param from The account from which the tokens were sent, i.e. the balance decreased + /// @param to The account to which the tokens were sent, i.e. the balance increased + /// @param value The amount of tokens that were transferred + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. + /// @param owner The account that approved spending of its tokens + /// @param spender The account for which the spending allowance was modified + /// @param value The new allowance from the owner to the spender + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Factory.sol b/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Factory.sol new file mode 100644 index 000000000..6faddad7c --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Factory.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @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; +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Pool.sol b/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Pool.sol new file mode 100644 index 000000000..ee93905a6 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3Pool.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import {IUniswapV3PoolImmutables} from './pool/IUniswapV3PoolImmutables.sol'; +import {IUniswapV3PoolState} from './pool/IUniswapV3PoolState.sol'; +import {IUniswapV3PoolDerivedState} from './pool/IUniswapV3PoolDerivedState.sol'; +import {IUniswapV3PoolActions} from './pool/IUniswapV3PoolActions.sol'; +import {IUniswapV3PoolOwnerActions} from './pool/IUniswapV3PoolOwnerActions.sol'; +import {IUniswapV3PoolErrors} from './pool/IUniswapV3PoolErrors.sol'; +import {IUniswapV3PoolEvents} from './pool/IUniswapV3PoolEvents.sol'; + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolErrors, + IUniswapV3PoolEvents +{ + +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3PoolDeployer.sol b/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3PoolDeployer.sol new file mode 100644 index 000000000..11e93f099 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/IUniswapV3PoolDeployer.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title An interface for a contract that is capable of deploying Uniswap V3 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 +interface IUniswapV3PoolDeployer { + /// @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 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 + /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// Returns tickSpacing The minimum number of ticks between initialized ticks + function parameters() + external + view + returns ( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ); +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/LICENSE b/contracts/dependencies/uniswapv3-core/interfaces/LICENSE new file mode 100644 index 000000000..7f6aca78c --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/LICENSE @@ -0,0 +1,445 @@ +This software is available under your choice of the GNU General Public +License, version 2 or later, or the Business Source License, as set +forth below. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + + +Business Source License 1.1 + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +Licensor: Uniswap Labs + +Licensed Work: Uniswap V3 Core + The Licensed Work is (c) 2021 Uniswap Labs + +Additional Use Grant: Any uses listed and defined at + v3-core-license-grants.uniswap.eth + +Change Date: The earlier of 2023-04-01 or a date specified at + v3-core-license-date.uniswap.eth + +Change License: GNU General Public License v2.0 or later + +----------------------------------------------------------------------------- + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark "Business Source License", +as long as you comply with the Covenants of Licensor below. + +----------------------------------------------------------------------------- + +Covenants of Licensor + +In consideration of the right to use this License’s text and the "Business +Source License" name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where "compatible" means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text "None". + +3. To specify a Change Date. + +4. Not to modify this License in any other way. + +----------------------------------------------------------------------------- + +Notice + +The Business Source License (this document, or the "License") is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. diff --git a/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3FlashCallback.sol b/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3FlashCallback.sol new file mode 100644 index 000000000..cbc982107 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3FlashCallback.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Callback for IUniswapV3PoolActions#flash +/// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface +interface IUniswapV3FlashCallback { + /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#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 UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @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 IUniswapV3PoolActions#flash call + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3MintCallback.sol b/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3MintCallback.sol new file mode 100644 index 000000000..8da62b52a --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3MintCallback.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Callback for IUniswapV3PoolActions#mint +/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface +interface IUniswapV3MintCallback { + /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#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 UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @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 IUniswapV3PoolActions#mint call + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external; +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3SwapCallback.sol b/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3SwapCallback.sol new file mode 100644 index 000000000..225d50b56 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/callback/IUniswapV3SwapCallback.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolActions.sol b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolActions.sol new file mode 100644 index 000000000..033332277 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolActions.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @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/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolDerivedState.sol b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolDerivedState.sol new file mode 100644 index 000000000..9f78fff6d --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolDerivedState.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); + + /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range + /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. + /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first + /// snapshot is taken and the second snapshot is taken. + /// @param tickLower The lower tick of the range + /// @param tickUpper The upper tick of the range + /// @return tickCumulativeInside The snapshot of the tick accumulator for the range + /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range + /// @return secondsInside The snapshot of seconds per liquidity for the range + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolErrors.sol b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolErrors.sol new file mode 100644 index 000000000..9891164d2 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolErrors.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Errors emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolErrors { + error LOK(); + error TLU(); + error TLM(); + error TUM(); + error AI(); + error M0(); + error M1(); + error AS(); + error IIA(); + error L(); + error F0(); + error F1(); +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolEvents.sol b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolEvents.sol new file mode 100644 index 000000000..1a34c4d6b --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolEvents.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolImmutables.sol b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolImmutables.sol new file mode 100644 index 000000000..f85cebcf1 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolImmutables.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @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/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolOwnerActions.sol b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolOwnerActions.sol new file mode 100644 index 000000000..08c4d1ba1 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolOwnerActions.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} diff --git a/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolState.sol b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolState.sol new file mode 100644 index 000000000..b41e81d75 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/interfaces/pool/IUniswapV3PoolState.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @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 + /// @return 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. + /// @return observationIndex The index of the last oracle observation that was written, + /// @return observationCardinality The current maximum number of observations stored in the pool, + /// @return observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// @return 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 + /// @return The liquidity at the current price of the pool + 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 + /// @return liquidityNet how much liquidity changes when the pool price crosses the tick, + /// @return feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// @return feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// @return tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// @return secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// @return secondsOutside the seconds spent on the other side of the tick from the current tick, + /// @return 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, + /// @return feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// @return feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// @return tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// @return 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, + /// @return tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// @return secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// @return 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/dependencies/uniswapv3-core/libraries/BitMath.sol b/contracts/dependencies/uniswapv3-core/libraries/BitMath.sol new file mode 100644 index 000000000..4e22e1ba3 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/BitMath.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.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); + + unchecked { + 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); + + unchecked { + 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/dependencies/uniswapv3-core/libraries/FixedPoint128.sol b/contracts/dependencies/uniswapv3-core/libraries/FixedPoint128.sol new file mode 100644 index 000000000..6d6948b10 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/FixedPoint128.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.4.0; + +/// @title FixedPoint128 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +library FixedPoint128 { + uint256 internal constant Q128 = 0x100000000000000000000000000000000; +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/FixedPoint96.sol b/contracts/dependencies/uniswapv3-core/libraries/FixedPoint96.sol new file mode 100644 index 000000000..63b42c294 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/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/dependencies/uniswapv3-core/libraries/FullMath.sol b/contracts/dependencies/uniswapv3-core/libraries/FullMath.sol new file mode 100644 index 000000000..e357426a1 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/FullMath.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^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) { + unchecked { + // 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 = (0 - 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) { + unchecked { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } + } +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/Oracle.sol b/contracts/dependencies/uniswapv3-core/libraries/Oracle.sol new file mode 100644 index 000000000..2aacc9fdb --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/Oracle.sol @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +/// @title Oracle +/// @notice Provides price and liquidity data useful for a wide variety of system designs +/// @dev Instances of stored oracle data, "observations", are collected in the oracle array +/// Every pool is initialized with an oracle array length of 1. Anyone can pay the SSTOREs to increase the +/// maximum length of the oracle array. New slots will be added when the array is fully populated. +/// Observations are overwritten when the full length of the oracle array is populated. +/// The most recent observation is available, independent of the length of the oracle array, by passing 0 to observe() +library Oracle { + error I(); + error OLD(); + + struct Observation { + // the block timestamp of the observation + uint32 blockTimestamp; + // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized + int56 tickCumulative; + // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized + uint160 secondsPerLiquidityCumulativeX128; + // whether or not the observation is initialized + bool initialized; + } + + /// @notice Transforms a previous observation into a new observation, given the passage of time and the current tick and liquidity values + /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows + /// @param last The specified observation to be transformed + /// @param blockTimestamp The timestamp of the new observation + /// @param tick The active tick at the time of the new observation + /// @param liquidity The total in-range liquidity at the time of the new observation + /// @return Observation The newly populated observation + function transform( + Observation memory last, + uint32 blockTimestamp, + int24 tick, + uint128 liquidity + ) private pure returns (Observation memory) { + unchecked { + uint32 delta = blockTimestamp - last.blockTimestamp; + return + Observation({ + blockTimestamp: blockTimestamp, + tickCumulative: last.tickCumulative + int56(tick) * int56(uint56(delta)), + secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128 + + ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)), + initialized: true + }); + } + } + + /// @notice Initialize the oracle array by writing the first slot. Called once for the lifecycle of the observations array + /// @param self The stored oracle array + /// @param time The time of the oracle initialization, via block.timestamp truncated to uint32 + /// @return cardinality The number of populated elements in the oracle array + /// @return cardinalityNext The new length of the oracle array, independent of population + function initialize(Observation[65535] storage self, uint32 time) + internal + returns (uint16 cardinality, uint16 cardinalityNext) + { + self[0] = Observation({ + blockTimestamp: time, + tickCumulative: 0, + secondsPerLiquidityCumulativeX128: 0, + initialized: true + }); + return (1, 1); + } + + /// @notice Writes an oracle observation to the array + /// @dev Writable at most once per block. Index represents the most recently written element. cardinality and index must be tracked externally. + /// If the index is at the end of the allowable array length (according to cardinality), and the next cardinality + /// is greater than the current one, cardinality may be increased. This restriction is created to preserve ordering. + /// @param self The stored oracle array + /// @param index The index of the observation that was most recently written to the observations array + /// @param blockTimestamp The timestamp of the new observation + /// @param tick The active tick at the time of the new observation + /// @param liquidity The total in-range liquidity at the time of the new observation + /// @param cardinality The number of populated elements in the oracle array + /// @param cardinalityNext The new length of the oracle array, independent of population + /// @return indexUpdated The new index of the most recently written element in the oracle array + /// @return cardinalityUpdated The new cardinality of the oracle array + function write( + Observation[65535] storage self, + uint16 index, + uint32 blockTimestamp, + int24 tick, + uint128 liquidity, + uint16 cardinality, + uint16 cardinalityNext + ) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) { + unchecked { + Observation memory last = self[index]; + + // early return if we've already written an observation this block + if (last.blockTimestamp == blockTimestamp) return (index, cardinality); + + // if the conditions are right, we can bump the cardinality + if (cardinalityNext > cardinality && index == (cardinality - 1)) { + cardinalityUpdated = cardinalityNext; + } else { + cardinalityUpdated = cardinality; + } + + indexUpdated = (index + 1) % cardinalityUpdated; + self[indexUpdated] = transform(last, blockTimestamp, tick, liquidity); + } + } + + /// @notice Prepares the oracle array to store up to `next` observations + /// @param self The stored oracle array + /// @param current The current next cardinality of the oracle array + /// @param next The proposed next cardinality which will be populated in the oracle array + /// @return next The next cardinality which will be populated in the oracle array + function grow( + Observation[65535] storage self, + uint16 current, + uint16 next + ) internal returns (uint16) { + unchecked { + if (current <= 0) revert I(); + // no-op if the passed next value isn't greater than the current next value + if (next <= current) return current; + // store in each slot to prevent fresh SSTOREs in swaps + // this data will not be used because the initialized boolean is still false + for (uint16 i = current; i < next; i++) self[i].blockTimestamp = 1; + return next; + } + } + + /// @notice comparator for 32-bit timestamps + /// @dev safe for 0 or 1 overflows, a and b _must_ be chronologically before or equal to time + /// @param time A timestamp truncated to 32 bits + /// @param a A comparison timestamp from which to determine the relative position of `time` + /// @param b From which to determine the relative position of `time` + /// @return Whether `a` is chronologically <= `b` + function lte( + uint32 time, + uint32 a, + uint32 b + ) private pure returns (bool) { + unchecked { + // if there hasn't been overflow, no need to adjust + if (a <= time && b <= time) return a <= b; + + uint256 aAdjusted = a > time ? a : a + 2**32; + uint256 bAdjusted = b > time ? b : b + 2**32; + + return aAdjusted <= bAdjusted; + } + } + + /// @notice Fetches the observations beforeOrAt and atOrAfter a target, i.e. where [beforeOrAt, atOrAfter] is satisfied. + /// The result may be the same observation, or adjacent observations. + /// @dev The answer must be contained in the array, used when the target is located within the stored observation + /// boundaries: older than the most recent observation and younger, or the same age as, the oldest observation + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param target The timestamp at which the reserved observation should be for + /// @param index The index of the observation that was most recently written to the observations array + /// @param cardinality The number of populated elements in the oracle array + /// @return beforeOrAt The observation recorded before, or at, the target + /// @return atOrAfter The observation recorded at, or after, the target + function binarySearch( + Observation[65535] storage self, + uint32 time, + uint32 target, + uint16 index, + uint16 cardinality + ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { + unchecked { + uint256 l = (index + 1) % cardinality; // oldest observation + uint256 r = l + cardinality - 1; // newest observation + uint256 i; + while (true) { + i = (l + r) / 2; + + beforeOrAt = self[i % cardinality]; + + // we've landed on an uninitialized tick, keep searching higher (more recently) + if (!beforeOrAt.initialized) { + l = i + 1; + continue; + } + + atOrAfter = self[(i + 1) % cardinality]; + + bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target); + + // check if we've found the answer! + if (targetAtOrAfter && lte(time, target, atOrAfter.blockTimestamp)) break; + + if (!targetAtOrAfter) r = i - 1; + else l = i + 1; + } + } + } + + /// @notice Fetches the observations beforeOrAt and atOrAfter a given target, i.e. where [beforeOrAt, atOrAfter] is satisfied + /// @dev Assumes there is at least 1 initialized observation. + /// Used by observeSingle() to compute the counterfactual accumulator values as of a given block timestamp. + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param target The timestamp at which the reserved observation should be for + /// @param tick The active tick at the time of the returned or simulated observation + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The total pool liquidity at the time of the call + /// @param cardinality The number of populated elements in the oracle array + /// @return beforeOrAt The observation which occurred at, or before, the given timestamp + /// @return atOrAfter The observation which occurred at, or after, the given timestamp + function getSurroundingObservations( + Observation[65535] storage self, + uint32 time, + uint32 target, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { + unchecked { + // optimistically set before to the newest observation + beforeOrAt = self[index]; + + // if the target is chronologically at or after the newest observation, we can early return + if (lte(time, beforeOrAt.blockTimestamp, target)) { + if (beforeOrAt.blockTimestamp == target) { + // if newest observation equals target, we're in the same block, so we can ignore atOrAfter + return (beforeOrAt, atOrAfter); + } else { + // otherwise, we need to transform + return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity)); + } + } + + // now, set before to the oldest observation + beforeOrAt = self[(index + 1) % cardinality]; + if (!beforeOrAt.initialized) beforeOrAt = self[0]; + + // ensure that the target is chronologically at or after the oldest observation + if (!lte(time, beforeOrAt.blockTimestamp, target)) revert OLD(); + + // if we've reached this point, we have to binary search + return binarySearch(self, time, target, index, cardinality); + } + } + + /// @dev Reverts if an observation at or before the desired observation timestamp does not exist. + /// 0 may be passed as `secondsAgo' to return the current cumulative values. + /// If called with a timestamp falling between two observations, returns the counterfactual accumulator values + /// at exactly the timestamp between the two observations. + /// @param self The stored oracle array + /// @param time The current block timestamp + /// @param secondsAgo The amount of time to look back, in seconds, at which point to return an observation + /// @param tick The current tick + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The current in-range pool liquidity + /// @param cardinality The number of populated elements in the oracle array + /// @return tickCumulative The tick * time elapsed since the pool was first initialized, as of `secondsAgo` + /// @return secondsPerLiquidityCumulativeX128 The time elapsed / max(1, liquidity) since the pool was first initialized, as of `secondsAgo` + function observeSingle( + Observation[65535] storage self, + uint32 time, + uint32 secondsAgo, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) internal view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) { + unchecked { + if (secondsAgo == 0) { + Observation memory last = self[index]; + if (last.blockTimestamp != time) last = transform(last, time, tick, liquidity); + return (last.tickCumulative, last.secondsPerLiquidityCumulativeX128); + } + + uint32 target = time - secondsAgo; + + (Observation memory beforeOrAt, Observation memory atOrAfter) = getSurroundingObservations( + self, + time, + target, + tick, + index, + liquidity, + cardinality + ); + + if (target == beforeOrAt.blockTimestamp) { + // we're at the left boundary + return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128); + } else if (target == atOrAfter.blockTimestamp) { + // we're at the right boundary + return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128); + } else { + // we're in the middle + uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp; + uint32 targetDelta = target - beforeOrAt.blockTimestamp; + return ( + beforeOrAt.tickCumulative + + ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / int56(uint56(observationTimeDelta))) * + int56(uint56(targetDelta)), + beforeOrAt.secondsPerLiquidityCumulativeX128 + + uint160( + (uint256( + atOrAfter.secondsPerLiquidityCumulativeX128 - + beforeOrAt.secondsPerLiquidityCumulativeX128 + ) * targetDelta) / observationTimeDelta + ) + ); + } + } + } + + /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` + /// @dev Reverts if `secondsAgos` > oldest observation + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an observation + /// @param tick The current tick + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The current in-range pool liquidity + /// @param cardinality The number of populated elements in the oracle array + /// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo` + /// @return secondsPerLiquidityCumulativeX128s The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo` + function observe( + Observation[65535] storage self, + uint32 time, + uint32[] memory secondsAgos, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) { + unchecked { + if (cardinality <= 0) revert I(); + + tickCumulatives = new int56[](secondsAgos.length); + secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length); + for (uint256 i = 0; i < secondsAgos.length; i++) { + (tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) = observeSingle( + self, + time, + secondsAgos[i], + tick, + index, + liquidity, + cardinality + ); + } + } + } +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/Position.sol b/contracts/dependencies/uniswapv3-core/libraries/Position.sol new file mode 100644 index 000000000..4e1837058 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/Position.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {FullMath} from './FullMath.sol'; +import {FixedPoint128} from './FixedPoint128.sol'; + +/// @title Position +/// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary +/// @dev Positions store additional state for tracking fees owed to the position +library Position { + error NP(); + + // info stored for each user's position + struct Info { + // the amount of liquidity owned by this position + uint128 liquidity; + // fee growth per unit of liquidity as of the last update to liquidity or fees owed + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // the fees owed to the position owner in token0/token1 + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + /// @notice Returns the Info struct of a position, given an owner and position boundaries + /// @param self The mapping containing all user positions + /// @param owner The address of the position owner + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @return position The position info struct of the given owners' position + function get( + mapping(bytes32 => Info) storage self, + address owner, + int24 tickLower, + int24 tickUpper + ) internal view returns (Position.Info storage position) { + position = self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))]; + } + + /// @notice Credits accumulated fees to a user's position + /// @param self The individual position to update + /// @param liquidityDelta The change in pool liquidity as a result of the position update + /// @param feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries + /// @param feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries + function update( + Info storage self, + int128 liquidityDelta, + uint256 feeGrowthInside0X128, + uint256 feeGrowthInside1X128 + ) internal { + Info memory _self = self; + + uint128 liquidityNext; + if (liquidityDelta == 0) { + if (_self.liquidity <= 0) revert NP(); // disallow pokes for 0 liquidity positions + liquidityNext = _self.liquidity; + } else { + liquidityNext = liquidityDelta < 0 + ? _self.liquidity - uint128(-liquidityDelta) + : _self.liquidity + uint128(liquidityDelta); + } + + // calculate accumulated fees. overflow in the subtraction of fee growth is expected + uint128 tokensOwed0; + uint128 tokensOwed1; + unchecked { + tokensOwed0 = uint128( + FullMath.mulDiv( + feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + tokensOwed1 = uint128( + FullMath.mulDiv( + feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + + // update the position + if (liquidityDelta != 0) self.liquidity = liquidityNext; + self.feeGrowthInside0LastX128 = feeGrowthInside0X128; + self.feeGrowthInside1LastX128 = feeGrowthInside1X128; + if (tokensOwed0 > 0 || tokensOwed1 > 0) { + // overflow is acceptable, user must withdraw before they hit type(uint128).max fees + self.tokensOwed0 += tokensOwed0; + self.tokensOwed1 += tokensOwed1; + } + } + } +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/SafeCast.sol b/contracts/dependencies/uniswapv3-core/libraries/SafeCast.sol new file mode 100644 index 000000000..a8ea22987 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/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/dependencies/uniswapv3-core/libraries/SqrtPriceMath.sol b/contracts/dependencies/uniswapv3-core/libraries/SqrtPriceMath.sol new file mode 100644 index 000000000..a3369e18d --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/SqrtPriceMath.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {SafeCast} from './SafeCast.sol'; + +import {FullMath} from './FullMath.sol'; +import {UnsafeMath} from './UnsafeMath.sol'; +import {FixedPoint96} from './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 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) { + unchecked { + 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)); + } + } + // denominator is checked for overflow + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96) + amount)); + } else { + unchecked { + 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) + 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 + unchecked { + 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) { + unchecked { + 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) { + unchecked { + 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) { + unchecked { + 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) { + unchecked { + return + liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + } +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/SwapMath.sol b/contracts/dependencies/uniswapv3-core/libraries/SwapMath.sol new file mode 100644 index 000000000..ddb8b6859 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/SwapMath.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {FullMath} from './FullMath.sol'; +import {SqrtPriceMath} from './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 + ) + { + unchecked { + 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/dependencies/uniswapv3-core/libraries/Tick.sol b/contracts/dependencies/uniswapv3-core/libraries/Tick.sol new file mode 100644 index 000000000..97f8c5768 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/Tick.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {SafeCast} from './SafeCast.sol'; + +import {TickMath} from './TickMath.sol'; + +/// @title Tick +/// @notice Contains functions for managing tick processes and relevant calculations +library Tick { + error LO(); + + using SafeCast for int256; + + // info stored for each initialized individual tick + struct Info { + // the total position liquidity that references this tick + uint128 liquidityGross; + // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), + int128 liquidityNet; + // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint256 feeGrowthOutside0X128; + uint256 feeGrowthOutside1X128; + // the cumulative tick value on the other side of the tick + int56 tickCumulativeOutside; + // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint160 secondsPerLiquidityOutsideX128; + // the seconds spent on the other side of the tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint32 secondsOutside; + // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 + // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks + bool initialized; + } + + /// @notice Derives max liquidity per tick from given tick spacing + /// @dev Executed within the pool constructor + /// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing` + /// e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ... + /// @return The max liquidity per tick + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128) { + unchecked { + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1; + return type(uint128).max / numTicks; + } + } + + /// @notice Retrieves fee growth data + /// @param self The mapping containing all tick information for initialized ticks + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @param tickCurrent The current tick + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @return feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries + /// @return feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries + function getFeeGrowthInside( + mapping(int24 => Tick.Info) storage self, + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + unchecked { + Info storage lower = self[tickLower]; + Info storage upper = self[tickUpper]; + + // calculate fee growth below + uint256 feeGrowthBelow0X128; + uint256 feeGrowthBelow1X128; + if (tickCurrent >= tickLower) { + feeGrowthBelow0X128 = lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = lower.feeGrowthOutside1X128; + } else { + feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128; + } + + // calculate fee growth above + uint256 feeGrowthAbove0X128; + uint256 feeGrowthAbove1X128; + if (tickCurrent < tickUpper) { + feeGrowthAbove0X128 = upper.feeGrowthOutside0X128; + feeGrowthAbove1X128 = upper.feeGrowthOutside1X128; + } else { + feeGrowthAbove0X128 = feeGrowthGlobal0X128 - upper.feeGrowthOutside0X128; + feeGrowthAbove1X128 = feeGrowthGlobal1X128 - upper.feeGrowthOutside1X128; + } + + feeGrowthInside0X128 = feeGrowthGlobal0X128 - feeGrowthBelow0X128 - feeGrowthAbove0X128; + feeGrowthInside1X128 = feeGrowthGlobal1X128 - feeGrowthBelow1X128 - feeGrowthAbove1X128; + } + } + + /// @notice Updates a tick and returns true if the tick was flipped from initialized to uninitialized, or vice versa + /// @param self The mapping containing all tick information for initialized ticks + /// @param tick The tick that will be updated + /// @param tickCurrent The current tick + /// @param liquidityDelta A new amount of liquidity to be added (subtracted) when tick is crossed from left to right (right to left) + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @param secondsPerLiquidityCumulativeX128 The all-time seconds per max(1, liquidity) of the pool + /// @param tickCumulative The tick * time elapsed since the pool was first initialized + /// @param time The current block timestamp cast to a uint32 + /// @param upper true for updating a position's upper tick, or false for updating a position's lower tick + /// @param maxLiquidity The maximum liquidity allocation for a single tick + /// @return flipped Whether the tick was flipped from initialized to uninitialized, or vice versa + function update( + mapping(int24 => Tick.Info) storage self, + int24 tick, + int24 tickCurrent, + int128 liquidityDelta, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time, + bool upper, + uint128 maxLiquidity + ) internal returns (bool flipped) { + Tick.Info storage info = self[tick]; + + uint128 liquidityGrossBefore = info.liquidityGross; + uint128 liquidityGrossAfter = liquidityDelta < 0 + ? liquidityGrossBefore - uint128(-liquidityDelta) + : liquidityGrossBefore + uint128(liquidityDelta); + + if (liquidityGrossAfter > maxLiquidity) revert LO(); + + flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0); + + if (liquidityGrossBefore == 0) { + // by convention, we assume that all growth before a tick was initialized happened _below_ the tick + if (tick <= tickCurrent) { + info.feeGrowthOutside0X128 = feeGrowthGlobal0X128; + info.feeGrowthOutside1X128 = feeGrowthGlobal1X128; + info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128; + info.tickCumulativeOutside = tickCumulative; + info.secondsOutside = time; + } + info.initialized = true; + } + + info.liquidityGross = liquidityGrossAfter; + + // when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed) + info.liquidityNet = upper ? info.liquidityNet - liquidityDelta : info.liquidityNet + liquidityDelta; + } + + /// @notice Clears tick data + /// @param self The mapping containing all initialized tick information for initialized ticks + /// @param tick The tick that will be cleared + function clear(mapping(int24 => Tick.Info) storage self, int24 tick) internal { + delete self[tick]; + } + + /// @notice Transitions to next tick as needed by price movement + /// @param self The mapping containing all tick information for initialized ticks + /// @param tick The destination tick of the transition + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @param secondsPerLiquidityCumulativeX128 The current seconds per liquidity + /// @param tickCumulative The tick * time elapsed since the pool was first initialized + /// @param time The current block.timestamp + /// @return liquidityNet The amount of liquidity added (subtracted) when tick is crossed from left to right (right to left) + function cross( + mapping(int24 => Tick.Info) storage self, + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) internal returns (int128 liquidityNet) { + unchecked { + Tick.Info storage info = self[tick]; + info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128; + info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128; + info.secondsPerLiquidityOutsideX128 = + secondsPerLiquidityCumulativeX128 - + info.secondsPerLiquidityOutsideX128; + info.tickCumulativeOutside = tickCumulative - info.tickCumulativeOutside; + info.secondsOutside = time - info.secondsOutside; + liquidityNet = info.liquidityNet; + } + } +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/TickBitmap.sol b/contracts/dependencies/uniswapv3-core/libraries/TickBitmap.sol new file mode 100644 index 000000000..1cc5bb9ce --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/TickBitmap.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {BitMath} from './BitMath.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) { + unchecked { + wordPos = int16(tick >> 8); + bitPos = uint8(int8(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 { + unchecked { + 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 self 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( + mapping(int16 => uint256) storage self, + int24 tick, + int24 tickSpacing, + bool lte + ) internal view returns (int24 next, bool initialized) { + unchecked { + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(compressed + 1); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = self[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(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; + } + } + } +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/TickMath.sol b/contracts/dependencies/uniswapv3-core/libraries/TickMath.sol new file mode 100644 index 000000000..715a9fd3a --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/TickMath.sol @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^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 { + error T(); + error R(); + + /// @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) { + unchecked { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + if (absTick > uint256(int256(MAX_TICK))) revert 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) { + unchecked { + // second inequality must be < because the price can never reach the price at the max tick + if (!(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO)) revert 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/dependencies/uniswapv3-core/libraries/TransferHelper.sol b/contracts/dependencies/uniswapv3-core/libraries/TransferHelper.sol new file mode 100644 index 000000000..2a7aebd85 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/TransferHelper.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.6.0; + +import {IERC20Minimal} from '../interfaces/IERC20Minimal.sol'; + +/// @title TransferHelper +/// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false +library TransferHelper { + error TF(); + + /// @notice Transfers tokens from msg.sender to a recipient + /// @dev Calls transfer on token contract, errors with TF if transfer fails + /// @param token The contract address of the token which will be transferred + /// @param to The recipient of the transfer + /// @param value The value of the transfer + function safeTransfer( + address token, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = token.call( + abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value) + ); + if (!(success && (data.length == 0 || abi.decode(data, (bool))))) revert TF(); + } +} diff --git a/contracts/dependencies/uniswapv3-core/libraries/UnsafeMath.sol b/contracts/dependencies/uniswapv3-core/libraries/UnsafeMath.sol new file mode 100644 index 000000000..f62f84676 --- /dev/null +++ b/contracts/dependencies/uniswapv3-core/libraries/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/dependencies/uniswapv3-periphery/NonfungiblePositionManager.sol b/contracts/dependencies/uniswapv3-periphery/NonfungiblePositionManager.sol new file mode 100644 index 000000000..221d470de --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/NonfungiblePositionManager.sol @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; +pragma abicoder v2; + +import {IUniswapV3Pool} from '../uniswapv3-core/interfaces/IUniswapV3Pool.sol'; +import '../uniswapv3-core/libraries/FixedPoint128.sol'; +import '../uniswapv3-core/libraries/FullMath.sol'; + +import './interfaces/INonfungiblePositionManager.sol'; +import './interfaces/INonfungibleTokenPositionDescriptor.sol'; +import './libraries/PositionKey.sol'; +import './libraries/PoolAddress.sol'; +import {LiquidityManagement} from './base/LiquidityManagement.sol'; +import './base/PeripheryImmutableState.sol'; +import './base/Multicall.sol'; +import './base/ERC721Permit.sol'; +import {PeripheryValidation} from './base/PeripheryValidation.sol'; +import './base/SelfPermit.sol'; +import {PoolInitializer} from './base/PoolInitializer.sol'; + +/// @title NFT positions +/// @notice Wraps Uniswap V3 positions in the ERC721 non-fungible token interface +contract NonfungiblePositionManager is + INonfungiblePositionManager, + Multicall, + ERC721Permit, + PeripheryImmutableState, + PoolInitializer, + LiquidityManagement, + PeripheryValidation, + SelfPermit +{ + // details about the uniswap position + struct Position { + // the nonce for permits + uint96 nonce; + // the address that is approved for spending this token + address operator; + // the ID of the pool with which this token is connected + uint80 poolId; + // the tick range of the position + int24 tickLower; + int24 tickUpper; + // the liquidity of the position + uint128 liquidity; + // the fee growth of the aggregate position as of the last action on the individual position + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // how many uncollected tokens are owed to the position, as of the last computation + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + /// @dev IDs of pools assigned by this contract + mapping(address => uint80) private _poolIds; + + /// @dev Pool keys by pool ID, to save on SSTOREs for position data + mapping(uint80 => PoolAddress.PoolKey) private _poolIdToPoolKey; + + /// @dev The token ID position data + mapping(uint256 => Position) private _positions; + + /// @dev The ID of the next token that will be minted. Skips 0 + uint176 private _nextId = 1; + /// @dev The ID of the next pool that is used for the first time. Skips 0 + uint80 private _nextPoolId = 1; + + /// @dev The address of the token descriptor contract, which handles generating token URIs for position tokens + address private immutable _tokenDescriptor; + + constructor( + address _factory, + address _WETH9, + address _tokenDescriptor_ + ) ERC721Permit('Uniswap V3 Positions NFT-V1', 'UNI-V3-POS', '1') PeripheryImmutableState(_factory, _WETH9) { + _tokenDescriptor = _tokenDescriptor_; + } + + /// @inheritdoc INonfungiblePositionManager + function positions(uint256 tokenId) + external + view + override + returns ( + uint96 nonce, + address operator, + address token0, + address token1, + uint24 fee, + int24 tickLower, + int24 tickUpper, + uint128 liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ) + { + Position memory position = _positions[tokenId]; + require(position.poolId != 0, 'Invalid token ID'); + PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId]; + return ( + position.nonce, + position.operator, + poolKey.token0, + poolKey.token1, + poolKey.fee, + position.tickLower, + position.tickUpper, + position.liquidity, + position.feeGrowthInside0LastX128, + position.feeGrowthInside1LastX128, + position.tokensOwed0, + position.tokensOwed1 + ); + } + + /// @dev Caches a pool key + function cachePoolKey(address pool, PoolAddress.PoolKey memory poolKey) private returns (uint80 poolId) { + poolId = _poolIds[pool]; + if (poolId == 0) { + _poolIds[pool] = (poolId = _nextPoolId++); + _poolIdToPoolKey[poolId] = poolKey; + } + } + + /// @inheritdoc INonfungiblePositionManager + function mint(MintParams calldata params) + external + payable + override + checkDeadline(params.deadline) + returns ( + uint256 tokenId, + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ) + { + IUniswapV3Pool pool; + (liquidity, amount0, amount1, pool) = addLiquidity( + AddLiquidityParams({ + token0: params.token0, + token1: params.token1, + fee: params.fee, + recipient: address(this), + tickLower: params.tickLower, + tickUpper: params.tickUpper, + amount0Desired: params.amount0Desired, + amount1Desired: params.amount1Desired, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min + }) + ); + + _mint(params.recipient, (tokenId = _nextId++)); + + bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper); + (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey); + + // idempotent set + uint80 poolId = cachePoolKey( + address(pool), + PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee}) + ); + + _positions[tokenId] = Position({ + nonce: 0, + operator: address(0), + poolId: poolId, + tickLower: params.tickLower, + tickUpper: params.tickUpper, + liquidity: liquidity, + feeGrowthInside0LastX128: feeGrowthInside0LastX128, + feeGrowthInside1LastX128: feeGrowthInside1LastX128, + tokensOwed0: 0, + tokensOwed1: 0 + }); + + emit IncreaseLiquidity(tokenId, liquidity, amount0, amount1); + } + + modifier isAuthorizedForToken(uint256 tokenId) { + require(_isApprovedOrOwner(msg.sender, tokenId), 'Not approved'); + _; + } + + function tokenURI(uint256 tokenId) public view override(ERC721, IERC721Metadata) returns (string memory) { + require(_exists(tokenId)); + return INonfungibleTokenPositionDescriptor(_tokenDescriptor).tokenURI(this, tokenId); + } + + // save bytecode by removing implementation of unused method + function baseURI() public pure returns (string memory) {} + + /// @inheritdoc INonfungiblePositionManager + function increaseLiquidity(IncreaseLiquidityParams calldata params) + external + payable + override + checkDeadline(params.deadline) + returns ( + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ) + { + Position storage position = _positions[params.tokenId]; + + PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId]; + + IUniswapV3Pool pool; + (liquidity, amount0, amount1, pool) = addLiquidity( + AddLiquidityParams({ + token0: poolKey.token0, + token1: poolKey.token1, + fee: poolKey.fee, + tickLower: position.tickLower, + tickUpper: position.tickUpper, + amount0Desired: params.amount0Desired, + amount1Desired: params.amount1Desired, + amount0Min: params.amount0Min, + amount1Min: params.amount1Min, + recipient: address(this) + }) + ); + + bytes32 positionKey = PositionKey.compute(address(this), position.tickLower, position.tickUpper); + + // this is now updated to the current transaction + (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey); + + position.tokensOwed0 += uint128( + FullMath.mulDiv( + feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128, + position.liquidity, + FixedPoint128.Q128 + ) + ); + position.tokensOwed1 += uint128( + FullMath.mulDiv( + feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128, + position.liquidity, + FixedPoint128.Q128 + ) + ); + + position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128; + position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128; + position.liquidity += liquidity; + + emit IncreaseLiquidity(params.tokenId, liquidity, amount0, amount1); + } + + /// @inheritdoc INonfungiblePositionManager + function decreaseLiquidity(DecreaseLiquidityParams calldata params) + external + payable + override + isAuthorizedForToken(params.tokenId) + checkDeadline(params.deadline) + returns (uint256 amount0, uint256 amount1) + { + require(params.liquidity > 0); + Position storage position = _positions[params.tokenId]; + + uint128 positionLiquidity = position.liquidity; + require(positionLiquidity >= params.liquidity); + + PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId]; + IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); + (amount0, amount1) = pool.burn(position.tickLower, position.tickUpper, params.liquidity); + + require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check'); + + bytes32 positionKey = PositionKey.compute(address(this), position.tickLower, position.tickUpper); + // this is now updated to the current transaction + (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey); + + position.tokensOwed0 += + uint128(amount0) + + uint128( + FullMath.mulDiv( + feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128, + positionLiquidity, + FixedPoint128.Q128 + ) + ); + position.tokensOwed1 += + uint128(amount1) + + uint128( + FullMath.mulDiv( + feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128, + positionLiquidity, + FixedPoint128.Q128 + ) + ); + + position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128; + position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128; + // subtraction is safe because we checked positionLiquidity is gte params.liquidity + position.liquidity = positionLiquidity - params.liquidity; + + emit DecreaseLiquidity(params.tokenId, params.liquidity, amount0, amount1); + } + + /// @inheritdoc INonfungiblePositionManager + function collect(CollectParams calldata params) + external + payable + override + isAuthorizedForToken(params.tokenId) + returns (uint256 amount0, uint256 amount1) + { + require(params.amount0Max > 0 || params.amount1Max > 0); + // allow collecting to the nft position manager address with address 0 + address recipient = params.recipient == address(0) ? address(this) : params.recipient; + + Position storage position = _positions[params.tokenId]; + + PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId]; + + IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); + + (uint128 tokensOwed0, uint128 tokensOwed1) = (position.tokensOwed0, position.tokensOwed1); + + // trigger an update of the position fees owed and fee growth snapshots if it has any liquidity + if (position.liquidity > 0) { + pool.burn(position.tickLower, position.tickUpper, 0); + (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions( + PositionKey.compute(address(this), position.tickLower, position.tickUpper) + ); + + tokensOwed0 += uint128( + FullMath.mulDiv( + feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128, + position.liquidity, + FixedPoint128.Q128 + ) + ); + tokensOwed1 += uint128( + FullMath.mulDiv( + feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128, + position.liquidity, + FixedPoint128.Q128 + ) + ); + + position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128; + position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128; + } + + // compute the arguments to give to the pool#collect method + (uint128 amount0Collect, uint128 amount1Collect) = ( + params.amount0Max > tokensOwed0 ? tokensOwed0 : params.amount0Max, + params.amount1Max > tokensOwed1 ? tokensOwed1 : params.amount1Max + ); + + // the actual amounts collected are returned + (amount0, amount1) = pool.collect( + recipient, + position.tickLower, + position.tickUpper, + amount0Collect, + amount1Collect + ); + + // sometimes there will be a few less wei than expected due to rounding down in core, but we just subtract the full amount expected + // instead of the actual amount so we can burn the token + (position.tokensOwed0, position.tokensOwed1) = (tokensOwed0 - amount0Collect, tokensOwed1 - amount1Collect); + + emit Collect(params.tokenId, recipient, amount0Collect, amount1Collect); + } + + /// @inheritdoc INonfungiblePositionManager + function burn(uint256 tokenId) external payable override isAuthorizedForToken(tokenId) { + Position storage position = _positions[tokenId]; + require(position.liquidity == 0 && position.tokensOwed0 == 0 && position.tokensOwed1 == 0, 'Not cleared'); + delete _positions[tokenId]; + _burn(tokenId); + } + + function _getAndIncrementNonce(uint256 tokenId) internal override returns (uint256) { + return uint256(_positions[tokenId].nonce++); + } + + /// @inheritdoc IERC721 + function getApproved(uint256 tokenId) public view override(ERC721, IERC721) returns (address) { + require(_exists(tokenId), 'ERC721: approved query for nonexistent token'); + + return _positions[tokenId].operator; + } + + /// @dev Overrides _approve to use the operator in the position, which is packed with the position permit nonce + function _approve(address to, uint256 tokenId) internal override(ERC721) { + _positions[tokenId].operator = to; + emit Approval(ownerOf(tokenId), to, tokenId); + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/SwapRouter.sol b/contracts/dependencies/uniswapv3-periphery/SwapRouter.sol new file mode 100644 index 000000000..116caaf3b --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/SwapRouter.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; +pragma abicoder v2; + +import '../uniswapv3-core/libraries/SafeCast.sol'; +import '../uniswapv3-core/libraries/TickMath.sol'; +import '../uniswapv3-core/interfaces/IUniswapV3Pool.sol'; + +import './interfaces/ISwapRouter.sol'; +import './base/PeripheryImmutableState.sol'; +import './base/PeripheryValidation.sol'; +import './base/PeripheryPaymentsWithFee.sol'; +import './base/Multicall.sol'; +import './base/SelfPermit.sol'; +import './libraries/Path.sol'; +import './libraries/PoolAddress.sol'; +import {CallbackValidation} from './libraries/CallbackValidation.sol'; +import './interfaces/external/IWETH9.sol'; + +/// @title Uniswap V3 Swap Router +/// @notice Router for stateless execution of swaps against Uniswap V3 +contract SwapRouter is + ISwapRouter, + PeripheryImmutableState, + PeripheryValidation, + PeripheryPaymentsWithFee, + Multicall, + SelfPermit +{ + using Path for bytes; + using SafeCast for uint256; + + /// @dev Used as the placeholder value for amountInCached, because the computed amount in for an exact output swap + /// can never actually be this value + uint256 private constant DEFAULT_AMOUNT_IN_CACHED = type(uint256).max; + + /// @dev Transient storage variable used for returning the computed amount in for an exact output swap. + uint256 private amountInCached = DEFAULT_AMOUNT_IN_CACHED; + + constructor(address _factory, address _WETH9) PeripheryImmutableState(_factory, _WETH9) {} + + /// @dev Returns the pool for the given token pair and fee. The pool contract may or may not exist. + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) private view returns (IUniswapV3Pool) { + return IUniswapV3Pool(PoolAddress.computeAddress(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee))); + } + + struct SwapCallbackData { + bytes path; + address payer; + } + + /// @inheritdoc IUniswapV3SwapCallback + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata _data + ) external override { + require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported + SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee); + + (bool isExactInput, uint256 amountToPay) = amount0Delta > 0 + ? (tokenIn < tokenOut, uint256(amount0Delta)) + : (tokenOut < tokenIn, uint256(amount1Delta)); + if (isExactInput) { + pay(tokenIn, data.payer, msg.sender, amountToPay); + } else { + // either initiate the next swap or pay + if (data.path.hasMultiplePools()) { + data.path = data.path.skipToken(); + exactOutputInternal(amountToPay, msg.sender, 0, data); + } else { + amountInCached = amountToPay; + tokenIn = tokenOut; // swap in/out because exact output swaps are reversed + pay(tokenIn, data.payer, msg.sender, amountToPay); + } + } + } + + /// @dev Performs a single exact input swap + function exactInputInternal( + uint256 amountIn, + address recipient, + uint160 sqrtPriceLimitX96, + SwapCallbackData memory data + ) private returns (uint256 amountOut) { + // allow swapping to the router address with address 0 + if (recipient == address(0)) recipient = address(this); + + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + + bool zeroForOne = tokenIn < tokenOut; + + (int256 amount0, int256 amount1) = getPool(tokenIn, tokenOut, fee).swap( + recipient, + zeroForOne, + amountIn.toInt256(), + sqrtPriceLimitX96 == 0 + ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) + : sqrtPriceLimitX96, + abi.encode(data) + ); + + return uint256(-(zeroForOne ? amount1 : amount0)); + } + + /// @inheritdoc ISwapRouter + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + override + checkDeadline(params.deadline) + returns (uint256 amountOut) + { + amountOut = exactInputInternal( + params.amountIn, + params.recipient, + params.sqrtPriceLimitX96, + SwapCallbackData({path: abi.encodePacked(params.tokenIn, params.fee, params.tokenOut), payer: msg.sender}) + ); + require(amountOut >= params.amountOutMinimum, 'Too little received'); + } + + /// @inheritdoc ISwapRouter + function exactInput(ExactInputParams memory params) + external + payable + override + checkDeadline(params.deadline) + returns (uint256 amountOut) + { + address payer = msg.sender; // msg.sender pays for the first hop + + while (true) { + bool hasMultiplePools = params.path.hasMultiplePools(); + + // the outputs of prior swaps become the inputs to subsequent ones + params.amountIn = exactInputInternal( + params.amountIn, + hasMultiplePools ? address(this) : params.recipient, // for intermediate swaps, this contract custodies + 0, + SwapCallbackData({ + path: params.path.getFirstPool(), // only the first pool in the path is necessary + payer: payer + }) + ); + + // decide whether to continue or terminate + if (hasMultiplePools) { + payer = address(this); // at this point, the caller has paid + params.path = params.path.skipToken(); + } else { + amountOut = params.amountIn; + break; + } + } + + require(amountOut >= params.amountOutMinimum, 'Too little received'); + } + + /// @dev Performs a single exact output swap + function exactOutputInternal( + uint256 amountOut, + address recipient, + uint160 sqrtPriceLimitX96, + SwapCallbackData memory data + ) private returns (uint256 amountIn) { + // allow swapping to the router address with address 0 + if (recipient == address(0)) recipient = address(this); + + (address tokenOut, address tokenIn, uint24 fee) = data.path.decodeFirstPool(); + + bool zeroForOne = tokenIn < tokenOut; + + (int256 amount0Delta, int256 amount1Delta) = getPool(tokenIn, tokenOut, fee).swap( + recipient, + zeroForOne, + -amountOut.toInt256(), + sqrtPriceLimitX96 == 0 + ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) + : sqrtPriceLimitX96, + abi.encode(data) + ); + + uint256 amountOutReceived; + (amountIn, amountOutReceived) = zeroForOne + ? (uint256(amount0Delta), uint256(-amount1Delta)) + : (uint256(amount1Delta), uint256(-amount0Delta)); + // it's technically possible to not receive the full output amount, + // so if no price limit has been specified, require this possibility away + if (sqrtPriceLimitX96 == 0) require(amountOutReceived == amountOut); + } + + /// @inheritdoc ISwapRouter + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + override + checkDeadline(params.deadline) + returns (uint256 amountIn) + { + // avoid an SLOAD by using the swap return data + amountIn = exactOutputInternal( + params.amountOut, + params.recipient, + params.sqrtPriceLimitX96, + SwapCallbackData({path: abi.encodePacked(params.tokenOut, params.fee, params.tokenIn), payer: msg.sender}) + ); + + require(amountIn <= params.amountInMaximum, 'Too much requested'); + // has to be reset even though we don't use it in the single hop case + amountInCached = DEFAULT_AMOUNT_IN_CACHED; + } + + /// @inheritdoc ISwapRouter + function exactOutput(ExactOutputParams calldata params) + external + payable + override + checkDeadline(params.deadline) + returns (uint256 amountIn) + { + // it's okay that the payer is fixed to msg.sender here, as they're only paying for the "final" exact output + // swap, which happens first, and subsequent swaps are paid for within nested callback frames + exactOutputInternal( + params.amountOut, + params.recipient, + 0, + SwapCallbackData({path: params.path, payer: msg.sender}) + ); + + amountIn = amountInCached; + require(amountIn <= params.amountInMaximum, 'Too much requested'); + amountInCached = DEFAULT_AMOUNT_IN_CACHED; + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/BlockTimestamp.sol b/contracts/dependencies/uniswapv3-periphery/base/BlockTimestamp.sol new file mode 100644 index 000000000..da027d452 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/BlockTimestamp.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Function for getting block timestamp +/// @dev Base contract that is overridden for tests +abstract contract BlockTimestamp { + /// @dev Method that exists purely to be overridden for tests + /// @return The current block timestamp + function _blockTimestamp() internal view virtual returns (uint256) { + return block.timestamp; + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/ERC721Permit.sol b/contracts/dependencies/uniswapv3-periphery/base/ERC721Permit.sol new file mode 100644 index 000000000..690172311 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/ERC721Permit.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import '../../openzeppelin/contracts/ERC721Enumerable.sol'; +import '../../openzeppelin/contracts/Address.sol'; + +import '../libraries/ChainId.sol'; +import '../interfaces/external/IERC1271.sol'; +import '../interfaces/IERC721Permit.sol'; +import './BlockTimestamp.sol'; + +/// @title ERC721 with permit +/// @notice Nonfungible tokens that support an approve via signature, i.e. permit +abstract contract ERC721Permit is BlockTimestamp, ERC721Enumerable, IERC721Permit { + /// @dev Gets the current nonce for a token ID and then increments it, returning the original value + function _getAndIncrementNonce(uint256 tokenId) internal virtual returns (uint256); + + /// @dev The hash of the name used in the permit signature verification + bytes32 private immutable nameHash; + + /// @dev The hash of the version string used in the permit signature verification + bytes32 private immutable versionHash; + + /// @notice Computes the nameHash and versionHash + constructor( + string memory name_, + string memory symbol_, + string memory version_ + ) ERC721(name_, symbol_) { + nameHash = keccak256(bytes(name_)); + versionHash = keccak256(bytes(version_)); + } + + /// @inheritdoc IERC721Permit + function DOMAIN_SEPARATOR() public view override returns (bytes32) { + return + keccak256( + abi.encode( + // keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)') + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, + nameHash, + versionHash, + ChainId.get(), + address(this) + ) + ); + } + + /// @inheritdoc IERC721Permit + /// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); + bytes32 public constant override PERMIT_TYPEHASH = + 0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad; + + /// @inheritdoc IERC721Permit + function permit( + address spender, + uint256 tokenId, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable override { + require(_blockTimestamp() <= deadline, 'Permit expired'); + + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, _getAndIncrementNonce(tokenId), deadline)) + ) + ); + address owner = ownerOf(tokenId); + require(spender != owner, 'ERC721Permit: approval to current owner'); + + if (Address.isContract(owner)) { + require(IERC1271(owner).isValidSignature(digest, abi.encodePacked(r, s, v)) == 0x1626ba7e, 'Unauthorized'); + } else { + address recoveredAddress = ecrecover(digest, v, r, s); + require(recoveredAddress != address(0), 'Invalid signature'); + require(recoveredAddress == owner, 'Unauthorized'); + } + + _approve(spender, tokenId); + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol b/contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol new file mode 100644 index 000000000..f31c0d3d4 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; +pragma abicoder v2; + +import '../../uniswapv3-core/interfaces/IUniswapV3Factory.sol'; +import {IUniswapV3Pool} from '../../uniswapv3-core/interfaces/IUniswapV3Pool.sol'; +import '../../uniswapv3-core/interfaces/callback/IUniswapV3MintCallback.sol'; +import '../../uniswapv3-core/libraries/TickMath.sol'; + +import '../libraries/PoolAddress.sol'; +import '../libraries/CallbackValidation.sol'; +import '../libraries/LiquidityAmounts.sol'; + +import './PeripheryPayments.sol'; +import './PeripheryImmutableState.sol'; +import 'hardhat/console.sol'; + +/// @title Liquidity management functions +/// @notice Internal functions for safely managing liquidity in Uniswap V3 +abstract contract LiquidityManagement is IUniswapV3MintCallback, PeripheryImmutableState, PeripheryPayments { + struct MintCallbackData { + PoolAddress.PoolKey poolKey; + address payer; + } + + /// @inheritdoc IUniswapV3MintCallback + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external override { + MintCallbackData memory decoded = abi.decode(data, (MintCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + if (amount0Owed > 0) pay(decoded.poolKey.token0, decoded.payer, msg.sender, amount0Owed); + if (amount1Owed > 0) pay(decoded.poolKey.token1, decoded.payer, msg.sender, amount1Owed); + } + + struct AddLiquidityParams { + address token0; + address token1; + uint24 fee; + address recipient; + int24 tickLower; + int24 tickUpper; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + } + + /// @notice Add liquidity to an initialized pool + function addLiquidity(AddLiquidityParams memory params) + internal + returns ( + uint128 liquidity, + uint256 amount0, + uint256 amount1, + IUniswapV3Pool pool + ) + { + PoolAddress.PoolKey memory poolKey = PoolAddress.PoolKey({ + token0: params.token0, + token1: params.token1, + fee: params.fee + }); + + pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); + console.log("pool add is ", PoolAddress.computeAddress(factory, poolKey)); + // pool.slot0(); + + // compute the liquidity amount + { + (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(params.tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(params.tickUpper); + + liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + sqrtRatioAX96, + sqrtRatioBX96, + params.amount0Desired, + params.amount1Desired + ); + console.log("sqrt ", sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96); + } + + + (amount0, amount1) = pool.mint( + params.recipient, + params.tickLower, + params.tickUpper, + liquidity, + abi.encode(MintCallbackData({poolKey: poolKey, payer: msg.sender})) + ); + + require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check'); + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/Multicall.sol b/contracts/dependencies/uniswapv3-periphery/base/Multicall.sol new file mode 100644 index 000000000..19cc58a9e --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/Multicall.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; +pragma abicoder v2; + +import '../interfaces/IMulticall.sol'; + +/// @title Multicall +/// @notice Enables calling multiple methods in a single call to the contract +abstract contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/PeripheryImmutableState.sol b/contracts/dependencies/uniswapv3-periphery/base/PeripheryImmutableState.sol new file mode 100644 index 000000000..ac88e65a7 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/PeripheryImmutableState.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import '../interfaces/IPeripheryImmutableState.sol'; + +/// @title Immutable state +/// @notice Immutable state used by periphery contracts +abstract contract PeripheryImmutableState is IPeripheryImmutableState { + /// @inheritdoc IPeripheryImmutableState + address public immutable override factory; + /// @inheritdoc IPeripheryImmutableState + address public immutable override WETH9; + + constructor(address _factory, address _WETH9) { + factory = _factory; + WETH9 = _WETH9; + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/PeripheryPayments.sol b/contracts/dependencies/uniswapv3-periphery/base/PeripheryPayments.sol new file mode 100644 index 000000000..b076b8b49 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/PeripheryPayments.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +import '../../openzeppelin/contracts/IERC20.sol'; + +import '../interfaces/IPeripheryPayments.sol'; +import '../interfaces/external/IWETH9.sol'; + +import '../libraries/TransferHelper.sol'; + +import './PeripheryImmutableState.sol'; + +abstract contract PeripheryPayments is IPeripheryPayments, PeripheryImmutableState { + receive() external payable { + require(msg.sender == WETH9, 'Not WETH9'); + } + + /// @inheritdoc IPeripheryPayments + function unwrapWETH9(uint256 amountMinimum, address recipient) public payable override { + uint256 balanceWETH9 = IWETH9(WETH9).balanceOf(address(this)); + require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9'); + + if (balanceWETH9 > 0) { + IWETH9(WETH9).withdraw(balanceWETH9); + TransferHelper.safeTransferETH(recipient, balanceWETH9); + } + } + + /// @inheritdoc IPeripheryPayments + function sweepToken( + address token, + uint256 amountMinimum, + address recipient + ) public payable override { + uint256 balanceToken = IERC20(token).balanceOf(address(this)); + require(balanceToken >= amountMinimum, 'Insufficient token'); + + if (balanceToken > 0) { + TransferHelper.safeTransfer(token, recipient, balanceToken); + } + } + + /// @inheritdoc IPeripheryPayments + function refundETH() external payable override { + if (address(this).balance > 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance); + } + + /// @param token The token to pay + /// @param payer The entity that must pay + /// @param recipient The entity that will receive payment + /// @param value The amount to pay + function pay( + address token, + address payer, + address recipient, + uint256 value + ) internal { + if (token == WETH9 && address(this).balance >= value) { + // pay with WETH9 + IWETH9(WETH9).deposit{value: value}(); // wrap only what is needed to pay + IWETH9(WETH9).transfer(recipient, value); + } else if (payer == address(this)) { + // pay with tokens already in the contract (for the exact input multihop case) + TransferHelper.safeTransfer(token, recipient, value); + } else { + // pull payment + TransferHelper.safeTransferFrom(token, payer, recipient, value); + } + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/PeripheryPaymentsWithFee.sol b/contracts/dependencies/uniswapv3-periphery/base/PeripheryPaymentsWithFee.sol new file mode 100644 index 000000000..37fdcf8a9 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/PeripheryPaymentsWithFee.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +import '../../openzeppelin/contracts/IERC20.sol'; + +import './PeripheryPayments.sol'; +import '../interfaces/IPeripheryPaymentsWithFee.sol'; + +import '../interfaces/external/IWETH9.sol'; +import '../libraries/TransferHelper.sol'; + +abstract contract PeripheryPaymentsWithFee is PeripheryPayments, IPeripheryPaymentsWithFee { + /// @inheritdoc IPeripheryPaymentsWithFee + function unwrapWETH9WithFee( + uint256 amountMinimum, + address recipient, + uint256 feeBips, + address feeRecipient + ) public payable override { + require(feeBips > 0 && feeBips <= 100); + + uint256 balanceWETH9 = IWETH9(WETH9).balanceOf(address(this)); + require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9'); + + if (balanceWETH9 > 0) { + IWETH9(WETH9).withdraw(balanceWETH9); + uint256 feeAmount = (balanceWETH9 * feeBips) / 10_000; + if (feeAmount > 0) TransferHelper.safeTransferETH(feeRecipient, feeAmount); + TransferHelper.safeTransferETH(recipient, balanceWETH9 - feeAmount); + } + } + + /// @inheritdoc IPeripheryPaymentsWithFee + function sweepTokenWithFee( + address token, + uint256 amountMinimum, + address recipient, + uint256 feeBips, + address feeRecipient + ) public payable override { + require(feeBips > 0 && feeBips <= 100); + + uint256 balanceToken = IERC20(token).balanceOf(address(this)); + require(balanceToken >= amountMinimum, 'Insufficient token'); + + if (balanceToken > 0) { + uint256 feeAmount = (balanceToken * feeBips) / 10_000; + if (feeAmount > 0) TransferHelper.safeTransfer(token, feeRecipient, feeAmount); + TransferHelper.safeTransfer(token, recipient, balanceToken - feeAmount); + } + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/PeripheryValidation.sol b/contracts/dependencies/uniswapv3-periphery/base/PeripheryValidation.sol new file mode 100644 index 000000000..4aea86cf5 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/PeripheryValidation.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import './BlockTimestamp.sol'; + +abstract contract PeripheryValidation is BlockTimestamp { + modifier checkDeadline(uint256 deadline) { + require(_blockTimestamp() <= deadline, 'Transaction too old'); + _; + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/PoolInitializer.sol b/contracts/dependencies/uniswapv3-periphery/base/PoolInitializer.sol new file mode 100644 index 000000000..df90c60bf --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/PoolInitializer.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import '../../uniswapv3-core/interfaces/IUniswapV3Factory.sol'; +import '../../uniswapv3-core/interfaces/IUniswapV3Pool.sol'; + +import './PeripheryImmutableState.sol'; +import '../interfaces/IPoolInitializer.sol'; +import 'hardhat/console.sol'; + +/// @title Creates and initializes V3 Pools +abstract contract PoolInitializer is IPoolInitializer, PeripheryImmutableState { + /// @inheritdoc IPoolInitializer + function createAndInitializePoolIfNecessary( + address token0, + address token1, + uint24 fee, + uint160 sqrtPriceX96 + ) external payable override returns (address pool) { + require(token0 < token1); + pool = IUniswapV3Factory(factory).getPool(token0, token1, fee); + + if (pool == address(0)) { + pool = IUniswapV3Factory(factory).createPool(token0, token1, fee); + IUniswapV3Pool(pool).initialize(sqrtPriceX96); + (uint160 sqrtPriceX96temp, , , , , , ) = IUniswapV3Pool(pool).slot0(); + + console.log("new pool addy", pool); + } else { + (uint160 sqrtPriceX96Existing, , , , , , ) = IUniswapV3Pool(pool).slot0(); + if (sqrtPriceX96Existing == 0) { + IUniswapV3Pool(pool).initialize(sqrtPriceX96); + } + } + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/base/SelfPermit.sol b/contracts/dependencies/uniswapv3-periphery/base/SelfPermit.sol new file mode 100644 index 000000000..20e841cc3 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/base/SelfPermit.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import '../../openzeppelin/contracts/IERC20.sol'; +import '../../openzeppelin/contracts/draft-IERC20Permit.sol'; + +import '../interfaces/ISelfPermit.sol'; +import '../interfaces/external/IERC20PermitAllowed.sol'; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function +/// that requires an approval in a single transaction. +abstract contract SelfPermit is ISelfPermit { + /// @inheritdoc ISelfPermit + function selfPermit( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public payable override { + IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitIfNecessary( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable override { + if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowed( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public payable override { + IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowedIfNecessary( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable override { + if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max) + selfPermitAllowed(token, nonce, expiry, v, r, s); + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IERC20Metadata.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IERC20Metadata.sol new file mode 100644 index 000000000..0521267cf --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IERC20Metadata.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import '../../openzeppelin/contracts/IERC20.sol'; + +/// @title IERC20Metadata +/// @title Interface for ERC20 Metadata +/// @notice Extension to IERC20 that includes token metadata +interface IERC20Metadata is IERC20 { + /// @return The name of the token + function name() external view returns (string memory); + + /// @return The symbol of the token + function symbol() external view returns (string memory); + + /// @return The number of decimal places the token has + function decimals() external view returns (uint8); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IERC721Permit.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IERC721Permit.sol new file mode 100644 index 000000000..cc0effe40 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IERC721Permit.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +import '../../openzeppelin/contracts/IERC721.sol'; + +/// @title ERC721 with permit +/// @notice Extension to ERC721 that includes a permit function for signature based approvals +interface IERC721Permit is IERC721 { + /// @notice The permit typehash used in the permit signature + /// @return The typehash for the permit + function PERMIT_TYPEHASH() external pure returns (bytes32); + + /// @notice The domain separator used in the permit signature + /// @return The domain seperator used in encoding of permit signature + function DOMAIN_SEPARATOR() external view returns (bytes32); + + /// @notice Approve of a specific token ID for spending by spender via signature + /// @param spender The account that is being approved + /// @param tokenId The ID of the token that is being approved for spending + /// @param deadline The deadline timestamp by which the call must be mined for the approve to work + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address spender, + uint256 tokenId, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; +} diff --git a/contracts/dependencies/uniswap/IMulticall.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IMulticall.sol similarity index 100% rename from contracts/dependencies/uniswap/IMulticall.sol rename to contracts/dependencies/uniswapv3-periphery/interfaces/IMulticall.sol diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/INonfungiblePositionManager.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/INonfungiblePositionManager.sol new file mode 100644 index 000000000..705cb3fde --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/INonfungiblePositionManager.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +import '../../openzeppelin/contracts/IERC721Metadata.sol'; +import '../../openzeppelin/contracts/IERC721Enumerable.sol'; + +import './IPoolInitializer.sol'; +import './IERC721Permit.sol'; +import './IPeripheryPayments.sol'; +import './IPeripheryImmutableState.sol'; +import '../libraries/PoolAddress.sol'; + +/// @title Non-fungible token for positions +/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred +/// and authorized. +interface INonfungiblePositionManager is + IPoolInitializer, + IPeripheryPayments, + IPeripheryImmutableState, + IERC721Metadata, + IERC721Enumerable, + IERC721Permit +{ + /// @notice Emitted when liquidity is increased for a position NFT + /// @dev Also emitted when a token is minted + /// @param tokenId The ID of the token for which liquidity was increased + /// @param liquidity The amount by which liquidity for the NFT position was increased + /// @param amount0 The amount of token0 that was paid for the increase in liquidity + /// @param amount1 The amount of token1 that was paid for the increase in liquidity + event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + /// @notice Emitted when liquidity is decreased for a position NFT + /// @param tokenId The ID of the token for which liquidity was decreased + /// @param liquidity The amount by which liquidity for the NFT position was decreased + /// @param amount0 The amount of token0 that was accounted for the decrease in liquidity + /// @param amount1 The amount of token1 that was accounted for the decrease in liquidity + event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); + /// @notice Emitted when tokens are collected for a position NFT + /// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior + /// @param tokenId The ID of the token for which underlying tokens were collected + /// @param recipient The address of the account that received the collected tokens + /// @param amount0 The amount of token0 owed to the position that was collected + /// @param amount1 The amount of token1 owed to the position that was collected + event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1); + + /// @notice Returns the position information associated with a given token ID. + /// @dev Throws if the token ID is not valid. + /// @param tokenId The ID of the token that represents the position + /// @return nonce The nonce for permits + /// @return operator The address that is approved for spending + /// @return token0 The address of the token0 for a specific pool + /// @return token1 The address of the token1 for a specific pool + /// @return fee The fee associated with the pool + /// @return tickLower The lower end of the tick range for the position + /// @return tickUpper The higher end of the tick range for the position + /// @return liquidity The liquidity of the position + /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position + /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position + /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation + /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation + function positions(uint256 tokenId) + external + view + returns ( + uint96 nonce, + address operator, + address token0, + address token1, + uint24 fee, + int24 tickLower, + int24 tickUpper, + uint128 liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + struct MintParams { + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + address recipient; + uint256 deadline; + } + + /// @notice Creates a new position wrapped in a NFT + /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized + /// a method does not exist, i.e. the pool is assumed to be initialized. + /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata + /// @return tokenId The ID of the token that represents the minted position + /// @return liquidity The amount of liquidity for this position + /// @return amount0 The amount of token0 + /// @return amount1 The amount of token1 + function mint(MintParams calldata params) + external + payable + returns ( + uint256 tokenId, + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ); + + struct IncreaseLiquidityParams { + uint256 tokenId; + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + + /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender` + /// @param params tokenId The ID of the token for which liquidity is being increased, + /// amount0Desired The desired amount of token0 to be spent, + /// amount1Desired The desired amount of token1 to be spent, + /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check, + /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check, + /// deadline The time by which the transaction must be included to effect the change + /// @return liquidity The new liquidity amount as a result of the increase + /// @return amount0 The amount of token0 to acheive resulting liquidity + /// @return amount1 The amount of token1 to acheive resulting liquidity + function increaseLiquidity(IncreaseLiquidityParams calldata params) + external + payable + returns ( + uint128 liquidity, + uint256 amount0, + uint256 amount1 + ); + + struct DecreaseLiquidityParams { + uint256 tokenId; + uint128 liquidity; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + } + + /// @notice Decreases the amount of liquidity in a position and accounts it to the position + /// @param params tokenId The ID of the token for which liquidity is being decreased, + /// amount The amount by which liquidity will be decreased, + /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity, + /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity, + /// deadline The time by which the transaction must be included to effect the change + /// @return amount0 The amount of token0 accounted to the position's tokens owed + /// @return amount1 The amount of token1 accounted to the position's tokens owed + function decreaseLiquidity(DecreaseLiquidityParams calldata params) + external + payable + returns (uint256 amount0, uint256 amount1); + + struct CollectParams { + uint256 tokenId; + address recipient; + uint128 amount0Max; + uint128 amount1Max; + } + + /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient + /// @param params tokenId The ID of the NFT for which tokens are being collected, + /// recipient The account that should receive the tokens, + /// amount0Max The maximum amount of token0 to collect, + /// amount1Max The maximum amount of token1 to collect + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1); + + /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens + /// must be collected first. + /// @param tokenId The ID of the token that is being burned + function burn(uint256 tokenId) external payable; +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/INonfungibleTokenPositionDescriptor.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/INonfungibleTokenPositionDescriptor.sol new file mode 100644 index 000000000..b3f27b86b --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/INonfungibleTokenPositionDescriptor.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import './INonfungiblePositionManager.sol'; + +/// @title Describes position NFT tokens via URI +interface INonfungibleTokenPositionDescriptor { + /// @notice Produces the URI describing a particular token ID for a position manager + /// @dev Note this URI may be a data: URI with the JSON contents directly inlined + /// @param positionManager The position manager for which to describe the token + /// @param tokenId The ID of the token for which to produce a description, which may not be valid + /// @return The URI of the ERC721-compliant metadata + function tokenURI(INonfungiblePositionManager positionManager, uint256 tokenId) + external + view + returns (string memory); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryImmutableState.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryImmutableState.sol new file mode 100644 index 000000000..b3378052d --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryImmutableState.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Immutable state +/// @notice Functions that return immutable state of the router +interface IPeripheryImmutableState { + /// @return Returns the address of the Uniswap V3 factory + function factory() external view returns (address); + + /// @return Returns the address of WETH9 + function WETH9() external view returns (address); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPayments.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPayments.sol new file mode 100644 index 000000000..ac74f8c9e --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPayments.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +/// @title Periphery Payments +/// @notice Functions to ease deposits and withdrawals of ETH +interface IPeripheryPayments { + /// @notice Unwraps the contract's WETH9 balance and sends it to recipient as ETH. + /// @dev The amountMinimum parameter prevents malicious contracts from stealing WETH9 from users. + /// @param amountMinimum The minimum amount of WETH9 to unwrap + /// @param recipient The address receiving ETH + function unwrapWETH9(uint256 amountMinimum, address recipient) external payable; + + /// @notice Refunds any ETH balance held by this contract to the `msg.sender` + /// @dev Useful for bundling with mint or increase liquidity that uses ether, or exact output swaps + /// that use ether for the input amount + function refundETH() external payable; + + /// @notice Transfers the full amount of a token held by this contract to recipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users + /// @param token The contract address of the token which will be transferred to `recipient` + /// @param amountMinimum The minimum amount of token required for a transfer + /// @param recipient The destination address of the token + function sweepToken( + address token, + uint256 amountMinimum, + address recipient + ) external payable; +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPaymentsWithFee.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPaymentsWithFee.sol new file mode 100644 index 000000000..907e44675 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IPeripheryPaymentsWithFee.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +import './IPeripheryPayments.sol'; + +/// @title Periphery Payments +/// @notice Functions to ease deposits and withdrawals of ETH +interface IPeripheryPaymentsWithFee is IPeripheryPayments { + /// @notice Unwraps the contract's WETH9 balance and sends it to recipient as ETH, with a percentage between + /// 0 (exclusive), and 1 (inclusive) going to feeRecipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing WETH9 from users. + function unwrapWETH9WithFee( + uint256 amountMinimum, + address recipient, + uint256 feeBips, + address feeRecipient + ) external payable; + + /// @notice Transfers the full amount of a token held by this contract to recipient, with a percentage between + /// 0 (exclusive) and 1 (inclusive) going to feeRecipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users + function sweepTokenWithFee( + address token, + uint256 amountMinimum, + address recipient, + uint256 feeBips, + address feeRecipient + ) external payable; +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IPoolInitializer.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IPoolInitializer.sol new file mode 100644 index 000000000..d2949b3d6 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IPoolInitializer.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Creates and initializes V3 Pools +/// @notice Provides a method for creating and initializing a pool, if necessary, for bundling with other methods that +/// require the pool to exist. +interface IPoolInitializer { + /// @notice Creates a new pool if it does not exist, then initializes if not initialized + /// @dev This method can be bundled with others via IMulticall for the first action (e.g. mint) performed against a pool + /// @param token0 The contract address of token0 of the pool + /// @param token1 The contract address of token1 of the pool + /// @param fee The fee amount of the v3 pool for the specified token pair + /// @param sqrtPriceX96 The initial square root price of the pool as a Q64.96 value + /// @return pool Returns the pool address based on the pair of tokens and fee, will return the newly created pool address if necessary + function createAndInitializePoolIfNecessary( + address token0, + address token1, + uint24 fee, + uint160 sqrtPriceX96 + ) external payable returns (address pool); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IQuoter.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IQuoter.sol new file mode 100644 index 000000000..3410e0cd0 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IQuoter.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Quoter Interface +/// @notice Supports quoting the calculated amounts from exact input or exact output swaps +/// @dev These functions are not marked view because they rely on calling non-view functions and reverting +/// to compute the result. They are also not gas efficient and should not be called on-chain. +interface IQuoter { + /// @notice Returns the amount out received for a given exact input swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee + /// @param amountIn The amount of the first token to swap + /// @return amountOut The amount of the last token that would be received + function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut); + + /// @notice Returns the amount out received for a given exact input but for a swap of a single pool + /// @param tokenIn The token being swapped in + /// @param tokenOut The token being swapped out + /// @param fee The fee of the token pool to consider for the pair + /// @param amountIn The desired input amount + /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountOut The amount of `tokenOut` that would be received + function quoteExactInputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountIn, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountOut); + + /// @notice Returns the amount in required for a given exact output swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order + /// @param amountOut The amount of the last token to receive + /// @return amountIn The amount of first token required to be paid + function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn); + + /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool + /// @param tokenIn The token being swapped in + /// @param tokenOut The token being swapped out + /// @param fee The fee of the token pool to consider for the pair + /// @param amountOut The desired output amount + /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountIn The amount required as the input for the swap in order to receive `amountOut` + function quoteExactOutputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountOut, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountIn); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IQuoterV2.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IQuoterV2.sol new file mode 100644 index 000000000..3c2961b2c --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IQuoterV2.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title QuoterV2 Interface +/// @notice Supports quoting the calculated amounts from exact input or exact output swaps. +/// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap. +/// @dev These functions are not marked view because they rely on calling non-view functions and reverting +/// to compute the result. They are also not gas efficient and should not be called on-chain. +interface IQuoterV2 { + /// @notice Returns the amount out received for a given exact input swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee + /// @param amountIn The amount of the first token to swap + /// @return amountOut The amount of the last token that would be received + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactInput(bytes memory path, uint256 amountIn) + external + returns ( + uint256 amountOut, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); + + struct QuoteExactInputSingleParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + /// @notice Returns the amount out received for a given exact input but for a swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams` + /// tokenIn The token being swapped in + /// tokenOut The token being swapped out + /// fee The fee of the token pool to consider for the pair + /// amountIn The desired input amount + /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountOut The amount of `tokenOut` that would be received + /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksCrossed The number of initialized ticks that the swap crossed + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactInputSingle(QuoteExactInputSingleParams memory params) + external + returns ( + uint256 amountOut, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); + + /// @notice Returns the amount in required for a given exact output swap without executing the swap + /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order + /// @param amountOut The amount of the last token to receive + /// @return amountIn The amount of first token required to be paid + /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactOutput(bytes memory path, uint256 amountOut) + external + returns ( + uint256 amountIn, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); + + struct QuoteExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint256 amount; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams` + /// tokenIn The token being swapped in + /// tokenOut The token being swapped out + /// fee The fee of the token pool to consider for the pair + /// amountOut The desired output amount + /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap + /// @return amountIn The amount required as the input for the swap in order to receive `amountOut` + /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksCrossed The number of initialized ticks that the swap crossed + /// @return gasEstimate The estimate of the gas that the swap consumes + function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) + external + returns ( + uint256 amountIn, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/ISelfPermit.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/ISelfPermit.sol new file mode 100644 index 000000000..0ab8a25af --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/ISelfPermit.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermit( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// Can be used instead of #selfPermit to prevent calls from failing due to a frontrun of a call to #selfPermit + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitIfNecessary( + address token, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowed( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// Can be used instead of #selfPermitAllowed to prevent calls from failing due to a frontrun of a call to #selfPermitAllowed. + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowedIfNecessary( + address token, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol new file mode 100644 index 000000000..0f6a488a4 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +import '../../uniswapv3-core/interfaces/callback/IUniswapV3SwapCallback.sol'; + +/// @title Router token swapping functionality +/// @notice Functions for swapping tokens via Uniswap V3 +interface ISwapRouter is IUniswapV3SwapCallback { + struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); + + struct ExactInputParams { + bytes path; + address recipient; + uint256 deadline; + uint256 amountIn; + uint256 amountOutMinimum; + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); + + struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 deadline; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); + + struct ExactOutputParams { + bytes path; + address recipient; + uint256 deadline; + uint256 amountOut; + uint256 amountInMaximum; + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/ITickLens.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/ITickLens.sol new file mode 100644 index 000000000..89bac2d41 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/ITickLens.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +/// @title Tick Lens +/// @notice Provides functions for fetching chunks of tick data for a pool +/// @dev This avoids the waterfall of fetching the tick bitmap, parsing the bitmap to know which ticks to fetch, and +/// then sending additional multicalls to fetch the tick data +interface ITickLens { + struct PopulatedTick { + int24 tick; + int128 liquidityNet; + uint128 liquidityGross; + } + + /// @notice Get all the tick data for the populated ticks from a word of the tick bitmap of a pool + /// @param pool The address of the pool for which to fetch populated tick data + /// @param tickBitmapIndex The index of the word in the tick bitmap for which to parse the bitmap and + /// fetch all the populated ticks + /// @return populatedTicks An array of tick data for the given word in the tick bitmap + function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex) + external + view + returns (PopulatedTick[] memory populatedTicks); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/IV3Migrator.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/IV3Migrator.sol new file mode 100644 index 000000000..6eb6e42fa --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/IV3Migrator.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.5; +pragma abicoder v2; + +import './IMulticall.sol'; +import './ISelfPermit.sol'; +import './IPoolInitializer.sol'; + +/// @title V3 Migrator +/// @notice Enables migration of liqudity from Uniswap v2-compatible pairs into Uniswap v3 pools +interface IV3Migrator is IMulticall, ISelfPermit, IPoolInitializer { + struct MigrateParams { + address pair; // the Uniswap v2-compatible pair + uint256 liquidityToMigrate; // expected to be balanceOf(msg.sender) + uint8 percentageToMigrate; // represented as a numerator over 100 + address token0; + address token1; + uint24 fee; + int24 tickLower; + int24 tickUpper; + uint256 amount0Min; // must be discounted by percentageToMigrate + uint256 amount1Min; // must be discounted by percentageToMigrate + address recipient; + uint256 deadline; + bool refundAsETH; + } + + /// @notice Migrates liquidity to v3 by burning v2 liquidity and minting a new position for v3 + /// @dev Slippage protection is enforced via `amount{0,1}Min`, which should be a discount of the expected values of + /// the maximum amount of v3 liquidity that the v2 liquidity can get. For the special case of migrating to an + /// out-of-range position, `amount{0,1}Min` may be set to 0, enforcing that the position remains out of range + /// @param params The params necessary to migrate v2 liquidity, encoded as `MigrateParams` in calldata + function migrate(MigrateParams calldata params) external; +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC1271.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC1271.sol new file mode 100644 index 000000000..824b03ea4 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC1271.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Interface for verifying contract-based account signatures +/// @notice Interface that verifies provided signature for the data +/// @dev Interface defined by EIP-1271 +interface IERC1271 { + /// @notice Returns whether the provided signature is valid for the provided data + /// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes. + /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5). + /// MUST allow external calls. + /// @param hash Hash of the data to be signed + /// @param signature Signature byte array associated with _data + /// @return magicValue The bytes4 magic value 0x1626ba7e + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC20PermitAllowed.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC20PermitAllowed.sol new file mode 100644 index 000000000..a817e1cff --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/external/IERC20PermitAllowed.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title Interface for permit +/// @notice Interface used by DAI/CHAI for permit +interface IERC20PermitAllowed { + /// @notice Approve the spender to spend some tokens via the holder signature + /// @dev This is the permit interface used by DAI and CHAI + /// @param holder The address of the token holder, the token owner + /// @param spender The address of the token spender + /// @param nonce The holder's nonce, increases at each call to permit + /// @param expiry The timestamp at which the permit is no longer valid + /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} diff --git a/contracts/dependencies/uniswapv3-periphery/interfaces/external/IWETH9.sol b/contracts/dependencies/uniswapv3-periphery/interfaces/external/IWETH9.sol new file mode 100644 index 000000000..abdda6ecb --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/interfaces/external/IWETH9.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +import '../../../openzeppelin/contracts/IERC20.sol'; + +/// @title Interface for WETH9 +interface IWETH9 is IERC20 { + /// @notice Deposit ether to get wrapped ether + function deposit() external payable; + + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external; +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/BytesLib.sol b/contracts/dependencies/uniswapv3-periphery/libraries/BytesLib.sol new file mode 100644 index 000000000..1079bb19d --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/BytesLib.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * @title Solidity Bytes Arrays Utils + * @author Gonçalo Sá + * + * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. + * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. + */ +pragma solidity >=0.8.0 <0.9.0; + +library BytesLib { + function slice( + bytes memory _bytes, + uint256 _start, + uint256 _length + ) internal pure returns (bytes memory) { + require(_length + 31 >= _length, 'slice_overflow'); + require(_bytes.length >= _start + _length, 'slice_outOfBounds'); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { + require(_bytes.length >= _start + 20, 'toAddress_outOfBounds'); + address tempAddress; + + assembly { + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + } + + return tempAddress; + } + + function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { + require(_start + 3 >= _start, 'toUint24_overflow'); + require(_bytes.length >= _start + 3, 'toUint24_outOfBounds'); + uint24 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x3), _start)) + } + + return tempUint; + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/CallbackValidation.sol b/contracts/dependencies/uniswapv3-periphery/libraries/CallbackValidation.sol new file mode 100644 index 000000000..7eb19288b --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/CallbackValidation.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import '../../uniswapv3-core/interfaces/IUniswapV3Pool.sol'; +import './PoolAddress.sol'; + +/// @notice Provides validation for callbacks from Uniswap V3 Pools +library CallbackValidation { + /// @notice Returns the address of a valid Uniswap V3 Pool + /// @param factory The contract address of the Uniswap V3 factory + /// @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 V3 pool contract address + function verifyCallback( + address factory, + address tokenA, + address tokenB, + uint24 fee + ) internal view returns (IUniswapV3Pool pool) { + return verifyCallback(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee)); + } + + /// @notice Returns the address of a valid Uniswap V3 Pool + /// @param factory The contract address of the Uniswap V3 factory + /// @param poolKey The identifying key of the V3 pool + /// @return pool The V3 pool contract address + function verifyCallback(address factory, PoolAddress.PoolKey memory poolKey) + internal + view + returns (IUniswapV3Pool pool) + { + pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); + require(msg.sender == address(pool)); + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/ChainId.sol b/contracts/dependencies/uniswapv3-periphery/libraries/ChainId.sol new file mode 100644 index 000000000..7e67989c6 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/ChainId.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.7.0; + +/// @title Function for getting the current chain ID +library ChainId { + /// @dev Gets the current chain ID + /// @return chainId The current chain ID + function get() internal view returns (uint256 chainId) { + assembly { + chainId := chainid() + } + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/LiquidityAmounts.sol b/contracts/dependencies/uniswapv3-periphery/libraries/LiquidityAmounts.sol new file mode 100644 index 000000000..bdfe361ee --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/LiquidityAmounts.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import '../../uniswapv3-core/libraries/FullMath.sol'; +import '../../uniswapv3-core/libraries/FixedPoint96.sol'; + +/// @title Liquidity amount functions +/// @notice Provides functions for computing liquidity amounts from token amounts and prices +library LiquidityAmounts { + /// @notice Downcasts uint256 to uint128 + /// @param x The uint258 to be downcasted + /// @return y The passed value, downcasted to uint128 + function toUint128(uint256 x) private pure returns (uint128 y) { + require((y = uint128(x)) == x); + } + + /// @notice Computes the amount of liquidity received for a given amount of token0 and price range + /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount0 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount0( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount0 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96); + unchecked { + return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96)); + } + } + + /// @notice Computes the amount of liquidity received for a given amount of token1 and price range + /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)). + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param amount1 The amount1 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount1( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + unchecked { + return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96)); + } + } + + /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current + /// pool prices and the prices at the tick boundaries + /// @param sqrtRatioX96 A sqrt price representing the current pool prices + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount of token0 being sent in + /// @param amount1 The amount of token1 being sent in + /// @return liquidity The maximum amount of liquidity received + function getLiquidityForAmounts( + uint160 sqrtRatioX96, + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + if (sqrtRatioX96 <= sqrtRatioAX96) { + liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0); + } else if (sqrtRatioX96 < sqrtRatioBX96) { + uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0); + uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1); + + liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; + } else { + liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1); + } + } + + /// @notice Computes the amount of token0 for a given amount of liquidity and a price range + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param liquidity The liquidity being valued + /// @return amount0 The amount of token0 + function getAmount0ForLiquidity( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount0) { + unchecked { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + FullMath.mulDiv( + uint256(liquidity) << FixedPoint96.RESOLUTION, + sqrtRatioBX96 - sqrtRatioAX96, + sqrtRatioBX96 + ) / sqrtRatioAX96; + } + } + + /// @notice Computes the amount of token1 for a given amount of liquidity and a price range + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param liquidity The liquidity being valued + /// @return amount1 The amount of token1 + function getAmount1ForLiquidity( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + unchecked { + return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + } + + /// @notice Computes the token0 and token1 value for a given amount of liquidity, the current + /// pool prices and the prices at the tick boundaries + /// @param sqrtRatioX96 A sqrt price representing the current pool prices + /// @param sqrtRatioAX96 A sqrt price representing the first tick boundary + /// @param sqrtRatioBX96 A sqrt price representing the second tick boundary + /// @param liquidity The liquidity being valued + /// @return amount0 The amount of token0 + /// @return amount1 The amount of token1 + function getAmountsForLiquidity( + uint160 sqrtRatioX96, + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity + ) internal pure returns (uint256 amount0, uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + if (sqrtRatioX96 <= sqrtRatioAX96) { + amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } else if (sqrtRatioX96 < sqrtRatioBX96) { + amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity); + amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity); + } else { + amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity); + } + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/Path.sol b/contracts/dependencies/uniswapv3-periphery/libraries/Path.sol new file mode 100644 index 000000000..d6ee864b9 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/Path.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.6.0; + +import './BytesLib.sol'; + +/// @title Functions for manipulating path data for multihop swaps +library Path { + using BytesLib for bytes; + + /// @dev The length of the bytes encoded address + uint256 private constant ADDR_SIZE = 20; + /// @dev The length of the bytes encoded fee + uint256 private constant FEE_SIZE = 3; + + /// @dev The offset of a single token address and pool fee + uint256 private constant NEXT_OFFSET = ADDR_SIZE + FEE_SIZE; + /// @dev The offset of an encoded pool key + uint256 private constant POP_OFFSET = NEXT_OFFSET + ADDR_SIZE; + /// @dev The minimum length of an encoding that contains 2 or more pools + uint256 private constant MULTIPLE_POOLS_MIN_LENGTH = POP_OFFSET + NEXT_OFFSET; + + /// @notice Returns true iff the path contains two or more pools + /// @param path The encoded swap path + /// @return True if path contains two or more pools, otherwise false + function hasMultiplePools(bytes memory path) internal pure returns (bool) { + return path.length >= MULTIPLE_POOLS_MIN_LENGTH; + } + + /// @notice Returns the number of pools in the path + /// @param path The encoded swap path + /// @return The number of pools in the path + function numPools(bytes memory path) internal pure returns (uint256) { + // Ignore the first token address. From then on every fee and token offset indicates a pool. + return ((path.length - ADDR_SIZE) / NEXT_OFFSET); + } + + /// @notice Decodes the first pool in path + /// @param path The bytes encoded swap path + /// @return tokenA The first token of the given pool + /// @return tokenB The second token of the given pool + /// @return fee The fee level of the pool + function decodeFirstPool(bytes memory path) + internal + pure + returns ( + address tokenA, + address tokenB, + uint24 fee + ) + { + tokenA = path.toAddress(0); + fee = path.toUint24(ADDR_SIZE); + tokenB = path.toAddress(NEXT_OFFSET); + } + + /// @notice Gets the segment corresponding to the first pool in the path + /// @param path The bytes encoded swap path + /// @return The segment containing all data necessary to target the first pool in the path + function getFirstPool(bytes memory path) internal pure returns (bytes memory) { + return path.slice(0, POP_OFFSET); + } + + /// @notice Skips a token + fee element from the buffer and returns the remainder + /// @param path The swap path + /// @return The remaining token + fee elements in the path + function skipToken(bytes memory path) internal pure returns (bytes memory) { + return path.slice(NEXT_OFFSET, path.length - NEXT_OFFSET); + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/PoolAddress.sol b/contracts/dependencies/uniswapv3-periphery/libraries/PoolAddress.sol new file mode 100644 index 000000000..8d5261723 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/PoolAddress.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity 0.8.10; + +/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee +library PoolAddress { + bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; + + /// @notice The identifying key of the pool + struct PoolKey { + address token0; + address token1; + uint24 fee; + } + + /// @notice Returns PoolKey: the ordered tokens with the matched fee levels + /// @param tokenA The first token of a pool, unsorted + /// @param tokenB The second token of a pool, unsorted + /// @param fee The fee level of the pool + /// @return Poolkey The pool details with ordered token0 and token1 assignments + function getPoolKey( + address tokenA, + address tokenB, + uint24 fee + ) internal pure returns (PoolKey memory) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); + } + + /// @notice Deterministically computes the pool address given the factory and PoolKey + /// @param factory The Uniswap V3 factory contract address + /// @param key The PoolKey + /// @return pool The contract address of the V3 pool + function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) { + require(key.token0 < key.token1); + + pool = 0xCeDA90B69A9547F3b30CD12eF69C94C8D1225Fe3; + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/PositionKey.sol b/contracts/dependencies/uniswapv3-periphery/libraries/PositionKey.sol new file mode 100644 index 000000000..a60fc18f3 --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/PositionKey.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +library PositionKey { + /// @dev Returns the key of the position in the core library + function compute( + address owner, + int24 tickLower, + int24 tickUpper + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(owner, tickLower, tickUpper)); + } +} diff --git a/contracts/dependencies/uniswapv3-periphery/libraries/TransferHelper.sol b/contracts/dependencies/uniswapv3-periphery/libraries/TransferHelper.sol new file mode 100644 index 000000000..6fd4f850f --- /dev/null +++ b/contracts/dependencies/uniswapv3-periphery/libraries/TransferHelper.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.6.0; + +import '../../openzeppelin/contracts/IERC20.sol'; + +library TransferHelper { + /// @notice Transfers tokens from the targeted address to the given destination + /// @notice Errors with 'STF' if transfer fails + /// @param token The contract address of the token to be transferred + /// @param from The originating address from which the tokens will be transferred + /// @param to The destination address of the transfer + /// @param value The amount to be transferred + function safeTransferFrom( + address token, + address from, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = token.call( + abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value) + ); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF'); + } + + /// @notice Transfers tokens from msg.sender to a recipient + /// @dev Errors with ST if transfer fails + /// @param token The contract address of the token which will be transferred + /// @param to The recipient of the transfer + /// @param value The value of the transfer + function safeTransfer( + address token, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST'); + } + + /// @notice Approves the stipulated contract to spend the given allowance in the given token + /// @dev Errors with 'SA' if transfer fails + /// @param token The contract address of the token to be approved + /// @param to The target of the approval + /// @param value The amount of the given token the target will be allowed to spend + function safeApprove( + address token, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA'); + } + + /// @notice Transfers ETH to the recipient address + /// @dev Fails with `STE` + /// @param to The destination of the transfer + /// @param value The value to be transferred + function safeTransferETH(address to, uint256 value) internal { + (bool success, ) = to.call{value: value}(new bytes(0)); + require(success, 'STE'); + } +} diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index a115f08af..5418b9082 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -408,106 +408,6 @@ library SupplyLogic { return amountToWithdraw; } - function executeDecreaseUniswapV3Liquidity( - mapping(address => DataTypes.ReserveData) storage reservesData, - mapping(uint256 => address) storage reservesList, - DataTypes.UserConfigurationMap storage userConfig, - DataTypes.ExecuteDecreaseUniswapV3LiquidityParams memory params - ) external { - DataTypes.ReserveData storage reserve = reservesData[params.asset]; - DataTypes.ReserveCache memory reserveCache = reserve.cache(); - - //currently don't need to update state for erc721 - //reserve.updateState(reserveCache); - - INToken nToken = INToken(reserveCache.xTokenAddress); - require( - nToken.getXTokenType() == XTokenType.NTokenUniswapV3, - Errors.ONLY_UNIV3_ALLOWED - ); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = params.tokenId; - ValidationLogic.validateWithdrawERC721( - reservesData, - reserveCache, - params.asset, - tokenIds - ); - - INTokenUniswapV3(reserveCache.xTokenAddress).decreaseUniswapV3Liquidity( - params.user, - params.tokenId, - params.liquidityDecrease, - params.amount0Min, - params.amount1Min, - params.receiveEthAsWeth - ); - - bool isUsedAsCollateral = ICollateralizableERC721( - reserveCache.xTokenAddress - ).isUsedAsCollateral(params.tokenId); - if (isUsedAsCollateral) { - if (userConfig.isBorrowingAny()) { - ValidationLogic.validateHFAndLtvERC721( - reservesData, - reservesList, - userConfig, - params.asset, - tokenIds, - params.user, - params.reservesCount, - params.oracle - ); - } - } - } - - function executeCollectSupplyUniswapV3Fees( - mapping(address => DataTypes.ReserveData) storage reservesData, - mapping(uint256 => address) storage reservesList, - DataTypes.UserConfigurationMap storage userConfig, - DataTypes.ExecuteCollectAndSupplyUniswapV3FeesParams memory params - ) external { - DataTypes.ReserveData storage reserve = reservesData[params.asset]; - DataTypes.ReserveCache memory reserveCache = reserve.cache(); - - INToken nToken = INToken(reserveCache.xTokenAddress); - require( - nToken.getXTokenType() == XTokenType.NTokenUniswapV3, - Errors.ONLY_UNIV3_ALLOWED - ); - - address positionOwner = nToken.ownerOf(params.tokenId); - address incentiveReceiver = address(0); - - if (msg.sender != positionOwner) { - (, , , , , , , uint256 healthFactor, , ) = GenericLogic - .calculateUserAccountData( - reservesData, - reservesList, - DataTypes.CalculateUserAccountDataParams({ - userConfig: userConfig, - reservesCount: params.reservesCount, - user: positionOwner, - oracle: params.oracle - }) - ); - - require( - healthFactor < DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, - Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD - ); - incentiveReceiver = msg.sender; - } - - INTokenUniswapV3(reserveCache.xTokenAddress).collectSupplyUniswapV3Fees( - positionOwner, - params.tokenId, - incentiveReceiver - ); - } - /** * @notice Validates a transfer of PTokens. The sender is subjected to health factor validation to avoid * collateralization constraints violation. diff --git a/contracts/protocol/libraries/logic/UniswapV3Logic.sol b/contracts/protocol/libraries/logic/UniswapV3Logic.sol new file mode 100644 index 000000000..e7a701fd4 --- /dev/null +++ b/contracts/protocol/libraries/logic/UniswapV3Logic.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.10; + +import {INToken} from "../../../interfaces/INToken.sol"; +import {ICollateralizableERC721} from "../../../interfaces/ICollateralizableERC721.sol"; +import {Errors} from "../helpers/Errors.sol"; +import {UserConfiguration} from "../configuration/UserConfiguration.sol"; +import {DataTypes} from "../types/DataTypes.sol"; +import {ValidationLogic} from "./ValidationLogic.sol"; +import {ReserveLogic} from "./ReserveLogic.sol"; +import {GenericLogic} from "./GenericLogic.sol"; +import {XTokenType} from "../../../interfaces/IXTokenType.sol"; +import {INTokenUniswapV3} from "../../../interfaces/INTokenUniswapV3.sol"; + +/** + * @title UniswapV3Logic library + * + * @notice Implements the base logic for UniswapV3 + */ +library UniswapV3Logic { + using ReserveLogic for DataTypes.ReserveData; + using UserConfiguration for DataTypes.UserConfigurationMap; + + + function executeDecreaseUniswapV3Liquidity( + mapping(address => DataTypes.ReserveData) storage reservesData, + mapping(uint256 => address) storage reservesList, + DataTypes.UserConfigurationMap storage userConfig, + DataTypes.ExecuteDecreaseUniswapV3LiquidityParams memory params + ) external { + DataTypes.ReserveData storage reserve = reservesData[params.asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); + + //currently don't need to update state for erc721 + //reserve.updateState(reserveCache); + + INToken nToken = INToken(reserveCache.xTokenAddress); + require( + nToken.getXTokenType() == XTokenType.NTokenUniswapV3, + Errors.ONLY_UNIV3_ALLOWED + ); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = params.tokenId; + ValidationLogic.validateWithdrawERC721( + reservesData, + reserveCache, + params.asset, + tokenIds + ); + + INTokenUniswapV3(reserveCache.xTokenAddress).decreaseUniswapV3Liquidity( + params.user, + params.tokenId, + params.liquidityDecrease, + params.amount0Min, + params.amount1Min, + params.receiveEthAsWeth + ); + + bool isUsedAsCollateral = ICollateralizableERC721( + reserveCache.xTokenAddress + ).isUsedAsCollateral(params.tokenId); + if (isUsedAsCollateral) { + if (userConfig.isBorrowingAny()) { + ValidationLogic.validateHFAndLtvERC721( + reservesData, + reservesList, + userConfig, + params.asset, + tokenIds, + params.user, + params.reservesCount, + params.oracle + ); + } + } + } + + function executeCollectSupplyUniswapV3Fees( + mapping(address => DataTypes.ReserveData) storage reservesData, + mapping(uint256 => address) storage reservesList, + DataTypes.UserConfigurationMap storage userConfig, + DataTypes.ExecuteCollectAndSupplyUniswapV3FeesParams memory params + ) external { + DataTypes.ReserveData storage reserve = reservesData[params.asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); + + INToken nToken = INToken(reserveCache.xTokenAddress); + require( + nToken.getXTokenType() == XTokenType.NTokenUniswapV3, + Errors.ONLY_UNIV3_ALLOWED + ); + + address positionOwner = nToken.ownerOf(params.tokenId); + address incentiveReceiver = address(0); + + if (msg.sender != positionOwner) { + (, , , , , , , uint256 healthFactor, , ) = GenericLogic + .calculateUserAccountData( + reservesData, + reservesList, + DataTypes.CalculateUserAccountDataParams({ + userConfig: userConfig, + reservesCount: params.reservesCount, + user: positionOwner, + oracle: params.oracle + }) + ); + + require( + healthFactor < DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD + ); + incentiveReceiver = msg.sender; + } + + INTokenUniswapV3(reserveCache.xTokenAddress).collectSupplyUniswapV3Fees( + positionOwner, + params.tokenId, + incentiveReceiver + ); + } + +} \ No newline at end of file diff --git a/contracts/protocol/pool/PoolCore.sol b/contracts/protocol/pool/PoolCore.sol index 3c3ee6d01..981a51fbd 100644 --- a/contracts/protocol/pool/PoolCore.sol +++ b/contracts/protocol/pool/PoolCore.sol @@ -8,6 +8,7 @@ import {PoolLogic} from "../libraries/logic/PoolLogic.sol"; import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; import {SupplyLogic} from "../libraries/logic/SupplyLogic.sol"; import {MarketplaceLogic} from "../libraries/logic/MarketplaceLogic.sol"; +import {UniswapV3Logic} from "../libraries/logic/UniswapV3Logic.sol"; import {BorrowLogic} from "../libraries/logic/BorrowLogic.sol"; import {LiquidationLogic} from "../libraries/logic/LiquidationLogic.sol"; import {AuctionLogic} from "../libraries/logic/AuctionLogic.sol"; @@ -242,7 +243,7 @@ contract PoolCore is DataTypes.PoolStorage storage ps = poolStorage(); return - SupplyLogic.executeCollectSupplyUniswapV3Fees( + UniswapV3Logic.executeCollectSupplyUniswapV3Fees( ps._reserves, ps._reservesList, ps._usersConfig[msg.sender], @@ -266,7 +267,7 @@ contract PoolCore is DataTypes.PoolStorage storage ps = poolStorage(); return - SupplyLogic.executeDecreaseUniswapV3Liquidity( + UniswapV3Logic.executeDecreaseUniswapV3Liquidity( ps._reserves, ps._reservesList, ps._usersConfig[msg.sender], diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 31da064d9..e335c2845 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -228,6 +228,10 @@ import { NTokenBAKC__factory, AirdropFlashClaimReceiver__factory, AirdropFlashClaimReceiver, + NonfungiblePositionManager__factory, + SwapRouter__factory, + UniswapV3Logic__factory, + UniswapV3Logic } from "../types"; import {MockContract} from "ethereum-waffle"; import { @@ -245,8 +249,8 @@ import { withSaveAndVerify, } from "./contracts-helpers"; -import * as nonfungiblePositionManager from "@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"; -import * as uniSwapRouter from "@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json"; +// import * as nonfungiblePositionManager from "@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json"; +// import * as uniSwapRouter from "@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json"; import * as nFTDescriptor from "@uniswap/v3-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json"; import * as nonfungibleTokenPositionDescriptor from "@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json"; import {Address} from "hardhat-deploy/dist/types"; @@ -327,6 +331,14 @@ export const deploySupplyLogic = async (verify?: boolean) => verify ) as Promise; +export const deployUniswapV3Logic = async (verify?: boolean) => + withSaveAndVerify( + new UniswapV3Logic__factory(await getFirstSigner()), + eContractid.UniswapV3Logic, + [], + verify + ) as Promise; + export const deployFlashClaimLogic = async (verify?: boolean) => withSaveAndVerify( new FlashClaimLogic__factory(await getFirstSigner()), @@ -385,6 +397,7 @@ export const deployPoolCoreLibraries = async ( }, verify ); + const uniswapV3Logic = await deployUniswapV3Logic(verify); const flashClaimLogic = await deployFlashClaimLogic(verify); return { @@ -398,6 +411,8 @@ export const deployPoolCoreLibraries = async ( borrowLogic.address, ["contracts/protocol/libraries/logic/FlashClaimLogic.sol:FlashClaimLogic"]: flashClaimLogic.address, + ["contracts/protocol/libraries/logic/UniswapV3Logic.sol:UniswapV3Logic"]: + uniswapV3Logic.address, }; }; @@ -1639,12 +1654,8 @@ export const deployNonfungiblePositionManager = async ( args: [string, string, string], verify?: boolean ) => { - const nonfungiblePositionManagerFactory = ( - await DRE.ethers.getContractFactoryFromArtifact(nonfungiblePositionManager) - ).connect(await getFirstSigner()); - return withSaveAndVerify( - nonfungiblePositionManagerFactory, + new NonfungiblePositionManager__factory(await getFirstSigner()), eContractid.UniswapV3, [...args], verify @@ -1655,12 +1666,10 @@ export const deployUniswapSwapRouter = async ( args: [string, string], verify?: boolean ) => { - const swapRouter = ( - await DRE.ethers.getContractFactoryFromArtifact(uniSwapRouter) - ).connect(await getFirstSigner()); + return withSaveAndVerify( - swapRouter, + new SwapRouter__factory(await getFirstSigner()), eContractid.UniswapV3SwapRouter, [...args], verify diff --git a/helpers/types.ts b/helpers/types.ts index 910ecfe17..ca9716b14 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -101,6 +101,7 @@ export enum eContractid { LiquidationLogic = "LiquidationLogic", AuctionLogic = "AuctionLogic", PoolLogic = "PoolLogic", + UniswapV3Logic = "UniswapV3Logic", ConfiguratorLogic = "ConfiguratorLogic", PoolProxy = "PoolProxy", PriceOracle = "PriceOracle", From 4d747c527608035e8a03117ce981cd6059d2fa90 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 23 Jan 2023 17:17:39 -0800 Subject: [PATCH 07/11] chore: adds event for uniV3 compounding --- contracts/protocol/libraries/logic/UniswapV3Logic.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/protocol/libraries/logic/UniswapV3Logic.sol b/contracts/protocol/libraries/logic/UniswapV3Logic.sol index e7a701fd4..08b7841ae 100644 --- a/contracts/protocol/libraries/logic/UniswapV3Logic.sol +++ b/contracts/protocol/libraries/logic/UniswapV3Logic.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; +pragma solidity =0.8.10; import {INToken} from "../../../interfaces/INToken.sol"; import {ICollateralizableERC721} from "../../../interfaces/ICollateralizableERC721.sol"; @@ -21,6 +21,7 @@ library UniswapV3Logic { using ReserveLogic for DataTypes.ReserveData; using UserConfiguration for DataTypes.UserConfigurationMap; + event CollectSupplyUniswapV3Fees(uint256 tokenId); function executeDecreaseUniswapV3Liquidity( mapping(address => DataTypes.ReserveData) storage reservesData, @@ -120,6 +121,8 @@ library UniswapV3Logic { params.tokenId, incentiveReceiver ); + + emit CollectSupplyUniswapV3Fees(params.tokenId); } } \ No newline at end of file From 1b2b2bb1ca171521f260267371c3df114b888ea4 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 23 Jan 2023 17:19:23 -0800 Subject: [PATCH 08/11] chore: lint --- contracts/protocol/libraries/logic/UniswapV3Logic.sol | 5 ++--- helpers/contracts-deployments.ts | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/protocol/libraries/logic/UniswapV3Logic.sol b/contracts/protocol/libraries/logic/UniswapV3Logic.sol index 08b7841ae..576315565 100644 --- a/contracts/protocol/libraries/logic/UniswapV3Logic.sol +++ b/contracts/protocol/libraries/logic/UniswapV3Logic.sol @@ -121,8 +121,7 @@ library UniswapV3Logic { params.tokenId, incentiveReceiver ); - + emit CollectSupplyUniswapV3Fees(params.tokenId); } - -} \ No newline at end of file +} diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index e335c2845..01489d378 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -231,7 +231,7 @@ import { NonfungiblePositionManager__factory, SwapRouter__factory, UniswapV3Logic__factory, - UniswapV3Logic + UniswapV3Logic, } from "../types"; import {MockContract} from "ethereum-waffle"; import { @@ -1666,8 +1666,6 @@ export const deployUniswapSwapRouter = async ( args: [string, string], verify?: boolean ) => { - - return withSaveAndVerify( new SwapRouter__factory(await getFirstSigner()), eContractid.UniswapV3SwapRouter, From 413cfe768f0f40e2950b96ca6ed52d365f8d6312 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 23 Jan 2023 17:32:50 -0800 Subject: [PATCH 09/11] chore: adds supply from xtoken and re-entrancy --- contracts/interfaces/IPoolCore.sol | 13 +++++++ contracts/protocol/pool/PoolCore.sol | 34 +++++++++++++++++-- .../protocol/tokenization/NTokenUniswapV3.sol | 10 +++--- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/contracts/interfaces/IPoolCore.sol b/contracts/interfaces/IPoolCore.sol index 047011be8..879f24864 100644 --- a/contracts/interfaces/IPoolCore.sol +++ b/contracts/interfaces/IPoolCore.sol @@ -209,6 +209,19 @@ interface IPoolCore { uint16 referralCode ) external; + /** + * @notice same as supply but caller can only be xToken. + * this function has no re-entrancy intentionally to be able to suppport multiple supply calls + **/ + + function supplyFromXToken( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode, + address xTokenUnderlyingAsset + ) external; + /** * @notice Supplies multiple `tokenIds` of underlying ERC721 asset into the reserve, receiving in return overlying nTokens. * - E.g. User supplies 2 BAYC and gets in return 2 nBAYC diff --git a/contracts/protocol/pool/PoolCore.sol b/contracts/protocol/pool/PoolCore.sol index 981a51fbd..585d87687 100644 --- a/contracts/protocol/pool/PoolCore.sol +++ b/contracts/protocol/pool/PoolCore.sol @@ -110,6 +110,37 @@ contract PoolCore is ); } + /// @inheritdoc IPoolCore + function supplyFromXToken( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode, + address xTokenUnderlyingAsset + ) external virtual override { + DataTypes.PoolStorage storage ps = poolStorage(); + DataTypes.ReserveData storage xtokenReserve = ps._reserves[ + xTokenUnderlyingAsset + ]; + + require( + msg.sender == xtokenReserve.xTokenAddress, + Errors.CALLER_NOT_XTOKEN + ); + + SupplyLogic.executeSupply( + ps._reserves, + ps._usersConfig[onBehalfOf], + DataTypes.ExecuteSupplyParams({ + asset: asset, + amount: amount, + onBehalfOf: onBehalfOf, + payer: msg.sender, + referralCode: referralCode + }) + ); + } + /// @inheritdoc IPoolCore function supplyERC721( address asset, @@ -238,8 +269,7 @@ contract PoolCore is function collectCompoundAndSupplyUniswapV3Fees( address asset, uint256 tokenId - ) external virtual override { - // we need re-entrancy here. might need to create a special supplyERC20 from NToken as a workaround + ) external virtual override nonReentrant { DataTypes.PoolStorage storage ps = poolStorage(); return diff --git a/contracts/protocol/tokenization/NTokenUniswapV3.sol b/contracts/protocol/tokenization/NTokenUniswapV3.sol index 15a08f1f9..c492d1e3a 100644 --- a/contracts/protocol/tokenization/NTokenUniswapV3.sol +++ b/contracts/protocol/tokenization/NTokenUniswapV3.sol @@ -292,11 +292,12 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { address(this) ); if (token0BalanceAfter > token0BalanceBefore) { - POOL.supply( + POOL.supplyFromXToken( token0, token0BalanceAfter - token0BalanceBefore, user, - 0x0 + 0x0, + _underlyingAsset ); } } @@ -306,11 +307,12 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { address(this) ); if (token1BalanceAfter > token1BalanceBefore) { - POOL.supply( + POOL.supplyFromXToken( token1, token1BalanceAfter - token1BalanceBefore, user, - 0x0 + 0x0, + _underlyingAsset ); } } From c10a8aa24fc4b20e7a857064a536aae2816de2f1 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 23 Jan 2023 17:35:23 -0800 Subject: [PATCH 10/11] chore: removes logs --- .../uniswapv3-periphery/base/LiquidityManagement.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol b/contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol index f31c0d3d4..1ba0cde16 100644 --- a/contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol +++ b/contracts/dependencies/uniswapv3-periphery/base/LiquidityManagement.sol @@ -66,8 +66,6 @@ abstract contract LiquidityManagement is IUniswapV3MintCallback, PeripheryImmuta }); pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); - console.log("pool add is ", PoolAddress.computeAddress(factory, poolKey)); - // pool.slot0(); // compute the liquidity amount { @@ -82,7 +80,6 @@ abstract contract LiquidityManagement is IUniswapV3MintCallback, PeripheryImmuta params.amount0Desired, params.amount1Desired ); - console.log("sqrt ", sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96); } From 43342957c954cc12afdbc0cd211583de05dfd977 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Wed, 1 Feb 2023 16:58:55 -0800 Subject: [PATCH 11/11] chore: fix typo --- contracts/interfaces/IPoolCore.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IPoolCore.sol b/contracts/interfaces/IPoolCore.sol index 879f24864..a7570a28b 100644 --- a/contracts/interfaces/IPoolCore.sol +++ b/contracts/interfaces/IPoolCore.sol @@ -211,7 +211,7 @@ interface IPoolCore { /** * @notice same as supply but caller can only be xToken. - * this function has no re-entrancy intentionally to be able to suppport multiple supply calls + * this function has no re-entrancy intentionally to be able to support multiple supply calls **/ function supplyFromXToken(