diff --git a/src/contracts/AbstractARM.sol b/src/contracts/AbstractARM.sol index d6ca721..245613a 100644 --- a/src/contracts/AbstractARM.sol +++ b/src/contracts/AbstractARM.sol @@ -372,23 +372,14 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { * From the Trader's perspective, this is the buy price. */ function setPrices(uint256 buyT1, uint256 sellT1) external onlyOperatorOrOwner { - // Limit funds and loss when called by the Operator - if (msg.sender == operator) { - require(sellT1 >= crossPrice, "ARM: sell price too low"); - require(buyT1 < crossPrice, "ARM: buy price too high"); - } - _setTraderates( - PRICE_SCALE * PRICE_SCALE / sellT1, // base (t0) -> token (t1) - buyT1 // token (t1) -> base (t0) - ); - } + // Ensure buy price is always below past sell prices + require(sellT1 >= crossPrice, "ARM: sell price too low"); + require(buyT1 < crossPrice, "ARM: buy price too high"); - function _setTraderates(uint256 _baseToTokenRate, uint256 _tokenToBaseRate) internal { - require((PRICE_SCALE * PRICE_SCALE / (_baseToTokenRate)) > _tokenToBaseRate, "ARM: Price cross"); - traderate0 = _baseToTokenRate; - traderate1 = _tokenToBaseRate; + traderate0 = PRICE_SCALE * PRICE_SCALE / sellT1; // base (t0) -> token (t1); + traderate1 = buyT1; // token (t1) -> base (t0) - emit TraderateChanged(_baseToTokenRate, _tokenToBaseRate); + emit TraderateChanged(traderate0, traderate1); } /** @@ -556,7 +547,9 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { function totalAssets() public view virtual returns (uint256) { (uint256 fees, uint256 newAvailableAssets) = _feesAccrued(); - if (fees > newAvailableAssets) return 0; + // total assets should only go up from the initial deposit amount that is burnt + // but in case of something unforeseen, return MIN_TOTAL_SUPPLY if fees is greater than the available assets + if (fees > newAvailableAssets) return MIN_TOTAL_SUPPLY; // Remove the performance fee from the available assets return newAvailableAssets - fees; @@ -587,12 +580,13 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { function _externalWithdrawQueue() internal view virtual returns (uint256 assets); /// @notice Calculates the amount of shares for a given amount of liquidity assets + /// @dev Total assets can't be zero. The lowest it can be is MIN_TOTAL_SUPPLY function convertToShares(uint256 assets) public view returns (uint256 shares) { - uint256 totalAssetsMem = totalAssets(); - shares = (totalAssetsMem == 0) ? assets : (assets * totalSupply()) / totalAssetsMem; + shares = assets * totalSupply() / totalAssets(); } /// @notice Calculates the amount of liquidity assets for a given amount of shares + /// @dev Total supply can't be zero. The lowest it can be is MIN_TOTAL_SUPPLY function convertToAssets(uint256 shares) public view returns (uint256 assets) { assets = (shares * totalAssets()) / totalSupply(); } diff --git a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol index bb41d4f..dff508b 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol @@ -302,7 +302,8 @@ contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ { depositInLidoARM(address(this), DEFAULT_AMOUNT) { // set stETH/WETH buy price to 1 - lidoARM.setPrices(1e36, 1e36 + 1); + lidoARM.setCrossPrice(1e36); + lidoARM.setPrices(1e36 - 1, 1e36); // User Swap stETH for 3/4 of WETH in the ARM deal(address(steth), address(this), DEFAULT_AMOUNT); diff --git a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol b/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol index 20964a3..540582b 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol @@ -89,25 +89,10 @@ contract Fork_Concrete_lidoARM_Setters_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- Set Prices - REVERTING TESTS ////////////////////////////////////////////////////// - function test_RevertWhen_SetPrices_Because_PriceCross() public asLidoARMOwner { - vm.expectRevert("ARM: Price cross"); - lidoARM.setPrices(90 * 1e33, 89 * 1e33); - - vm.expectRevert("ARM: Price cross"); - lidoARM.setPrices(72, 70); - - vm.expectRevert("ARM: Price cross"); - lidoARM.setPrices(1005 * 1e33, 1000 * 1e33); - - // Both set to 1.0 - vm.expectRevert("ARM: Price cross"); - lidoARM.setPrices(1e36, 1e36); - } - - function test_RevertWhen_SetPrices_Because_PriceRange() public asOperator { - // buy price 11 basis points higher than 1.0 + function test_RevertWhen_SetPrices_Because_PriceRange_Operator() public asOperator { + // buy price 1 basis points higher than 1.0 vm.expectRevert("ARM: buy price too high"); - lidoARM.setPrices(1.0011 * 1e36, 1.002 * 1e36); + lidoARM.setPrices(1.0001 * 1e36, 1.002 * 1e36); // sell price 11 basis points lower than 1.0 vm.expectRevert("ARM: sell price too low"); @@ -118,6 +103,16 @@ contract Fork_Concrete_lidoARM_Setters_Test_ is Fork_Shared_Test_ { lidoARM.setPrices(1e18, 1e18); } + function test_RevertWhen_SetPrices_Because_PriceRange_Owner() public asLidoARMOwner { + // buy price 1 basis points higher than 1.0 + vm.expectRevert("ARM: buy price too high"); + lidoARM.setPrices(1.0001 * 1e36, 1.002 * 1e36); + + // sell price 11 basis points lower than 1.0 + vm.expectRevert("ARM: sell price too low"); + lidoARM.setPrices(0.998 * 1e36, 0.9989 * 1e36); + } + function test_RevertWhen_SetPrices_Because_NotOwnerOrOperator() public asRandomAddress { vm.expectRevert("ARM: Only operator or owner can call this function."); lidoARM.setPrices(0, 0); @@ -151,14 +146,6 @@ contract Fork_Concrete_lidoARM_Setters_Test_ is Fork_Shared_Test_ { assertEq(lidoARM.traderate1(), 992 * 1e33); } - function test_SetPrices_Owner() public asLidoARMOwner { - // buy price 11 basis points higher than 1.0 - lidoARM.setPrices(10011e32, 10020e32); - - // sell price 11 basis points lower than 1.0 - lidoARM.setPrices(9980e32, 9989e32); - } - ////////////////////////////////////////////////////// /// --- Set Cross Price - REVERTING TESTS ////////////////////////////////////////////////////// @@ -203,8 +190,8 @@ contract Fork_Concrete_lidoARM_Setters_Test_ is Fork_Shared_Test_ { // 20 basis points lower than 1.0 vm.expectEmit({emitter: address(lidoARM)}); - emit AbstractARM.CrossPriceUpdated(0.9980e36); - lidoARM.setCrossPrice(0.9980e36); + emit AbstractARM.CrossPriceUpdated(0.998e36); + lidoARM.setCrossPrice(0.998e36); } function test_SetCrossPrice_With_StETH_PriceUp_Owner() public { diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol b/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol index d0f697a..93c9798 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol @@ -11,7 +11,7 @@ contract Fork_Concrete_LidoARM_SwapExactTokensForTokens_Test is Fork_Shared_Test /// --- CONSTANTS ////////////////////////////////////////////////////// uint256 private constant MIN_PRICE0 = 980e33; // 0.98 - uint256 private constant MAX_PRICE0 = 1_000e33; // 1.00 + uint256 private constant MAX_PRICE0 = 1_000e33 - 1; // just under 1.00 uint256 private constant MIN_PRICE1 = 1_000e33; // 1.00 uint256 private constant MAX_PRICE1 = 1_020e33; // 1.02 uint256 private constant MAX_WETH_RESERVE = 1_000_000 ether; // 1M WETH, no limit, but need to be consistent. @@ -306,6 +306,7 @@ contract Fork_Concrete_LidoARM_SwapExactTokensForTokens_Test is Fork_Shared_Test // Use random stETH/WETH sell price between 0.98 and 1, // the buy price doesn't matter as it is not used in this test. price = _bound(price, MIN_PRICE1, MAX_PRICE1); + lidoARM.setCrossPrice(1e36); lidoARM.setPrices(MIN_PRICE0, price); // Set random amount of stETH in the ARM @@ -369,6 +370,7 @@ contract Fork_Concrete_LidoARM_SwapExactTokensForTokens_Test is Fork_Shared_Test // Use random stETH/WETH buy price between MIN_PRICE0 and MAX_PRICE0, // the sell price doesn't matter as it is not used in this test. price = _bound(price, MIN_PRICE0, MAX_PRICE0); + lidoARM.setCrossPrice(1e36); lidoARM.setPrices(price, MAX_PRICE1); // Set random amount of WETH in the ARM diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol b/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol index 6541bb5..5029dd4 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol @@ -11,7 +11,7 @@ contract Fork_Concrete_LidoARM_SwapTokensForExactTokens_Test is Fork_Shared_Test /// --- CONSTANTS ////////////////////////////////////////////////////// uint256 private constant MIN_PRICE0 = 980e33; // 0.98 - uint256 private constant MAX_PRICE0 = 1_000e33; // 1.00 + uint256 private constant MAX_PRICE0 = 1_000e33 - 1; // just under 1.00 uint256 private constant MIN_PRICE1 = 1_000e33; // 1.00 uint256 private constant MAX_PRICE1 = 1_020e33; // 1.02 uint256 private constant MAX_WETH_RESERVE = 1_000_000 ether; // 1M WETH, no limit, but need to be consistent. diff --git a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol b/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol index 5ed9187..686c7af 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol @@ -146,6 +146,6 @@ contract Fork_Concrete_LidoARM_TotalAssets_Test_ is Fork_Shared_Test_ { simulateAssetGainInLidoARM(DEFAULT_AMOUNT * 2, address(weth), false) { // vm.expectRevert(stdError.arithmeticError); - assertEq(lidoARM.totalAssets(), 0); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY); } }