From 702818b99da8e9c927fc4bbae342a971174c7969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Buend=C3=ADa?= Date: Thu, 11 Nov 2021 00:22:41 +0100 Subject: [PATCH] feat: uniswapV3LiquidityPosition (#717) * feat: add UniswapV3LiquidityPosition lib, parser, and helper contracts * chore: add uniswap peripheral contracts and openzeppelin solc 7 npm dependencies * refactor: copy necessary interfaces for use in this external position only, due to solc 7 * chore: add deployment scripts * chore: add helper utils * test: add test suite * chore: update codegen to handle contract namespace clashes Co-authored-by: Sean Casey --- .solhint.json | 3 +- .../UniswapV3LiquidityPositionLibBase1.sol | 33 + .../UniswapV3LiquidityPositionDataDecoder.sol | 93 +++ .../UniswapV3LiquidityPositionLib.sol | 444 +++++++++++++ .../UniswapV3LiquidityPositionParser.sol | 181 ++++++ ...sitionParserUniswapV3LiquidityPosition.sol | 32 + ...rnalPositionUniswapV3LiquidityPosition.sol | 22 + .../IUniswapV3LiquidityPosition.sol | 20 + ...eInterpreterUniswapV3LiquidityPosition.sol | 28 + deploy/scripts/config/Mainnet.ts | 3 +- .../extensions/ExternalPositionManager.ts | 14 +- .../UniswapV3LiquidityPositionLib.ts | 25 + .../UniswapV3LiquidityPositionParser.ts | 26 + deploy/utils/config.ts | 1 + hardhat.config.ts | 27 +- package.json | 6 +- packages/protocol/package.json | 4 +- packages/protocol/src/contracts.ts | 2 + .../src/utils/external-positions/index.ts | 1 + .../src/utils/external-positions/types.ts | 1 + .../uniswap-v3-liquidity.ts | 89 +++ packages/testutils/package.json | 6 +- .../extensions/external-positions/index.ts | 1 + .../uniswap-v3-liquidity.ts | 281 +++++++++ .../extensions/integrations/uniswapV3.ts | 3 +- packages/testutils/src/whales.ts | 2 + .../ExternalPositionManager.test.ts | 2 +- .../CompoundDebtPositionLib.test.ts | 0 .../UniswapV3LiquidityPositionLib.test.ts | 595 ++++++++++++++++++ yarn.lock | 541 +++++++++++++--- 30 files changed, 2392 insertions(+), 94 deletions(-) create mode 100644 contracts/persistent/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLibBase1.sol create mode 100644 contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionDataDecoder.sol create mode 100644 contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLib.sol create mode 100644 contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionParser.sol create mode 100644 contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionParserUniswapV3LiquidityPosition.sol create mode 100644 contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionUniswapV3LiquidityPosition.sol create mode 100644 contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IUniswapV3LiquidityPosition.sol create mode 100644 contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IValueInterpreterUniswapV3LiquidityPosition.sol create mode 100644 deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionLib.ts create mode 100644 deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionParser.ts create mode 100644 packages/protocol/src/utils/external-positions/uniswap-v3-liquidity.ts create mode 100644 packages/testutils/src/scaffolding/extensions/external-positions/uniswap-v3-liquidity.ts rename tests/release/extensions/external-position-manager/{external-positions => libs}/CompoundDebtPositionLib.test.ts (100%) create mode 100644 tests/release/extensions/external-position-manager/libs/UniswapV3LiquidityPositionLib.test.ts diff --git a/.solhint.json b/.solhint.json index 6f7b6f179..b5324386d 100644 --- a/.solhint.json +++ b/.solhint.json @@ -1,7 +1,8 @@ { "extends": "solhint:recommended", "rules": { - "compiler-version": ["error", "0.6.12"], + "compiler-version": ["error", "0.6.12 || 0.7.6"], + "func-visibility": ["error", { "ignoreConstructors": true }], "no-empty-blocks": "off", "not-rely-on-time": "off", "avoid-low-level-calls": "off", diff --git a/contracts/persistent/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLibBase1.sol b/contracts/persistent/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLibBase1.sol new file mode 100644 index 000000000..8c45260e5 --- /dev/null +++ b/contracts/persistent/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLibBase1.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + + (c) Enzyme Council + + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +pragma solidity 0.7.6; + +/// @title UniswapV3LiquidityPositionLibBase1 Contract +/// @author Enzyme Council +/// @notice A persistent contract containing all required storage variables and +/// required functions for a UniswapV3LiquidityPositionLib implementation +/// @dev DO NOT EDIT CONTRACT. If new events or storage are necessary, they should be added to +/// a numbered UniswapV3LiquidityPositionLibBaseXXX that inherits the previous base. +/// e.g., `UniswapV3LiquidityPositionLibBase2 is UniswapV3LiquidityPositionLibBase1` +abstract contract UniswapV3LiquidityPositionLibBase1 { + event Initialized(address token0, address token1); + + event NFTPositionAdded(uint256 indexed tokenId); + + event NFTPositionRemoved(uint256 indexed tokenId); + + uint256[] internal nftIds; + // token0 and token1 are assigned deterministically by sort order, + // so will be the same for all fees + address internal token0; + address internal token1; +} diff --git a/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionDataDecoder.sol b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionDataDecoder.sol new file mode 100644 index 000000000..e7cd4c726 --- /dev/null +++ b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionDataDecoder.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + (c) Enzyme Council + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +pragma solidity 0.7.6; + +/// @title UniswapV3LiquidityPositionDataDecoder Contract +/// @author Enzyme Council +/// @notice Abstract contract containing data decodings for UniswapV3LiquidityPosition payloads +abstract contract UniswapV3LiquidityPositionDataDecoder { + /// @dev Helper to decode args used during the AddLiquidity action + function __decodeAddLiquidityActionArgs(bytes memory _actionArgs) + internal + pure + returns ( + uint256 nftId_, + uint256 amount0Desired_, + uint256 amount1Desired_, + uint256 amount0Min_, + uint256 amount1Min_ + ) + { + return abi.decode(_actionArgs, (uint256, uint256, uint256, uint256, uint256)); + } + + /// @dev Helper to decode args used during the Collect action + function __decodeCollectActionArgs(bytes memory _actionArgs) + internal + pure + returns (uint256 nftId_) + { + return abi.decode(_actionArgs, (uint256)); + } + + /// @dev Helper to decode args used during init() + function __decodeInitArgs(bytes memory _initArgs) + internal + pure + returns (address token0_, address token1_) + { + return abi.decode(_initArgs, (address, address)); + } + + /// @dev Helper to decode args used during the Mint action + function __decodeMintActionArgs(bytes memory _actionArgs) + internal + pure + returns ( + uint24 fee_, + int24 tickLower_, + int24 tickUpper_, + uint256 amount0Desired_, + uint256 amount1Desired_, + uint256 amount0Min_, + uint256 amount1Min_ + ) + { + return abi.decode(_actionArgs, (uint24, int24, int24, uint256, uint256, uint256, uint256)); + } + + /// @dev Helper to decode args used during the Purge action + function __decodePurgeActionArgs(bytes memory _actionArgs) + internal + pure + returns ( + uint256 nftId_, + uint128 liquidity_, + uint256 amount0Min_, + uint256 amount1Min_ + ) + { + return abi.decode(_actionArgs, (uint256, uint128, uint256, uint256)); + } + + /// @dev Helper to decode args used during the RemoveLiquidity action + function __decodeRemoveLiquidityActionArgs(bytes memory _actionArgs) + internal + pure + returns ( + uint256 nftId_, + uint128 liquidity_, + uint256 amount0Min_, + uint256 amount1Min_ + ) + { + return abi.decode(_actionArgs, (uint256, uint128, uint256, uint256)); + } +} diff --git a/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLib.sol b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLib.sol new file mode 100644 index 000000000..d84a8e9ff --- /dev/null +++ b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLib.sol @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + (c) Enzyme Council + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import "@openzeppelin-solc-0.7/contracts/math/SafeMath.sol"; +import "@openzeppelin-solc-0.7/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin-solc-0.7/contracts/token/ERC20/SafeERC20.sol"; +import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol"; +import "@uniswap/v3-periphery/contracts/libraries/PositionValue.sol"; +import "../../../../../persistent/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionLibBase1.sol"; +import "./interfaces/IUniswapV3LiquidityPosition.sol"; +import "./interfaces/IValueInterpreterUniswapV3LiquidityPosition.sol"; +import "./UniswapV3LiquidityPositionDataDecoder.sol"; + +/// @title UniswapV3LiquidityPositionLib Contract +/// @author Enzyme Council +/// @notice An External Position library contract for uniswap liquidity positions +contract UniswapV3LiquidityPositionLib is + IUniswapV3LiquidityPosition, + UniswapV3LiquidityPositionLibBase1, + UniswapV3LiquidityPositionDataDecoder +{ + using SafeERC20 for ERC20; + using SafeMath for uint256; + + address private immutable NON_FUNGIBLE_TOKEN_MANAGER; + address private immutable VALUE_INTERPRETER; + + uint256 private constant TRUSTED_RATE_INITIAL_VIRTUAL_BALANCE = 10**18; + uint256 private constant UNISWAP_SQRT_INFLATE_FACTOR = 2**192; + + constructor(address _nonFungibleTokenManager, address _valueInterpreter) { + NON_FUNGIBLE_TOKEN_MANAGER = _nonFungibleTokenManager; + VALUE_INTERPRETER = _valueInterpreter; + } + + /// @notice Initializes the external position + /// @param _initArgs The encoded data to use during initialization + function init(bytes memory _initArgs) external override { + require(token0 == address(0), "init: Already initialized"); + + (address token0Val, address token1Val) = __decodeInitArgs(_initArgs); + + token0 = token0Val; + token1 = token1Val; + + emit Initialized(token0Val, token1Val); + } + + /// @notice Receives and executes a call from the Vault + /// @param _actionData Encoded data to execute the action + function receiveCallFromVault(bytes memory _actionData) external override { + (uint256 actionId, bytes memory actionArgs) = abi.decode(_actionData, (uint256, bytes)); + + if ( + actionId == uint256(IUniswapV3LiquidityPosition.UniswapV3LiquidityPositionActions.Mint) + ) { + ( + uint24 fee, + int24 tickLower, + int24 tickUpper, + uint256 amount0Desired, + uint256 amount1Desired, + uint256 amount0Min, + uint256 amount1Min + ) = __decodeMintActionArgs(actionArgs); + + __mint( + INonfungiblePositionManager.MintParams({ + token0: token0, + token1: token1, + fee: fee, + tickLower: tickLower, + tickUpper: tickUpper, + amount0Desired: amount0Desired, + amount1Desired: amount1Desired, + amount0Min: amount0Min, + amount1Min: amount1Min, + recipient: address(this), + deadline: block.timestamp + }) + ); + } else if ( + actionId == + uint256(IUniswapV3LiquidityPosition.UniswapV3LiquidityPositionActions.AddLiquidity) + ) { + ( + uint256 nftId, + uint256 amount0Desired, + uint256 amount1Desired, + uint256 amount0Min, + uint256 amount1Min + ) = __decodeAddLiquidityActionArgs(actionArgs); + + __addLiquidity( + INonfungiblePositionManager.IncreaseLiquidityParams({ + tokenId: nftId, + amount0Desired: amount0Desired, + amount1Desired: amount1Desired, + amount0Min: amount0Min, + amount1Min: amount1Min, + deadline: block.timestamp + }) + ); + } else if ( + actionId == + uint256(IUniswapV3LiquidityPosition.UniswapV3LiquidityPositionActions.RemoveLiquidity) + ) { + ( + uint256 nftId, + uint128 liquidity, + uint256 amount0Min, + uint256 amount1Min + ) = __decodeRemoveLiquidityActionArgs(actionArgs); + + __removeLiquidity( + INonfungiblePositionManager.DecreaseLiquidityParams({ + tokenId: nftId, + liquidity: liquidity, + amount0Min: amount0Min, + amount1Min: amount1Min, + deadline: block.timestamp + 1 + }) + ); + } else if ( + actionId == + uint256(IUniswapV3LiquidityPosition.UniswapV3LiquidityPositionActions.Purge) + ) { + ( + uint256 nftId, + uint128 liquidity, + uint256 amount0Min, + uint256 amount1Min + ) = __decodePurgeActionArgs(actionArgs); + + __purge(nftId, liquidity, amount0Min, amount1Min); + } else if ( + actionId == + uint256(IUniswapV3LiquidityPosition.UniswapV3LiquidityPositionActions.Collect) + ) { + __collect(__decodeCollectActionArgs(actionArgs)); + } else { + revert("receiveCallFromVault: Invalid actionId"); + } + } + + // PRIVATE FUNCTIONS + + /// @dev Adds liquidity to the uniswap position + function __addLiquidity(INonfungiblePositionManager.IncreaseLiquidityParams memory _params) + private + { + (, uint256 amount0, uint256 amount1) = INonfungiblePositionManager( + getNonFungibleTokenManager() + ) + .increaseLiquidity(_params); + + if (amount0 < _params.amount0Desired) { + ERC20(token0).safeTransfer(msg.sender, ERC20(token0).balanceOf(address(this))); + } + + if (amount1 < _params.amount1Desired) { + ERC20(token1).safeTransfer(msg.sender, ERC20(token1).balanceOf(address(this))); + } + } + + /// @dev Helper to approve a target account with the max amount of an asset. + /// Copied from AssetHelpers.sol + function __approveAssetMaxAsNeeded( + address _asset, + address _target, + uint256 _neededAmount + ) internal { + uint256 allowance = ERC20(_asset).allowance(address(this), _target); + if (allowance < _neededAmount) { + if (allowance > 0) { + ERC20(_asset).safeApprove(_target, 0); + } + ERC20(_asset).safeApprove(_target, type(uint256).max); + } + } + + /// @dev Collects all uncollected amounts from the nft position and sends it to the vaultProxy + function __collect(uint256 _nftId) private { + INonfungiblePositionManager(getNonFungibleTokenManager()).collect( + INonfungiblePositionManager.CollectParams({ + tokenId: _nftId, + recipient: address(msg.sender), + amount0Max: type(uint128).max, + amount1Max: type(uint128).max + }) + ); + } + + /// @dev Helper to get the total liquidity of an nft position. + /// Uses a low-level staticcall() and truncated decoding of `.positions()` + /// in order to avoid compilation error. + function __getLiquidityForNFT(uint256 _nftId) private view returns (uint128 liquidity_) { + (bool success, bytes memory returnData) = getNonFungibleTokenManager().staticcall( + abi.encodeWithSelector(INonfungiblePositionManager.positions.selector, _nftId) + ); + require(success, string(returnData)); + + (, , , , , , , liquidity_) = abi.decode( + returnData, + (uint96, address, address, address, uint24, int24, int24, uint128) + ); + + return liquidity_; + } + + /// @dev Mints a new uniswap position, receiving an nft as a receipt + function __mint(INonfungiblePositionManager.MintParams memory _params) private { + __approveAssetMaxAsNeeded( + _params.token0, + getNonFungibleTokenManager(), + _params.amount0Desired + ); + __approveAssetMaxAsNeeded( + _params.token1, + getNonFungibleTokenManager(), + _params.amount1Desired + ); + + (uint256 tokenId, , uint256 amount0, uint256 amount1) = INonfungiblePositionManager( + getNonFungibleTokenManager() + ) + .mint(_params); + + token0 = _params.token0; + token1 = _params.token1; + + nftIds.push(tokenId); + + // Transfer back to the vaultProxy tokens not added as liquidity + if (amount0 < _params.amount0Desired) { + ERC20(_params.token0).safeTransfer( + msg.sender, + ERC20(_params.token0).balanceOf(address(this)) + ); + } + + if (amount1 < _params.amount1Desired) { + ERC20(_params.token1).safeTransfer( + msg.sender, + ERC20(_params.token1).balanceOf(address(this)) + ); + } + + emit NFTPositionAdded(tokenId); + } + + /// @dev Purges a position by removing all liquidity, + /// collecting and transferring all tokens owed to the vault, + /// and burning the nft. + /// _liquidity == 0 signifies no liquidity to be removed (i.e., only collect and burn). + /// 0 < _liquidity 0 < max uint128 signifies the full amount of liquidity is known (more gas-efficient). + /// _liquidity == max uint128 signifies the full amount of liquidity is unknown. + function __purge( + uint256 _nftId, + uint128 _liquidity, + uint256 _amount0Min, + uint256 _amount1Min + ) private { + if (_liquidity == type(uint128).max) { + // This consumes a lot of unnecessary gas because of all the SLOAD operations, + // when we only care about `liquidity`. + // Should ideally only be used in the rare case where a griefing attack + // (i.e., frontrunning the tx and adding extra liquidity dust) is a concern. + _liquidity = __getLiquidityForNFT(_nftId); + } + + if (_liquidity > 0) { + INonfungiblePositionManager(getNonFungibleTokenManager()).decreaseLiquidity( + INonfungiblePositionManager.DecreaseLiquidityParams({ + tokenId: _nftId, + liquidity: _liquidity, + amount0Min: _amount0Min, + amount1Min: _amount1Min, + deadline: block.timestamp + }) + ); + } + + __collect(_nftId); + + // Reverts if liquidity or uncollected tokens are remaining + INonfungiblePositionManager(getNonFungibleTokenManager()).burn(_nftId); + + // Can later replace with the helper from AddressArrayLib.sol, updated for solc 7 + uint256 nftCount = nftIds.length; + for (uint256 i; i < nftCount; i++) { + if (nftIds[i] == _nftId) { + if (i < nftCount - 1) { + nftIds[i] = nftIds[nftCount - 1]; + } + nftIds.pop(); + break; + } + } + + emit NFTPositionRemoved(_nftId); + } + + /// @dev Removes liquidity from the uniswap position and transfers the tokens back to the vault + function __removeLiquidity(INonfungiblePositionManager.DecreaseLiquidityParams memory _params) + private + { + INonfungiblePositionManager(getNonFungibleTokenManager()).decreaseLiquidity(_params); + + __collect(_params.tokenId); + } + + /// @dev Uniswap square root function. See: + /// https://github.com/Uniswap/uniswap-lib/blob/6ddfedd5716ba85b905bf34d7f1f3c659101a1bc/contracts/libraries/Babylonian.sol + function __uniswapSqrt(uint256 _y) private pure returns (uint256 z_) { + if (_y > 3) { + z_ = _y; + uint256 x = _y / 2 + 1; + while (x < z_) { + z_ = x; + x = (_y / x + x) / 2; + } + } else if (_y != 0) { + z_ = 1; + } + // else z_ = 0 + + return z_; + } + + /////////////////// + // STATE GETTERS // + /////////////////// + + // EXTERNAL FUNCTIONS + + /// @notice Retrieves the debt assets (negative value) of the external position + /// @return assets_ Debt assets + /// @return amounts_ Debt asset amounts + function getDebtAssets() + external + pure + override + returns (address[] memory assets_, uint256[] memory amounts_) + { + return (assets_, amounts_); + } + + /// @notice Retrieves the managed assets (positive value) of the external position + /// @return assets_ Managed assets + /// @return amounts_ Managed asset amounts + function getManagedAssets() + external + override + returns (address[] memory assets_, uint256[] memory amounts_) + { + uint256[] memory nftIdsCopy = getNftIds(); + if (nftIdsCopy.length == 0) { + return (assets_, amounts_); + } + + assets_ = new address[](2); + amounts_ = new uint256[](2); + + address token0Copy = getToken0(); + address token1Copy = getToken1(); + assets_[0] = token0Copy; + assets_[1] = token1Copy; + + uint256 token0VirtualReserves = IValueInterpreterUniswapV3LiquidityPosition( + VALUE_INTERPRETER + ) + .calcCanonicalAssetValue(token1Copy, TRUSTED_RATE_INITIAL_VIRTUAL_BALANCE, token0Copy); + // Adapted from UniswapV3 white paper formula 6.4 + uint160 sqrtPriceX96 = uint160( + __uniswapSqrt( + (UNISWAP_SQRT_INFLATE_FACTOR.mul(TRUSTED_RATE_INITIAL_VIRTUAL_BALANCE)).div( + token0VirtualReserves + ) + ) + ); + + for (uint256 i; i < nftIdsCopy.length; i++) { + (uint256 amount0, uint256 amount1) = PositionValue.total( + INonfungiblePositionManager(getNonFungibleTokenManager()), + nftIdsCopy[i], + sqrtPriceX96 + ); + + amounts_[0] = amounts_[0].add(amount0); + amounts_[1] = amounts_[1].add(amount1); + } + + return (assets_, amounts_); + } + + // PUBLIC FUNCTIONS + + /// @notice Gets the `nftIds` variable + /// @return nftIds_ The `nftIds` variable value + function getNftIds() public view returns (uint256[] memory nftIds_) { + return nftIds; + } + + /// @notice Gets the `NON_FUNGIBLE_TOKEN_MANAGER` variable + /// @return nonFungibleTokenManager_ The `NON_FUNGIBLE_TOKEN_MANAGER` variable value + function getNonFungibleTokenManager() public view returns (address nonFungibleTokenManager_) { + return NON_FUNGIBLE_TOKEN_MANAGER; + } + + /// @notice Gets the pair of tokens of the pool + /// @return token0_ The `token0` variable value + /// @return token1_ The `token1` variable value + function getPair() public view override returns (address token0_, address token1_) { + return (getToken0(), getToken1()); + } + + /// @notice Gets the `token0` variable + /// @return token0_ The `token0` variable value + function getToken0() public view returns (address token0_) { + return token0; + } + + /// @notice Gets the `token1` variable + /// @return token1_ The `token1` variable value + function getToken1() public view returns (address token1_) { + return token1; + } + + /// @notice Gets the `VALUE_INTERPRETER` variable + /// @return valueInterpreter_ The `NON_FUNGIBLE_TOKEN_MANAGER` variable value + function getValueInterpreter() public view returns (address valueInterpreter_) { + return VALUE_INTERPRETER; + } +} diff --git a/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionParser.sol b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionParser.sol new file mode 100644 index 000000000..c2e4c5b07 --- /dev/null +++ b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/UniswapV3LiquidityPositionParser.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + + (c) Enzyme Council + + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +import "@openzeppelin-solc-0.7/contracts/token/ERC721/ERC721.sol"; +import "./interfaces/IExternalPositionParserUniswapV3LiquidityPosition.sol"; +import "./interfaces/IUniswapV3LiquidityPosition.sol"; +import "./interfaces/IValueInterpreterUniswapV3LiquidityPosition.sol"; +import "./UniswapV3LiquidityPositionDataDecoder.sol"; + +pragma solidity 0.7.6; + +/// @title UniswapV3LiquidityPositionParser +/// @author Enzyme Council +/// @notice Parser for UniswapV3 Liquidity Positions +contract UniswapV3LiquidityPositionParser is + IExternalPositionParserUniswapV3LiquidityPosition, + UniswapV3LiquidityPositionDataDecoder +{ + address private immutable UNISWAP_V3_NON_FUNGIBLE_POSITION_MANAGER; + address private immutable VALUE_INTERPRETER; + + constructor(address _valueInterpreter, address _nonfungiblePositionManager) { + UNISWAP_V3_NON_FUNGIBLE_POSITION_MANAGER = _nonfungiblePositionManager; + VALUE_INTERPRETER = _valueInterpreter; + } + + /// @notice Parses the assets to send and receive for the callOnExternalPosition + /// @param _externalPosition The _externalPosition to be called + /// @param _actionId The _actionId for the callOnExternalPosition + /// @param _encodedActionArgs The encoded parameters for the callOnExternalPosition + /// @return assetsToTransfer_ The assets to be transferred from the Vault + /// @return amountsToTransfer_ The amounts to be transferred from the Vault + /// @return assetsToReceive_ The assets to be received at the Vault + function parseAssetsForAction( + address _externalPosition, + uint256 _actionId, + bytes memory _encodedActionArgs + ) + external + view + override + returns ( + address[] memory assetsToTransfer_, + uint256[] memory amountsToTransfer_, + address[] memory assetsToReceive_ + ) + { + if ( + _actionId == + uint256(IUniswapV3LiquidityPosition.UniswapV3LiquidityPositionActions.Mint) + ) { + (, , , uint256 amount0Desired, uint256 amount1Desired, , ) = __decodeMintActionArgs( + _encodedActionArgs + ); + + assetsToTransfer_ = new address[](2); + amountsToTransfer_ = new uint256[](2); + + (assetsToTransfer_[0], assetsToTransfer_[1]) = IUniswapV3LiquidityPosition( + _externalPosition + ) + .getPair(); + + amountsToTransfer_[0] = amount0Desired; + amountsToTransfer_[1] = amount1Desired; + } else if ( + _actionId == + uint256(IUniswapV3LiquidityPosition.UniswapV3LiquidityPositionActions.AddLiquidity) + ) { + ( + uint256 nftId, + uint256 amount0Desired, + uint256 amount1Desired, + , + + ) = __decodeAddLiquidityActionArgs(_encodedActionArgs); + + // Cheaper than storing an additional mapping of nfts or looping through the nftIds array + require( + _externalPosition == + ERC721(getUniswapV3NonfungiblePositionManager()).ownerOf(nftId), + "__decodeEncodedActionArgs: Invalid nftId" + ); + + assetsToTransfer_ = new address[](2); + amountsToTransfer_ = new uint256[](2); + + (assetsToTransfer_[0], assetsToTransfer_[1]) = IUniswapV3LiquidityPosition( + _externalPosition + ) + .getPair(); + + amountsToTransfer_[0] = amount0Desired; + amountsToTransfer_[1] = amount1Desired; + } else { + // RemoveLiquidity, Purge, or Collect + assetsToReceive_ = new address[](2); + (assetsToReceive_[0], assetsToReceive_[1]) = IUniswapV3LiquidityPosition( + _externalPosition + ) + .getPair(); + } + + return (assetsToTransfer_, amountsToTransfer_, assetsToReceive_); + } + + /// @notice Parse and validate input arguments to be used when initializing a newly-deployed ExternalPositionProxy + /// @param _initializationData The initialization data of the external position + /// @return initArgs_ Parsed and encoded args for ExternalPositionProxy.init() + function parseInitArgs(address, bytes memory _initializationData) + external + view + override + returns (bytes memory initArgs_) + { + (address token0, address token1) = __decodeInitArgs(_initializationData); + + require(__poolIsSupportable(token0, token1), "parseInitArgs: Unsupported pair"); + // We do not validate whether an external position for the fund already exists for the pair, + // but callers should be aware that one instance can be used for multiple nft positions + // within the same pair + + return _initializationData; + } + + // PRIVATE FUNCTIONS + + /// @dev Helper to determine if a pool is supportable, based on whether a trusted rate + /// is available for its underlying token pair. Both of the underlying tokens must be supported, + /// and at least one must be a supported primitive asset. + function __poolIsSupportable(address _tokenA, address _tokenB) + private + view + returns (bool isSupportable_) + { + + IValueInterpreterUniswapV3LiquidityPosition valueInterpreterContract + = IValueInterpreterUniswapV3LiquidityPosition(getValueInterpreter()); + + if (valueInterpreterContract.isSupportedPrimitiveAsset(_tokenA)) { + if (valueInterpreterContract.isSupportedAsset(_tokenB)) { + return true; + } + } else if ( + valueInterpreterContract.isSupportedDerivativeAsset(_tokenA) && + valueInterpreterContract.isSupportedPrimitiveAsset(_tokenB) + ) { + return true; + } + + return false; + } + + /////////////////// + // STATE GETTERS // + /////////////////// + + /// @notice Gets the `UNISWAP_V3_NON_FUNGIBLE_POSITION_MANAGER` variable value + /// @return nonfungiblePositionManager_ The `UNISWAP_V3_NON_FUNGIBLE_POSITION_MANAGER` variable value + function getUniswapV3NonfungiblePositionManager() + public + view + returns (address nonfungiblePositionManager_) + { + return UNISWAP_V3_NON_FUNGIBLE_POSITION_MANAGER; + } + + /// @notice Gets the `VALUE_INTERPRETER` variable value + /// @return valueInterpreter_ The `VALUE_INTERPRETER` variable value + function getValueInterpreter() public view returns (address valueInterpreter_) { + return VALUE_INTERPRETER; + } +} diff --git a/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionParserUniswapV3LiquidityPosition.sol b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionParserUniswapV3LiquidityPosition.sol new file mode 100644 index 000000000..5835f35ea --- /dev/null +++ b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionParserUniswapV3LiquidityPosition.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + + (c) Enzyme Council + + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +pragma solidity 0.7.6; + +/// @title IExternalPositionParserUniswapV3LiquidityPosition Interface +/// @author Enzyme Council +interface IExternalPositionParserUniswapV3LiquidityPosition { + function parseAssetsForAction( + address _externalPosition, + uint256 _actionId, + bytes memory _encodedActionArgs + ) + external + returns ( + address[] memory assetsToTransfer_, + uint256[] memory amountsToTransfer_, + address[] memory assetsToReceive_ + ); + + function parseInitArgs(address _vaultProxy, bytes memory _initializationData) + external + returns (bytes memory initArgs_); +} diff --git a/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionUniswapV3LiquidityPosition.sol b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionUniswapV3LiquidityPosition.sol new file mode 100644 index 000000000..7a231f159 --- /dev/null +++ b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IExternalPositionUniswapV3LiquidityPosition.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + (c) Enzyme Council + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +pragma solidity 0.7.6; + +/// @title IExternalPositionUniswapV3LiquidityPosition Contract +/// @author Enzyme Council +interface IExternalPositionUniswapV3LiquidityPosition { + function getDebtAssets() external returns (address[] memory, uint256[] memory); + + function getManagedAssets() external returns (address[] memory, uint256[] memory); + + function init(bytes memory) external; + + function receiveCallFromVault(bytes memory) external; +} diff --git a/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IUniswapV3LiquidityPosition.sol b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IUniswapV3LiquidityPosition.sol new file mode 100644 index 000000000..18982d455 --- /dev/null +++ b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IUniswapV3LiquidityPosition.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + (c) Enzyme Council + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +import "./IExternalPositionUniswapV3LiquidityPosition.sol"; + +pragma solidity 0.7.6; + +/// @title IUnis`wapV3LiquidityPosition Interface +/// @author Enzyme Council +interface IUniswapV3LiquidityPosition is IExternalPositionUniswapV3LiquidityPosition { + enum UniswapV3LiquidityPositionActions {Mint, AddLiquidity, RemoveLiquidity, Collect, Purge} + + function getPair() external view returns (address, address); +} diff --git a/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IValueInterpreterUniswapV3LiquidityPosition.sol b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IValueInterpreterUniswapV3LiquidityPosition.sol new file mode 100644 index 000000000..0797f6d10 --- /dev/null +++ b/contracts/release/extensions/external-position-manager/external-positions/uniswap-v3-liquidity/interfaces/IValueInterpreterUniswapV3LiquidityPosition.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + This file is part of the Enzyme Protocol. + + (c) Enzyme Council + + For the full license information, please view the LICENSE + file that was distributed with this source code. +*/ + +pragma solidity 0.7.6; + +/// @title IValueInterpreterUniswapV3LiquidityPosition interface +/// @author Enzyme Council +interface IValueInterpreterUniswapV3LiquidityPosition { + function calcCanonicalAssetValue( + address, + uint256, + address + ) external returns (uint256); + + function isSupportedAsset(address) external view returns (bool); + + function isSupportedDerivativeAsset(address) external view returns (bool); + + function isSupportedPrimitiveAsset(address) external view returns (bool); +} diff --git a/deploy/scripts/config/Mainnet.ts b/deploy/scripts/config/Mainnet.ts index 3c7f4b422..6a0c6e13f 100644 --- a/deploy/scripts/config/Mainnet.ts +++ b/deploy/scripts/config/Mainnet.ts @@ -330,7 +330,8 @@ const mainnetConfig: DeploymentConfig = { router: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', }, uniswapV3: { - router: '0xE592427A0AEce92De3Edee1F18E0157C05861564' + router: '0xE592427A0AEce92De3Edee1F18E0157C05861564', + nonFungiblePositionManager: '0xC36442b4a4522E871399CD717aBDD847Ab11FE88' }, unsupportedAssets, weth: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', diff --git a/deploy/scripts/release/extensions/ExternalPositionManager.ts b/deploy/scripts/release/extensions/ExternalPositionManager.ts index 54353a17a..2d713bedc 100644 --- a/deploy/scripts/release/extensions/ExternalPositionManager.ts +++ b/deploy/scripts/release/extensions/ExternalPositionManager.ts @@ -9,6 +9,10 @@ const fn: DeployFunction = async function (hre) { const compoundDebtPositionLib = await get('CompoundDebtPositionLib'); const compoundDebtPositionParser = await get('CompoundDebtPositionParser'); + + const uniswapV3ExternalPositionLib = await get('UniswapV3LiquidityPositionLib'); + const uniswapV3ExternalPositionParser = await get('UniswapV3LiquidityPositionParser'); + const deployer = (await getSigners())[0]; const fundDeployer = await get('FundDeployer'); const externalPositionFactory = await get('ExternalPositionFactory'); @@ -26,7 +30,7 @@ const fn: DeployFunction = async function (hre) { const externalPositionFactoryInstance = new ExternalPositionFactory(externalPositionFactory.address, deployer); await externalPositionFactoryInstance.addPositionDeployers([externalPositionManager]); - await externalPositionFactoryInstance.addNewPositionTypes(['COMPOUND_DEBT']); + await externalPositionFactoryInstance.addNewPositionTypes(['COMPOUND_DEBT', 'UNISWAP_V3_LIQUIDITY']); const externalPositionManagerInstance = new ExternalPositionManager(externalPositionManager.address, deployer); await externalPositionManagerInstance.updateExternalPositionTypesInfo( @@ -34,6 +38,12 @@ const fn: DeployFunction = async function (hre) { [compoundDebtPositionLib], [compoundDebtPositionParser], ); + + await externalPositionManagerInstance.updateExternalPositionTypesInfo( + [1], + [uniswapV3ExternalPositionLib], + [uniswapV3ExternalPositionParser], + ); } }; fn.tags = ['Release', 'ExternalPositionManager']; @@ -45,6 +55,8 @@ fn.dependencies = [ 'ChainlinkPriceFeed', 'ExternalPositionFactory', 'PolicyManager', + 'UniswapV3LiquidityPositionLib', + 'UniswapV3LiquidityPositionParser', ]; export default fn; diff --git a/deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionLib.ts b/deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionLib.ts new file mode 100644 index 000000000..4bc7106ec --- /dev/null +++ b/deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionLib.ts @@ -0,0 +1,25 @@ +import { DeployFunction } from 'hardhat-deploy/types'; + +import { loadConfig } from '../../../../utils/config'; + +const fn: DeployFunction = async function (hre) { + const { + deployments: { deploy, get }, + ethers: { getSigners }, + } = hre; + + const deployer = (await getSigners())[0]; + const valueInterpreter = await get('ValueInterpreter'); + const config = await loadConfig(hre); + + await deploy('UniswapV3LiquidityPositionLib', { + args: [config.uniswapV3.nonFungiblePositionManager, valueInterpreter.address], + from: deployer.address, + log: true, + skipIfAlreadyDeployed: true, + }); +}; + +fn.tags = ['Release', 'ExternalPositions', 'UniswapV3LiquidityPositionLib']; +fn.dependencies = ['Config', 'ValueInterpreter']; +export default fn; diff --git a/deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionParser.ts b/deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionParser.ts new file mode 100644 index 000000000..46cae8220 --- /dev/null +++ b/deploy/scripts/release/extensions/external-positions/UniswapV3LiquidityPositionParser.ts @@ -0,0 +1,26 @@ +import { DeployFunction } from 'hardhat-deploy/types'; + +import { loadConfig } from '../../../../utils/config'; + +const fn: DeployFunction = async function (hre) { + const { + deployments: { deploy, get }, + ethers: { getSigners }, + } = hre; + + const config = await loadConfig(hre); + const deployer = (await getSigners())[0]; + const valueInterpreter = await get('ValueInterpreter'); + + await deploy('UniswapV3LiquidityPositionParser', { + args: [valueInterpreter.address, config.uniswapV3.nonFungiblePositionManager], + from: deployer.address, + log: true, + skipIfAlreadyDeployed: true, + }); +}; + +fn.tags = ['Release', 'ExternalPositions', 'UniswapV3LiquidityPositionParser']; +fn.dependencies = ['Config', 'ValueInterpreter']; + +export default fn; diff --git a/deploy/utils/config.ts b/deploy/utils/config.ts index 48fe349aa..9d52d4a41 100644 --- a/deploy/utils/config.ts +++ b/deploy/utils/config.ts @@ -92,6 +92,7 @@ export interface DeploymentConfig { }; uniswapV3: { router: string; + nonFungiblePositionManager: string; }; yearn: { vaultV2: { diff --git a/hardhat.config.ts b/hardhat.config.ts index 4071caee6..a1898dfe8 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -130,7 +130,32 @@ const config: HardhatUserConfig = { runs: 200, }, }, - version: '0.6.12', + compilers: [ + { + version: '0.7.6', + settings: { + optimizer: { + details: { + yul: false, + }, + enabled: true, + runs: 200, + }, + }, + }, + { + version: '0.6.12', + settings: { + optimizer: { + details: { + yul: false, + }, + enabled: true, + runs: 200, + }, + }, + }, + ], }, }; diff --git a/package.json b/package.json index 4cd56bea5..a9c678ffb 100644 --- a/package.json +++ b/package.json @@ -39,14 +39,16 @@ "dependencies": { "@changesets/cli": "^2.17.0", "@enzymefinance/babel-config": "^1.0.14", - "@enzymefinance/ethers": "^1.0.0", - "@enzymefinance/hardhat": "^1.0.0", + "@enzymefinance/ethers": "^1.0.9", + "@enzymefinance/hardhat": "^1.0.10", "@manypkg/cli": "^0.18.0", + "@openzeppelin-solc-0.7/contracts": "npm:@openzeppelin/contracts@3.4.2-solc-0.7", "@openzeppelin/contracts": "^3.4.1", "@preconstruct/cli": "^2.1.4", "@types/jest": "^27.0.1", "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", + "@uniswap/v3-periphery": "github:uniswap/v3-periphery", "babel-jest": "^27.2.0", "decimal.js": "^10.3.1", "dotenv": "^10.0.0", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index b4b854e09..f1ccd0ca4 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -35,8 +35,8 @@ ], "dependencies": { "@babel/runtime": "^7.15.4", - "@enzymefinance/ethers": "^1.0.0", + "@enzymefinance/ethers": "^1.0.9", "decimal.js": "^10.3.1", - "ethers": "^5.4.6" + "ethers": "^5.5.1" } } diff --git a/packages/protocol/src/contracts.ts b/packages/protocol/src/contracts.ts index c8cfbbb13..46b120c5d 100644 --- a/packages/protocol/src/contracts.ts +++ b/packages/protocol/src/contracts.ts @@ -73,6 +73,8 @@ export * from './codegen/ZeroExV2Adapter'; export * from './codegen/CompoundDebtPositionParser'; export * from './codegen/CompoundDebtPositionLib'; export * from './codegen/IExternalPositionParser'; +export * from './codegen/UniswapV3LiquidityPositionParser'; +export * from './codegen/UniswapV3LiquidityPositionLib'; // Fees export * from './codegen/IFee'; diff --git a/packages/protocol/src/utils/external-positions/index.ts b/packages/protocol/src/utils/external-positions/index.ts index 146469c30..555328362 100644 --- a/packages/protocol/src/utils/external-positions/index.ts +++ b/packages/protocol/src/utils/external-positions/index.ts @@ -1,2 +1,3 @@ export * from './actions'; export * from './types'; +export * from './uniswap-v3-liquidity'; diff --git a/packages/protocol/src/utils/external-positions/types.ts b/packages/protocol/src/utils/external-positions/types.ts index e47e3ac45..0d5f2ad58 100644 --- a/packages/protocol/src/utils/external-positions/types.ts +++ b/packages/protocol/src/utils/external-positions/types.ts @@ -1,3 +1,4 @@ export enum ExternalPositionType { CompoundDebtPosition = '0', + UniswapV3LiquidityPosition = '1', } diff --git a/packages/protocol/src/utils/external-positions/uniswap-v3-liquidity.ts b/packages/protocol/src/utils/external-positions/uniswap-v3-liquidity.ts new file mode 100644 index 000000000..981bd1c4b --- /dev/null +++ b/packages/protocol/src/utils/external-positions/uniswap-v3-liquidity.ts @@ -0,0 +1,89 @@ +import { AddressLike } from '@enzymefinance/ethers'; +import { BigNumberish } from 'ethers'; +import { encodeArgs } from '../encoding'; + +export enum UniswapV3LiquidityPositionActionId { + Mint = '0', + AddLiquidity = '1', + RemoveLiquidity = '2', + Collect = '3', + Purge = '4', +} + +export function uniswapV3LiquidityPositionAddLiquidityArgs({ + nftId, + amount0Desired, + amount1Desired, + amount0Min, + amount1Min, +}: { + nftId: BigNumberish; + amount0Desired: BigNumberish; + amount1Desired: BigNumberish; + amount0Min: BigNumberish; + amount1Min: BigNumberish; +}) { + return encodeArgs( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256'], + [nftId, amount0Desired, amount1Desired, amount0Min, amount1Min], + ); +} + +export function uniswapV3LiquidityPositionCollectArgs({ nftId }: { nftId: BigNumberish }) { + return encodeArgs(['uint256'], [nftId]); +} + +export function uniswapV3LiquidityPositionInitArgs({ token0, token1 }: { token0: AddressLike; token1: AddressLike }) { + return encodeArgs(['address', 'address'], [token0, token1]); +} + +export function uniswapV3LiquidityPositionMintArgs({ + fee, + tickLower, + tickUpper, + amount0Desired, + amount1Desired, + amount0Min, + amount1Min, +}: { + fee: BigNumberish; + tickLower: BigNumberish; + tickUpper: BigNumberish; + amount0Desired: BigNumberish; + amount1Desired: BigNumberish; + amount0Min: BigNumberish; + amount1Min: BigNumberish; +}) { + return encodeArgs( + ['uint24', 'int24', 'int24', 'uint256', 'uint256', 'uint256', 'uint256'], + [fee, tickLower, tickUpper, amount0Desired, amount1Desired, amount0Min, amount1Min], + ); +} + +export function uniswapV3LiquidityPositionPurgeArgs({ + nftId, + liquidity, + amount0Min, + amount1Min, +}: { + nftId: BigNumberish; + liquidity: BigNumberish; + amount0Min: BigNumberish; + amount1Min: BigNumberish; +}) { + return encodeArgs(['uint256', 'uint128', 'uint256', 'uint256'], [nftId, liquidity, amount0Min, amount1Min]); +} + +export function uniswapV3LiquidityPositionRemoveLiquidityArgs({ + nftId, + liquidity, + amount0Min, + amount1Min, +}: { + nftId: BigNumberish; + liquidity: BigNumberish; + amount0Min: BigNumberish; + amount1Min: BigNumberish; +}) { + return encodeArgs(['uint256', 'uint128', 'uint256', 'uint256'], [nftId, liquidity, amount0Min, amount1Min]); +} diff --git a/packages/testutils/package.json b/packages/testutils/package.json index c1909480c..87884959b 100644 --- a/packages/testutils/package.json +++ b/packages/testutils/package.json @@ -9,9 +9,9 @@ }, "dependencies": { "@babel/runtime": "^7.15.4", - "@enzymefinance/ethers": "^1.0.0", - "@enzymefinance/hardhat": "^1.0.0", + "@enzymefinance/ethers": "^1.0.9", + "@enzymefinance/hardhat": "^1.0.10", "@enzymefinance/protocol": "^4.0.0-next.2", - "ethers": "^5.4.6" + "ethers": "^5.5.1" } } diff --git a/packages/testutils/src/scaffolding/extensions/external-positions/index.ts b/packages/testutils/src/scaffolding/extensions/external-positions/index.ts index eae4e990f..ecc3b5e7b 100644 --- a/packages/testutils/src/scaffolding/extensions/external-positions/index.ts +++ b/packages/testutils/src/scaffolding/extensions/external-positions/index.ts @@ -1,3 +1,4 @@ export * from './actions'; export * from './compound'; export * from './mocks'; +export * from './uniswap-v3-liquidity'; diff --git a/packages/testutils/src/scaffolding/extensions/external-positions/uniswap-v3-liquidity.ts b/packages/testutils/src/scaffolding/extensions/external-positions/uniswap-v3-liquidity.ts new file mode 100644 index 000000000..934921481 --- /dev/null +++ b/packages/testutils/src/scaffolding/extensions/external-positions/uniswap-v3-liquidity.ts @@ -0,0 +1,281 @@ +import { AddressLike, Call, Contract, contract, extractEvent } from '@enzymefinance/ethers'; +import { SignerWithAddress } from '@enzymefinance/hardhat'; +import { + callOnExternalPositionArgs, + ComptrollerLib, + encodeArgs, + ExternalPositionManager, + ExternalPositionManagerActionId, + ExternalPositionType, + UniswapV3LiquidityPositionActionId, + uniswapV3LiquidityPositionAddLiquidityArgs, + uniswapV3LiquidityPositionCollectArgs, + uniswapV3LiquidityPositionInitArgs, + UniswapV3LiquidityPositionLib, + uniswapV3LiquidityPositionMintArgs, + uniswapV3LiquidityPositionPurgeArgs, + uniswapV3LiquidityPositionRemoveLiquidityArgs, + VaultLib, +} from '@enzymefinance/protocol'; +import { BigNumber, BigNumberish } from 'ethers'; + +export enum UniswapV3FeeAmount { + LOW = 500, + MEDIUM = 3000, + HIGH = 10000, +} + +export const uniswapV3LiquidityPositionGetMinTick = (tickSpacing: number) => + Math.ceil(-887272 / tickSpacing) * tickSpacing; +export const uniswapV3LiquidityPositionGetMaxTick = (tickSpacing: number) => + Math.floor(887272 / tickSpacing) * tickSpacing; + +export interface IUniswapV3NonFungibleTokenManager extends Contract { + positions: Call< + (tokenId: BigNumberish) => { + nonce: BigNumber; + operator: string; + token0: string; + token1: string; + fee: BigNumber; + tickLower: BigNumber; + tickUpper: BigNumber; + liquidity: BigNumber; + feeGrowthInside0LastX128: BigNumber; + feeGrowthInside1LastX128: BigNumber; + tokensOwed0: BigNumber; + tokensOwed1: BigNumber; + }, + Contract + >; +} + +export const IUniswapV3NonFungibleTokenManager = contract()` + function positions(uint256 tokenId) 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) +`; + +export async function createUniswapV3LiquidityPosition({ + signer, + comptrollerProxy, + externalPositionManager, + token0, + token1, +}: { + signer: SignerWithAddress; + comptrollerProxy: ComptrollerLib; + externalPositionManager: ExternalPositionManager; + token0: AddressLike; + token1: AddressLike; +}) { + const initArgs = uniswapV3LiquidityPositionInitArgs({ + token0, + token1, + }); + + const receipt = await comptrollerProxy + .connect(signer) + .callOnExtension( + externalPositionManager, + ExternalPositionManagerActionId.CreateExternalPosition, + encodeArgs(['uint256', 'bytes'], [ExternalPositionType.UniswapV3LiquidityPosition, initArgs]), + ); + + const vaultProxy = new VaultLib(await comptrollerProxy.getVaultProxy(), signer); + const externalPositions = await vaultProxy.getActiveExternalPositions.call(); + const externalPositionProxyAddress = externalPositions[externalPositions.length - 1]; + + return { receipt, externalPositionProxyAddress }; +} + +export async function uniswapV3LiquidityPositionAddLiquidity({ + signer, + comptrollerProxy, + externalPositionManager, + externalPositionProxy, + nftId, + amount0Desired, + amount1Desired, + amount0Min = 0, + amount1Min = 0, +}: { + signer: SignerWithAddress; + comptrollerProxy: ComptrollerLib; + externalPositionManager: ExternalPositionManager; + externalPositionProxy: AddressLike; + nftId: BigNumberish; + amount0Desired: BigNumberish; + amount1Desired: BigNumberish; + amount0Min?: BigNumberish; + amount1Min?: BigNumberish; +}) { + const actionArgs = uniswapV3LiquidityPositionAddLiquidityArgs({ + nftId, + amount0Desired, + amount1Desired, + amount0Min, + amount1Min, + }); + + const callArgs = callOnExternalPositionArgs({ + externalPositionProxy, + actionId: UniswapV3LiquidityPositionActionId.AddLiquidity, + actionArgs, + }); + + return comptrollerProxy + .connect(signer) + .callOnExtension(externalPositionManager, ExternalPositionManagerActionId.CallOnExternalPosition, callArgs); +} + +export async function uniswapV3LiquidityPositionCollect({ + signer, + comptrollerProxy, + externalPositionManager, + externalPositionProxy, + nftId, +}: { + signer: SignerWithAddress; + comptrollerProxy: ComptrollerLib; + externalPositionManager: ExternalPositionManager; + externalPositionProxy: AddressLike; + nftId: BigNumberish; +}) { + const actionArgs = uniswapV3LiquidityPositionCollectArgs({ + nftId, + }); + + const callArgs = callOnExternalPositionArgs({ + externalPositionProxy, + actionId: UniswapV3LiquidityPositionActionId.Collect, + actionArgs, + }); + + return comptrollerProxy + .connect(signer) + .callOnExtension(externalPositionManager, ExternalPositionManagerActionId.CallOnExternalPosition, callArgs); +} + +export async function uniswapV3LiquidityPositionMint({ + signer, + comptrollerProxy, + externalPositionManager, + externalPositionProxy, + fee, + tickLower, + tickUpper, + amount0Desired, + amount1Desired, + amount0Min = 0, + amount1Min = 0, +}: { + signer: SignerWithAddress; + comptrollerProxy: ComptrollerLib; + externalPositionManager: ExternalPositionManager; + externalPositionProxy: AddressLike; + fee: BigNumberish; + tickLower: BigNumberish; + tickUpper: BigNumberish; + amount0Desired: BigNumberish; + amount1Desired: BigNumberish; + amount0Min?: BigNumberish; + amount1Min?: BigNumberish; +}) { + const actionArgs = uniswapV3LiquidityPositionMintArgs({ + fee, + tickLower, + tickUpper, + amount0Desired, + amount1Desired, + amount0Min, + amount1Min, + }); + + const callArgs = callOnExternalPositionArgs({ + externalPositionProxy, + actionId: UniswapV3LiquidityPositionActionId.Mint, + actionArgs, + }); + + const receipt = await comptrollerProxy + .connect(signer) + .callOnExtension(externalPositionManager, ExternalPositionManagerActionId.CallOnExternalPosition, callArgs); + + const externalPosition = new UniswapV3LiquidityPositionLib(externalPositionProxy, provider); + const nftId = extractEvent(receipt, externalPosition.abi.getEvent('NFTPositionAdded'))[0].args.tokenId; + + return { nftId, receipt }; +} + +export async function uniswapV3LiquidityPositionPurge({ + signer, + comptrollerProxy, + externalPositionManager, + externalPositionProxy, + nftId, + liquidity, + amount0Min = 0, + amount1Min = 0, +}: { + signer: SignerWithAddress; + comptrollerProxy: ComptrollerLib; + externalPositionManager: ExternalPositionManager; + externalPositionProxy: AddressLike; + nftId: BigNumberish; + liquidity: BigNumberish; + amount0Min?: BigNumberish; + amount1Min?: BigNumberish; +}) { + const actionArgs = uniswapV3LiquidityPositionPurgeArgs({ + nftId, + liquidity, + amount0Min, + amount1Min, + }); + + const callArgs = callOnExternalPositionArgs({ + externalPositionProxy, + actionId: UniswapV3LiquidityPositionActionId.Purge, + actionArgs, + }); + + return comptrollerProxy + .connect(signer) + .callOnExtension(externalPositionManager, ExternalPositionManagerActionId.CallOnExternalPosition, callArgs); +} + +export async function uniswapV3LiquidityPositionRemoveLiquidity({ + signer, + comptrollerProxy, + externalPositionManager, + externalPositionProxy, + nftId, + liquidity, + amount0Min = 0, + amount1Min = 0, +}: { + signer: SignerWithAddress; + comptrollerProxy: ComptrollerLib; + externalPositionManager: ExternalPositionManager; + externalPositionProxy: AddressLike; + nftId: BigNumberish; + liquidity: BigNumberish; + amount0Min?: BigNumberish; + amount1Min?: BigNumberish; +}) { + const actionArgs = uniswapV3LiquidityPositionRemoveLiquidityArgs({ + nftId, + liquidity, + amount0Min, + amount1Min, + }); + + const callArgs = callOnExternalPositionArgs({ + externalPositionProxy, + actionId: UniswapV3LiquidityPositionActionId.RemoveLiquidity, + actionArgs, + }); + + return comptrollerProxy + .connect(signer) + .callOnExtension(externalPositionManager, ExternalPositionManagerActionId.CallOnExternalPosition, callArgs); +} diff --git a/packages/testutils/src/scaffolding/extensions/integrations/uniswapV3.ts b/packages/testutils/src/scaffolding/extensions/integrations/uniswapV3.ts index 549e79dbf..72b3acb81 100644 --- a/packages/testutils/src/scaffolding/extensions/integrations/uniswapV3.ts +++ b/packages/testutils/src/scaffolding/extensions/integrations/uniswapV3.ts @@ -34,7 +34,8 @@ export async function uniswapV3TakeOrder({ }) { if (seedFund) { // Seed the VaultProxy with enough outgoingAsset for the tx - await pathAddresses[0].transfer(await comptrollerProxy.getVaultProxy(), outgoingAssetAmount); + const vaultProxy = await comptrollerProxy.getVaultProxy(); + await pathAddresses[0].transfer(vaultProxy, outgoingAssetAmount); } const takeOrderArgs = uniswapV3TakeOrderArgs({ diff --git a/packages/testutils/src/whales.ts b/packages/testutils/src/whales.ts index 638a49d7e..a39cde75d 100644 --- a/packages/testutils/src/whales.ts +++ b/packages/testutils/src/whales.ts @@ -6,7 +6,9 @@ const whales = { bat: '0x12274c71304bc0e6b38a56b94d2949b118feb838', bnb: '0xbe0eb53f46cd790cd13851d5eff43d12404d33e8', bnt: '0x7d1ed1601a12a172269436fa95fe156650603c1d', + busd: '0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503', comp: '0x0f50d31b3eaefd65236dd3736b863cffa4c63c4e', + crv: '0x4ce799e6eD8D64536b67dD428565d52A531B3640', dai: '0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503', knc: '0x09d51654bd9efbfcb56da3491989cc1444095fff', link: '0xbe6977e08d4479c0a6777539ae0e8fa27be4e9d6', diff --git a/tests/release/extensions/external-position-manager/ExternalPositionManager.test.ts b/tests/release/extensions/external-position-manager/ExternalPositionManager.test.ts index 9b8deec41..b6599c19a 100644 --- a/tests/release/extensions/external-position-manager/ExternalPositionManager.test.ts +++ b/tests/release/extensions/external-position-manager/ExternalPositionManager.test.ts @@ -144,7 +144,7 @@ describe('receiveCallFromComptroller', () => { denominationAsset: new StandardToken(fork.config.primitives.usdc, provider), }); - const callArgs = encodeArgs(['uint256', 'bytes'], [1, '0x']); + const callArgs = encodeArgs(['uint256', 'bytes'], [999, '0x']); await expect( comptrollerProxy diff --git a/tests/release/extensions/external-position-manager/external-positions/CompoundDebtPositionLib.test.ts b/tests/release/extensions/external-position-manager/libs/CompoundDebtPositionLib.test.ts similarity index 100% rename from tests/release/extensions/external-position-manager/external-positions/CompoundDebtPositionLib.test.ts rename to tests/release/extensions/external-position-manager/libs/CompoundDebtPositionLib.test.ts diff --git a/tests/release/extensions/external-position-manager/libs/UniswapV3LiquidityPositionLib.test.ts b/tests/release/extensions/external-position-manager/libs/UniswapV3LiquidityPositionLib.test.ts new file mode 100644 index 000000000..b4be4bbda --- /dev/null +++ b/tests/release/extensions/external-position-manager/libs/UniswapV3LiquidityPositionLib.test.ts @@ -0,0 +1,595 @@ +import { SignerWithAddress } from '@enzymefinance/hardhat'; +import { ComptrollerLib, StandardToken, UniswapV3LiquidityPositionLib, VaultLib } from '@enzymefinance/protocol'; +import { + assertEvent, + createNewFund, + createUniswapV3LiquidityPosition, + deployProtocolFixture, + getAssetUnit, + IUniswapV3NonFungibleTokenManager, + ProtocolDeployment, + UniswapV3FeeAmount, + uniswapV3LiquidityPositionAddLiquidity, + uniswapV3LiquidityPositionCollect, + uniswapV3LiquidityPositionGetMaxTick, + uniswapV3LiquidityPositionGetMinTick, + uniswapV3LiquidityPositionMint, + uniswapV3LiquidityPositionRemoveLiquidity, + uniswapV3TakeOrder, +} from '@enzymefinance/testutils'; +import { BigNumber, constants, utils } from 'ethers'; +import hre from 'hardhat'; + +let comptrollerProxy: ComptrollerLib, vaultProxy: VaultLib; +let fundOwner: SignerWithAddress; + +let fork: ProtocolDeployment; +beforeEach(async () => { + fork = await deployProtocolFixture(); + [fundOwner] = fork.accounts; + + const newFundRes = await createNewFund({ + signer: fundOwner, + fundOwner, + fundDeployer: fork.deployment.fundDeployer, + denominationAsset: new StandardToken(fork.config.primitives.usdc, hre.ethers.provider), + }); + + comptrollerProxy = newFundRes.comptrollerProxy; + vaultProxy = newFundRes.vaultProxy; +}); + +describe('init', () => { + it.todo('write tests'); +}); + +describe('receiveCallFromVault', () => { + let uniswapV3LiquidityPositionDaiUsdc: UniswapV3LiquidityPositionLib; + + let nftManager: IUniswapV3NonFungibleTokenManager; + let token0: StandardToken, token1: StandardToken; + beforeEach(async () => { + nftManager = new IUniswapV3NonFungibleTokenManager(fork.config.uniswapV3.nonFungiblePositionManager, provider); + token0 = new StandardToken(fork.config.primitives.dai, whales.dai); + token1 = new StandardToken(fork.config.primitives.usdc, whales.usdc); + + const uniswapV3LiquidityPositionAddress = ( + await createUniswapV3LiquidityPosition({ + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + token0, + token1, + }) + ).externalPositionProxyAddress; + uniswapV3LiquidityPositionDaiUsdc = new UniswapV3LiquidityPositionLib(uniswapV3LiquidityPositionAddress, provider); + }); + + it('reverts when it is called from an account different than vault', async () => { + await expect( + uniswapV3LiquidityPositionDaiUsdc.connect(fundOwner).receiveCallFromVault(utils.randomBytes(0)), + ).rejects.toBeRevertedWith('Only the vault can make this call'); + }); + + describe('Mint', () => { + it('works as expected (different decimal pair tokens)', async () => { + const amount0Desired = await getAssetUnit(token0); + const amount1Desired = await getAssetUnit(token1); + const fee = UniswapV3FeeAmount.LOW; + // Use max range, so a pair of stables should get roughly the same value for both assets + const tickLower = uniswapV3LiquidityPositionGetMinTick(fee); + const tickUpper = uniswapV3LiquidityPositionGetMaxTick(fee); + + // Seed fund with tokens + await token0.transfer(vaultProxy, amount0Desired); + await token1.transfer(vaultProxy, amount1Desired); + + const preTxNftsCount = (await uniswapV3LiquidityPositionDaiUsdc.getNftIds()).length; + const preVaultToken0Balance = await token0.balanceOf(vaultProxy); + const preVaultToken1Balance = await token1.balanceOf(vaultProxy); + + const { nftId, receipt } = await uniswapV3LiquidityPositionMint({ + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + fee, + tickLower, + tickUpper, + amount0Desired, + amount1Desired, + }); + + const postVaultToken0Balance = await token0.balanceOf(vaultProxy); + const postVaultToken1Balance = await token1.balanceOf(vaultProxy); + + // Assert the NFT position was created correctly in Uniswap + const positions = await nftManager.positions(nftId); + expect(positions).toMatchFunctionOutput(nftManager.positions, { + nonce: expect.anything(), + operator: constants.AddressZero, + token0, + token1, + fee, + tickLower, + tickUpper, + liquidity: expect.anything(), + feeGrowthInside0LastX128: 0, + feeGrowthInside1LastX128: 0, + tokensOwed0: 0, + tokensOwed1: 0, + }); + expect(positions.liquidity).toBeGtBigNumber(0); + + // Assert correct local state change and event + const postTxNftIds = await uniswapV3LiquidityPositionDaiUsdc.getNftIds(); + expect(postTxNftIds.length).toBe(preTxNftsCount + 1); + expect(postTxNftIds[postTxNftIds.length - 1]).toEqBigNumber(nftId); + + assertEvent(receipt, uniswapV3LiquidityPositionDaiUsdc.abi.getEvent('NFTPositionAdded'), { + tokenId: nftId, + }); + + // Assert expected token balance changes + + // No tokens should remain in the external position proxy + expect(await token0.balanceOf(uniswapV3LiquidityPositionDaiUsdc)).toEqBigNumber(0); + expect(await token1.balanceOf(uniswapV3LiquidityPositionDaiUsdc)).toEqBigNumber(0); + + // There should be a remaining balance of either token0 or token1 in the vault, as not the entire amount of both would be fully spent + const netTokenBalancesDiff = preVaultToken0Balance + .add(preVaultToken1Balance) + .sub(postVaultToken0Balance) + .sub(postVaultToken1Balance); + expect(netTokenBalancesDiff).toBeGtBigNumber(0); + + // Managed assets should be roughly the amount of assets added + const managedAssets = await uniswapV3LiquidityPositionDaiUsdc.getManagedAssets.call(); + expect(managedAssets.amounts_[0]).toBeAroundBigNumber(amount0Desired); + expect(managedAssets.amounts_[1]).toBeAroundBigNumber(amount1Desired); + }); + }); + + describe('AddLiquidity', () => { + it('works as expected (different decimal pair tokens)', async () => { + const token0 = new StandardToken(fork.config.primitives.dai, whales.dai); + const token1 = new StandardToken(fork.config.primitives.usdc, whales.usdc); + + const mintAmount0Desired = await getAssetUnit(token0); + const mintAmount1Desired = await getAssetUnit(token1); + + // Use max range, so a pair of stables should get roughly the same value for both assets + const tickLower = uniswapV3LiquidityPositionGetMinTick(UniswapV3FeeAmount.LOW); + const tickUpper = uniswapV3LiquidityPositionGetMaxTick(UniswapV3FeeAmount.LOW); + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + const { nftId } = await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + const addLiquidityAmount0Desired = mintAmount0Desired; + const addLiquidityAmount1Desired = mintAmount1Desired; + + const positionsBefore = await nftManager.positions(nftId); + + // Seed fund with tokens + await token0.transfer(vaultProxy, addLiquidityAmount0Desired); + await token1.transfer(vaultProxy, addLiquidityAmount1Desired); + + const preVaultToken0Balance = await token0.balanceOf(vaultProxy); + const preVaultToken1Balance = await token1.balanceOf(vaultProxy); + + await uniswapV3LiquidityPositionAddLiquidity({ + comptrollerProxy: comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + nftId, + amount0Desired: addLiquidityAmount0Desired, + amount1Desired: addLiquidityAmount1Desired, + }); + + const postVaultToken0Balance = await token0.balanceOf(vaultProxy); + const postVaultToken1Balance = await token1.balanceOf(vaultProxy); + + // Liquidity should have increased + const positionsAfter = await nftManager.positions(nftId); + expect(positionsAfter.liquidity).toBeGteBigNumber(positionsBefore.liquidity); + + // Assert expected token balance changes + + // No tokens should remain in the external position proxy + expect(await token0.balanceOf(uniswapV3LiquidityPositionDaiUsdc)).toEqBigNumber(0); + expect(await token1.balanceOf(uniswapV3LiquidityPositionDaiUsdc)).toEqBigNumber(0); + + // There should be a remaining balance of either token0 or token1 in the vault, as not the entire amount of both would be fully spent + const netTokenBalancesDiff = preVaultToken0Balance + .add(preVaultToken1Balance) + .sub(postVaultToken0Balance) + .sub(postVaultToken1Balance); + expect(netTokenBalancesDiff).toBeGtBigNumber(0); + + // Managed assets should be roughly 2x the amount of assets added + const managedAssets = await uniswapV3LiquidityPositionDaiUsdc.getManagedAssets.call(); + + expect(managedAssets.amounts_[0]).toBeAroundBigNumber(addLiquidityAmount0Desired.add(mintAmount0Desired)); + expect(managedAssets.amounts_[1]).toBeAroundBigNumber(addLiquidityAmount1Desired.add(mintAmount1Desired)); + }); + }); + + describe('RemoveLiquidity', () => { + it('works as expected (different decimal pair tokens)', async () => { + const token0 = new StandardToken(fork.config.primitives.dai, whales.dai); + const token1 = new StandardToken(fork.config.primitives.usdc, whales.usdc); + + const mintAmount0Desired = await getAssetUnit(token0); + const mintAmount1Desired = await getAssetUnit(token1); + + const tickLower = uniswapV3LiquidityPositionGetMinTick(UniswapV3FeeAmount.LOW); + const tickUpper = uniswapV3LiquidityPositionGetMaxTick(UniswapV3FeeAmount.LOW); + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + const { nftId } = await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + // Remove half of liquidity + const positionsBefore = await nftManager.positions(nftId); + const liquidityRemoved = positionsBefore.liquidity.div(2); + + const preVaultToken0Balance = await token0.balanceOf(vaultProxy); + const preVaultToken1Balance = await token1.balanceOf(vaultProxy); + + await uniswapV3LiquidityPositionRemoveLiquidity({ + comptrollerProxy: comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + nftId, + liquidity: liquidityRemoved, + }); + + // Assert desired liquidity was removed + const positionsAfter = await nftManager.positions(nftId); + expect(positionsAfter.liquidity).toEqBigNumber(positionsBefore.liquidity.sub(liquidityRemoved)); + + // Vault balances of both tokens should have increased (no need to assert exact amounts, i.e., test that UniV3 works) + expect(await token0.balanceOf(vaultProxy)).toBeGtBigNumber(preVaultToken0Balance); + expect(await token1.balanceOf(vaultProxy)).toBeGtBigNumber(preVaultToken1Balance); + }); + }); + + describe('Collect', () => { + it('works as expected', async () => { + const token0 = new StandardToken(fork.config.primitives.dai, whales.dai); + const token1 = new StandardToken(fork.config.primitives.usdc, whales.usdc); + + const mintAmount0Desired = await getAssetUnit(token0); + const mintAmount1Desired = await getAssetUnit(token1); + + const tickLower = uniswapV3LiquidityPositionGetMinTick(UniswapV3FeeAmount.LOW); + const tickUpper = uniswapV3LiquidityPositionGetMaxTick(UniswapV3FeeAmount.LOW); + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + const { nftId } = await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + // Execute an order to collect fees + await uniswapV3TakeOrder({ + comptrollerProxy, + integrationManager: fork.deployment.integrationManager, + fundOwner, + uniswapV3Adapter: fork.deployment.uniswapV3Adapter, + pathAddresses: [token0, token1], + pathFees: [BigNumber.from(UniswapV3FeeAmount.LOW)], + outgoingAssetAmount: utils.parseEther('10000'), + minIncomingAssetAmount: 1, + seedFund: true, + }); + + // Add again liquidity to restore the state of tokens owed to the latest state + // (A swap does not update the NFT manager state) + const addLiquidityAmount0Desired = mintAmount0Desired; + const addLiquidityAmount1Desired = mintAmount1Desired; + + // Seed fund with tokens + await token0.transfer(vaultProxy, addLiquidityAmount0Desired); + await token1.transfer(vaultProxy, addLiquidityAmount1Desired); + + await uniswapV3LiquidityPositionAddLiquidity({ + comptrollerProxy: comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + nftId, + amount0Desired: addLiquidityAmount0Desired, + amount1Desired: addLiquidityAmount1Desired, + }); + + const positionsBefore = await nftManager.positions(nftId); + + const preCollectVaultToken0Balance = await token0.balanceOf(vaultProxy); + const preCollectVaultToken1Balance = await token1.balanceOf(vaultProxy); + + await uniswapV3LiquidityPositionCollect({ + comptrollerProxy: comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + nftId, + }); + + const postCollectVaultToken0Balance = await token0.balanceOf(vaultProxy); + const postCollectVaultToken1Balance = await token1.balanceOf(vaultProxy); + + const positionsAfter = await nftManager.positions(nftId); + + expect(postCollectVaultToken0Balance.sub(preCollectVaultToken0Balance)).toEqBigNumber( + positionsBefore.tokensOwed0.sub(positionsAfter.tokensOwed0), + ); + expect(postCollectVaultToken1Balance.sub(preCollectVaultToken1Balance)).toEqBigNumber( + positionsBefore.tokensOwed1.sub(positionsAfter.tokensOwed1), + ); + }); + }); + + describe('getManagedAssets', () => { + fit('works as expected (wide range, different price)', async () => { + const [fundOwner] = fork.accounts; + const token0 = new StandardToken(fork.config.primitives.dai, whales.dai); + const token1 = new StandardToken(fork.config.primitives.usdc, whales.usdc); + + const mintAmount0Desired = await getAssetUnit(token0); + const mintAmount1Desired = await getAssetUnit(token1); + + const tickLower = uniswapV3LiquidityPositionGetMinTick(UniswapV3FeeAmount.MEDIUM); + const tickUpper = uniswapV3LiquidityPositionGetMaxTick(UniswapV3FeeAmount.MEDIUM); + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPositionDaiUsdc, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + const { assets_, amounts_ } = await uniswapV3LiquidityPositionDaiUsdc.getManagedAssets.call(); + + // Using two stablecoins, the amount of managed assets should be roughly the supplied amount + expect(assets_[0]).toEqual(token0.address); + expect(assets_[1]).toEqual(token1.address); + expect(amounts_[0]).toBeAroundBigNumber(mintAmount0Desired); + expect(amounts_[1]).toBeAroundBigNumber(mintAmount1Desired); + }); + + it('works as expected (wide range, same decimals, same price)', async () => { + const [fundOwner] = fork.accounts; + const token0 = new StandardToken(fork.config.primitives.busd, whales.busd); + const token1 = new StandardToken(fork.config.primitives.dai, whales.dai); + + const mintAmount0Desired = utils.parseUnits('1', 18); + const mintAmount1Desired = utils.parseUnits('1', 18); + + const tickLower = uniswapV3LiquidityPositionGetMinTick(UniswapV3FeeAmount.MEDIUM); + const tickUpper = uniswapV3LiquidityPositionGetMaxTick(UniswapV3FeeAmount.MEDIUM); + + const uniswapV3LiquidityPositionAddress = ( + await createUniswapV3LiquidityPosition({ + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + token0, + token1, + }) + ).externalPositionProxyAddress; + + const uniswapV3LiquidityPosition = new UniswapV3LiquidityPositionLib(uniswapV3LiquidityPositionAddress, provider); + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPositionAddress, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + const { assets_, amounts_ } = await uniswapV3LiquidityPosition.getManagedAssets.call(); + + // Using two stablecoins, the amount of managed assets should be roughly the supplied amount + expect(assets_[0]).toEqual(token0.address); + expect(assets_[1]).toEqual(token1.address); + expect(amounts_[0]).toBeAroundBigNumber(mintAmount0Desired); + expect(amounts_[1]).toBeAroundBigNumber(mintAmount1Desired); + }); + + it('works as expected (small range, same price, in range)', async () => { + const [fundOwner] = fork.accounts; + const token0 = new StandardToken(fork.config.primitives.busd, whales.busd); + const token1 = new StandardToken(fork.config.primitives.dai, whales.dai); + + const mintAmount0Desired = utils.parseUnits('1', 18); + const mintAmount1Desired = utils.parseUnits('1', 18); + + const tickLower = -100; + const tickUpper = 100; + + const uniswapV3LiquidityPositionAddress = ( + await createUniswapV3LiquidityPosition({ + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + token0, + token1, + }) + ).externalPositionProxyAddress; + + const uniswapV3LiquidityPosition = new UniswapV3LiquidityPositionLib(uniswapV3LiquidityPositionAddress, provider); + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPositionAddress, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + const { assets_, amounts_ } = await uniswapV3LiquidityPosition.getManagedAssets.call(); + + expect(assets_[0]).toEqual(token0.address); + expect(assets_[1]).toEqual(token1.address); + expect(amounts_[0]).toBeGteBigNumber(BigNumber.from('0')); + expect(amounts_[1]).toBeGteBigNumber(BigNumber.from('0')); + }); + + it('works as expected (small range, same price, price above range)', async () => { + const [fundOwner] = fork.accounts; + const token0 = new StandardToken(fork.config.primitives.busd, whales.busd); + const token1 = new StandardToken(fork.config.primitives.dai, whales.dai); + + const mintAmount0Desired = utils.parseUnits('1', 18); + const mintAmount1Desired = utils.parseUnits('1', 18); + + const tickLower = -5000; + const tickUpper = -4000; + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + const uniswapV3LiquidityPositionAddress = ( + await createUniswapV3LiquidityPosition({ + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + token0, + token1, + }) + ).externalPositionProxyAddress; + + const uniswapV3LiquidityPosition = new UniswapV3LiquidityPositionLib(uniswapV3LiquidityPositionAddress, provider); + + await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPosition, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + const { assets_, amounts_ } = await uniswapV3LiquidityPosition.getManagedAssets.call(); + + expect(assets_[0]).toEqual(token0.address); + expect(assets_[1]).toEqual(token1.address); + expect(amounts_[0]).toEqBigNumber(BigNumber.from('0')); + expect(amounts_[1]).toBeGteBigNumber(BigNumber.from('0')); + }); + + it('works as expected (small range, same price, price below range)', async () => { + const [fundOwner] = fork.accounts; + const token0 = new StandardToken(fork.config.primitives.busd, whales.busd); + const token1 = new StandardToken(fork.config.primitives.dai, whales.dai); + + const mintAmount0Desired = utils.parseUnits('1', 18); + const mintAmount1Desired = utils.parseUnits('1', 18); + + const tickLower = 4000; + const tickUpper = 5000; + + // Seed fund with tokens + await token0.transfer(vaultProxy, mintAmount0Desired); + await token1.transfer(vaultProxy, mintAmount1Desired); + + const uniswapV3LiquidityPositionAddress = ( + await createUniswapV3LiquidityPosition({ + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + signer: fundOwner, + token0, + token1, + }) + ).externalPositionProxyAddress; + + const uniswapV3LiquidityPosition = new UniswapV3LiquidityPositionLib(uniswapV3LiquidityPositionAddress, provider); + + await uniswapV3LiquidityPositionMint({ + signer: fundOwner, + comptrollerProxy, + externalPositionManager: fork.deployment.externalPositionManager, + externalPositionProxy: uniswapV3LiquidityPosition, + fee: UniswapV3FeeAmount.LOW, + tickLower, + tickUpper, + amount0Desired: mintAmount0Desired, + amount1Desired: mintAmount1Desired, + }); + + const { assets_, amounts_ } = await uniswapV3LiquidityPosition.getManagedAssets.call(); + expect(assets_[0]).toEqual(token0.address); + expect(assets_[1]).toEqual(token1.address); + expect(amounts_[0]).toBeGteBigNumber(BigNumber.from('0')); + expect(amounts_[1]).toEqBigNumber(BigNumber.from('0')); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 90153f897..11f79166f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1215,22 +1215,22 @@ istanbul-lib-coverage "^3.0.0" uuid "^8.3.1" -"@enzymefinance/ethers@1.0.0", "@enzymefinance/ethers@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@enzymefinance/ethers/-/ethers-1.0.0.tgz#8f57f090ce82f87b13a1622b1b6e16d00ed21a2f" - integrity sha512-S+flSHN81n7v1eYMpbtzAvkT7Z5s6V92xNbYmHznqFSsFlqKuj1ZrxzvNw3Wg4PsepKEPDT8LsKo/9+N/etR5A== +"@enzymefinance/ethers@1.0.9", "@enzymefinance/ethers@^1.0.9": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@enzymefinance/ethers/-/ethers-1.0.9.tgz#8d82d6f54041b74db24aa08a51bf9da17bfdb16b" + integrity sha512-YgCPx2tZ3S4eyp4kWUm7veCvzV7QfBq5zDLHBQkN85YMsFHtBmuZkHWpd0/TuKN+RDC6bWVfVjQJRHyuuAdAxA== dependencies: "@babel/runtime" "^7.15.3" -"@enzymefinance/hardhat@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@enzymefinance/hardhat/-/hardhat-1.0.0.tgz#1f59e92498e0b20d20b934acd5dfcbaa29a75371" - integrity sha512-OeLOte3jzX8qITNja5c8KaaHyp1spBup0+t+EuNUuYAOG8SkPsU/cyC4LjKbSK03Tjyaa65SzP7RUVns8l+MQA== +"@enzymefinance/hardhat@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@enzymefinance/hardhat/-/hardhat-1.0.10.tgz#26433e646113a9178330412b5114d0fc5f956583" + integrity sha512-XnuouBMvs8XgATQnx2a15p8opE5+0YgxbKCfUC1jyI5sutjld0xPWcgyx3lr7xF1WMa7X08AtoZ7tVTgKgO2SQ== dependencies: "@babel/runtime" "^7.15.3" "@enzymefinance/codegen" "1.0.0" "@enzymefinance/coverage" "1.0.0" - "@enzymefinance/ethers" "1.0.0" + "@enzymefinance/ethers" "1.0.9" "@nomiclabs/hardhat-ethers" "^2.0.2" deepmerge "^4.2.2" fs-extra "^10.0.0" @@ -1333,7 +1333,22 @@ rustbn.js "~0.2.0" util.promisify "^1.0.1" -"@ethersproject/abi@5.4.1", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0": +"@ethersproject/abi@5.5.0", "@ethersproject/abi@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613" + integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w== + dependencies: + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + +"@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.4.1.tgz#6ac28fafc9ef6f5a7a37e30356a2eb31fa05d39b" integrity sha512-9mhbjUk76BiSluiiW4BaYyI58KSbDMMQpCLdsAR+RsT2GyATiNYxVv+pGWRrekmsIdY3I+hOqsYQSTkc8L/mcg== @@ -1348,7 +1363,20 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/strings" "^5.4.0" -"@ethersproject/abstract-provider@5.4.1", "@ethersproject/abstract-provider@^5.4.0": +"@ethersproject/abstract-provider@5.5.1", "@ethersproject/abstract-provider@^5.5.0": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5" + integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/web" "^5.5.0" + +"@ethersproject/abstract-provider@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz#e404309a29f771bd4d28dbafadcaa184668c2a6e" integrity sha512-3EedfKI3LVpjSKgAxoUaI+gB27frKsxzm+r21w9G60Ugk+3wVLQwhi1LsEJAKNV7WoZc8CIpNrATlL1QFABjtQ== @@ -1361,7 +1389,18 @@ "@ethersproject/transactions" "^5.4.0" "@ethersproject/web" "^5.4.0" -"@ethersproject/abstract-signer@5.4.1", "@ethersproject/abstract-signer@^5.4.0", "@ethersproject/abstract-signer@^5.4.1": +"@ethersproject/abstract-signer@5.5.0", "@ethersproject/abstract-signer@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d" + integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA== + dependencies: + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + +"@ethersproject/abstract-signer@^5.4.0", "@ethersproject/abstract-signer@^5.4.1": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz#e4e9abcf4dd4f1ba0db7dff9746a5f78f355ea81" integrity sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA== @@ -1372,7 +1411,18 @@ "@ethersproject/logger" "^5.4.0" "@ethersproject/properties" "^5.4.0" -"@ethersproject/address@5.4.0", "@ethersproject/address@^5.4.0": +"@ethersproject/address@5.5.0", "@ethersproject/address@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f" + integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + +"@ethersproject/address@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.4.0.tgz#ba2d00a0f8c4c0854933b963b9a3a9f6eb4a37a3" integrity sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q== @@ -1383,14 +1433,29 @@ "@ethersproject/logger" "^5.4.0" "@ethersproject/rlp" "^5.4.0" -"@ethersproject/base64@5.4.0", "@ethersproject/base64@^5.4.0": +"@ethersproject/base64@5.5.0", "@ethersproject/base64@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090" + integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + +"@ethersproject/base64@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.4.0.tgz#7252bf65295954c9048c7ca5f43e5c86441b2a9a" integrity sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ== dependencies: "@ethersproject/bytes" "^5.4.0" -"@ethersproject/basex@5.4.0", "@ethersproject/basex@^5.4.0": +"@ethersproject/basex@5.5.0", "@ethersproject/basex@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.5.0.tgz#e40a53ae6d6b09ab4d977bd037010d4bed21b4d3" + integrity sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + +"@ethersproject/basex@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.4.0.tgz#0a2da0f4e76c504a94f2b21d3161ed9438c7f8a6" integrity sha512-J07+QCVJ7np2bcpxydFVf/CuYo9mZ7T73Pe7KQY4c1lRlrixMeblauMxHXD0MPwFmUHZIILDNViVkykFBZylbg== @@ -1398,7 +1463,16 @@ "@ethersproject/bytes" "^5.4.0" "@ethersproject/properties" "^5.4.0" -"@ethersproject/bignumber@5.4.1", "@ethersproject/bignumber@^5.4.0", "@ethersproject/bignumber@^5.4.1": +"@ethersproject/bignumber@5.5.0", "@ethersproject/bignumber@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527" + integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + bn.js "^4.11.9" + +"@ethersproject/bignumber@^5.4.0", "@ethersproject/bignumber@^5.4.1": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.4.1.tgz#64399d3b9ae80aa83d483e550ba57ea062c1042d" integrity sha512-fJhdxqoQNuDOk6epfM7yD6J8Pol4NUCy1vkaGAkuujZm0+lNow//MKu1hLhRiYV4BsOHyBv5/lsTjF+7hWwhJg== @@ -1407,21 +1481,51 @@ "@ethersproject/logger" "^5.4.0" bn.js "^4.11.9" -"@ethersproject/bytes@5.4.0", "@ethersproject/bytes@^5.4.0": +"@ethersproject/bytes@5.5.0", "@ethersproject/bytes@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c" + integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog== + dependencies: + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/bytes@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e" integrity sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA== dependencies: "@ethersproject/logger" "^5.4.0" -"@ethersproject/constants@5.4.0", "@ethersproject/constants@^5.4.0": +"@ethersproject/constants@5.5.0", "@ethersproject/constants@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e" + integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + +"@ethersproject/constants@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.4.0.tgz#ee0bdcb30bf1b532d2353c977bf2ef1ee117958a" integrity sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q== dependencies: "@ethersproject/bignumber" "^5.4.0" -"@ethersproject/contracts@5.4.1", "@ethersproject/contracts@^5.4.1": +"@ethersproject/contracts@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.5.0.tgz#b735260d4bd61283a670a82d5275e2a38892c197" + integrity sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + +"@ethersproject/contracts@^5.4.1": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.4.1.tgz#3eb4f35b7fe60a962a75804ada2746494df3e470" integrity sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w== @@ -1437,7 +1541,21 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/transactions" "^5.4.0" -"@ethersproject/hash@5.4.0", "@ethersproject/hash@^5.4.0": +"@ethersproject/hash@5.5.0", "@ethersproject/hash@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9" + integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg== + dependencies: + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + +"@ethersproject/hash@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.4.0.tgz#d18a8e927e828e22860a011f39e429d388344ae0" integrity sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA== @@ -1451,7 +1569,25 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/strings" "^5.4.0" -"@ethersproject/hdnode@5.4.0", "@ethersproject/hdnode@^5.4.0": +"@ethersproject/hdnode@5.5.0", "@ethersproject/hdnode@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.5.0.tgz#4a04e28f41c546f7c978528ea1575206a200ddf6" + integrity sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q== + dependencies: + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/basex" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/pbkdf2" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + "@ethersproject/signing-key" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/wordlists" "^5.5.0" + +"@ethersproject/hdnode@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.4.0.tgz#4bc9999b9a12eb5ce80c5faa83114a57e4107cac" integrity sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q== @@ -1469,7 +1605,26 @@ "@ethersproject/transactions" "^5.4.0" "@ethersproject/wordlists" "^5.4.0" -"@ethersproject/json-wallets@5.4.0", "@ethersproject/json-wallets@^5.4.0": +"@ethersproject/json-wallets@5.5.0", "@ethersproject/json-wallets@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz#dd522d4297e15bccc8e1427d247ec8376b60e325" + integrity sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ== + dependencies: + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/hdnode" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/pbkdf2" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/random" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/json-wallets@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz#2583341cfe313fc9856642e8ace3080154145e95" integrity sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ== @@ -1488,7 +1643,15 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.4.0", "@ethersproject/keccak256@^5.4.0": +"@ethersproject/keccak256@5.5.0", "@ethersproject/keccak256@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492" + integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg== + dependencies: + "@ethersproject/bytes" "^5.5.0" + js-sha3 "0.8.0" + +"@ethersproject/keccak256@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.4.0.tgz#7143b8eea4976080241d2bd92e3b1f1bf7025318" integrity sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A== @@ -1496,19 +1659,39 @@ "@ethersproject/bytes" "^5.4.0" js-sha3 "0.5.7" -"@ethersproject/logger@5.4.1", "@ethersproject/logger@^5.4.0": +"@ethersproject/logger@5.5.0", "@ethersproject/logger@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d" + integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg== + +"@ethersproject/logger@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.1.tgz#503bd33683538b923c578c07d1c2c0dd18672054" integrity sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A== -"@ethersproject/networks@5.4.2", "@ethersproject/networks@^5.4.0": +"@ethersproject/networks@5.5.0", "@ethersproject/networks@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.0.tgz#babec47cab892c51f8dd652ce7f2e3e14283981a" + integrity sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA== + dependencies: + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/networks@^5.4.0": version "5.4.2" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35" integrity sha512-eekOhvJyBnuibfJnhtK46b8HimBc5+4gqpvd1/H9LEl7Q7/qhsIhM81dI9Fcnjpk3jB1aTy6bj0hz3cifhNeYw== dependencies: "@ethersproject/logger" "^5.4.0" -"@ethersproject/pbkdf2@5.4.0", "@ethersproject/pbkdf2@^5.4.0": +"@ethersproject/pbkdf2@5.5.0", "@ethersproject/pbkdf2@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz#e25032cdf02f31505d47afbf9c3e000d95c4a050" + integrity sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + +"@ethersproject/pbkdf2@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz#ed88782a67fda1594c22d60d0ca911a9d669641c" integrity sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g== @@ -1516,14 +1699,46 @@ "@ethersproject/bytes" "^5.4.0" "@ethersproject/sha2" "^5.4.0" -"@ethersproject/properties@5.4.1", "@ethersproject/properties@^5.4.0": +"@ethersproject/properties@5.5.0", "@ethersproject/properties@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995" + integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA== + dependencies: + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/properties@^5.4.0": version "5.4.1" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.4.1.tgz#9f051f976ce790142c6261ccb7b826eaae1f2f36" integrity sha512-cyCGlF8wWlIZyizsj2PpbJ9I7rIlUAfnHYwy/T90pdkSn/NFTa5YWZx2wTJBe9V7dD65dcrrEMisCRUJiq6n3w== dependencies: "@ethersproject/logger" "^5.4.0" -"@ethersproject/providers@5.4.5", "@ethersproject/providers@^5.4.4": +"@ethersproject/providers@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.0.tgz#bc2876a8fe5e0053ed9828b1f3767ae46e43758b" + integrity sha512-xqMbDnS/FPy+J/9mBLKddzyLLAQFjrVff5g00efqxPzcAwXiR+SiCGVy6eJ5iAIirBOATjx7QLhDNPGV+AEQsw== + dependencies: + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/basex" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/random" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/web" "^5.5.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/providers@^5.4.4": version "5.4.5" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.5.tgz#eb2ea2a743a8115f79604a8157233a3a2c832928" integrity sha512-1GkrvkiAw3Fj28cwi1Sqm8ED1RtERtpdXmRfwIBGmqBSN5MoeRUHuwHPppMtbPayPgpFcvD7/Gdc9doO5fGYgw== @@ -1548,7 +1763,15 @@ bech32 "1.1.4" ws "7.4.6" -"@ethersproject/random@5.4.0", "@ethersproject/random@^5.4.0": +"@ethersproject/random@5.5.0", "@ethersproject/random@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.0.tgz#305ed9e033ca537735365ac12eed88580b0f81f9" + integrity sha512-egGYZwZ/YIFKMHcoBUo8t3a8Hb/TKYX8BCBoLjudVCZh892welR3jOxgOmb48xznc9bTcMm7Tpwc1gHC1PFNFQ== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/random@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.4.0.tgz#9cdde60e160d024be39cc16f8de3b9ce39191e16" integrity sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw== @@ -1556,7 +1779,15 @@ "@ethersproject/bytes" "^5.4.0" "@ethersproject/logger" "^5.4.0" -"@ethersproject/rlp@5.4.0", "@ethersproject/rlp@^5.4.0": +"@ethersproject/rlp@5.5.0", "@ethersproject/rlp@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0" + integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/rlp@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.4.0.tgz#de61afda5ff979454e76d3b3310a6c32ad060931" integrity sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg== @@ -1564,7 +1795,16 @@ "@ethersproject/bytes" "^5.4.0" "@ethersproject/logger" "^5.4.0" -"@ethersproject/sha2@5.4.0", "@ethersproject/sha2@^5.4.0": +"@ethersproject/sha2@5.5.0", "@ethersproject/sha2@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.5.0.tgz#a40a054c61f98fd9eee99af2c3cc6ff57ec24db7" + integrity sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + hash.js "1.1.7" + +"@ethersproject/sha2@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.4.0.tgz#c9a8db1037014cbc4e9482bd662f86c090440371" integrity sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg== @@ -1573,7 +1813,19 @@ "@ethersproject/logger" "^5.4.0" hash.js "1.1.7" -"@ethersproject/signing-key@5.4.0", "@ethersproject/signing-key@^5.4.0": +"@ethersproject/signing-key@5.5.0", "@ethersproject/signing-key@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0" + integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + bn.js "^4.11.9" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/signing-key@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.4.0.tgz#2f05120984e81cf89a3d5f6dec5c68ee0894fbec" integrity sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A== @@ -1585,7 +1837,19 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/solidity@5.4.0", "@ethersproject/solidity@^5.4.0": +"@ethersproject/solidity@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.5.0.tgz#2662eb3e5da471b85a20531e420054278362f93f" + integrity sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw== + dependencies: + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/sha2" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + +"@ethersproject/solidity@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.4.0.tgz#1305e058ea02dc4891df18b33232b11a14ece9ec" integrity sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ== @@ -1596,7 +1860,16 @@ "@ethersproject/sha2" "^5.4.0" "@ethersproject/strings" "^5.4.0" -"@ethersproject/strings@5.4.0", "@ethersproject/strings@^5.4.0": +"@ethersproject/strings@5.5.0", "@ethersproject/strings@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549" + integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + +"@ethersproject/strings@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.4.0.tgz#fb12270132dd84b02906a8d895ae7e7fa3d07d9a" integrity sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA== @@ -1605,7 +1878,22 @@ "@ethersproject/constants" "^5.4.0" "@ethersproject/logger" "^5.4.0" -"@ethersproject/transactions@5.4.0", "@ethersproject/transactions@^5.4.0": +"@ethersproject/transactions@5.5.0", "@ethersproject/transactions@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908" + integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA== + dependencies: + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ethersproject/signing-key" "^5.5.0" + +"@ethersproject/transactions@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.4.0.tgz#a159d035179334bd92f340ce0f77e83e9e1522e0" integrity sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ== @@ -1620,16 +1908,37 @@ "@ethersproject/rlp" "^5.4.0" "@ethersproject/signing-key" "^5.4.0" -"@ethersproject/units@5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.4.0.tgz#d57477a4498b14b88b10396062c8cbbaf20c79fe" - integrity sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg== +"@ethersproject/units@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.5.0.tgz#104d02db5b5dc42cc672cc4587bafb87a95ee45e" + integrity sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag== dependencies: - "@ethersproject/bignumber" "^5.4.0" - "@ethersproject/constants" "^5.4.0" - "@ethersproject/logger" "^5.4.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/logger" "^5.5.0" -"@ethersproject/wallet@5.4.0", "@ethersproject/wallet@^5.4.0": +"@ethersproject/wallet@5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.5.0.tgz#322a10527a440ece593980dca6182f17d54eae75" + integrity sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q== + dependencies: + "@ethersproject/abstract-provider" "^5.5.0" + "@ethersproject/abstract-signer" "^5.5.0" + "@ethersproject/address" "^5.5.0" + "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/hdnode" "^5.5.0" + "@ethersproject/json-wallets" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/random" "^5.5.0" + "@ethersproject/signing-key" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/wordlists" "^5.5.0" + +"@ethersproject/wallet@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.4.0.tgz#fa5b59830b42e9be56eadd45a16a2e0933ad9353" integrity sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ== @@ -1650,7 +1959,18 @@ "@ethersproject/transactions" "^5.4.0" "@ethersproject/wordlists" "^5.4.0" -"@ethersproject/web@5.4.0", "@ethersproject/web@^5.4.0": +"@ethersproject/web@5.5.0", "@ethersproject/web@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.0.tgz#0e5bb21a2b58fb4960a705bfc6522a6acf461e28" + integrity sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA== + dependencies: + "@ethersproject/base64" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + +"@ethersproject/web@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.4.0.tgz#49fac173b96992334ed36a175538ba07a7413d1f" integrity sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og== @@ -1661,7 +1981,18 @@ "@ethersproject/properties" "^5.4.0" "@ethersproject/strings" "^5.4.0" -"@ethersproject/wordlists@5.4.0", "@ethersproject/wordlists@^5.4.0": +"@ethersproject/wordlists@5.5.0", "@ethersproject/wordlists@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.5.0.tgz#aac74963aa43e643638e5172353d931b347d584f" + integrity sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q== + dependencies: + "@ethersproject/bytes" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/strings" "^5.5.0" + +"@ethersproject/wordlists@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.4.0.tgz#f34205ec3bbc9e2c49cadaee774cf0b07e7573d7" integrity sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA== @@ -1952,6 +2283,16 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.2.tgz#c472abcba0c5185aaa4ad4070146e95213c68511" integrity sha512-6quxWe8wwS4X5v3Au8q1jOvXYEPkS1Fh+cME5u6AwNdnI4uERvPlVjlgRWzpnb+Rrt1l/cEqiNRH9GlsBMSDQg== +"@openzeppelin-solc-0.7/contracts@npm:@openzeppelin/contracts@3.4.2-solc-0.7": + version "3.4.2-solc-0.7" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" + integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA== + +"@openzeppelin/contracts@3.4.1-solc-0.7-2": + version "3.4.1-solc-0.7-2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz#371c67ebffe50f551c3146a9eec5fe6ffe862e92" + integrity sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q== + "@openzeppelin/contracts@^3.4.1": version "3.4.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527" @@ -2487,6 +2828,32 @@ "@typescript-eslint/types" "4.31.1" eslint-visitor-keys "^2.0.0" +"@uniswap/lib@^4.0.1-alpha": + version "4.0.1-alpha" + resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" + integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== + +"@uniswap/v2-core@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + +"@uniswap/v3-core@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" + integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA== + +"@uniswap/v3-periphery@github:uniswap/v3-periphery": + version "1.3.0" + resolved "https://codeload.github.com/uniswap/v3-periphery/tar.gz/80f26c86c57b8a5e4b913f42844d4c8bd274d058" + dependencies: + "@openzeppelin/contracts" "3.4.1-solc-0.7-2" + "@uniswap/lib" "^4.0.1-alpha" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + base64-sol "1.0.1" + hardhat-watcher "^2.1.1" + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -2904,6 +3271,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64-sol@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/base64-sol/-/base64-sol-1.0.1.tgz#91317aa341f0bc763811783c5729f1c2574600f6" + integrity sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -3252,7 +3624,7 @@ chokidar@3.3.0: optionalDependencies: fsevents "~2.1.1" -chokidar@^3.4.0, chokidar@^3.5.2: +chokidar@^3.4.0, chokidar@^3.4.3, chokidar@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -4299,41 +4671,41 @@ ethereumjs-util@^7.0.7, ethereumjs-util@^7.1.0: ethjs-util "0.1.6" rlp "^2.2.4" -ethers@^5.4.6: - version "5.4.6" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.4.6.tgz#fe0a023956b5502c947f58e82fbcf9a73e5e75b6" - integrity sha512-F7LXARyB/Px3AQC6/QKedWZ8eqCkgOLORqL4B/F0Mag/K+qJSFGqsR36EaOZ6fKg3ZonI+pdbhb4A8Knt/43jQ== - dependencies: - "@ethersproject/abi" "5.4.1" - "@ethersproject/abstract-provider" "5.4.1" - "@ethersproject/abstract-signer" "5.4.1" - "@ethersproject/address" "5.4.0" - "@ethersproject/base64" "5.4.0" - "@ethersproject/basex" "5.4.0" - "@ethersproject/bignumber" "5.4.1" - "@ethersproject/bytes" "5.4.0" - "@ethersproject/constants" "5.4.0" - "@ethersproject/contracts" "5.4.1" - "@ethersproject/hash" "5.4.0" - "@ethersproject/hdnode" "5.4.0" - "@ethersproject/json-wallets" "5.4.0" - "@ethersproject/keccak256" "5.4.0" - "@ethersproject/logger" "5.4.1" - "@ethersproject/networks" "5.4.2" - "@ethersproject/pbkdf2" "5.4.0" - "@ethersproject/properties" "5.4.1" - "@ethersproject/providers" "5.4.5" - "@ethersproject/random" "5.4.0" - "@ethersproject/rlp" "5.4.0" - "@ethersproject/sha2" "5.4.0" - "@ethersproject/signing-key" "5.4.0" - "@ethersproject/solidity" "5.4.0" - "@ethersproject/strings" "5.4.0" - "@ethersproject/transactions" "5.4.0" - "@ethersproject/units" "5.4.0" - "@ethersproject/wallet" "5.4.0" - "@ethersproject/web" "5.4.0" - "@ethersproject/wordlists" "5.4.0" +ethers@^5.5.1: + version "5.5.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.1.tgz#d3259a95a42557844aa543906c537106c0406fbf" + integrity sha512-RodEvUFZI+EmFcE6bwkuJqpCYHazdzeR1nMzg+YWQSmQEsNtfl1KHGfp/FWZYl48bI/g7cgBeP2IlPthjiVngw== + dependencies: + "@ethersproject/abi" "5.5.0" + "@ethersproject/abstract-provider" "5.5.1" + "@ethersproject/abstract-signer" "5.5.0" + "@ethersproject/address" "5.5.0" + "@ethersproject/base64" "5.5.0" + "@ethersproject/basex" "5.5.0" + "@ethersproject/bignumber" "5.5.0" + "@ethersproject/bytes" "5.5.0" + "@ethersproject/constants" "5.5.0" + "@ethersproject/contracts" "5.5.0" + "@ethersproject/hash" "5.5.0" + "@ethersproject/hdnode" "5.5.0" + "@ethersproject/json-wallets" "5.5.0" + "@ethersproject/keccak256" "5.5.0" + "@ethersproject/logger" "5.5.0" + "@ethersproject/networks" "5.5.0" + "@ethersproject/pbkdf2" "5.5.0" + "@ethersproject/properties" "5.5.0" + "@ethersproject/providers" "5.5.0" + "@ethersproject/random" "5.5.0" + "@ethersproject/rlp" "5.5.0" + "@ethersproject/sha2" "5.5.0" + "@ethersproject/signing-key" "5.5.0" + "@ethersproject/solidity" "5.5.0" + "@ethersproject/strings" "5.5.0" + "@ethersproject/transactions" "5.5.0" + "@ethersproject/units" "5.5.0" + "@ethersproject/wallet" "5.5.0" + "@ethersproject/web" "5.5.0" + "@ethersproject/wordlists" "5.5.0" ethjs-util@0.1.6, ethjs-util@^0.1.3: version "0.1.6" @@ -5019,6 +5391,13 @@ hardhat-deploy@^0.9.0: murmur-128 "^0.2.1" qs "^6.9.4" +hardhat-watcher@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.1.1.tgz#8b05fec429ed45da11808bbf6054a90f3e34c51a" + integrity sha512-zilmvxAYD34IofBrwOliQn4z92UiDmt2c949DW4Gokf0vS0qk4YTfVCi/LmUBICThGygNANE3WfnRTpjCJGtDA== + dependencies: + chokidar "^3.4.3" + hardhat@^2.6.2, hardhat@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.6.4.tgz#9ff3f139f697bfc4e14836a3fef3ca4c62357d65"