Skip to content

Commit

Permalink
totalAssets returns MIN_TOTAL_SUPPLY if fees > available assets
Browse files Browse the repository at this point in the history
setPrice enforcement of crossPrice also applies to Owner to ensure no asset losses
remove old Price cross check as that's replaced by the crossPrice check
  • Loading branch information
naddison36 committed Oct 9, 2024
1 parent db0b95a commit fecb2c0
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 50 deletions.
30 changes: 12 additions & 18 deletions src/contracts/AbstractARM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
3 changes: 2 additions & 1 deletion test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
43 changes: 15 additions & 28 deletions test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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);
Expand Down Expand Up @@ -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
//////////////////////////////////////////////////////
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

0 comments on commit fecb2c0

Please sign in to comment.