diff --git a/contracts/ArrakisV2.sol b/contracts/ArrakisV2.sol index 111d015..a47d5d6 100644 --- a/contracts/ArrakisV2.sol +++ b/contracts/ArrakisV2.sol @@ -28,10 +28,11 @@ import { import {Position} from "./libraries/Position.sol"; import {Pool} from "./libraries/Pool.sol"; import {Underlying as UnderlyingHelper} from "./libraries/Underlying.sol"; +import {hundredPercent} from "./constants/CArrakisV2.sol"; -/// @title Arrakis vault version 2 -/// @notice Smart contract managing liquidity providing strategy using -/// multiple LP positions on multiple uniswap v3 pools. +/// @title ArrakisV2 LP vault version 2 +/// @notice Smart contract managing liquidity providing strategy for a given token pair +/// using multiple Uniswap V3 LP positions on multiple fee tiers. /// @author Arrakis Finance /// @dev DO NOT ADD STATE VARIABLES - APPEND THEM TO ArrakisV2Storage contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { @@ -50,15 +51,11 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { _uniswapV3CallBack(amount0Owed_, amount1Owed_); } - /// @notice mint Arrakis V2 tokens by participating to the - /// vault strategy - /// @param mintAmount_ represent the amount of Arrakis V2 tokens - /// we want to mint. - /// @param receiver_ address that will receive Arrakis V2 tokens. - /// @return amount0 amount of token0 needed to mint mintAmount_ - /// vault tokens. - /// @return amount1 amount of token1 needed to mint mintAmount_ - /// vault tokens. + /// @notice mint Arrakis V2 shares by depositing underlying + /// @param mintAmount_ represent the amount of Arrakis V2 shares to mint. + /// @param receiver_ address that will receive Arrakis V2 shares. + /// @return amount0 amount of token0 needed to mint mintAmount_ of shares. + /// @return amount1 amount of token1 needed to mint mintAmount_ of shares. // solhint-disable-next-line function-max-lines function mint(uint256 mintAmount_, address receiver_) external @@ -71,8 +68,8 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { "R" ); address me = address(this); - uint256 totalSupply = totalSupply(); - bool isTotalSupplyGtZero = totalSupply > 0; + uint256 ts = totalSupply(); + bool isTotalSupplyGtZero = ts > 0; ( uint256 current0, uint256 current1, @@ -89,7 +86,7 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { }) ) : (init0, init1, 0, 0); - uint256 denominator = isTotalSupplyGtZero ? totalSupply : 1 ether; + uint256 denominator = isTotalSupplyGtZero ? ts : 1 ether; /// @dev current0 and current1 include fees and left over (but not manager balances) amount0 = FullMath.mulDivRoundingUp(mintAmount_, current0, denominator); @@ -132,22 +129,22 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { emit LogMint(receiver_, mintAmount_, amount0, amount1); } - /// @notice burn Arrakis V2 tokens functions - /// @param burns_ ranges where burns lps and collect tokens. - /// @param burnAmount_ amount of vault token to burn. - /// @param receiver_ address of tokens receiver. - /// @return amount0 amount of token0 associated to burnAmount of - /// vault tokens, and returned to receiver. - /// @return amount1 amount of token1 associated to burnAmount of - /// vault tokens, and returned to receiver. + /// @notice burn Arrakis V2 shares and withdraw underlying. + /// @param burns_ ranges to burn liquidity from and collect underlying. + /// @param burnAmount_ amount of vault shares to burn. + /// @param receiver_ address to receive underlying tokens withdrawn. + /// @return amount0 amount of token0 sent to receiver + /// @return amount1 amount of token1 sent to receiver // solhint-disable-next-line function-max-lines, code-complexity function burn( BurnLiquidity[] calldata burns_, uint256 burnAmount_, address receiver_ ) external nonReentrant returns (uint256 amount0, uint256 amount1) { - uint256 totalSupply = totalSupply(); - require(totalSupply > 0, "TS"); + require(burnAmount_ > 0, "BA"); + + uint256 ts = totalSupply(); + require(ts > 0, "TS"); UnderlyingOutput memory underlying; ( @@ -173,16 +170,8 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { { // the proportion of user balance. - amount0 = FullMath.mulDiv( - underlying.amount0, - burnAmount_, - totalSupply - ); - amount1 = FullMath.mulDiv( - underlying.amount1, - burnAmount_, - totalSupply - ); + amount0 = FullMath.mulDiv(underlying.amount0, burnAmount_, ts); + amount1 = FullMath.mulDiv(underlying.amount1, burnAmount_, ts); } if ( @@ -209,8 +198,15 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { Withdraw memory total; { - for (uint256 i = 0; i < burns_.length; i++) { + for (uint256 i; i < burns_.length; i++) { require(burns_[i].liquidity != 0, "LZ"); + { + (bool exist, ) = Position.rangeExist( + ranges, + burns_[i].range + ); + require(exist, "RRNE"); + } Withdraw memory withdraw = _withdraw( IUniswapV3Pool( @@ -250,13 +246,13 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { require( (leftover0 <= underlying.leftOver0) || ((leftover0 - underlying.leftOver0) <= - FullMath.mulDiv(total.burn0, _burnBuffer, 10000)), + FullMath.mulDiv(total.burn0, _burnBuffer, hundredPercent)), "L0" ); require( (leftover1 <= underlying.leftOver1) || ((leftover1 - underlying.leftOver1) <= - FullMath.mulDiv(total.burn1, _burnBuffer, 10000)), + FullMath.mulDiv(total.burn1, _burnBuffer, hundredPercent)), "L1" ); @@ -267,14 +263,12 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { emit LogBurn(receiver_, burnAmount_, amount0, amount1); } - /// @notice rebalance vault position on uniswap v3 - /// @param rangesToAdd_ list of ranges whiteslisted to provide - /// liquidity on these ranges. + /// @notice rebalance ArrakisV2 vault's UniswapV3 positions + /// @param rangesToAdd_ list of new ranges to initialize (add to ranges array). /// @param rebalanceParams_ rebalance params, containing ranges where /// we need to collect tokens and ranges where we need to mint tokens. /// Also contain swap payload to changes token0/token1 proportion. - /// @param rangesToRemove_ list of ranges to unwhiteslist, will check if - /// they still contain liquidity on these ranges. + /// @param rangesToRemove_ list of ranges to remove from ranges array (only when liquidity==0) /// @dev only Manager contract can call this contract. // solhint-disable-next-line function-max-lines function rebalance( @@ -282,7 +276,7 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { Rebalance calldata rebalanceParams_, Range[] calldata rangesToRemove_ ) external onlyManager { - for (uint256 i = 0; i < rangesToAdd_.length; i++) { + for (uint256 i; i < rangesToAdd_.length; i++) { (bool exist, ) = Position.rangeExist(ranges, rangesToAdd_[i]); require(!exist, "NRRE"); // check that the pool exist on Uniswap V3. @@ -300,7 +294,7 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { _rebalance(rebalanceParams_); require(token0.balanceOf(address(this)) >= managerBalance0, "MB0"); require(token1.balanceOf(address(this)) >= managerBalance1, "MB1"); - for (uint256 i = 0; i < rangesToRemove_.length; i++) { + for (uint256 i; i < rangesToRemove_.length; i++) { (bool exist, uint256 index) = Position.rangeExist( ranges, rangesToRemove_[i] @@ -315,8 +309,6 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { rangesToRemove_[i] ); - delete ranges[index]; - for (uint256 j = index; j < ranges.length - 1; j++) { ranges[j] = ranges[j + 1]; } @@ -324,8 +316,8 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { } } - /// @notice will send managers fees to manager - /// @dev anyone can call this function. + /// @notice will send manager fees to manager + /// @dev anyone can call this function function withdrawManagerBalance() external { uint256 amount0 = managerBalance0; uint256 amount1 = managerBalance1; @@ -352,10 +344,13 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { // Burns. uint256 aggregator0 = 0; uint256 aggregator1 = 0; - for (uint256 i = 0; i < rebalanceParams_.removes.length; i++) { - address poolAddr = factory.getPool( - address(token0), - address(token1), + IUniswapV3Factory mFactory = factory; + address mToken0Addr = address(token0); + address mToken1Addr = address(token1); + for (uint256 i; i < rebalanceParams_.removes.length; i++) { + address poolAddr = mFactory.getPool( + mToken0Addr, + mToken1Addr, rebalanceParams_.removes[i].range.feeTier ); IUniswapV3Pool pool = IUniswapV3Pool(poolAddr); @@ -432,11 +427,11 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { // Mints. aggregator0 = 0; aggregator1 = 0; - for (uint256 i = 0; i < rebalanceParams_.deposits.length; i++) { + for (uint256 i; i < rebalanceParams_.deposits.length; i++) { IUniswapV3Pool pool = IUniswapV3Pool( - factory.getPool( - address(token0), - address(token1), + mFactory.getPool( + mToken0Addr, + mToken1Addr, rebalanceParams_.deposits[i].range.feeTier ) ); @@ -488,7 +483,7 @@ contract ArrakisV2 is IUniswapV3MintCallback, ArrakisV2Storage { } function _applyFees(uint256 fee0_, uint256 fee1_) internal { - managerBalance0 += (fee0_ * managerFeeBPS) / 10000; - managerBalance1 += (fee1_ * managerFeeBPS) / 10000; + managerBalance0 += (fee0_ * managerFeeBPS) / hundredPercent; + managerBalance1 += (fee1_ * managerFeeBPS) / hundredPercent; } } diff --git a/contracts/ArrakisV2Beacon.sol b/contracts/ArrakisV2Beacon.sol index e914859..8206dae 100644 --- a/contracts/ArrakisV2Beacon.sol +++ b/contracts/ArrakisV2Beacon.sol @@ -5,12 +5,17 @@ import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; -/// @title ArrakisV2Beacon sm containing vault implementation. +/// @title ArrakisV2Beacon stores vault implementation. contract ArrakisV2Beacon is UpgradeableBeacon { // solhint-disable-next-line no-empty-blocks constructor(address implementation_, address owner_) UpgradeableBeacon(implementation_) { + require( + implementation_ != address(0), + "implementation is address zero" + ); + require(owner_ != address(0), "owner is address zero"); _transferOwnership(owner_); } } diff --git a/contracts/ArrakisV2Factory.sol b/contracts/ArrakisV2Factory.sol index f551022..1525f68 100644 --- a/contracts/ArrakisV2Factory.sol +++ b/contracts/ArrakisV2Factory.sol @@ -28,12 +28,9 @@ contract ArrakisV2Factory is ArrakisV2FactoryStorage { ArrakisV2FactoryStorage(arrakisV2Beacon_) {} // solhint-disable-line no-empty-blocks - /// @notice will deploy an instance of Vault using Beacon or - /// transparentProxy - /// @param params_ contains all data needed to create an instance of - /// ArrakisV2 vault. - /// @param isBeacon_ boolean, if true the instance will be a beacon proxy - /// or a transparent proxy. + /// @notice Deploys an instance of Vault using BeaconProxy or TransparentProxy. + /// @param params_ contains all data needed to create an instance of ArrakisV2 vault. + /// @param isBeacon_ boolean, if true the instance will be BeaconProxy or TransparentProxy. /// @return vault the address of the Arrakis V2 vault instance created. function deployVault(InitializePayload calldata params_, bool isBeacon_) external @@ -46,8 +43,7 @@ contract ArrakisV2Factory is ArrakisV2FactoryStorage { // #region public external view functions. - /// @notice get Arrakis V2 vault token name for - /// two corresponding tokens. + /// @notice get Arrakis V2 standard token name for two corresponding tokens. /// @param token0_ address of the first token. /// @param token1_ address of the second token. /// @return name name of the arrakis V2 vault. @@ -61,24 +57,37 @@ contract ArrakisV2Factory is ArrakisV2FactoryStorage { return _append("Arrakis Vault V2 ", symbol0, "/", symbol1); } - /// @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(); - } - /// @notice get a list of vaults created by this factory + /// @param startIndex_ start index + /// @param endIndex_ end index /// @return vaults list of all created vaults. - function vaults() public view returns (address[] memory) { - uint256 length = numVaults(); - address[] memory vs = new address[](length); - for (uint256 i = 0; i < length; i++) { + function vaults(uint256 startIndex_, uint256 endIndex_) + external + view + returns (address[] memory) + { + require( + startIndex_ < endIndex_, + "start index is equal or greater than end index." + ); + require( + endIndex_ <= numVaults(), + "end index is greater than vaults array length" + ); + address[] memory vs = new address[](endIndex_ - startIndex_); + for (uint256 i = startIndex_; i < endIndex_; i++) { vs[i] = _vaults.at(i); } return vs; } + /// @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(); + } + // #endregion public external view functions. // #region internal functions diff --git a/contracts/ArrakisV2Helper.sol b/contracts/ArrakisV2Helper.sol index 63e3fec..c9064db 100644 --- a/contracts/ArrakisV2Helper.sol +++ b/contracts/ArrakisV2Helper.sol @@ -19,6 +19,7 @@ import { } from "./structs/SArrakisV2.sol"; import {Amount} from "./structs/SArrakisV2Helper.sol"; +/// @title ArrakisV2Helper helpers for querying common info about ArrakisV2 vaults contract ArrakisV2Helper is IArrakisV2Helper { IUniswapV3Factory public immutable factory; @@ -26,10 +27,10 @@ contract ArrakisV2Helper is IArrakisV2Helper { factory = factory_; } - /// @notice get underlying, fees and left over separatly. + /// @notice get total underlying, also returns uncollected fees and leftover separatly. /// @param vault_ Arrakis V2 vault to get underlying info about. /// @return underlying struct containing underlying amounts of - /// token0 and token1, fees of token0 and token1, finally left over + /// token0 and token1, fees of token0 and token1, finally leftover /// on vault of token0 and token1. function totalUnderlyingWithFeesAndLeftOver(IArrakisV2 vault_) external @@ -38,7 +39,7 @@ contract ArrakisV2Helper is IArrakisV2Helper { { UnderlyingPayload memory underlyingPayload = UnderlyingPayload({ ranges: vault_.getRanges(), - factory: vault_.factory(), + factory: factory, token0: address(vault_.token0()), token1: address(vault_.token1()), self: address(vault_) @@ -59,7 +60,7 @@ contract ArrakisV2Helper is IArrakisV2Helper { IArrakisV2(underlyingPayload.self).managerBalance1(); } - /// @notice get underlying, fees. + /// @notice get total underlying, also returns uncollected fees separately. /// @param vault_ Arrakis V2 vault to get underlying info about. /// @return amount0 amount of underlying of token 0. /// @return amount1 amount of underlying of token 1. @@ -77,7 +78,7 @@ contract ArrakisV2Helper is IArrakisV2Helper { { UnderlyingPayload memory underlyingPayload = UnderlyingPayload({ ranges: vault_.getRanges(), - factory: vault_.factory(), + factory: factory, token0: address(vault_.token0()), token1: address(vault_.token1()), self: address(vault_) @@ -98,7 +99,7 @@ contract ArrakisV2Helper is IArrakisV2Helper { { UnderlyingPayload memory underlyingPayload = UnderlyingPayload({ ranges: vault_.getRanges(), - factory: vault_.factory(), + factory: factory, token0: address(vault_.token0()), token1: address(vault_.token1()), self: address(vault_) diff --git a/contracts/ArrakisV2Resolver.sol b/contracts/ArrakisV2Resolver.sol index e5ca06f..6309471 100644 --- a/contracts/ArrakisV2Resolver.sol +++ b/contracts/ArrakisV2Resolver.sol @@ -10,9 +10,6 @@ import {IArrakisV2} from "./interfaces/IArrakisV2.sol"; import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -import { - ISwapRouter -} from "@arrakisfi/v3-lib-0.8/contracts/interfaces/ISwapRouter.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Underlying as UnderlyingHelper} from "./libraries/Underlying.sol"; import {Position as PositionHelper} from "./libraries/Position.sol"; @@ -32,22 +29,16 @@ import { SwapPayload } from "./structs/SArrakisV2.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {hundredPercent} from "./constants/CArrakisV2.sol"; -/// @title Smart contract for resolver/ computing payload -/// that need to be sent to Arrakis V2 vault. +/// @title ArrakisV2Resolver helpers that resolve / compute payloads for ArrakisV2 calls contract ArrakisV2Resolver is IArrakisV2Resolver { IUniswapV3Factory public immutable factory; IArrakisV2Helper public immutable helper; - ISwapRouter public immutable swapRouter; - constructor( - IUniswapV3Factory factory_, - IArrakisV2Helper helper_, - ISwapRouter swapRouter_ - ) { + constructor(IUniswapV3Factory factory_, IArrakisV2Helper helper_) { factory = factory_; helper = helper_; - swapRouter = swapRouter_; } /// @notice Standard rebalance (without swapping) @@ -77,11 +68,11 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { ); uint256 numberOfPosLiq; - for (uint256 i = 0; i < ranges.length; i++) { + for (uint256 i; i < ranges.length; i++) { uint128 liquidity; { (liquidity, , , , ) = IUniswapV3Pool( - vaultV2_.factory().getPool( + factory.getPool( token0Addr, token1Addr, ranges[i].feeTier @@ -106,7 +97,7 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { rebalanceParams.removes = new PositionLiquidity[](numberOfPosLiq); uint256 j; - for (uint256 i = 0; i < pl.length; i++) { + for (uint256 i; i < pl.length; i++) { if (pl[i].liquidity > 0) { rebalanceParams.removes[j] = pl[i]; j++; @@ -120,10 +111,10 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { rangeWeights_.length ); - for (uint256 i = 0; i < rangeWeights_.length; i++) { + for (uint256 i; i < rangeWeights_.length; i++) { RangeWeight memory rangeWeight = rangeWeights_[i]; (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool( - vaultV2_.factory().getPool( + factory.getPool( token0Addr, token1Addr, rangeWeight.range.feeTier @@ -134,8 +125,8 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { sqrtPriceX96, TickMath.getSqrtRatioAtTick(rangeWeight.range.lowerTick), TickMath.getSqrtRatioAtTick(rangeWeight.range.upperTick), - FullMath.mulDiv(amount0, rangeWeight.weight, 10000), - FullMath.mulDiv(amount1, rangeWeight.weight, 10000) + FullMath.mulDiv(amount0, rangeWeight.weight, hundredPercent), + FullMath.mulDiv(amount1, rangeWeight.weight, hundredPercent) ); rebalanceParams.deposits[i] = PositionLiquidity({ @@ -145,7 +136,7 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { } } - /// @notice Standard Burn proportional burn. + /// @notice Standard Burn (proportional burn from all ranges). /// @param amountToBurn_ amount of Arrakis V2 token to burn. /// @param vaultV2_ Arrakis V2 vault. /// @return burns list of ranges and liquidities to burn. @@ -206,11 +197,11 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { burns = new BurnLiquidity[](ranges.length); - for (uint256 i = 0; i < ranges.length; i++) { + for (uint256 i; i < ranges.length; i++) { uint128 liquidity; { (liquidity, , , , ) = IUniswapV3Pool( - vaultV2_.factory().getPool( + factory.getPool( address(vaultV2_.token0()), address(vaultV2_.token1()), ranges[i].feeTier @@ -239,12 +230,9 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { /// @param vaultV2_ Arrakis V2 vault. /// @param amount0Max_ max amount of token 0. /// @param amount1Max_ max amount of token 1. - /// @return amount0 of token 0 need to be approved to Arrakis V2 vault - /// before calling mint function of Arrakis V2 - /// @return amount1 of token 1 need to be approved to Arrakis V2 vault - /// before calling mint function of Arrakis V2 - /// @return mintAmount amount to be sent as param to mint function of - /// Arrakis V2 vault. + /// @return amount0 of token 0 expected to be deposited. + /// @return amount1 of token 1 expected to be deposited. + /// @return mintAmount amount f shares expected to be minted. // solhint-disable-next-line function-max-lines function getMintAmounts( IArrakisV2 vaultV2_, @@ -282,14 +270,14 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { ); } - /// @notice return amount0 and amount1 of token0 and token1 - /// for a correspond amount of liquidity. + /// @notice Exposes Uniswap's getAmountsForLiquidity helper function, + /// returns amount0 and amount1 for a given amount of liquidity. function getAmountsForLiquidity( int24 currentTick_, int24 lowerTick_, int24 upperTick_, uint128 liquidity_ - ) public pure returns (uint256 amount0, uint256 amount1) { + ) external pure returns (uint256 amount0, uint256 amount1) { return LiquidityAmounts.getAmountsForLiquidity( TickMath.getSqrtRatioAtTick(currentTick_), @@ -310,7 +298,7 @@ contract ArrakisV2Resolver is IArrakisV2Resolver { totalWeight += rangeWeights_[i].weight; } - require(totalWeight <= 10000, "total weight"); + require(totalWeight <= hundredPercent, "total weight"); } // #endregion view internal functions. diff --git a/contracts/__mocks__/ManagerProxyMock.sol b/contracts/__mocks__/ManagerProxyMock.sol index 17b7271..bb0333e 100644 --- a/contracts/__mocks__/ManagerProxyMock.sol +++ b/contracts/__mocks__/ManagerProxyMock.sol @@ -3,9 +3,8 @@ pragma solidity 0.8.13; import {IArrakisV2} from "./../interfaces/IArrakisV2.sol"; import {Range, Rebalance} from "./../structs/SArrakisV2.sol"; -import {IManager} from "../interfaces/IManager.sol"; -contract ManagerProxyMock is IManager { +contract ManagerProxyMock { // solhint-disable-next-line const-name-snakecase uint16 public constant managerFeeBPS = 100; diff --git a/contracts/abstract/ArrakisV2FactoryStorage.sol b/contracts/abstract/ArrakisV2FactoryStorage.sol index 41e324f..e26819e 100644 --- a/contracts/abstract/ArrakisV2FactoryStorage.sol +++ b/contracts/abstract/ArrakisV2FactoryStorage.sol @@ -36,6 +36,7 @@ abstract contract ArrakisV2FactoryStorage is // #endregion constructor. function initialize(address _owner_) external initializer { + require(_owner_ != address(0), "owner is address zero"); _transferOwnership(_owner_); emit InitFactory(_owner_); } @@ -44,11 +45,12 @@ abstract contract ArrakisV2FactoryStorage is /// @notice upgrade vaults instance using transparent proxy /// with the current implementation /// @param vaults_ the list of vault. + /// @dev only callable by owner function upgradeVaults(address[] memory vaults_) external onlyOwner { + address implementation = arrakisV2Beacon.implementation(); + require(implementation != address(0), "implementation is address zero"); for (uint256 i = 0; i < vaults_.length; i++) { - ITransparentUpgradeableProxy(vaults_[i]).upgradeTo( - arrakisV2Beacon.implementation() - ); + ITransparentUpgradeableProxy(vaults_[i]).upgradeTo(implementation); } } @@ -56,6 +58,7 @@ abstract contract ArrakisV2FactoryStorage is /// with the current implementation and call the instance /// @param vaults_ the list of vault. /// @param datas_ payloads of instances call. + /// @dev only callable by owner function upgradeVaultsAndCall( address[] memory vaults_, bytes[] calldata datas_ @@ -71,6 +74,7 @@ abstract contract ArrakisV2FactoryStorage is /// @notice make the vault immutable /// @param vaults_ the list of vault. + /// @dev only callable by owner function makeVaultsImmutable(address[] memory vaults_) external onlyOwner { for (uint256 i = 0; i < vaults_.length; i++) { ITransparentUpgradeableProxy(vaults_[i]).changeAdmin(address(1)); @@ -84,7 +88,7 @@ abstract contract ArrakisV2FactoryStorage is /// @notice get vault instance admin /// @param proxy instance of Arrakis V2. /// @return admin address of Arrakis V2 instance admin. - function getProxyAdmin(address proxy) public view returns (address) { + function getProxyAdmin(address proxy) external view returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("admin()")) == 0xf851a440 (bool success, bytes memory returndata) = proxy.staticcall( @@ -98,7 +102,7 @@ abstract contract ArrakisV2FactoryStorage is /// @param proxy instance of Arrakis V2. /// @return implementation address of Arrakis V2 implementation. function getProxyImplementation(address proxy) - public + external view returns (address) { diff --git a/contracts/abstract/ArrakisV2Storage.sol b/contracts/abstract/ArrakisV2Storage.sol index c806db9..15e886f 100644 --- a/contracts/abstract/ArrakisV2Storage.sol +++ b/contracts/abstract/ArrakisV2Storage.sol @@ -8,7 +8,6 @@ import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {IManager} from "../interfaces/IManager.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -22,8 +21,9 @@ import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {Range, Rebalance, InitializePayload} from "../structs/SArrakisV2.sol"; +import {fiftyPercent} from "../constants/CArrakisV2.sol"; -/// @title Arrakis V2 Storage Smart Contract +/// @title ArrakisV2Storage base contract containing all ArrakisV2 storage variables. // solhint-disable-next-line max-states-count abstract contract ArrakisV2Storage is OwnableUpgradeable, @@ -65,20 +65,24 @@ abstract contract ArrakisV2Storage is // #region events event LogMint( - address receiver, + address indexed receiver, uint256 mintAmount, uint256 amount0In, uint256 amount1In ); event LogBurn( - address receiver, + address indexed receiver, uint256 burnAmount, uint256 amount0Out, uint256 amount1Out ); - event LPBurned(address user, uint256 burnAmount0, uint256 burnAmount1); + event LPBurned( + address indexed user, + uint256 burnAmount0, + uint256 burnAmount1 + ); event LogRebalance(Rebalance rebalanceParams); @@ -126,7 +130,9 @@ abstract contract ArrakisV2Storage is require(params_.feeTiers.length > 0, "NFT"); require(params_.token0 != address(0), "T0"); require(params_.token0 < params_.token1, "WTO"); - + require(params_.owner != address(0), "OAZ"); + require(params_.manager != address(0), "MAZ"); + require(params_.burnBuffer <= fiftyPercent, "MTMB"); require(params_.init0 > 0 || params_.init1 > 0, "I"); __ERC20_init(name_, symbol_); @@ -143,19 +149,21 @@ abstract contract ArrakisV2Storage is manager = params_.manager; _burnBuffer = params_.burnBuffer; + init0 = params_.init0; + init1 = params_.init1; emit LogAddPools(params_.feeTiers); - emit LogSetInits(init0 = params_.init0, init1 = params_.init1); + emit LogSetInits(params_.init0, params_.init1); emit LogSetManager(params_.manager); emit LogSetBurnBuffer(params_.burnBuffer); } // #region setter functions - /// @notice set initials virtual allocation of token0 and token1 + /// @notice set initial virtual allocation of token0 and token1 /// @param init0_ initial virtual allocation of token 0. /// @param init1_ initial virtual allocation of token 1. - /// @dev only be callable by restrictedMinter or by default by the owner. + /// @dev only callable by restrictedMint or by owner if restrictedMint is unset. function setInits(uint256 init0_, uint256 init1_) external { require(init0_ > 0 || init1_ > 0, "I"); require(totalSupply() == 0, "TS"); @@ -168,15 +176,15 @@ abstract contract ArrakisV2Storage is /// @notice whitelist pools /// @param feeTiers_ list of fee tiers associated to pools to whitelist. - /// @dev only be callable by owner. + /// @dev only callable by owner. function addPools(uint24[] calldata feeTiers_) external onlyOwner { _addPools(feeTiers_, address(token0), address(token1)); emit LogAddPools(feeTiers_); } /// @notice unwhitelist pools - /// @param pools_ list of pool to remove from whitelist. - /// @dev only be callable by owner. + /// @param pools_ list of pools to remove from whitelist. + /// @dev only callable by owner. function removePools(address[] calldata pools_) external onlyOwner { for (uint256 i = 0; i < pools_.length; i++) { require(_pools.contains(pools_[i]), "NP"); @@ -187,8 +195,8 @@ abstract contract ArrakisV2Storage is } /// @notice whitelist routers - /// @param routers_ list of routers addresses to whitelist. - /// @dev only be callable by owner. + /// @param routers_ list of router addresses to whitelist. + /// @dev only callable by owner. function whitelistRouters(address[] calldata routers_) external onlyOwner { _whitelistRouters(routers_); emit LogWhitelistRouters(routers_); @@ -196,7 +204,7 @@ abstract contract ArrakisV2Storage is /// @notice blacklist routers /// @param routers_ list of routers addresses to blacklist. - /// @dev only be callable by owner. + /// @dev only callable by owner. function blacklistRouters(address[] calldata routers_) external onlyOwner { for (uint256 i = 0; i < routers_.length; i++) { require(_routers.contains(routers_[i]), "RW"); @@ -208,38 +216,42 @@ abstract contract ArrakisV2Storage is /// @notice set manager /// @param manager_ manager address. - /// @dev only be callable by owner. + /// @dev only callable by owner. function setManager(address manager_) external onlyOwner { - emit LogSetManager(manager = manager_); + manager = manager_; + emit LogSetManager(manager_); } /// @notice set manager fee bps - /// @param managerFeeBPS_ manager fee. - /// @dev only be callable by manager. + /// @param managerFeeBPS_ manager fee in basis points. + /// @dev only callable by manager. function setManagerFeeBPS(uint16 managerFeeBPS_) external onlyManager { - emit LogSetManagerFeeBPS(managerFeeBPS = managerFeeBPS_); + managerFeeBPS = managerFeeBPS_; + emit LogSetManagerFeeBPS(managerFeeBPS_); } /// @notice set restricted minter /// @param minter_ address of restricted minter. - /// @dev only be callable by owner. + /// @dev only callable by owner. function setRestrictedMint(address minter_) external onlyOwner { - emit LogRestrictedMint(restrictedMint = minter_); + restrictedMint = minter_; + emit LogRestrictedMint(minter_); } /// @notice set burn buffer /// @param newBurnBuffer_ buffer value. - /// @dev only be callable by owner. + /// @dev only callable by owner. function setBurnBuffer(uint16 newBurnBuffer_) external onlyOwner { - require(newBurnBuffer_ < 5000, "MTMB"); - emit LogSetBurnBuffer(_burnBuffer = newBurnBuffer_); + require(newBurnBuffer_ <= fiftyPercent, "MTMB"); + _burnBuffer = newBurnBuffer_; + emit LogSetBurnBuffer(newBurnBuffer_); } // #endregion setter functions // #region getter functions - /// @notice get list of ranges where liquidity are put + /// @notice get full list of ranges, guaranteed to contain all active vault LP Positions. /// @return ranges list of ranges function getRanges() external view returns (Range[] memory) { return ranges; diff --git a/contracts/constants/CArrakisV2.sol b/contracts/constants/CArrakisV2.sol new file mode 100644 index 0000000..4ee0f78 --- /dev/null +++ b/contracts/constants/CArrakisV2.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.13; + +uint16 constant hundredPercent = 10_000; +uint16 constant fiftyPercent = 5_000; diff --git a/contracts/interfaces/IArrakisV2.sol b/contracts/interfaces/IArrakisV2.sol index 54ccae3..2eb1917 100644 --- a/contracts/interfaces/IArrakisV2.sol +++ b/contracts/interfaces/IArrakisV2.sol @@ -5,7 +5,6 @@ import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IManager} from "./IManager.sol"; import {InitializePayload} from "../structs/SArrakisV2.sol"; import {BurnLiquidity, Range, Rebalance} from "../structs/SArrakisV2.sol"; diff --git a/contracts/interfaces/IArrakisV2Factory.sol b/contracts/interfaces/IArrakisV2Factory.sol index 088d99a..3ca2b7c 100644 --- a/contracts/interfaces/IArrakisV2Factory.sol +++ b/contracts/interfaces/IArrakisV2Factory.sol @@ -19,7 +19,10 @@ interface IArrakisV2Factory { function numVaults() external view returns (uint256); - function vaults() external view returns (address[] memory); + function vaults(uint256 startIndex_, uint256 endIndex_) + external + view + returns (address[] memory); function getProxyAdmin(address proxy) external view returns (address); diff --git a/contracts/interfaces/IManager.sol b/contracts/interfaces/IManager.sol deleted file mode 100644 index 75f13b5..0000000 --- a/contracts/interfaces/IManager.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.13; - -interface IManager { - function managerFeeBPS() external view returns (uint16); -} diff --git a/contracts/interfaces/ISwapRouter.sol b/contracts/interfaces/ISwapRouter.sol new file mode 100644 index 0000000..98f6496 --- /dev/null +++ b/contracts/interfaces/ISwapRouter.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.13; + +import { + ISwapRouter +} from "@arrakisfi/v3-lib-0.8/contracts/interfaces/ISwapRouter.sol"; diff --git a/contracts/libraries/Position.sol b/contracts/libraries/Position.sol index f4db3fc..71fb1a8 100644 --- a/contracts/libraries/Position.sol +++ b/contracts/libraries/Position.sol @@ -37,7 +37,7 @@ library Position { pure returns (bool ok, uint256 index) { - for (uint256 i = 0; i < currentRanges_.length; i++) { + for (uint256 i; i < currentRanges_.length; i++) { ok = range_.lowerTick == currentRanges_[i].lowerTick && range_.upperTick == currentRanges_[i].upperTick && diff --git a/contracts/libraries/Underlying.sol b/contracts/libraries/Underlying.sol index aea3fd1..09d64e9 100644 --- a/contracts/libraries/Underlying.sol +++ b/contracts/libraries/Underlying.sol @@ -34,7 +34,7 @@ library Underlying { uint256 fee1 ) { - for (uint256 i = 0; i < underlyingPayload_.ranges.length; i++) { + for (uint256 i; i < underlyingPayload_.ranges.length; i++) { { IUniswapV3Pool pool = IUniswapV3Pool( underlyingPayload_.factory.getPool( diff --git a/deploy/ArrakisV2Resolver.deploy.ts b/deploy/ArrakisV2Resolver.deploy.ts index 7b8bc8a..4267619 100644 --- a/deploy/ArrakisV2Resolver.deploy.ts +++ b/deploy/ArrakisV2Resolver.deploy.ts @@ -25,7 +25,6 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { args: [ addresses.UniswapV3Factory, (await ethers.getContract("ArrakisV2Helper")).address, - addresses.SwapRouter, ], libraries: { Position: (await ethers.getContract("Position")).address, diff --git a/hardhat.config.ts b/hardhat.config.ts index 6631574..25e2988 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: 899 }, + optimizer: { enabled: true, runs: 866 }, }, }, ], diff --git a/package.json b/package.json index fa2fa8e..59a98bc 100644 --- a/package.json +++ b/package.json @@ -95,5 +95,5 @@ "homepage": "https://github.com/ArrakisFinance/v2-core#readme", "description": "Arrakis V2 Automated Market Maker", "license": "MIT", - "version": "0.1.2" + "version": "0.1.3" } diff --git a/test/unit_tests/ArrakisV2Factory.test.ts b/test/unit_tests/ArrakisV2Factory.test.ts index 222398b..2e09ec9 100644 --- a/test/unit_tests/ArrakisV2Factory.test.ts +++ b/test/unit_tests/ArrakisV2Factory.test.ts @@ -167,10 +167,6 @@ describe("Factory function unit test", function () { expect(await arrakisV2Factory.numVaults()).to.be.eq(1); }); - it("#4: unit test get vaults", async () => { - expect((await arrakisV2Factory.vaults()).length).to.be.eq(0); - }); - it("#5: unit test get vaults", async () => { const slot0 = await uniswapV3Pool.slot0(); const tickSpacing = await uniswapV3Pool.tickSpacing(); @@ -201,7 +197,10 @@ describe("Factory function unit test", function () { true ); - expect((await arrakisV2Factory.vaults()).length).to.be.eq(1); + expect( + (await arrakisV2Factory.vaults(0, await arrakisV2Factory.numVaults())) + .length + ).to.be.eq(1); }); it("#6: get implementation", async () => {