From e49c5dae750b669430ad5af9c19f8493f338cf6c Mon Sep 17 00:00:00 2001 From: Gevarist <98388264+Gevarist@users.noreply.github.com> Date: Fri, 2 Dec 2022 02:29:54 +0100 Subject: [PATCH] fix : audits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add max manager fee checks * fix: AR-213 first minter can change LP token pricing * fix: AR-214 and AR-215 * chore: AR 224 add getRanges * chore: remove manager interface call * fix: AR 225 check liquidity is zero * chore: final clean up Co-authored-by: κασσάνδρα.eth <0xDADA@protonmail.com> --- contracts/ArrakisV2.sol | 115 +++++++++--------- contracts/ArrakisV2Factory.sol | 4 +- contracts/ArrakisV2Helper.sol | 33 +---- contracts/ArrakisV2Resolver.sol | 16 ++- contracts/__mocks__/ManagerProxyMock.sol | 4 + .../__mocks__/functions/MockFArrakisV2.sol | 6 +- .../abstract/ArrakisV2FactoryStorage.sol | 10 +- contracts/abstract/ArrakisV2Storage.sol | 62 ++++++---- contracts/interfaces/IArrakisV2.sol | 12 +- contracts/interfaces/IArrakisV2Helper.sol | 5 - contracts/libraries/Manager.sol | 15 --- contracts/libraries/Underlying.sol | 27 ++-- contracts/structs/SArrakisV2.sol | 1 + deploy/ArrakisV2.deploy.ts | 7 +- deploy/ArrakisV2Resolver.deploy.ts | 1 - deploy/Manager.deploy.ts | 37 ------ deploy/Underlying.deploy.ts | 3 +- hardhat.config.ts | 2 +- test/integration_tests/ArrakisV2.test.ts | 88 +------------- test/unit_tests/ArrakisV2.test.ts | 1 + test/unit_tests/ArrakisV2Factory.test.ts | 18 ++- test/unit_tests/ArrakisV2Helper.test.ts | 20 +-- test/unit_tests/FArrakisV2.test.ts | 8 +- 23 files changed, 161 insertions(+), 334 deletions(-) delete mode 100644 contracts/libraries/Manager.sol delete mode 100644 deploy/Manager.deploy.ts diff --git a/contracts/ArrakisV2.sol b/contracts/ArrakisV2.sol index 5808bb5..6bcec7c 100644 --- a/contracts/ArrakisV2.sol +++ b/contracts/ArrakisV2.sol @@ -27,7 +27,6 @@ import { } from "./structs/SArrakisV2.sol"; import {Position} from "./libraries/Position.sol"; import {Pool} from "./libraries/Pool.sol"; -import {Manager} from "./libraries/Manager.sol"; import {Underlying as UnderlyingHelper} from "./libraries/Underlying.sol"; /// @dev DO NOT ADD STATE VARIABLES - APPEND THEM TO ArrakisV2Storage @@ -35,9 +34,8 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { using SafeERC20 for IERC20; using EnumerableSet for EnumerableSet.AddressSet; - constructor(IUniswapV3Factory factory_, address arrakisTreasury_) - ArrakisV2Storage(factory_, arrakisTreasury_) - {} // solhint-disable-line no-empty-blocks + // solhint-disable-next-line no-empty-blocks + constructor(IUniswapV3Factory factory_) ArrakisV2Storage(factory_) {} /// @notice Uniswap V3 callback fn, called back on pool.mint function uniswapV3MintCallback( @@ -61,12 +59,13 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { ); address me = address(this); uint256 totalSupply = totalSupply(); + bool isTotalSupplyGtZero = totalSupply > 0; ( uint256 current0, uint256 current1, uint256 fee0, uint256 fee1 - ) = totalSupply > 0 + ) = isTotalSupplyGtZero ? UnderlyingHelper.totalUnderlyingWithFees( UnderlyingPayload({ ranges: ranges, @@ -77,12 +76,35 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { }) ) : (init0, init1, 0, 0); - uint256 denominator = totalSupply > 0 ? totalSupply : 1 ether; - /// @dev current0 and current1 include fees and left over (but not admin balances) + uint256 denominator = isTotalSupplyGtZero ? totalSupply : 1 ether; + /// @dev current0 and current1 include fees and left over (but not manager balances) amount0 = FullMath.mulDivRoundingUp(mintAmount_, current0, denominator); amount1 = FullMath.mulDivRoundingUp(mintAmount_, current1, denominator); + // #region check amount0 is a multiple of current0. + + if (!isTotalSupplyGtZero) { + uint256 amount0Mint = FullMath.mulDiv( + amount0, + denominator, + current0 + ); + uint256 amount1Mint = FullMath.mulDiv( + amount1, + denominator, + current1 + ); + + require( + (amount0Mint < amount1Mint ? amount0Mint : amount1Mint) == + mintAmount_, + "A0&A1" + ); + } + + // #endregion check amount0 is a multiple of current0. + _mint(receiver_, mintAmount_); // transfer amounts owed to contract @@ -123,10 +145,10 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { ); underlying.leftOver0 = token0.balanceOf(address(this)) - - (managerBalance0 + arrakisBalance0); + managerBalance0; underlying.leftOver1 = token1.balanceOf(address(this)) - - (managerBalance1 + arrakisBalance1); + managerBalance1; { // the proportion of user balance. @@ -200,15 +222,21 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { token1.safeTransfer(receiver_, amount1); } + // intentional underflow revert if managerBalance > contract's token balance + uint256 leftover0 = token0.balanceOf(address(this)) - managerBalance0; + uint256 leftover1 = token1.balanceOf(address(this)) - managerBalance1; + require( - token0.balanceOf(address(this)) >= - managerBalance0 + arrakisBalance0, - "MB0" + (leftover0 <= underlying.leftOver0) || + ((leftover0 - underlying.leftOver0) <= + FullMath.mulDiv(total.burn0, _burnBuffer, 10000)), + "L0" ); require( - token1.balanceOf(address(this)) >= - managerBalance1 + arrakisBalance1, - "MB1" + (leftover1 <= underlying.leftOver1) || + ((leftover1 - underlying.leftOver1) <= + FullMath.mulDiv(total.burn1, _burnBuffer, 10000)), + "L1" ); // For monitoring how much user burn LP token for getting their token back. @@ -220,37 +248,28 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { // solhint-disable-next-line function-max-lines function rebalance( - Range[] calldata ranges_, + Range[] calldata rangesToAdd_, Rebalance calldata rebalanceParams_, Range[] calldata rangesToRemove_ ) external onlyManager { - for (uint256 i = 0; i < ranges_.length; i++) { - (bool exist, ) = Position.rangeExist(ranges, ranges_[i]); + for (uint256 i = 0; i < rangesToAdd_.length; i++) { + (bool exist, ) = Position.rangeExist(ranges, rangesToAdd_[i]); require(!exist, "NRRE"); // check that the pool exist on Uniswap V3. address pool = factory.getPool( address(token0), address(token1), - ranges_[i].feeTier + rangesToAdd_[i].feeTier ); require(pool != address(0), "NUP"); require(_pools.contains(pool), "P"); - // TODO: can reuse the pool got previously. - require(Pool.validateTickSpacing(pool, ranges_[i]), "RTS"); + require(Pool.validateTickSpacing(pool, rangesToAdd_[i]), "RTS"); - ranges.push(ranges_[i]); + ranges.push(rangesToAdd_[i]); } _rebalance(rebalanceParams_); - require( - token0.balanceOf(address(this)) >= - managerBalance0 + arrakisBalance0, - "MB0" - ); - require( - token1.balanceOf(address(this)) >= - managerBalance1 + arrakisBalance1, - "MB1" - ); + require(token0.balanceOf(address(this)) >= managerBalance0, "MB0"); + require(token1.balanceOf(address(this)) >= managerBalance1, "MB1"); for (uint256 i = 0; i < rangesToRemove_.length; i++) { (bool exist, uint256 index) = Position.rangeExist( ranges, @@ -283,40 +302,22 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { managerBalance1 = 0; if (amount0 > 0) { - token0.safeTransfer(address(manager), amount0); + token0.safeTransfer(manager, amount0); } if (amount1 > 0) { - token1.safeTransfer(address(manager), amount1); + token1.safeTransfer(manager, amount1); } emit LogWithdrawManagerBalance(amount0, amount1); } - function withdrawArrakisBalance() external { - uint256 amount0 = arrakisBalance0; - uint256 amount1 = arrakisBalance1; - - arrakisBalance0 = 0; - arrakisBalance1 = 0; - - if (amount0 > 0) { - token0.safeTransfer(arrakisTreasury, amount0); - } - - if (amount1 > 0) { - token1.safeTransfer(arrakisTreasury, amount1); - } - - emit LogWithdrawArrakisBalance(amount0, amount1); - } - // solhint-disable-next-line function-max-lines, code-complexity function _rebalance(Rebalance calldata rebalanceParams_) internal nonReentrant { - // Burns + // Burns. uint256 aggregator0 = 0; uint256 aggregator1 = 0; for (uint256 i = 0; i < rebalanceParams_.removes.length; i++) { @@ -344,8 +345,7 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { emit LogCollectedFees(aggregator0, aggregator1); } - // Swap - + // Swap. if (rebalanceParams_.swap.amountIn > 0) { { require(_routers.contains(rebalanceParams_.swap.router), "NR"); @@ -443,8 +443,6 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { liquidity_ ); - /// @dev relying on return values here means we WILL BREAK with fee-on-transfer tokens - /// we assume this is fine as UniswapV3 is already broken for fee-on-transfer tokens (uint256 collect0, uint256 collect1) = pool_.collect( address(this), lowerTick_, @@ -458,10 +456,7 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { } function _applyFees(uint256 fee0_, uint256 fee1_) internal { - uint16 managerFeeBPS = Manager.getManagerFeeBPS(manager); managerBalance0 += (fee0_ * managerFeeBPS) / 10000; managerBalance1 += (fee1_ * managerFeeBPS) / 10000; - arrakisBalance0 += (fee0_ * arrakisFeeBPS) / 10000; - arrakisBalance1 += (fee1_ * arrakisFeeBPS) / 10000; } } diff --git a/contracts/ArrakisV2Factory.sol b/contracts/ArrakisV2Factory.sol index 1c966d3..cb087b4 100644 --- a/contracts/ArrakisV2Factory.sol +++ b/contracts/ArrakisV2Factory.sol @@ -48,8 +48,8 @@ contract ArrakisV2Factory is ArrakisV2FactoryStorage { return _append("Arrakis Vault V2 ", symbol0, "/", symbol1); } - /// @notice numVaults counts the total number of Harvesters in existence - /// @return result total number of Harvesters deployed + /// @notice numVaults counts the total number of vaults in existence + /// @return result total number of vaults deployed function numVaults() public view returns (uint256 result) { return _vaults.length(); } diff --git a/contracts/ArrakisV2Helper.sol b/contracts/ArrakisV2Helper.sol index 78e7f8d..db969d3 100644 --- a/contracts/ArrakisV2Helper.sol +++ b/contracts/ArrakisV2Helper.sol @@ -32,7 +32,7 @@ contract ArrakisV2Helper is IArrakisV2Helper { returns (UnderlyingOutput memory underlying) { UnderlyingPayload memory underlyingPayload = UnderlyingPayload({ - ranges: ranges(vault_), + ranges: vault_.getRanges(), factory: vault_.factory(), token0: address(vault_.token0()), token1: address(vault_.token1()), @@ -48,12 +48,10 @@ contract ArrakisV2Helper is IArrakisV2Helper { underlying.leftOver0 = IERC20(underlyingPayload.token0).balanceOf(underlyingPayload.self) - - IArrakisV2(underlyingPayload.self).managerBalance0() - - IArrakisV2(underlyingPayload.self).arrakisBalance0(); + IArrakisV2(underlyingPayload.self).managerBalance0(); underlying.leftOver1 = IERC20(underlyingPayload.token1).balanceOf(underlyingPayload.self) - - IArrakisV2(underlyingPayload.self).managerBalance1() - - IArrakisV2(underlyingPayload.self).arrakisBalance1(); + IArrakisV2(underlyingPayload.self).managerBalance1(); } function totalUnderlyingWithFees(IArrakisV2 vault_) @@ -67,7 +65,7 @@ contract ArrakisV2Helper is IArrakisV2Helper { ) { UnderlyingPayload memory underlyingPayload = UnderlyingPayload({ - ranges: ranges(vault_), + ranges: vault_.getRanges(), factory: vault_.factory(), token0: address(vault_.token0()), token1: address(vault_.token1()), @@ -84,7 +82,7 @@ contract ArrakisV2Helper is IArrakisV2Helper { returns (uint256 amount0, uint256 amount1) { UnderlyingPayload memory underlyingPayload = UnderlyingPayload({ - ranges: ranges(vault_), + ranges: vault_.getRanges(), factory: vault_.factory(), token0: address(vault_.token0()), token1: address(vault_.token1()), @@ -168,27 +166,6 @@ contract ArrakisV2Helper is IArrakisV2Helper { // #endregion Rebalance helper functions - function ranges(IArrakisV2 vault_) - public - view - returns (Range[] memory rgs) - { - uint256 index; - while (true) { - try vault_.ranges(index) returns (Range memory) { - index++; - } catch { - break; - } - } - - rgs = new Range[](index); - - for (uint256 i = 0; i < index; i++) { - rgs[i] = vault_.ranges(i); - } - } - // #region internal functions function _getAmountsAndFeesFromLiquidity( diff --git a/contracts/ArrakisV2Resolver.sol b/contracts/ArrakisV2Resolver.sol index 02269f9..01a671e 100644 --- a/contracts/ArrakisV2Resolver.sol +++ b/contracts/ArrakisV2Resolver.sol @@ -48,7 +48,7 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { swapRouter = swapRouter_; } - // no swapping. Standard rebalance. + // Standard rebalance (without swapping) // solhint-disable-next-line function-max-lines, code-complexity function standardRebalance( RangeWeight[] memory rangeWeights_, @@ -59,7 +59,7 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { address token0Addr; address token1Addr; { - Range[] memory ranges = helper.ranges(vaultV2_); + Range[] memory ranges = vaultV2_.getRanges(); token0Addr = address(vaultV2_.token0()); token1Addr = address(vaultV2_.token1()); @@ -108,8 +108,6 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { } } - // TODO check if sum of weight is < 10000 - _requireWeightUnder100(rangeWeights_); rebalanceParams.deposits = new PositionLiquidity[]( @@ -150,7 +148,7 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { uint256 totalSupply = vaultV2_.totalSupply(); require(totalSupply > 0, "total supply"); - Range[] memory ranges = helper.ranges(vaultV2_); + Range[] memory ranges = vaultV2_.getRanges(); { UnderlyingOutput memory underlying; @@ -170,12 +168,10 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { ); underlying.leftOver0 = vaultV2_.token0().balanceOf(address(vaultV2_)) - - vaultV2_.managerBalance0() - - vaultV2_.arrakisBalance0(); + vaultV2_.managerBalance0(); underlying.leftOver1 = vaultV2_.token1().balanceOf(address(vaultV2_)) - - vaultV2_.managerBalance1() - - vaultV2_.arrakisBalance1(); + vaultV2_.managerBalance1(); { uint256 amount0 = FullMath.mulDiv( @@ -217,6 +213,8 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { ); } + if (liquidity == 0) continue; + burns[i] = BurnLiquidity({ liquidity: SafeCast.toUint128( FullMath.mulDiv(liquidity, amountToBurn_, totalSupply) diff --git a/contracts/__mocks__/ManagerProxyMock.sol b/contracts/__mocks__/ManagerProxyMock.sol index d6ce021..17b7271 100644 --- a/contracts/__mocks__/ManagerProxyMock.sol +++ b/contracts/__mocks__/ManagerProxyMock.sol @@ -26,4 +26,8 @@ contract ManagerProxyMock is IManager { function fundVaultBalance(address vault) external payable { // empty } + + function setManagerFeeBPS(address vault_, uint16 fees_) external { + IArrakisV2(vault_).setManagerFeeBPS(fees_); + } } diff --git a/contracts/__mocks__/functions/MockFArrakisV2.sol b/contracts/__mocks__/functions/MockFArrakisV2.sol index a07aee3..60558da 100644 --- a/contracts/__mocks__/functions/MockFArrakisV2.sol +++ b/contracts/__mocks__/functions/MockFArrakisV2.sol @@ -60,15 +60,13 @@ contract MockFArrakisV2 { function subtractAdminFees( uint256 rawFee0_, uint256 rawFee1_, - uint16 managerFeeBPS_, - uint16 arrakisFeeBPS_ + uint16 managerFeeBPS_ ) external pure returns (uint256 fee0, uint256 fee1) { return UnderlyingHelper.subtractAdminFees( rawFee0_, rawFee1_, - managerFeeBPS_, - arrakisFeeBPS_ + managerFeeBPS_ ); } diff --git a/contracts/abstract/ArrakisV2FactoryStorage.sol b/contracts/abstract/ArrakisV2FactoryStorage.sol index 623f3d9..7ab3b9b 100644 --- a/contracts/abstract/ArrakisV2FactoryStorage.sol +++ b/contracts/abstract/ArrakisV2FactoryStorage.sol @@ -19,12 +19,8 @@ import { // solhint-disable-next-line max-states-count abstract contract ArrakisV2FactoryStorage is IArrakisV2Factory, - OwnableUpgradeable /* XXXX DONT MODIFY ORDERING XXXX */ - // APPEND ADDITIONAL BASE WITH STATE VARS BELOW: - // XXXX DONT MODIFY ORDERING XXXX + OwnableUpgradeable { - // XXXXXXXX DO NOT MODIFY ORDERING XXXXXXXX - using EnumerableSet for EnumerableSet.AddressSet; // solhint-disable-next-line const-name-snakecase @@ -33,10 +29,6 @@ abstract contract ArrakisV2FactoryStorage is IArrakisV2Beacon public immutable arrakisV2Beacon; EnumerableSet.AddressSet internal _vaults; - // APPPEND ADDITIONAL STATE VARS BELOW: - - // XXXXXXXX DO NOT MODIFY ORDERING XXXXXXXX - // #region constructor. constructor(IArrakisV2Beacon arrakisV2Beacon_) { diff --git a/contracts/abstract/ArrakisV2Storage.sol b/contracts/abstract/ArrakisV2Storage.sol index 5f21699..bd149d8 100644 --- a/contracts/abstract/ArrakisV2Storage.sol +++ b/contracts/abstract/ArrakisV2Storage.sol @@ -32,11 +32,7 @@ abstract contract ArrakisV2Storage is using SafeERC20 for IERC20; using EnumerableSet for EnumerableSet.AddressSet; - // solhint-disable-next-line const-name-snakecase - uint16 public constant arrakisFeeBPS = 250; - IUniswapV3Factory public immutable factory; - address public immutable arrakisTreasury; IERC20 public token0; IERC20 public token1; @@ -46,18 +42,12 @@ abstract contract ArrakisV2Storage is Range[] public ranges; - // #region arrakis data - - uint256 public arrakisBalance0; - uint256 public arrakisBalance1; - - // #endregion arrakis data - // #region manager data + uint16 public managerFeeBPS; uint256 public managerBalance0; uint256 public managerBalance1; - IManager public manager; + address public manager; address public restrictedMint; // #endregion manager data @@ -65,6 +55,12 @@ abstract contract ArrakisV2Storage is EnumerableSet.AddressSet internal _pools; EnumerableSet.AddressSet internal _routers; + // #region burn buffer + + uint16 internal _burnBuffer; + + // #endregion burn buffer + // #region events event LogMint( @@ -97,9 +93,11 @@ abstract contract ArrakisV2Storage is event LogAddPools(uint24[] feeTiers); event LogRemovePools(address[] pools); event LogSetManager(address newManager); + event LogSetManagerFeeBPS(uint16 managerFeeBPS); event LogRestrictedMint(address minter); event LogWhitelistRouters(address[] routers); event LogBlacklistRouters(address[] routers); + event LogSetBurnBuffer(uint16 newBurnBuffer); // #endregion Setting events // #endregion events @@ -107,17 +105,15 @@ abstract contract ArrakisV2Storage is // #region modifiers modifier onlyManager() { - require(address(manager) == msg.sender, "NM"); + require(manager == msg.sender, "NM"); _; } // #endregion modifiers - constructor(IUniswapV3Factory factory_, address arrakisTreasury_) { + constructor(IUniswapV3Factory factory_) { require(address(factory_) != address(0), "ZF"); - require(arrakisTreasury_ != address(0), "ZAT"); factory = factory_; - arrakisTreasury = arrakisTreasury_; } // solhint-disable-next-line function-max-lines @@ -143,11 +139,14 @@ abstract contract ArrakisV2Storage is _transferOwnership(params_.owner); - manager = IManager(params_.manager); + manager = params_.manager; + + _burnBuffer = params_.burnBuffer; emit LogAddPools(params_.feeTiers); emit LogSetInits(init0 = params_.init0, init1 = params_.init1); emit LogSetManager(params_.manager); + emit LogSetBurnBuffer(params_.burnBuffer); } // #region setter functions @@ -189,16 +188,33 @@ abstract contract ArrakisV2Storage is emit LogBlacklistRouters(routers_); } - function setManager(IManager manager_) external onlyOwner { - emit LogSetManager(address(manager = manager_)); + function setManager(address manager_) external onlyOwner { + emit LogSetManager(manager = manager_); + } + + function setManagerFeeBPS(uint16 managerFeeBPS_) external onlyManager { + emit LogSetManagerFeeBPS(managerFeeBPS = managerFeeBPS_); } function setRestrictedMint(address minter_) external onlyOwner { emit LogRestrictedMint(restrictedMint = minter_); } + function setBurnBuffer(uint16 newBurnBuffer_) external onlyOwner { + require(newBurnBuffer_ < 5000, "MTMB"); + emit LogSetBurnBuffer(_burnBuffer = newBurnBuffer_); + } + // #endregion setter functions + // #region getter functions + + function getRanges() external view returns (Range[] memory) { + return ranges; + } + + // #endregion getter functions + // #region internal functions function _uniswapV3CallBack(uint256 amount0_, uint256 amount1_) internal { @@ -206,15 +222,11 @@ abstract contract ArrakisV2Storage is if ( amount0_ > 0 && - amount0_ <= - token0.balanceOf(address(this)) - - (managerBalance0 + arrakisBalance0) + amount0_ <= token0.balanceOf(address(this)) - managerBalance0 ) token0.safeTransfer(msg.sender, amount0_); if ( amount1_ > 0 && - amount1_ <= - token1.balanceOf(address(this)) - - (managerBalance1 + arrakisBalance1) + amount1_ <= token1.balanceOf(address(this)) - managerBalance1 ) token1.safeTransfer(msg.sender, amount1_); } diff --git a/contracts/interfaces/IArrakisV2.sol b/contracts/interfaces/IArrakisV2.sol index 5465ba0..54ccae3 100644 --- a/contracts/interfaces/IArrakisV2.sol +++ b/contracts/interfaces/IArrakisV2.sol @@ -34,10 +34,10 @@ interface IArrakisV2 { Range[] calldata rangesToRemove_ ) external; - function withdrawArrakisBalance() external; - function withdrawManagerBalance() external; + function setManagerFeeBPS(uint16 managerFeeBPS_) external; + // #endregion state modifiying functions. function totalSupply() external view returns (uint256); @@ -54,15 +54,13 @@ interface IArrakisV2 { function ranges(uint256 index) external view returns (Range memory); - function arrakisFeeBPS() external view returns (uint16); + function manager() external view returns (address); - function manager() external view returns (IManager); + function managerFeeBPS() external view returns (uint16); function managerBalance0() external view returns (uint256); function managerBalance1() external view returns (uint256); - function arrakisBalance0() external view returns (uint256); - - function arrakisBalance1() external view returns (uint256); + function getRanges() external view returns (Range[] memory); } diff --git a/contracts/interfaces/IArrakisV2Helper.sol b/contracts/interfaces/IArrakisV2Helper.sol index d2c5511..67c052e 100644 --- a/contracts/interfaces/IArrakisV2Helper.sol +++ b/contracts/interfaces/IArrakisV2Helper.sol @@ -39,9 +39,4 @@ interface IArrakisV2Helper { external view returns (Amount[] memory amount0s, Amount[] memory amount1s); - - function ranges(IArrakisV2 vault_) - external - view - returns (Range[] memory rgs); } diff --git a/contracts/libraries/Manager.sol b/contracts/libraries/Manager.sol deleted file mode 100644 index e167ab2..0000000 --- a/contracts/libraries/Manager.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.13; - -import {IManager} from "../interfaces/IManager.sol"; -import {Range} from "../structs/SArrakisV2.sol"; - -library Manager { - function getManagerFeeBPS(IManager manager_) public view returns (uint16) { - try manager_.managerFeeBPS() returns (uint16 feeBPS) { - return feeBPS; - } catch { - return 0; - } - } -} diff --git a/contracts/libraries/Underlying.sol b/contracts/libraries/Underlying.sol index dc28d9e..a85bd01 100644 --- a/contracts/libraries/Underlying.sol +++ b/contracts/libraries/Underlying.sol @@ -19,7 +19,6 @@ import { GetFeesPayload } from "../structs/SArrakisV2.sol"; import {Position} from "./Position.sol"; -import {Manager} from "./Manager.sol"; library Underlying { // solhint-disable-next-line function-max-lines @@ -63,8 +62,9 @@ library Underlying { (uint256 fee0After, uint256 fee1After) = subtractAdminFees( fee0, fee1, - Manager.getManagerFeeBPS(arrakisV2.manager()), - arrakisV2.arrakisFeeBPS() + arrakisV2.managerFeeBPS(), + amount0, + amount1 ); amount0 += @@ -72,15 +72,13 @@ library Underlying { IERC20(underlyingPayload_.token0).balanceOf( underlyingPayload_.self ) - - arrakisV2.managerBalance0() - - arrakisV2.arrakisBalance0(); + arrakisV2.managerBalance0(); amount1 += fee1After + IERC20(underlyingPayload_.token1).balanceOf( underlyingPayload_.self ) - - arrakisV2.managerBalance1() - - arrakisV2.arrakisBalance1(); + arrakisV2.managerBalance1(); } function underlying(RangeData memory underlying_) @@ -162,30 +160,23 @@ library Underlying { function subtractAdminFees( uint256 rawFee0_, uint256 rawFee1_, - uint16 managerFeeBPS_, - uint16 arrakisFeeBPS_ + uint16 managerFeeBPS_ ) public pure returns (uint256 fee0, uint256 fee1) { - fee0 = - rawFee0_ - - ((rawFee0_ * (managerFeeBPS_ + arrakisFeeBPS_)) / 10000); - fee1 = - rawFee1_ - - ((rawFee1_ * (managerFeeBPS_ + arrakisFeeBPS_)) / 10000); + fee0 = rawFee0_ - ((rawFee0_ * (managerFeeBPS_)) / 10000); + fee1 = rawFee1_ - ((rawFee1_ * (managerFeeBPS_)) / 10000); } function subtractAdminFeesOnAmounts( uint256 rawFee0_, uint256 rawFee1_, uint16 managerFeeBPS_, - uint16 arrakisFeeBPS_, uint256 amount0_, uint256 amount1_ ) public pure returns (uint256 amount0, uint256 amount1) { (uint256 fee0, uint256 fee1) = subtractAdminFees( rawFee0_, rawFee1_, - managerFeeBPS_, - arrakisFeeBPS_ + managerFeeBPS_ ); amount0 = amount0_ - (rawFee0_ - fee0); amount1 = amount1_ - (rawFee1_ - fee1); diff --git a/contracts/structs/SArrakisV2.sol b/contracts/structs/SArrakisV2.sol index 6972b3b..a94d144 100644 --- a/contracts/structs/SArrakisV2.sol +++ b/contracts/structs/SArrakisV2.sol @@ -54,6 +54,7 @@ struct InitializePayload { uint256 init1; address manager; address[] routers; + uint16 burnBuffer; } // #region internal Structs diff --git a/deploy/ArrakisV2.deploy.ts b/deploy/ArrakisV2.deploy.ts index bba8912..e92ff0f 100644 --- a/deploy/ArrakisV2.deploy.ts +++ b/deploy/ArrakisV2.deploy.ts @@ -19,15 +19,14 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const addresses = getAddresses(hre.network.name); const { deploy } = deployments; - const { deployer, arrakisTreasury } = await getNamedAccounts(); + const { deployer } = await getNamedAccounts(); await deploy("ArrakisV2", { from: deployer, - args: [addresses.UniswapV3Factory, arrakisTreasury], + args: [addresses.UniswapV3Factory], libraries: { Pool: (await ethers.getContract("Pool")).address, Position: (await ethers.getContract("Position")).address, Underlying: (await ethers.getContract("Underlying")).address, - Manager: (await ethers.getContract("Manager")).address, }, log: hre.network.name != "hardhat" ? true : false, }); @@ -44,4 +43,4 @@ func.skip = async (hre: HardhatRuntimeEnvironment) => { return shouldSkip ? true : false; }; func.tags = ["ArrakisV2"]; -func.dependencies = ["Pool", "Position", "Underlying", "Manager"]; +func.dependencies = ["Pool", "Position", "Underlying"]; diff --git a/deploy/ArrakisV2Resolver.deploy.ts b/deploy/ArrakisV2Resolver.deploy.ts index 11ac4b8..7b8bc8a 100644 --- a/deploy/ArrakisV2Resolver.deploy.ts +++ b/deploy/ArrakisV2Resolver.deploy.ts @@ -30,7 +30,6 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { libraries: { Position: (await ethers.getContract("Position")).address, Underlying: (await ethers.getContract("Underlying")).address, - Manager: (await ethers.getContract("Manager")).address, }, log: hre.network.name != "hardhat" ? true : false, }); diff --git a/deploy/Manager.deploy.ts b/deploy/Manager.deploy.ts deleted file mode 100644 index 56b0ac0..0000000 --- a/deploy/Manager.deploy.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { deployments, getNamedAccounts } from "hardhat"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; -import { sleep } from "../src/utils"; - -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - if ( - hre.network.name === "mainnet" || - hre.network.name === "polygon" || - hre.network.name === "goerli" || - hre.network.name === "optimism" - ) { - console.log( - `Deploying Manager to ${hre.network.name}. Hit ctrl + c to abort` - ); - await sleep(10000); - } - - const { deploy } = deployments; - const { deployer } = await getNamedAccounts(); - await deploy("Manager", { - from: deployer, - log: hre.network.name != "hardhat" ? true : false, - }); -}; - -export default func; - -func.skip = async (hre: HardhatRuntimeEnvironment) => { - const shouldSkip = - hre.network.name === "mainnet" || - hre.network.name === "polygon" || - hre.network.name === "goerli" || - hre.network.name === "optimism"; - return shouldSkip ? true : false; -}; -func.tags = ["Manager"]; diff --git a/deploy/Underlying.deploy.ts b/deploy/Underlying.deploy.ts index 0ea6010..3a79d2d 100644 --- a/deploy/Underlying.deploy.ts +++ b/deploy/Underlying.deploy.ts @@ -22,7 +22,6 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { from: deployer, libraries: { Position: (await ethers.getContract("Position")).address, - Manager: (await ethers.getContract("Manager")).address, }, log: hre.network.name != "hardhat" ? true : false, }); @@ -39,4 +38,4 @@ func.skip = async (hre: HardhatRuntimeEnvironment) => { return shouldSkip ? true : false; }; func.tags = ["Underlying"]; -func.dependencies = ["Position", "Manager"]; +func.dependencies = ["Position"]; diff --git a/hardhat.config.ts b/hardhat.config.ts index bb5f1e4..6631574 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -84,7 +84,7 @@ const config: HardhatUserConfig = { { version: "0.8.13", settings: { - optimizer: { enabled: true, runs: 888 }, + optimizer: { enabled: true, runs: 899 }, }, }, ], diff --git a/test/integration_tests/ArrakisV2.test.ts b/test/integration_tests/ArrakisV2.test.ts index 8b0ca99..47a91e0 100644 --- a/test/integration_tests/ArrakisV2.test.ts +++ b/test/integration_tests/ArrakisV2.test.ts @@ -149,6 +149,7 @@ describe("Arrakis V2 integration test!!!", async function () { init1: res.amount1, manager: managerProxyMock.address, routers: [addresses.SwapRouter], + burnBuffer: 1000, }, true ); @@ -179,6 +180,11 @@ describe("Arrakis V2 integration test!!!", async function () { user )) as ArrakisV2; + await managerProxyMock.setManagerFeeBPS( + vaultV2.address, + await managerProxyMock.managerFeeBPS() + ); + // #region get some USDC and WETH tokens from Uniswap V3. swapRouter = (await ethers.getContractAt( @@ -480,13 +486,6 @@ describe("Arrakis V2 integration test!!!", async function () { balance = await vaultV2.balanceOf(userAddr); - expect(await usdc.balanceOf(vaultV2.address)).to.be.eq( - (await vaultV2.managerBalance0()).add(await vaultV2.arrakisBalance0()) - ); - expect(await wEth.balanceOf(vaultV2.address)).to.be.eq( - (await vaultV2.managerBalance1()).add(await vaultV2.arrakisBalance1()) - ); - expect(balance).to.be.eq(0); // #endregion burn token to get back token to user. @@ -534,40 +533,6 @@ describe("Arrakis V2 integration test!!!", async function () { expect(managerT1A).to.be.gt(managerT1B); // #region withdraw as manager. - - // #region withdraw as arrakis treasury. - - const arrakisAddr = await vaultV2.arrakisTreasury(); - - await user.sendTransaction({ - to: arrakisAddr, - value: ethers.utils.parseEther("1"), - }); - - const arrakisT0B = await usdc.balanceOf(arrakisAddr); - const arrakisT1B = await wEth.balanceOf(arrakisAddr); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [arrakisAddr], - }); - - const arrakisAddrSigner = await ethers.getSigner(arrakisAddr); - - await vaultV2.connect(arrakisAddrSigner).withdrawArrakisBalance(); - - await hre.network.provider.request({ - method: "hardhat_stopImpersonatingAccount", - params: [arrakisAddr], - }); - - const arrakisT0A = await usdc.balanceOf(arrakisAddr); - const arrakisT1A = await wEth.balanceOf(arrakisAddr); - - expect(arrakisT0A).to.be.gte(arrakisT0B); - expect(arrakisT1A).to.be.gt(arrakisT1B); - - // #region withdraw as arrakis treasury. }); it("#3: Rebalance without swap after mint and burn of Arrakis V2 tokens", async () => { @@ -666,13 +631,6 @@ describe("Arrakis V2 integration test!!!", async function () { balance = await vaultV2.balanceOf(userAddr); - expect(await usdc.balanceOf(vaultV2.address)).to.be.eq( - (await vaultV2.managerBalance0()).add(await vaultV2.arrakisBalance0()) - ); - expect(await wEth.balanceOf(vaultV2.address)).to.be.eq( - (await vaultV2.managerBalance1()).add(await vaultV2.arrakisBalance1()) - ); - expect(balance).to.be.eq(0); // #endregion burn token to get back token to user. @@ -720,39 +678,5 @@ describe("Arrakis V2 integration test!!!", async function () { expect(managerT1A).to.be.gt(managerT1B); // #region withdraw as manager. - - // #region withdraw as arrakis treasury. - - const arrakisAddr = await vaultV2.arrakisTreasury(); - - await user.sendTransaction({ - to: arrakisAddr, - value: ethers.utils.parseEther("1"), - }); - - const arrakisT0B = await usdc.balanceOf(arrakisAddr); - const arrakisT1B = await wEth.balanceOf(arrakisAddr); - - await hre.network.provider.request({ - method: "hardhat_impersonateAccount", - params: [arrakisAddr], - }); - - const arrakisAddrSigner = await ethers.getSigner(arrakisAddr); - - await vaultV2.connect(arrakisAddrSigner).withdrawArrakisBalance(); - - await hre.network.provider.request({ - method: "hardhat_stopImpersonatingAccount", - params: [arrakisAddr], - }); - - const arrakisT0A = await usdc.balanceOf(arrakisAddr); - const arrakisT1A = await wEth.balanceOf(arrakisAddr); - - expect(arrakisT0A).to.be.gte(arrakisT0B); - expect(arrakisT1A).to.be.gt(arrakisT1B); - - // #region withdraw as arrakis treasury. }); }); diff --git a/test/unit_tests/ArrakisV2.test.ts b/test/unit_tests/ArrakisV2.test.ts index 3778b1e..34bee5e 100644 --- a/test/unit_tests/ArrakisV2.test.ts +++ b/test/unit_tests/ArrakisV2.test.ts @@ -46,6 +46,7 @@ describe("ArrakisV2 functions unit test", function () { init1: ethers.constants.Zero, manager: managerAddr, routers: [addresses.SwapRouter], + burnBuffer: 1000, }); expect(await arrakisV2.token0()).to.be.eq(addresses.USDC); diff --git a/test/unit_tests/ArrakisV2Factory.test.ts b/test/unit_tests/ArrakisV2Factory.test.ts index 5626945..222398b 100644 --- a/test/unit_tests/ArrakisV2Factory.test.ts +++ b/test/unit_tests/ArrakisV2Factory.test.ts @@ -104,6 +104,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, true ); @@ -158,6 +159,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, true ); @@ -194,6 +196,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, true ); @@ -226,6 +229,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, false ); @@ -265,6 +269,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, false ); @@ -304,6 +309,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, false ); @@ -354,6 +360,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, false ); @@ -370,12 +377,11 @@ describe("Factory function unit test", function () { const deployResult = await deployments.deploy("ArrakisV2", { from: userAddr, - args: [addresses.UniswapV3Factory, userAddr], + args: [userAddr], libraries: { Pool: (await ethers.getContract("Pool")).address, Position: (await ethers.getContract("Position")).address, Underlying: (await ethers.getContract("Underlying")).address, - Manager: (await ethers.getContract("Manager")).address, }, log: hre.network.name != "hardhat" ? true : false, }); @@ -400,7 +406,7 @@ describe("Factory function unit test", function () { user )) as ArrakisV2Beacon; - beacon.upgradeTo(newImplementation.address); + await beacon.upgradeTo(newImplementation.address); // #endregion get arrakis v2 beacon. @@ -440,6 +446,7 @@ describe("Factory function unit test", function () { init1: res.amount1, manager: userAddr, routers: [], + burnBuffer: 1000, }, false ); @@ -456,12 +463,11 @@ describe("Factory function unit test", function () { const deployResult = await deployments.deploy("ArrakisV2", { from: userAddr, - args: [addresses.UniswapV3Factory, userAddr], + args: [userAddr], libraries: { Pool: (await ethers.getContract("Pool")).address, Position: (await ethers.getContract("Position")).address, Underlying: (await ethers.getContract("Underlying")).address, - Manager: (await ethers.getContract("Manager")).address, }, log: hre.network.name != "hardhat" ? true : false, }); @@ -480,7 +486,7 @@ describe("Factory function unit test", function () { user )) as ArrakisV2Beacon; - beacon.upgradeTo(newImplementation.address); + await beacon.upgradeTo(newImplementation.address); // #endregion get arrakis v2 beacon. diff --git a/test/unit_tests/ArrakisV2Helper.test.ts b/test/unit_tests/ArrakisV2Helper.test.ts index 1df4811..39c516e 100644 --- a/test/unit_tests/ArrakisV2Helper.test.ts +++ b/test/unit_tests/ArrakisV2Helper.test.ts @@ -118,6 +118,7 @@ describe("ArrakisV2Helper functions unit test", function () { init1: res.amount1, manager: managerProxyMock.address, routers: [], + burnBuffer: 1000, }, true ); @@ -269,16 +270,7 @@ describe("ArrakisV2Helper functions unit test", function () { } ); - it("#0: get ranges of vault", async () => { - const ranges = await arrakisV2Helper.ranges(arrakisV2.address); - - expect(ranges.length).to.be.eq(1); - expect(ranges[0].lowerTick).to.be.eq(lowerTick); - expect(ranges[0].upperTick).to.be.eq(upperTick); - expect(ranges[0].feeTier).to.be.eq(500); - }); - - it("#1: get token0 and token1 amounts of vault for first range", async () => { + it("#0: get token0 and token1 amounts of vault for first range", async () => { const result = await arrakisV2Helper.token0AndToken1ByRange( [ { @@ -296,7 +288,7 @@ describe("ArrakisV2Helper functions unit test", function () { expect(result.amount1s[0].amount).to.be.gt(0); }); - it("#2: get token0 and token1 amounts with their fees of vault for first range with fees", async () => { + it("#1: get token0 and token1 amounts with their fees of vault for first range with fees", async () => { const result = await arrakisV2Helper.token0AndToken1PlusFeesByRange( [ { @@ -316,14 +308,14 @@ describe("ArrakisV2Helper functions unit test", function () { expect(result.fee1s[0].amount).to.be.gt(0); }); - it("#3: get token0 and token1 amounts of vault ", async () => { + it("#2: get token0 and token1 amounts of vault ", async () => { const result = await arrakisV2Helper.totalUnderlying(arrakisV2.address); expect(result.amount0).to.be.gt(0); expect(result.amount1).to.be.gt(0); }); - it("#4: get token0 and token1 with their fees amounts of vault ", async () => { + it("#3: get token0 and token1 with their fees amounts of vault ", async () => { const result = await arrakisV2Helper.totalUnderlyingWithFees( arrakisV2.address ); @@ -333,7 +325,7 @@ describe("ArrakisV2Helper functions unit test", function () { expect(result.fee0).to.be.eq(0); expect(result.fee1).to.be.gt(0); }); - it("#5: get token0 and token1 with their fees and left over amounts of vault ", async () => { + it("#4: get token0 and token1 with their fees and left over amounts of vault ", async () => { const result = await arrakisV2Helper.totalUnderlyingWithFeesAndLeftOver( arrakisV2.address ); diff --git a/test/unit_tests/FArrakisV2.test.ts b/test/unit_tests/FArrakisV2.test.ts index f5d5396..496eb2e 100644 --- a/test/unit_tests/FArrakisV2.test.ts +++ b/test/unit_tests/FArrakisV2.test.ts @@ -27,21 +27,19 @@ describe("Arrakis V2 smart contract internal functions unit test", function () { const rawFee1 = 1_000_000; const managerFeeBPS = 1_000; - const arrakisFeeBPS = 250; const result = await mockFArrakisV2.subtractAdminFees( rawFee0, rawFee1, - managerFeeBPS, - arrakisFeeBPS + managerFeeBPS ); expect(result.fee0.toNumber()).to.be.eq( - 87_500, + 90_000, "Admin fee 0 calculation not ok." ); expect(result.fee1.toNumber()).to.be.eq( - 875_000, + 900_000, "Admin fee 0 calculation not ok." ); });