From f32bb9c692f4bbee2b9f54eee87329b8a41b514f Mon Sep 17 00:00:00 2001 From: Jun Kim <64379343+junkim012@users.noreply.github.com> Date: Mon, 6 May 2024 16:35:58 -0400 Subject: [PATCH 1/3] fix: revert to rebasing RewardToken --- src/IonPool.sol | 12 +- src/token/RewardToken.sol | 17 ++- src/vault/Vault.sol | 20 ++- test/fork/fuzz/lrt/EtherFiLibrary.t.sol | 2 +- test/integration/concrete/WeEthIonPool.t.sol | 11 +- test/invariant/IonPool/ActorManager.t.sol | 11 +- test/invariant/RewardToken/ActorManager.t.sol | 8 +- test/invariant/RewardToken/Handlers.t.sol | 8 +- test/unit/concrete/RewardToken.t.sol | 58 ++++---- test/unit/concrete/vault/Vault.t.sol | 140 ++++++++---------- test/unit/fuzz/IonPool.t.sol | 26 ++-- test/unit/fuzz/RewardToken.t.sol | 48 +++--- test/unit/fuzz/vault/Vault.t.sol | 2 +- 13 files changed, 177 insertions(+), 186 deletions(-) diff --git a/src/IonPool.sol b/src/IonPool.sol index 97e53c2a..233c48ca 100644 --- a/src/IonPool.sol +++ b/src/IonPool.sol @@ -364,7 +364,7 @@ contract IonPool is PausableUpgradeable, RewardToken { function _accrueInterest() internal returns (uint256 newTotalDebt) { IonPoolStorage storage $ = _getIonPoolStorage(); - uint256 totalEthSupply = getTotalUnderlyingClaimsUnaccrued(); + uint256 totalEthSupply = totalSupplyUnaccrued(); uint256 totalSupplyFactorIncrease; uint256 totalTreasuryMintAmount; @@ -419,7 +419,7 @@ contract IonPool is PausableUpgradeable, RewardToken { rateIncreases = new uint104[](ilksLength); timestampIncreases = new uint48[](ilksLength); - uint256 totalEthSupply = getTotalUnderlyingClaimsUnaccrued(); + uint256 totalEthSupply = totalSupplyUnaccrued(); for (uint8 i = 0; i < ilksLength;) { ( @@ -457,7 +457,7 @@ contract IonPool is PausableUpgradeable, RewardToken { returns (uint104 newRateIncrease, uint48 timestampIncrease) { (,, newRateIncrease,, timestampIncrease) = - _calculateRewardAndDebtDistributionForIlk(ilkIndex, getTotalUnderlyingClaimsUnaccrued()); + _calculateRewardAndDebtDistributionForIlk(ilkIndex, totalSupplyUnaccrued()); } function _calculateRewardAndDebtDistributionForIlk( @@ -512,7 +512,7 @@ contract IonPool is PausableUpgradeable, RewardToken { newDebtIncrease = _totalNormalizedDebt * newRateIncrease; // [RAD] // Income distribution - uint256 _normalizedTotalSupply = totalSupplyUnaccrued(); // [WAD] + uint256 _normalizedTotalSupply = normalizedTotalSupplyUnaccrued(); // [WAD] // If there is no supply, then nothing is being lent out. supplyFactorIncrease = _normalizedTotalSupply == 0 @@ -570,7 +570,7 @@ contract IonPool is PausableUpgradeable, RewardToken { uint256 _supplyCap = $.supplyCap; - if (getTotalUnderlyingClaims() > _supplyCap) revert DepositSurpassesSupplyCap(amount, _supplyCap); + if (totalSupply() > _supplyCap) revert DepositSurpassesSupplyCap(amount, _supplyCap); emit Supply(user, _msgSender(), amount, _supplyFactor, newTotalDebt); } @@ -954,7 +954,7 @@ contract IonPool is PausableUpgradeable, RewardToken { function getCurrentBorrowRate(uint8 ilkIndex) external view returns (uint256 borrowRate, uint256 reserveFactor) { IonPoolStorage storage $ = _getIonPoolStorage(); - uint256 totalEthSupply = getTotalUnderlyingClaimsUnaccrued(); + uint256 totalEthSupply = totalSupplyUnaccrued(); uint256 _totalNormalizedDebt = $.ilks[ilkIndex].totalNormalizedDebt; uint256 _rate = $.ilks[ilkIndex].rate; diff --git a/src/token/RewardToken.sol b/src/token/RewardToken.sol index 54137b43..d50aae14 100644 --- a/src/token/RewardToken.sol +++ b/src/token/RewardToken.sol @@ -453,7 +453,7 @@ abstract contract RewardToken is * @dev Current token balance * @param user to get balance of */ - function getUnderlyingClaimOf(address user) public view returns (uint256) { + function balanceOf(address user) public view returns (uint256) { RewardTokenStorage storage $ = _getRewardTokenStorage(); (uint256 totalSupplyFactorIncrease,,,,) = calculateRewardAndDebtDistribution(); @@ -465,7 +465,7 @@ abstract contract RewardToken is * @dev Accounting is done in normalized balances * @param user to get normalized balance of */ - function balanceOf(address user) external view returns (uint256) { + function normalizedBalanceOf(address user) external view returns (uint256) { RewardTokenStorage storage $ = _getRewardTokenStorage(); return $._normalizedBalances[user]; } @@ -494,7 +494,10 @@ abstract contract RewardToken is return $.treasury; } - function getTotalUnderlyingClaimsUnaccrued() public view returns (uint256) { + /** + * @dev Total claim of the underlying asset belonging to lenders not inclusive of the new interest to be accrued. + */ + function totalSupplyUnaccrued() public view returns (uint256) { RewardTokenStorage storage $ = _getRewardTokenStorage(); uint256 _normalizedTotalSupply = $.normalizedTotalSupply; @@ -507,9 +510,9 @@ abstract contract RewardToken is } /** - * @dev Total claim of the underlying asset belonging to lenders. + * @dev Total claim of the underlying asset belonging to lender inclusive of the new interest to be accrued. */ - function getTotalUnderlyingClaims() public view returns (uint256) { + function totalSupply() public view returns (uint256) { RewardTokenStorage storage $ = _getRewardTokenStorage(); uint256 _normalizedTotalSupply = $.normalizedTotalSupply; @@ -523,7 +526,7 @@ abstract contract RewardToken is return _normalizedTotalSupply.rayMulDown($.supplyFactor + totalSupplyFactorIncrease); } - function totalSupplyUnaccrued() public view returns (uint256) { + function normalizedTotalSupplyUnaccrued() public view returns (uint256) { RewardTokenStorage storage $ = _getRewardTokenStorage(); return $.normalizedTotalSupply; } @@ -533,7 +536,7 @@ abstract contract RewardToken is * * Normalized total supply and total supply are same in non-rebasing token. */ - function totalSupply() public view returns (uint256) { + function normalizedTotalSupply() public view returns (uint256) { RewardTokenStorage storage $ = _getRewardTokenStorage(); (uint256 totalSupplyFactorIncrease, uint256 totalTreasuryMintAmount,,,) = calculateRewardAndDebtDistribution(); diff --git a/src/vault/Vault.sol b/src/vault/Vault.sol index fc3231db..33d19436 100644 --- a/src/vault/Vault.sol +++ b/src/vault/Vault.sol @@ -197,10 +197,10 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy if (pool == IDLE) { if (BASE_ASSET.balanceOf(address(this)) != 0) revert InvalidIdleMarketRemovalNonZeroBalance(); } else { - // Checks `balanceOf` as it may be possible that - // `getUnderlyingClaimOf` returns zero even though the + // Checks `normalizedBalanceOf` as it may be possible that + // `balanceOf` returns zero even though the // `normalizedBalance` is zero. - if (pool.balanceOf(address(this)) != 0) revert InvalidMarketRemovalNonZeroSupply(pool); + if (pool.normalizedBalanceOf(address(this)) != 0) revert InvalidMarketRemovalNonZeroSupply(pool); BASE_ASSET.approve(address(pool), 0); } @@ -323,7 +323,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy MarketAllocation calldata allocation = allocations[i]; IIonPool pool = allocation.pool; - uint256 currentSupplied = pool == IDLE ? currentIdleDeposits : pool.getUnderlyingClaimOf(address(this)); + uint256 currentSupplied = pool == IDLE ? currentIdleDeposits : pool.balanceOf(address(this)); int256 assets = allocation.assets; // to deposit or withdraw // if `assets` is `type(int256).min`, this means fully withdraw from the market. @@ -645,7 +645,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy /** * @notice Returns the total claim that the vault has across all supported IonPools. - * @dev `IonPool.getUnderlyingClaimOf` returns the rebasing balance of the + * @dev `IonPool.balanceOf` returns the rebasing balance of the * lender receipt token that is pegged 1:1 to the underlying supplied asset. * @return assets The total assets held on the contract and inside the underlying * pools by this vault. @@ -655,8 +655,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy for (uint256 i; i != _supportedMarketsLength;) { IIonPool pool = IIonPool(supportedMarkets.at(i)); - uint256 assetsInPool = - pool == IDLE ? BASE_ASSET.balanceOf(address(this)) : pool.getUnderlyingClaimOf(address(this)); + uint256 assetsInPool = pool == IDLE ? BASE_ASSET.balanceOf(address(this)) : pool.balanceOf(address(this)); assets += assetsInPool; @@ -897,7 +896,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy * @return The max amount of assets withdrawable from this IonPool. */ function _withdrawable(IIonPool pool) internal view returns (uint256) { - uint256 currentSupplied = pool.getUnderlyingClaimOf(address(this)); + uint256 currentSupplied = pool.balanceOf(address(this)); // TODO should be balanceOf uint256 availableLiquidity = uint256(pool.extsload(ION_POOL_LIQUIDITY_SLOT)); return Math.min(currentSupplied, availableLiquidity); @@ -910,9 +909,8 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy * @return The max amount of assets depositable to this IonPool. */ function _depositable(IIonPool pool) internal view returns (uint256) { - uint256 allocationCapDiff = _zeroFloorSub(caps[pool], pool.getUnderlyingClaimOf(address(this))); - uint256 supplyCapDiff = - _zeroFloorSub(uint256(pool.extsload(ION_POOL_SUPPLY_CAP_SLOT)), pool.getTotalUnderlyingClaims()); + uint256 allocationCapDiff = _zeroFloorSub(caps[pool], pool.balanceOf(address(this))); + uint256 supplyCapDiff = _zeroFloorSub(uint256(pool.extsload(ION_POOL_SUPPLY_CAP_SLOT)), pool.totalSupply()); return Math.min(allocationCapDiff, supplyCapDiff); } diff --git a/test/fork/fuzz/lrt/EtherFiLibrary.t.sol b/test/fork/fuzz/lrt/EtherFiLibrary.t.sol index ce74d121..1152a078 100644 --- a/test/fork/fuzz/lrt/EtherFiLibrary.t.sol +++ b/test/fork/fuzz/lrt/EtherFiLibrary.t.sol @@ -31,7 +31,7 @@ contract EtherFiLibrary_FuzzTest is Test { function testForkFuzz_GetLstAmountOutForEthAmountIn(uint256 ethAmount) external { vm.assume(ethAmount != 0); - vm.assume(ethAmount < type(uint128).max); + vm.assume(ethAmount < type(uint96).max); uint256 lrtAmountOut = EtherFiLibrary.getLstAmountOutForEthAmountIn(WEETH_ADDRESS, ethAmount); diff --git a/test/integration/concrete/WeEthIonPool.t.sol b/test/integration/concrete/WeEthIonPool.t.sol index 6b99f878..ab566eb9 100644 --- a/test/integration/concrete/WeEthIonPool.t.sol +++ b/test/integration/concrete/WeEthIonPool.t.sol @@ -125,7 +125,7 @@ contract WeEthIonPool_IntegrationTest is WeEthIonPoolSharedSetup { ionPool.supply(lenderA, lenderAFirstSupplyAmount, lenderProofs[0]); vm.stopPrank(); - assertEq(ionPool.getUnderlyingClaimOf(lenderA), lenderAFirstSupplyAmount, "lender balance after 1st supply"); + assertEq(ionPool.balanceOf(lenderA), lenderAFirstSupplyAmount, "lender balance after 1st supply"); assertEq(lens.liquidity(iIonPool), lenderAFirstSupplyAmount, "liquidity after 1st supply"); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -172,10 +172,7 @@ contract WeEthIonPool_IntegrationTest is WeEthIonPoolSharedSetup { uint256 roundingError = ionPool.supplyFactor() / 1e27; assertApproxEqAbs( - ionPool.getUnderlyingClaimOf(lenderB), - lenderBFirstSupplyAmount, - roundingError, - "lenderB balance after 1st supply" + ionPool.balanceOf(lenderB), lenderBFirstSupplyAmount, roundingError, "lenderB balance after 1st supply" ); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -219,14 +216,14 @@ contract WeEthIonPool_IntegrationTest is WeEthIonPoolSharedSetup { vm.startPrank(lenderA); ionPool.withdraw(lenderA, lender1WithdrawAmountFail); - uint256 lenderABalanceBefore = ionPool.getUnderlyingClaimOf(lenderA); + uint256 lenderABalanceBefore = ionPool.balanceOf(lenderA); uint256 lender1WithdrawAmount = 10e18; ionPool.withdraw(lenderA, lender1WithdrawAmount); vm.stopPrank(); assertEq( - ionPool.getUnderlyingClaimOf(lenderA), + ionPool.balanceOf(lenderA), lenderABalanceBefore - lender1WithdrawAmount, "lenderA balance after 1st withdrawal" ); diff --git a/test/invariant/IonPool/ActorManager.t.sol b/test/invariant/IonPool/ActorManager.t.sol index ab85cda2..ea0f94fd 100644 --- a/test/invariant/IonPool/ActorManager.t.sol +++ b/test/invariant/IonPool/ActorManager.t.sol @@ -227,7 +227,7 @@ contract IonPool_InvariantTest is IonPoolSharedSetup { function invariant_LenderDepositsAddToBalance() external returns (bool) { for (uint256 i = 0; i < lenders.length; i++) { - assertEq(lenders[i].totalHoldingsNormalized(), ionPool.balanceOf(address(lenders[i]))); + assertEq(lenders[i].totalHoldingsNormalized(), ionPool.normalizedBalanceOf(address(lenders[i]))); } return !failed(); @@ -236,9 +236,12 @@ contract IonPool_InvariantTest is IonPoolSharedSetup { function invariant_LenderBalancesPlusTreasuryAddToTotalSupply() external returns (bool) { uint256 totalLenderNormalizedBalances; for (uint256 i = 0; i < lenders.length; i++) { - totalLenderNormalizedBalances += ionPool.balanceOf(address(lenders[i])); + totalLenderNormalizedBalances += ionPool.normalizedBalanceOf(address(lenders[i])); } - assertEq(totalLenderNormalizedBalances + ionPool.balanceOf(TREASURY), ionPool.totalSupplyUnaccrued()); + assertEq( + totalLenderNormalizedBalances + ionPool.normalizedBalanceOf(TREASURY), + ionPool.normalizedTotalSupplyUnaccrued() + ); return !failed(); } @@ -256,7 +259,7 @@ contract IonPool_InvariantTest is IonPoolSharedSetup { assertGe(lens.liquidity(iIonPool) + totalDebt, ionPool.totalSupplyUnaccrued()); assertGe( lens.liquidity(iIonPool).scaleUpToRad(18) + lens.debtUnaccrued(iIonPool), - ionPool.totalSupplyUnaccrued() * ionPool.supplyFactorUnaccrued() + ionPool.normalizedTotalSupplyUnaccrued() * ionPool.supplyFactorUnaccrued() ); return !failed(); diff --git a/test/invariant/RewardToken/ActorManager.t.sol b/test/invariant/RewardToken/ActorManager.t.sol index d759dab6..f15bfb94 100644 --- a/test/invariant/RewardToken/ActorManager.t.sol +++ b/test/invariant/RewardToken/ActorManager.t.sol @@ -105,20 +105,20 @@ contract RewardToken_InvariantTest is RewardTokenSharedSetup { uint256 totalSupplyByBalances; for (uint256 i = 0; i < userHandlers.length; i++) { UserHandler user = userHandlers[i]; - totalSupplyByBalances += rewardModule.balanceOf(address(user)); + totalSupplyByBalances += rewardModule.normalizedBalanceOf(address(user)); } underlying.balanceOf(address(rewardModule)); // update underlying balance rewardModule.totalSupply(); - assertEq(rewardModule.totalSupply(), totalSupplyByBalances); + assertEq(rewardModule.normalizedTotalSupply(), totalSupplyByBalances); } function invariant_lenderClaimAlwaysBacked() external { - uint256 lenderClaim = rewardModule.getTotalUnderlyingClaims(); + uint256 totalSupply = rewardModule.totalSupply(); uint256 underlyingBalance = underlying.balanceOf(address(rewardModule)); - assertGe(underlyingBalance, lenderClaim); + assertGe(underlyingBalance, totalSupply); } } diff --git a/test/invariant/RewardToken/Handlers.t.sol b/test/invariant/RewardToken/Handlers.t.sol index 57064718..eb89fd80 100644 --- a/test/invariant/RewardToken/Handlers.t.sol +++ b/test/invariant/RewardToken/Handlers.t.sol @@ -39,11 +39,11 @@ contract UserHandler is Handler { } function burn(address account, uint256 amount) external { - amount = bound(amount, 0, REWARD_MODULE.getUnderlyingClaimOf(account)); + amount = bound(amount, 0, REWARD_MODULE.balanceOf(account)); uint256 currentSupplyFactor = REWARD_MODULE.supplyFactor(); uint256 amountNormalized = amount.rayDivUp(currentSupplyFactor); - if (amountNormalized == 0 || amountNormalized > REWARD_MODULE.balanceOf(account)) return; + if (amountNormalized == 0 || amountNormalized > REWARD_MODULE.normalizedBalanceOf(account)) return; REWARD_MODULE.burn(account, account, amount); } @@ -66,11 +66,11 @@ contract SupplyFactorIncreaseHandler is Handler { uint256 oldSupplyFactor = REWARD_MODULE.supplyFactor(); amount = bound(amount, 1.1e27, 1.25e27); // between 1E-16 and 15% - uint256 oldTotalSupply = REWARD_MODULE.getTotalUnderlyingClaims(); + uint256 oldTotalSupply = REWARD_MODULE.totalSupply(); uint256 newSupplyFactor = oldSupplyFactor.rayMulDown(amount); REWARD_MODULE.setSupplyFactor(newSupplyFactor); - uint256 interestCreated = REWARD_MODULE.getTotalUnderlyingClaims() - oldTotalSupply; + uint256 interestCreated = REWARD_MODULE.totalSupply() - oldTotalSupply; UNDERLYING.mint(address(REWARD_MODULE), interestCreated + 1); } } diff --git a/test/unit/concrete/RewardToken.t.sol b/test/unit/concrete/RewardToken.t.sol index 32f79461..134448a4 100644 --- a/test/unit/concrete/RewardToken.t.sol +++ b/test/unit/concrete/RewardToken.t.sol @@ -42,7 +42,7 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { rewardModule.mint(address(0), amountOfRewards); rewardModule.mint(address(this), amountOfRewards); - assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewards); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewards); } @@ -66,14 +66,14 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), INITIAL_UNDERYLING); rewardModule.mint(address(this), amountOfRewards); - assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewards); vm.expectRevert(abi.encodeWithSelector(RewardToken.InvalidSender.selector, address(0))); rewardModule.burn(address(0), address(this), amountOfRewards); rewardModule.burn(address(this), address(this), amountOfRewards); - assertEq(rewardModule.balanceOf(address(this)), 0); + assertEq(rewardModule.normalizedBalanceOf(address(this)), 0); } function test_MintRewardWithSupplyFactorChange() external { @@ -85,8 +85,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { uint256 expectedNormalizedMint1 = amountOfRewards.rayDivDown(supplyFactorOld); - assertEq(rewardModule.balanceOf(address(this)), expectedNormalizedMint1); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), expectedNormalizedMint1); + assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewards); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewards); @@ -104,8 +104,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { uint256 totalDepositsNormalized = expectedNormalizedMint1 + expectedNormalizedMint2; uint256 totalValue = totalDepositsNormalized.rayMulDown(supplyFactorNew); - assertEq(rewardModule.balanceOf(address(this)), totalDepositsNormalized); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), totalValue); + assertEq(rewardModule.normalizedBalanceOf(address(this)), totalDepositsNormalized); + assertEq(rewardModule.balanceOf(address(this)), totalValue); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - totalDeposited); assertEq(underlying.balanceOf(address(rewardModule)), totalDeposited + interestCreated); @@ -129,8 +129,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { uint256 expectedNormalizedMint1 = amountOfRewards.rayDivDown(supplyFactorOld); - assertEq(rewardModule.balanceOf(address(this)), expectedNormalizedMint1); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), expectedNormalizedMint1); + assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewards); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewards); @@ -148,8 +148,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { uint256 totalDepositsNormalized = expectedNormalizedMint1 + expectedNormalizedMint2; uint256 totalValue = totalDepositsNormalized.rayMulDown(supplyFactorNew); - assertEq(rewardModule.balanceOf(address(this)), totalDepositsNormalized); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), totalValue); + assertEq(rewardModule.normalizedBalanceOf(address(this)), totalDepositsNormalized); + assertEq(rewardModule.balanceOf(address(this)), totalValue); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - totalDeposited); assertEq(underlying.balanceOf(address(rewardModule)), totalDeposited + interestCreated); @@ -174,9 +174,13 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { rewardModule.burn(address(this), address(this), burnAmount); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), totalValue - burnAmount); - assertEq(rewardModule.totalSupply(), totalDepositsNormalized - burnAmountNormalized, "total supply after burn"); - assertEq(rewardModule.balanceOf(address(this)), totalDepositsNormalized - burnAmountNormalized); + assertEq(rewardModule.balanceOf(address(this)), totalValue - burnAmount); + assertEq( + rewardModule.normalizedTotalSupply(), + totalDepositsNormalized - burnAmountNormalized, + "total supply after burn" + ); + assertEq(rewardModule.normalizedBalanceOf(address(this)), totalDepositsNormalized - burnAmountNormalized); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - totalDeposited + burnAmount); assertEq(underlying.balanceOf(address(rewardModule)), totalDeposited + interestCreated - burnAmount); } @@ -187,7 +191,7 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), INITIAL_UNDERYLING); rewardModule.mint(address(this), amountOfRewardTokens); - assertEq(rewardModule.balanceOf(address(this)), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(address(this)), amountOfRewardTokens); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewardTokens); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewardTokens); @@ -206,8 +210,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { rewardModule.transfer(address(this), amountOfRewardTokens); rewardModule.transfer(receivingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(address(this)), 0); - assertEq(rewardModule.balanceOf(receivingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(address(this)), 0); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), amountOfRewardTokens); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewardTokens); } @@ -217,8 +221,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), INITIAL_UNDERYLING); rewardModule.mint(sendingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), amountOfRewardTokens); - assertEq(rewardModule.balanceOf(receivingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), 0); assertEq(rewardModule.allowance(sendingUser, spender), 0); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewardTokens); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewardTokens); @@ -246,8 +250,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens + 1); rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), 0); - assertEq(rewardModule.balanceOf(receivingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), amountOfRewardTokens); assertEq(rewardModule.allowance(sendingUser, spender), 0); } @@ -257,8 +261,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), INITIAL_UNDERYLING); rewardModule.mint(sendingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), amountOfRewardTokens); - assertEq(rewardModule.balanceOf(receivingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), 0); assertEq(rewardModule.allowance(sendingUser, spender), 0); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewardTokens); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewardTokens); @@ -307,8 +311,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens + 1); rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), 0); - assertEq(rewardModule.balanceOf(receivingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), amountOfRewardTokens); assertEq(rewardModule.allowance(sendingUser, spender), 0); } @@ -318,8 +322,8 @@ contract RewardToken_UnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), INITIAL_UNDERYLING); rewardModule.mint(sendingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), amountOfRewardTokens); - assertEq(rewardModule.balanceOf(receivingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), 0); assertEq(rewardModule.allowance(sendingUser, spender), 0); assertEq(underlying.balanceOf(address(this)), INITIAL_UNDERYLING - amountOfRewardTokens); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewardTokens); diff --git a/test/unit/concrete/vault/Vault.t.sol b/test/unit/concrete/vault/Vault.t.sol index cafcdb22..bb73be56 100644 --- a/test/unit/concrete/vault/Vault.t.sol +++ b/test/unit/concrete/vault/Vault.t.sol @@ -298,7 +298,7 @@ contract VaultSetUpTest is VaultSharedSetup { vault.deposit(depositAmount, address(this)); - assertGt(weEthIonPool.balanceOf(address(vault)), 0, "deposited to weEthIonPool"); + assertGt(weEthIonPool.normalizedBalanceOf(address(vault)), 0, "deposited to weEthIonPool"); vm.prank(OWNER); vm.expectRevert(abi.encodeWithSelector(Vault.InvalidMarketRemovalNonZeroSupply.selector, weEthIonPool)); @@ -363,7 +363,7 @@ contract VaultSetUpTest is VaultSharedSetup { setERC20Balance(address(BASE_ASSET), address(this), depositAmount); vault.deposit(depositAmount, address(this)); - assertGt(weEthIonPool.balanceOf(address(vault)), 0, "deposited to weEthIonPool"); + assertGt(weEthIonPool.normalizedBalanceOf(address(vault)), 0, "deposited to weEthIonPool"); bytes memory reallocateCalldata = abi.encodeWithSelector(Vault.reallocate.selector, allocs); @@ -616,7 +616,7 @@ abstract contract VaultDeposit is VaultSharedSetup { updateSupplyCaps(vault, type(uint256).max, type(uint256).max, type(uint256).max); updateAllocationCaps(vault, type(uint256).max, type(uint256).max, type(uint256).max); - uint256 prevWeEthShares = weEthIonPool.balanceOf(address(vault)); + uint256 prevWeEthShares = weEthIonPool.normalizedBalanceOf(address(vault)); vault.deposit(depositAmount, address(this)); @@ -624,7 +624,7 @@ abstract contract VaultDeposit is VaultSharedSetup { assertEq(vault.balanceOf(address(this)), depositAmount, "user vault shares balance"); assertEq(BASE_ASSET.balanceOf(address(vault)), 0, "base asset balance should be zero"); assertEq( - weEthIonPool.getUnderlyingClaimOf(address(vault)), + weEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevWeEthShares, depositAmount, weEthIonPool.supplyFactor()), "vault iToken claim" ); @@ -638,9 +638,9 @@ abstract contract VaultDeposit is VaultSharedSetup { updateSupplyCaps(vault, type(uint256).max, type(uint256).max, type(uint256).max); updateAllocationCaps(vault, 1e18, 1e18, 1e18); - uint256 prevWeEthShares = weEthIonPool.balanceOf(address(vault)); - uint256 prevRsEthShares = rsEthIonPool.balanceOf(address(vault)); - uint256 prevRswEthShares = rswEthIonPool.balanceOf(address(vault)); + uint256 prevWeEthShares = weEthIonPool.normalizedBalanceOf(address(vault)); + uint256 prevRsEthShares = rsEthIonPool.normalizedBalanceOf(address(vault)); + uint256 prevRswEthShares = rswEthIonPool.normalizedBalanceOf(address(vault)); // 3e18 gets spread out equally amongst the three pools vault.deposit(depositAmount, address(this)); @@ -650,17 +650,17 @@ abstract contract VaultDeposit is VaultSharedSetup { assertEq(BASE_ASSET.balanceOf(address(vault)), 0, "base asset balance should be zero"); assertEq( - weEthIonPool.getUnderlyingClaimOf(address(vault)), + weEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevWeEthShares, 1e18, weEthIonPool.supplyFactor()), "weEth vault iToken claim" ); assertEq( - rsEthIonPool.getUnderlyingClaimOf(address(vault)), + rsEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevRsEthShares, 1e18, rsEthIonPool.supplyFactor()), "rsEth vault iToken claim" ); assertEq( - rswEthIonPool.getUnderlyingClaimOf(address(vault)), + rswEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevRswEthShares, 1e18, rswEthIonPool.supplyFactor()), "rswEth vault iToken claim" ); @@ -674,9 +674,9 @@ abstract contract VaultDeposit is VaultSharedSetup { updateSupplyCaps(vault, type(uint256).max, type(uint256).max, type(uint256).max); updateAllocationCaps(vault, 3e18, 5e18, 7e18); - uint256 prevWeEthShares = weEthIonPool.balanceOf(address(vault)); - uint256 prevRsEthShares = rsEthIonPool.balanceOf(address(vault)); - uint256 prevRswEthShares = rswEthIonPool.balanceOf(address(vault)); + uint256 prevWeEthShares = weEthIonPool.normalizedBalanceOf(address(vault)); + uint256 prevRsEthShares = rsEthIonPool.normalizedBalanceOf(address(vault)); + uint256 prevRswEthShares = rswEthIonPool.normalizedBalanceOf(address(vault)); // 3e18 gets spread out equally amongst the three pools vault.deposit(depositAmount, address(this)); @@ -686,17 +686,17 @@ abstract contract VaultDeposit is VaultSharedSetup { assertEq(BASE_ASSET.balanceOf(address(vault)), 0, "base asset balance should be zero"); assertEq( - weEthIonPool.getUnderlyingClaimOf(address(vault)), + weEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevWeEthShares, 2e18, weEthIonPool.supplyFactor()), "weEth vault iToken claim" ); assertEq( - rsEthIonPool.getUnderlyingClaimOf(address(vault)), + rsEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevRsEthShares, 3e18, rsEthIonPool.supplyFactor()), "rsEth vault iToken claim" ); assertEq( - rswEthIonPool.getUnderlyingClaimOf(address(vault)), + rswEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevRswEthShares, 5e18, rswEthIonPool.supplyFactor()), "rswEth vault iToken claim" ); @@ -712,9 +712,9 @@ abstract contract VaultDeposit is VaultSharedSetup { updateSupplyCaps(vault, 3e18, 10e18, 5e18); updateAllocationCaps(vault, 5e18, 7e18, 20e18); - uint256 prevWeEthShares = weEthIonPool.balanceOf(address(vault)); - uint256 prevRsEthShares = rsEthIonPool.balanceOf(address(vault)); - uint256 prevRswEthShares = rswEthIonPool.balanceOf(address(vault)); + uint256 prevWeEthShares = weEthIonPool.normalizedBalanceOf(address(vault)); + uint256 prevRsEthShares = rsEthIonPool.normalizedBalanceOf(address(vault)); + uint256 prevRswEthShares = rswEthIonPool.normalizedBalanceOf(address(vault)); vault.deposit(depositAmount, address(this)); @@ -723,17 +723,17 @@ abstract contract VaultDeposit is VaultSharedSetup { assertEq(BASE_ASSET.balanceOf(address(vault)), 0, "base asset balance should be zero"); assertEq( - weEthIonPool.getUnderlyingClaimOf(address(vault)), + weEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevWeEthShares, 2e18, weEthIonPool.supplyFactor()), "weEth vault iToken claim" ); assertEq( - rsEthIonPool.getUnderlyingClaimOf(address(vault)), + rsEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevRsEthShares, 3e18, rsEthIonPool.supplyFactor()), "rsEth vault iToken claim" ); assertEq( - rswEthIonPool.getUnderlyingClaimOf(address(vault)), + rswEthIonPool.balanceOf(address(vault)), claimAfterDeposit(prevRswEthShares, 7e18, rswEthIonPool.supplyFactor()), "rswEth vault iToken claim" ); @@ -839,9 +839,7 @@ abstract contract VaultWithdraw is VaultSharedSetup { ); assertEq(vault.totalSupply(), expectedNewTotalSupply, "vault shares total supply"); - assertEq( - vault.totalAssets(), rsEthIonPool.getUnderlyingClaimOf(address(vault)), "single market for total assets" - ); + assertEq(vault.totalAssets(), rsEthIonPool.balanceOf(address(vault)), "single market for total assets"); assertEq(BASE_ASSET.balanceOf(address(vault)), 0, "valt's base asset balance should be zero"); // user @@ -900,10 +898,10 @@ abstract contract VaultWithdraw is VaultSharedSetup { ); assertEq(vault.totalSupply(), expectedNewTotalSupply, "vault shares total supply"); - assertEq(rsEthIonPool.getUnderlyingClaimOf(address(vault)), 0, "vault pool1 balance"); - assertEq(rswEthIonPool.getUnderlyingClaimOf(address(vault)), 0, "vault pool2 balance"); + assertEq(rsEthIonPool.balanceOf(address(vault)), 0, "vault pool1 balance"); + assertEq(rswEthIonPool.balanceOf(address(vault)), 0, "vault pool2 balance"); assertLe( - expectedNewTotalAssets - weEthIonPool.getUnderlyingClaimOf(address(vault)), + expectedNewTotalAssets - weEthIonPool.balanceOf(address(vault)), weEthIonPool.supplyFactor() / RAY, "vault pool3 balance" ); @@ -962,9 +960,9 @@ abstract contract VaultReallocate is VaultSharedSetup { uint256 prevTotalAssets = vault.totalAssets(); - uint256 prevRsEthClaim = rsEthIonPool.getUnderlyingClaimOf(address(vault)); - uint256 prevRswEthClaim = rswEthIonPool.getUnderlyingClaimOf(address(vault)); - uint256 prevWeEthClaim = weEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 prevRsEthClaim = rsEthIonPool.balanceOf(address(vault)); + uint256 prevRswEthClaim = rswEthIonPool.balanceOf(address(vault)); + uint256 prevWeEthClaim = weEthIonPool.balanceOf(address(vault)); int256 rswEthDiff = -1e18; int256 weEthDiff = -2e18; @@ -988,19 +986,19 @@ abstract contract VaultReallocate is VaultSharedSetup { uint256 newTotalAssets = vault.totalAssets(); assertApproxEqAbs( - rsEthIonPool.getUnderlyingClaimOf(address(vault)), + rsEthIonPool.balanceOf(address(vault)), expNewRsEthClaim, rsEthIonPool.supplyFactor() / RAY, "rsEth vault iToken claim" ); assertApproxEqAbs( - rswEthIonPool.getUnderlyingClaimOf(address(vault)), + rswEthIonPool.balanceOf(address(vault)), expNewRswEthClaim, rswEthIonPool.supplyFactor() / RAY, "rswEth vault iToken claim" ); assertApproxEqAbs( - weEthIonPool.getUnderlyingClaimOf(address(vault)), + weEthIonPool.balanceOf(address(vault)), expNewWeEthClaim, weEthIonPool.supplyFactor() / RAY, "weEth vault iToken claim" @@ -1028,9 +1026,9 @@ abstract contract VaultReallocate is VaultSharedSetup { updateAllocationCaps(vault, type(uint256).max, type(uint256).max, type(uint256).max); - uint256 prevWeEthClaim = weEthIonPool.getUnderlyingClaimOf(address(vault)); - uint256 prevRswEthClaim = rswEthIonPool.getUnderlyingClaimOf(address(vault)); - uint256 prevRsEthClaim = rsEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 prevWeEthClaim = weEthIonPool.balanceOf(address(vault)); + uint256 prevRswEthClaim = rswEthIonPool.balanceOf(address(vault)); + uint256 prevRsEthClaim = rsEthIonPool.balanceOf(address(vault)); uint256 expRswEthClaim = prevRswEthClaim + prevWeEthClaim + prevRsEthClaim; uint256 prevTotalAssets = vault.totalAssets(); @@ -1052,10 +1050,10 @@ abstract contract VaultReallocate is VaultSharedSetup { uint256 newTotalAssets = vault.totalAssets(); - assertEq(rsEthIonPool.getUnderlyingClaimOf(address(vault)), 0, "rsEth vault iToken claim"); - assertEq(weEthIonPool.getUnderlyingClaimOf(address(vault)), 0, "weEth vault iToken claim"); + assertEq(rsEthIonPool.balanceOf(address(vault)), 0, "rsEth vault iToken claim"); + assertEq(weEthIonPool.balanceOf(address(vault)), 0, "weEth vault iToken claim"); assertApproxEqAbs( - rswEthIonPool.getUnderlyingClaimOf(address(vault)), + rswEthIonPool.balanceOf(address(vault)), expRswEthClaim, rswEthIonPool.supplyFactor() / RAY, "rswEth vault iToken claim" @@ -1081,7 +1079,7 @@ abstract contract VaultReallocate is VaultSharedSetup { uint256 prevTotalAssets = vault.totalAssets(); - uint256 weEthCurrentSupplied = weEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 weEthCurrentSupplied = weEthIonPool.balanceOf(address(vault)); // tries to deposit 2e18 + 2e18 to 3e18 allocation cap Vault.MarketAllocation[] memory allocs = new Vault.MarketAllocation[](3); @@ -1173,20 +1171,14 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { // rsEthIonPool should be full at 30e18 // rswEthIonPool should be at 10e18 assertLe( - 10e18 - weEthIonPool.getUnderlyingClaimOf(address(vault)), - postDepositClaimRE(10e18, weEthIonPoolSF), - "weEthIonPool" + 10e18 - weEthIonPool.balanceOf(address(vault)), postDepositClaimRE(10e18, weEthIonPoolSF), "weEthIonPool" ); assertEq(BASE_ASSET.balanceOf(address(vault)), 20e18, "IDLE"); assertLe( - 30e18 - rsEthIonPool.getUnderlyingClaimOf(address(vault)), - postDepositClaimRE(30e18, rsEthIonPoolSF), - "rsEthIonPool" + 30e18 - rsEthIonPool.balanceOf(address(vault)), postDepositClaimRE(30e18, rsEthIonPoolSF), "rsEthIonPool" ); assertLe( - 10e18 - rswEthIonPool.getUnderlyingClaimOf(address(vault)), - postDepositClaimRE(10e18, rswEthIonPoolSF), - "rswEthIonPool" + 10e18 - rswEthIonPool.balanceOf(address(vault)), postDepositClaimRE(10e18, rswEthIonPoolSF), "rswEthIonPool" ); assertEq(BASE_ASSET.balanceOf(address(this)), 0, "user balance"); } @@ -1221,18 +1213,18 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { assertEq(remainingAssets, 0, "test variables"); assertLe( - expectedWeEthIonPoolClaim - weEthIonPool.getUnderlyingClaimOf(address(vault)), + expectedWeEthIonPoolClaim - weEthIonPool.balanceOf(address(vault)), postDepositClaimRE(expectedWeEthIonPoolClaim, weEthIonPoolSF), "weEthIonPool" ); assertEq(BASE_ASSET.balanceOf(address(vault)), expectedIdleClaim, "IDLE"); assertLe( - expectedRsEthIonPoolClaim - rsEthIonPool.getUnderlyingClaimOf(address(vault)), + expectedRsEthIonPoolClaim - rsEthIonPool.balanceOf(address(vault)), postDepositClaimRE(expectedWeEthIonPoolClaim, rsEthIonPoolSF), "rsEthIonPool" ); assertLe( - expectedRswEthIonPoolClaim - rswEthIonPool.getUnderlyingClaimOf(address(vault)), + expectedRswEthIonPoolClaim - rswEthIonPool.balanceOf(address(vault)), postDepositClaimRE(expectedRswEthIonPoolClaim, rswEthIonPoolSF), "rswEthIonPool" ); @@ -1250,10 +1242,10 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { uint256 prevTotalAssets = vault.totalAssets(); uint256 supplyFactor = rsEthIonPool.supplyFactor(); - uint256 weEthIonPoolClaim = weEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 weEthIonPoolClaim = weEthIonPool.balanceOf(address(vault)); uint256 idleClaim = BASE_ASSET.balanceOf(address(vault)); - uint256 rsEthIonPoolClaim = rsEthIonPool.getUnderlyingClaimOf(address(vault)); - uint256 rswEthIonPoolClaim = rswEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 rsEthIonPoolClaim = rsEthIonPool.balanceOf(address(vault)); + uint256 rswEthIonPoolClaim = rswEthIonPool.balanceOf(address(vault)); uint256 withdrawAmount = 40e18; @@ -1283,14 +1275,14 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { postDepositClaimRE(withdrawAmount, supplyFactor), "vault total assets" ); - assertEq(weEthIonPool.getUnderlyingClaimOf(address(vault)), 0, "weEthIonPool claim"); + assertEq(weEthIonPool.balanceOf(address(vault)), 0, "weEthIonPool claim"); assertEq(BASE_ASSET.balanceOf(address(vault)), 0, "idle deposits"); assertLt( - (rsEthIonPoolClaim - rsEthIonPoolWithdraw) - rsEthIonPool.getUnderlyingClaimOf(address(vault)), + (rsEthIonPoolClaim - rsEthIonPoolWithdraw) - rsEthIonPool.balanceOf(address(vault)), postDepositClaimRE(withdrawAmount, supplyFactor), "rsEthIonPool claim" ); - assertEq(rswEthIonPool.getUnderlyingClaimOf(address(vault)), rswEthIonPoolClaim, "rswEthIonPool claim"); + assertEq(rswEthIonPool.balanceOf(address(vault)), rswEthIonPoolClaim, "rswEthIonPool claim"); // user gains withdrawn balance assertEq(BASE_ASSET.balanceOf(address(this)), withdrawAmount, "user base asset balance"); @@ -1349,10 +1341,10 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { vault.updateAllocationCaps(ionPoolToUpdate, newAllocationCaps); // 10 weEth 20 idle 30 rsEth 10 rswEth - uint256 prevWeEthClaim = weEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 prevWeEthClaim = weEthIonPool.balanceOf(address(vault)); uint256 prevIdleClaim = BASE_ASSET.balanceOf(address(vault)); - uint256 prevRsEthClaim = rsEthIonPool.getUnderlyingClaimOf(address(vault)); - uint256 prevRswEthClaim = rswEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 prevRsEthClaim = rsEthIonPool.balanceOf(address(vault)); + uint256 prevRswEthClaim = rswEthIonPool.balanceOf(address(vault)); uint256 weEthSF = weEthIonPool.supplyFactor(); uint256 rsEthSF = rsEthIonPool.supplyFactor(); @@ -1375,17 +1367,13 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { vault.reallocate(allocs); assertLt( - expectedWeEthClaim - weEthIonPool.getUnderlyingClaimOf(address(vault)), - postDepositClaimRE(0, weEthSF), - "weEthIonPol" + expectedWeEthClaim - weEthIonPool.balanceOf(address(vault)), postDepositClaimRE(0, weEthSF), "weEthIonPol" ); assertEq(BASE_ASSET.balanceOf(address(vault)), expectedIdleClaim, "IDLE"); assertLt( - expectedRsEthClaim - rsEthIonPool.getUnderlyingClaimOf(address(vault)), - postDepositClaimRE(0, rsEthSF), - "rswEthIonPol" + expectedRsEthClaim - rsEthIonPool.balanceOf(address(vault)), postDepositClaimRE(0, rsEthSF), "rswEthIonPol" ); - assertEq(prevRswEthClaim, rswEthIonPool.getUnderlyingClaimOf(address(vault)), "rswEthIonPool"); + assertEq(prevRswEthClaim, rswEthIonPool.balanceOf(address(vault)), "rswEthIonPool"); } function test_Reallocate_WithdrawFromIdle() public { @@ -1394,10 +1382,10 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { vault.deposit(depositAmount, address(this)); // 10 weEth 20 idle 30 rsEth 10 rswEth - uint256 prevWeEthClaim = weEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 prevWeEthClaim = weEthIonPool.balanceOf(address(vault)); uint256 prevIdleClaim = BASE_ASSET.balanceOf(address(vault)); - uint256 prevRsEthClaim = rsEthIonPool.getUnderlyingClaimOf(address(vault)); - uint256 prevRswEthClaim = rswEthIonPool.getUnderlyingClaimOf(address(vault)); + uint256 prevRsEthClaim = rsEthIonPool.balanceOf(address(vault)); + uint256 prevRswEthClaim = rswEthIonPool.balanceOf(address(vault)); uint256 weEthSF = weEthIonPool.supplyFactor(); uint256 rswEthSF = rswEthIonPool.supplyFactor(); @@ -1420,17 +1408,15 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { vault.reallocate(allocs); assertLt( - expectedWeEthClaim - weEthIonPool.getUnderlyingClaimOf(address(vault)), - postDepositClaimRE(0, weEthSF), - "weEthIonPol" + expectedWeEthClaim - weEthIonPool.balanceOf(address(vault)), postDepositClaimRE(0, weEthSF), "weEthIonPol" ); assertEq(BASE_ASSET.balanceOf(address(vault)), expectedIdleClaim, "IDLE"); assertLt( - expectedRswEthClaim - rswEthIonPool.getUnderlyingClaimOf(address(vault)), + expectedRswEthClaim - rswEthIonPool.balanceOf(address(vault)), postDepositClaimRE(0, rswEthSF), "rswEthIonPol" ); - assertEq(prevRsEthClaim, rsEthIonPool.getUnderlyingClaimOf(address(vault)), "rsEthIonPool"); + assertEq(prevRsEthClaim, rsEthIonPool.balanceOf(address(vault)), "rsEthIonPool"); } } diff --git a/test/unit/fuzz/IonPool.t.sol b/test/unit/fuzz/IonPool.t.sol index 57a82e6d..6414fe1b 100644 --- a/test/unit/fuzz/IonPool.t.sol +++ b/test/unit/fuzz/IonPool.t.sol @@ -71,11 +71,11 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven ionPool.supply(lender1, supplyAmount, new bytes32[](0)); assertEq(lens.liquidity(iIonPool), supplyAmountBeforeSupply + supplyAmount, "weth"); - assertEq(ionPool.balanceOf(lender1), normalizedAmount, "ionPool balanceOf"); + assertEq(ionPool.normalizedBalanceOf(lender1), normalizedAmount, "ionPool balanceOf"); uint256 roundingError = currentSupplyFactor / RAY; - assertLe(ionPool.getUnderlyingClaimOf(lender1) - roundingError, supplyAmount, "balanceOf rounding error"); + assertLe(ionPool.balanceOf(lender1) - roundingError, supplyAmount, "balanceOf rounding error"); } function testFuzz_SupplyBaseToDifferentAddress(uint256 supplyAmount) public { @@ -105,10 +105,10 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven ionPool.supply(address(this), supplyAmount, new bytes32[](0)); assertEq(lens.liquidity(iIonPool), supplyAmountBeforeSupply + supplyAmount); - assertEq(ionPool.balanceOf(address(this)), normalizedAmount); + assertEq(ionPool.normalizedBalanceOf(address(this)), normalizedAmount); uint256 roundingError = currentSupplyFactor / RAY; - assertLe(ionPool.getUnderlyingClaimOf(address(this)) - roundingError, supplyAmount); + assertLe(ionPool.balanceOf(address(this)) - roundingError, supplyAmount); } struct FuzzWithdrawBaseLocs { @@ -132,7 +132,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven // Changing supply factor, means that the interest will be deposited _changeSupplyFactorIfNeeded(); uint256 supplyAmountAfterRebase = lens.liquidity(iIonPool); - uint256 lender1BalanceAfterRebase = ionPool.getUnderlyingClaimOf(lender1); + uint256 lender1BalanceAfterRebase = ionPool.balanceOf(lender1); assertEq(supplyAmountAfterRebase, lender1BalanceAfterRebase); @@ -141,7 +141,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven vm.assume(locs.withdrawAmount > 0); uint256 underlyingBeforeWithdraw = underlying.balanceOf(lender1); - uint256 rewardAssetBalanceBeforeWithdraw = ionPool.getUnderlyingClaimOf(lender1); + uint256 rewardAssetBalanceBeforeWithdraw = ionPool.balanceOf(lender1); locs.currentTotalDebt = lens.debt(iIonPool); (locs.supplyFactorIncrease,,, locs.newDebtIncrease,) = ionPool.calculateRewardAndDebtDistribution(); @@ -159,7 +159,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven ionPool.withdraw(lender1, locs.withdrawAmount); uint256 underlyingAfterWithdraw = underlying.balanceOf(lender1); - uint256 rewardAssetBalanceAfterWithdraw = ionPool.getUnderlyingClaimOf(lender1); + uint256 rewardAssetBalanceAfterWithdraw = ionPool.balanceOf(lender1); uint256 underlyingWithdrawn = underlyingAfterWithdraw - underlyingBeforeWithdraw; uint256 rewardAssetBurned = rewardAssetBalanceBeforeWithdraw - rewardAssetBalanceAfterWithdraw; @@ -170,7 +170,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven assertGe(rewardAssetBurned, underlyingWithdrawn); uint256 roundingError = currentSupplyFactor / RAY; - assertLt(ionPool.balanceOf(lender1), lender1BalanceAfterRebase - locs.withdrawAmount + roundingError); + assertLt(ionPool.normalizedBalanceOf(lender1), lender1BalanceAfterRebase - locs.withdrawAmount + roundingError); } function testFuzz_WithdrawBaseToDifferentAddress(uint256 supplyAmount, uint256 withdrawAmount) public { @@ -187,7 +187,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven // Changing supply factor, means that the interest will be deposited _changeSupplyFactorIfNeeded(); uint256 supplyAmountAfterRebase = lens.liquidity(iIonPool); - uint256 lender1BalanceAfterRebase = ionPool.getUnderlyingClaimOf(lender1); + uint256 lender1BalanceAfterRebase = ionPool.balanceOf(lender1); assertEq(supplyAmountAfterRebase, lender1BalanceAfterRebase, "amount after rebase"); @@ -196,7 +196,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven vm.assume(locs.withdrawAmount > 0); uint256 underlyingBeforeWithdraw = underlying.balanceOf(lender2); - uint256 rewardAssetBalanceBeforeWithdraw = ionPool.getUnderlyingClaimOf(lender1); + uint256 rewardAssetBalanceBeforeWithdraw = ionPool.balanceOf(lender1); locs.currentTotalDebt = lens.debt(iIonPool); (locs.supplyFactorIncrease,,, locs.newDebtIncrease,) = ionPool.calculateRewardAndDebtDistribution(); @@ -214,7 +214,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven ionPool.withdraw(lender2, locs.withdrawAmount); uint256 underlyingAfterWithdraw = underlying.balanceOf(lender2); - uint256 rewardAssetBalanceAfterWithdraw = ionPool.getUnderlyingClaimOf(lender1); + uint256 rewardAssetBalanceAfterWithdraw = ionPool.balanceOf(lender1); uint256 underlyingWithdrawn = underlyingAfterWithdraw - underlyingBeforeWithdraw; uint256 rewardAssetBurned = rewardAssetBalanceBeforeWithdraw - rewardAssetBalanceAfterWithdraw; @@ -226,7 +226,7 @@ abstract contract IonPool_LenderFuzzTestBase is IonPoolSharedSetup, IIonPoolEven uint256 roundingError = currentSupplyFactor / RAY; assertLt( - ionPool.getUnderlyingClaimOf(lender1), + ionPool.balanceOf(lender1), lender1BalanceAfterRebase - locs.withdrawAmount + roundingError, "underlying claim" ); @@ -1268,7 +1268,7 @@ contract IonPool_BorrowerFuzzTest is IonPool_BorrowerFuzzTestBase { assertEq(lens.liquidity(iIonPool), INITIAL_LENDER_UNDERLYING_BALANCE); assertEq(underlying.balanceOf(address(ionPool)), INITIAL_LENDER_UNDERLYING_BALANCE); - assertEq(ionPool.balanceOf(lender2), INITIAL_LENDER_UNDERLYING_BALANCE); + assertEq(ionPool.normalizedBalanceOf(lender2), INITIAL_LENDER_UNDERLYING_BALANCE); for (uint256 i = 0; i < lens.ilkCount(iIonPool); i++) { assertEq(collaterals[i].allowance(borrower1, address(gemJoins[i])), type(uint256).max); diff --git a/test/unit/fuzz/RewardToken.t.sol b/test/unit/fuzz/RewardToken.t.sol index 6ffbfa3a..e8ad4d6b 100644 --- a/test/unit/fuzz/RewardToken.t.sol +++ b/test/unit/fuzz/RewardToken.t.sol @@ -29,7 +29,7 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { rewardModule.mint(address(0), amountOfRewards); rewardModule.mint(address(this), amountOfRewards); - assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewards); } @@ -45,14 +45,14 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), amountOfRewards); rewardModule.mint(address(this), amountOfRewards); - assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), 0); vm.expectRevert(abi.encodeWithSelector(RewardToken.InvalidSender.selector, address(0))); rewardModule.burn(address(0), address(this), amountOfRewards); rewardModule.burn(address(this), address(this), amountOfRewards); - assertEq(rewardModule.balanceOf(address(this)), 0); + assertEq(rewardModule.normalizedBalanceOf(address(this)), 0); } function testFuzz_MintRewardWithSupplyFactorChange(uint256 amountOfRewards, uint256 supplyFactorNew) external { @@ -71,8 +71,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { uint256 expectedNormalizedMint1 = amountOfRewards.rayDivDown(supplyFactorOld); - assertEq(rewardModule.balanceOf(address(this)), expectedNormalizedMint1); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), expectedNormalizedMint1); + assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewards); @@ -90,8 +90,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { uint256 totalDepositsNormalized = expectedNormalizedMint1 + expectedNormalizedMint2; uint256 totalValue = totalDepositsNormalized.rayMulDown(supplyFactorNew); - assertEq(rewardModule.balanceOf(address(this)), totalDepositsNormalized); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), totalValue); + assertEq(rewardModule.normalizedBalanceOf(address(this)), totalDepositsNormalized); + assertEq(rewardModule.balanceOf(address(this)), totalValue); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), totalDeposited + interestCreated); } @@ -112,8 +112,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { uint256 expectedNormalizedMint1 = amountOfRewards.rayDivDown(supplyFactorOld); - assertEq(rewardModule.balanceOf(address(this)), expectedNormalizedMint1); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), amountOfRewards); + assertEq(rewardModule.normalizedBalanceOf(address(this)), expectedNormalizedMint1); + assertEq(rewardModule.balanceOf(address(this)), amountOfRewards); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewards); @@ -131,8 +131,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { uint256 totalDepositsNormalized = expectedNormalizedMint1 + expectedNormalizedMint2; uint256 totalValue = totalDepositsNormalized.rayMulDown(supplyFactorNew); - assertEq(rewardModule.balanceOf(address(this)), totalDepositsNormalized); - assertEq(rewardModule.getUnderlyingClaimOf(address(this)), totalValue); + assertEq(rewardModule.normalizedBalanceOf(address(this)), totalDepositsNormalized); + assertEq(rewardModule.balanceOf(address(this)), totalValue); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), totalDeposited + interestCreated); @@ -153,7 +153,7 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), amountOfRewardTokens); rewardModule.mint(address(this), amountOfRewardTokens); - assertEq(rewardModule.balanceOf(address(this)), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(address(this)), amountOfRewardTokens); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewardTokens); @@ -172,8 +172,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { rewardModule.transfer(address(this), amountOfRewardTokens); rewardModule.transfer(receivingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(address(this)), 0); - assertEq(rewardModule.balanceOf(receivingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(address(this)), 0); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), amountOfRewardTokens); assertEq(underlying.balanceOf(address(this)), 0); } @@ -186,8 +186,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), amountOfRewardTokens); rewardModule.mint(sendingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), amountOfRewardTokens); - assertEq(rewardModule.balanceOf(receivingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), 0); assertEq(rewardModule.allowance(sendingUser, spender), 0); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewardTokens); @@ -215,8 +215,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens + 1); rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), 0); - assertEq(rewardModule.balanceOf(receivingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), amountOfRewardTokens); assertEq(rewardModule.allowance(sendingUser, spender), 0); } @@ -229,8 +229,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), amountOfRewardTokens); rewardModule.mint(sendingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), amountOfRewardTokens); - assertEq(rewardModule.balanceOf(receivingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), 0); assertEq(rewardModule.allowance(sendingUser, spender), 0); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), amountOfRewardTokens); @@ -278,8 +278,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens + 1); rewardModule.transferFrom(sendingUser, receivingUser, amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), 0); - assertEq(rewardModule.balanceOf(receivingUser), amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), amountOfRewardTokens); assertEq(rewardModule.allowance(sendingUser, spender), 0); } @@ -306,8 +306,8 @@ contract RewardToken_FuzzUnitTest is RewardTokenSharedSetup { underlying.approve(address(rewardModule), locals.amountOfRewardTokens); rewardModule.mint(sendingUser, locals.amountOfRewardTokens); - assertEq(rewardModule.balanceOf(sendingUser), locals.amountOfRewardTokens); - assertEq(rewardModule.balanceOf(receivingUser), 0); + assertEq(rewardModule.normalizedBalanceOf(sendingUser), locals.amountOfRewardTokens); + assertEq(rewardModule.normalizedBalanceOf(receivingUser), 0); assertEq(rewardModule.allowance(sendingUser, spender), 0); assertEq(underlying.balanceOf(address(this)), 0); assertEq(underlying.balanceOf(address(rewardModule)), locals.amountOfRewardTokens); diff --git a/test/unit/fuzz/vault/Vault.t.sol b/test/unit/fuzz/vault/Vault.t.sol index 5c8011ef..7f034cc1 100644 --- a/test/unit/fuzz/vault/Vault.t.sol +++ b/test/unit/fuzz/vault/Vault.t.sol @@ -37,7 +37,7 @@ contract Vault_Fuzz is VaultSharedSetup { weEthIonPool.supply(address(this), assets, new bytes32[](0)); uint256 expectedClaim = assets; - uint256 resultingClaim = weEthIonPool.getUnderlyingClaimOf(address(this)); + uint256 resultingClaim = weEthIonPool.balanceOf(address(this)); uint256 re = assets - ((assets * RAY - ((assets * RAY) % supplyFactor)) / RAY); assertLe(expectedClaim - resultingClaim, (supplyFactor - 2) / RAY + 1); From c7bf309e9dba7e19eb81e456fa4cd3254e5e65b9 Mon Sep 17 00:00:00 2001 From: Jun Kim <64379343+junkim012@users.noreply.github.com> Date: Mon, 6 May 2024 21:38:58 -0400 Subject: [PATCH 2/3] fix: querying unaccrued interest when paused, caching IDLE balance in reallocate, accrue fee when feePercentage is updated --- src/interfaces/IIonPool.sol | 1 + src/token/RewardToken.sol | 16 ++- src/vault/Vault.sol | 22 ++- test/unit/concrete/vault/Vault.t.sol | 198 ++++++++++++++++++++++++++- test/unit/fuzz/vault/Vault.t.sol | 111 +++++++++++++++ 5 files changed, 337 insertions(+), 11 deletions(-) diff --git a/src/interfaces/IIonPool.sol b/src/interfaces/IIonPool.sol index b28d071f..b7b9e587 100644 --- a/src/interfaces/IIonPool.sol +++ b/src/interfaces/IIonPool.sol @@ -234,4 +234,5 @@ interface IIonPool { function getTotalUnderlyingClaims() external view returns (uint256); function getUnderlyingClaimOf(address user) external view returns (uint256); function extsload(bytes32 slot) external view returns (bytes32); + function balanceOfUnaccrued(address user) external view returns (uint256); } diff --git a/src/token/RewardToken.sol b/src/token/RewardToken.sol index d50aae14..c1887128 100644 --- a/src/token/RewardToken.sol +++ b/src/token/RewardToken.sol @@ -333,8 +333,6 @@ abstract contract RewardToken is $._normalizedBalances[from] = oldSenderBalance - amountNormalized; } $._normalizedBalances[to] += amountNormalized; - - emit Transfer(from, to, amountNormalized); } /** @@ -450,7 +448,7 @@ abstract contract RewardToken is } /** - * @dev Current token balance + * @dev Current claim of the underlying token inclusive of interest to be accrued. * @param user to get balance of */ function balanceOf(address user) public view returns (uint256) { @@ -461,6 +459,14 @@ abstract contract RewardToken is return $._normalizedBalances[user].rayMulDown($.supplyFactor + totalSupplyFactorIncrease); } + /** + * @dev Current claim of the underlying token without accounting for interest to be accrued. + */ + function balanceOfUnaccrued(address user) public view returns (uint256) { + RewardTokenStorage storage $ = _getRewardTokenStorage(); + return $._normalizedBalances[user].rayMulDown($.supplyFactor); + } + /** * @dev Accounting is done in normalized balances * @param user to get normalized balance of @@ -532,9 +538,7 @@ abstract contract RewardToken is } /** - * @dev Current total supply - * - * Normalized total supply and total supply are same in non-rebasing token. + * @dev Normalized total supply. */ function normalizedTotalSupply() public view returns (uint256) { RewardTokenStorage storage $ = _getRewardTokenStorage(); diff --git a/src/vault/Vault.sol b/src/vault/Vault.sol index 33d19436..a3569fcd 100644 --- a/src/vault/Vault.sol +++ b/src/vault/Vault.sol @@ -16,7 +16,6 @@ import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { ReentrancyGuard } from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; import { AccessControlDefaultAdminRules } from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; - /** * @title Ion Lending Vault * @author Molecular Labs @@ -28,6 +27,7 @@ import { AccessControlDefaultAdminRules } from * * @custom:security-contact security@molecularlabs.io */ + contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, ReentrancyGuard { using EnumerableSet for EnumerableSet.AddressSet; using Math for uint256; @@ -115,6 +115,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy */ function updateFeePercentage(uint256 _feePercentage) external onlyRole(OWNER_ROLE) { if (_feePercentage > RAY) revert InvalidFeePercentage(); + _accrueFee(); feePercentage = _feePercentage; } @@ -343,6 +344,8 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy // to the user from the previous function scope. if (pool != IDLE) { pool.withdraw(address(this), transferAmt); + } else { + currentIdleDeposits -= transferAmt; } totalWithdrawn += transferAmt; @@ -372,6 +375,8 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy // contract. if (pool != IDLE) { pool.supply(address(this), transferAmt, new bytes32[](0)); + } else { + currentIdleDeposits += transferAmt; } totalSupplied += transferAmt; @@ -655,7 +660,16 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy for (uint256 i; i != _supportedMarketsLength;) { IIonPool pool = IIonPool(supportedMarkets.at(i)); - uint256 assetsInPool = pool == IDLE ? BASE_ASSET.balanceOf(address(this)) : pool.balanceOf(address(this)); + uint256 assetsInPool; + if (pool == IDLE) { + assetsInPool = BASE_ASSET.balanceOf(address(this)); + } else { + if (pool.paused()) { + assetsInPool = pool.balanceOfUnaccrued(address(this)); + } else { + assetsInPool = pool.balanceOf(address(this)); + } + } assets += assetsInPool; @@ -762,7 +776,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy (feeShares, newTotalAssets) = _accruedFeeShares(); if (feeShares != 0) _mint(feeRecipient, feeShares); - lastTotalAssets = newTotalAssets; // This update happens outside of this function in Metamorpho. + lastTotalAssets = newTotalAssets; emit FeeAccrued(feeShares, newTotalAssets); } @@ -896,7 +910,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy * @return The max amount of assets withdrawable from this IonPool. */ function _withdrawable(IIonPool pool) internal view returns (uint256) { - uint256 currentSupplied = pool.balanceOf(address(this)); // TODO should be balanceOf + uint256 currentSupplied = pool.balanceOf(address(this)); uint256 availableLiquidity = uint256(pool.extsload(ION_POOL_LIQUIDITY_SLOT)); return Math.min(currentSupplied, availableLiquidity); diff --git a/test/unit/concrete/vault/Vault.t.sol b/test/unit/concrete/vault/Vault.t.sol index bb73be56..60c3168a 100644 --- a/test/unit/concrete/vault/Vault.t.sol +++ b/test/unit/concrete/vault/Vault.t.sol @@ -1423,7 +1423,109 @@ abstract contract VaultWithIdlePool is VaultSharedSetup { contract VaultERC4626ExternalViews is VaultSharedSetup { function setUp() public override { super.setUp(); - // markets.push(IDLE); + } + + function test_TotalAssetsWithSinglePausedIonPool() public { + weEthIonPool.updateSupplyCap(type(uint256).max); + weEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); + + supply(address(this), weEthIonPool, 1000e18); + borrow(address(this), weEthIonPool, weEthGemJoin, 100e18, 70e18); + + uint256[] memory allocationCaps = new uint256[](3); + allocationCaps[0] = 20e18; + allocationCaps[1] = 0; + allocationCaps[2] = 0; + + vm.prank(OWNER); + vault.updateAllocationCaps(markets, allocationCaps); + + uint256 depositAmt = 10e18; + setERC20Balance(address(BASE_ASSET), address(this), depositAmt); + vault.deposit(depositAmt, address(this)); + + assertEq(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool balance"); + // pause the weEthIonPool + weEthIonPool.pause(); + + vm.warp(block.timestamp + 365 days); + + assertGt(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool accrues interest"); + assertLt( + weEthIonPool.balanceOfUnaccrued(address(vault)), + weEthIonPool.balanceOf(address(vault)), + "weEthIonPool unaccrued balance" + ); + assertTrue(weEthIonPool.paused(), "weEthIonPool is paused"); + + uint256 totalAssets = vault.totalAssets(); + assertEq(totalAssets, depositAmt, "total assets with paused IonPool does not include interest"); + } + + function test_TotalAssetsWithMultiplePausedIonPools() public { + // Make sure every pool has debt to accrue interest from + uint256 initialSupplyAmt = 1000e18; + weEthIonPool.updateSupplyCap(type(uint256).max); + rsEthIonPool.updateSupplyCap(type(uint256).max); + rswEthIonPool.updateSupplyCap(type(uint256).max); + + weEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); + rsEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); + rswEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); + + supply(address(this), weEthIonPool, initialSupplyAmt); + borrow(address(this), weEthIonPool, weEthGemJoin, 100e18, 70e18); + + supply(address(this), rsEthIonPool, initialSupplyAmt); + borrow(address(this), rsEthIonPool, rsEthGemJoin, 100e18, 70e18); + + supply(address(this), rswEthIonPool, initialSupplyAmt); + borrow(address(this), rswEthIonPool, rswEthGemJoin, 100e18, 70e18); + + uint256[] memory allocationCaps = new uint256[](3); + uint256 weEthIonPoolAmt = 10e18; + uint256 rsEthIonPoolAmt = 20e18; + uint256 rswEthIonPoolAmt = 30e18; + allocationCaps[0] = weEthIonPoolAmt; + allocationCaps[1] = rsEthIonPoolAmt; + allocationCaps[2] = rswEthIonPoolAmt; + + vm.prank(OWNER); + vault.updateAllocationCaps(markets, allocationCaps); + + uint256 depositAmt = 60e18; + setERC20Balance(address(BASE_ASSET), address(this), depositAmt); + vault.deposit(depositAmt, address(this)); + + assertEq(weEthIonPool.balanceOf(address(vault)), weEthIonPoolAmt, "weEthIonPool balance"); + assertEq(rsEthIonPool.balanceOf(address(vault)), rsEthIonPoolAmt, "rsEthIonPool balance"); + assertEq(rswEthIonPool.balanceOf(address(vault)), rswEthIonPoolAmt, "rswEthIonPool balance"); + + weEthIonPool.pause(); + // NOTE rsEthIonPool is not paused + rswEthIonPool.pause(); + + assertTrue(weEthIonPool.paused(), "weEthIonPool is paused"); + assertFalse(rsEthIonPool.paused(), "rsEthIonPool is not paused"); + assertTrue(rswEthIonPool.paused(), "rswEthIonPool is paused"); + + vm.warp(block.timestamp + 365 days); + + assertGt(weEthIonPool.balanceOf(address(vault)), weEthIonPoolAmt, "weEthIonPool balance increases"); + assertGt(rsEthIonPool.balanceOf(address(vault)), rsEthIonPoolAmt, "rsEthIonPool balance does not change"); + assertGt(rswEthIonPool.balanceOf(address(vault)), rswEthIonPoolAmt, "rswEthIonPool balance increases"); + + // The 'unaccrued' values should not change + assertEq(weEthIonPool.balanceOfUnaccrued(address(vault)), weEthIonPoolAmt, "weEthIonPool balance"); + assertEq(rsEthIonPool.balanceOfUnaccrued(address(vault)), rsEthIonPoolAmt, "rsEthIonPool balance"); + assertEq(rswEthIonPool.balanceOfUnaccrued(address(vault)), rswEthIonPoolAmt, "rswEthIonPool balance"); + + uint256 expectedTotalAssets = weEthIonPool.balanceOfUnaccrued(address(vault)) + + rsEthIonPool.balanceOf(address(vault)) + rswEthIonPool.balanceOfUnaccrued(address(vault)); + + assertEq( + vault.totalAssets(), expectedTotalAssets, "total assets without accounting for interest in paused IonPools" + ); } // --- Max --- @@ -1495,6 +1597,100 @@ contract VaultERC4626ExternalViews is VaultSharedSetup { function test_PreviewRedeem() public { } } +contract VaultInflationAttack is VaultSharedSetup { + function setUp() public override { + super.setUp(); + } + + /** + * Starting Attacker Balance: 11e18 + 10 + * Attacker Mint: 10 shares + * Attacker Donation: 11e18 + * Alice Deposit: 1e18 + * Alice Shares Minted: + * + * How much did the attacker lose during the donation? + * Attacker Donated 11e18, + */ + function test_InflationAttackNotProfitable() public { + IIonPool[] memory market = new IIonPool[](1); + market[0] = IDLE; + + uint256[] memory allocationCaps = new uint256[](1); + allocationCaps[0] = type(uint256).max; + + IIonPool[] memory queue = new IIonPool[](4); + queue[0] = IDLE; + queue[1] = weEthIonPool; + queue[2] = rsEthIonPool; + queue[3] = rswEthIonPool; + + vm.prank(OWNER); + vault.addSupportedMarkets(market, allocationCaps, queue, queue); + + uint256 donationAmt = 11e18; + uint256 mintAmt = 10; + + // fund attacker + setERC20Balance(address(BASE_ASSET), address(this), donationAmt + mintAmt); + + uint256 initialAssetBalance = BASE_ASSET.balanceOf(address(this)); + console2.log("attacker balance before : "); + console2.log(initialAssetBalance); + + vault.mint(mintAmt, address(this)); + uint256 attackerClaimAfterMint = vault.previewRedeem(vault.balanceOf(address(this))); + + console2.log("attackerClaimAfterMint: "); + console2.log(attackerClaimAfterMint); + + console2.log("donationAmt: "); + console2.log(donationAmt); + + // donate to inflate exchange rate by increasing `totalAssets` + IERC20(address(BASE_ASSET)).transfer(address(vault), donationAmt); + + // how much of this donation was captured by the virtual shares on the vault? + uint256 attackerClaimAfterDonation = vault.previewRedeem(vault.balanceOf(address(this))); + + console2.log("attackerClaimAfterDonation: "); + console2.log(attackerClaimAfterDonation); + + uint256 lossFromDonation = attackerClaimAfterMint + donationAmt - attackerClaimAfterDonation; + + console2.log("loss from donation: "); + console2.log(lossFromDonation); + + address alice = address(0xabcd); + setERC20Balance(address(BASE_ASSET), alice, 10e18 + 10); + + vm.startPrank(alice); + IERC20(address(BASE_ASSET)).approve(address(vault), 1e18); + vault.deposit(1e18, alice); + vm.stopPrank(); + + // Alice gained zero shares due to exchange rate inflation + uint256 aliceShares = vault.balanceOf(alice); + console.log("alice must lose all her shares : "); + console.log(aliceShares); + + // How much of alice's deposits were captured by the attacker's shares? + uint256 attackerClaimAfterAlice = vault.previewRedeem(vault.balanceOf(address(this))); + uint256 attackerGainFromAlice = attackerClaimAfterAlice - attackerClaimAfterDonation; + console2.log("attackerGainFromAlice: "); + console2.log(attackerGainFromAlice); + + vault.redeem(vault.balanceOf(address(this)) - 3, address(this), address(this)); + uint256 afterAssetBalance = BASE_ASSET.balanceOf(address(this)); + + console.log("attacker balance after : "); + console.log(afterAssetBalance); + + assertLe(attackerGainFromAlice, lossFromDonation, "attack must not be profitable"); + assertLe(afterAssetBalance, initialAssetBalance, "attacker must not be profitable"); + } +} + contract VaultDeposit_WithoutSupplyFactor is VaultDeposit { function setUp() public override(VaultDeposit) { super.setUp(); diff --git a/test/unit/fuzz/vault/Vault.t.sol b/test/unit/fuzz/vault/Vault.t.sol index 7f034cc1..32cc524f 100644 --- a/test/unit/fuzz/vault/Vault.t.sol +++ b/test/unit/fuzz/vault/Vault.t.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.21; +import { IIonPool } from "./../../../../src/interfaces/IIonPool.sol"; import { IonPoolExposed } from "../../../helpers/IonPoolSharedSetup.sol"; import { VaultSharedSetup } from "../../../helpers/VaultSharedSetup.sol"; import { Math } from "openzeppelin-contracts/contracts/utils/math/Math.sol"; import { WadRayMath, RAY, WAD } from "./../../../../src/libraries/math/WadRayMath.sol"; import { console2 } from "forge-std/console2.sol"; +import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; using Math for uint256; @@ -42,6 +44,17 @@ contract Vault_Fuzz is VaultSharedSetup { uint256 re = assets - ((assets * RAY - ((assets * RAY) % supplyFactor)) / RAY); assertLe(expectedClaim - resultingClaim, (supplyFactor - 2) / RAY + 1); } + + function testFuzz_FullyWithdrawableFromIonPool(uint256 assets) public { + uint256 supplyFactor = bound(assets, 1e27, 10e45); + uint256 normalizedAmt = bound(assets, 0, type(uint48).max); + + uint256 claim = normalizedAmt * supplyFactor / RAY; + uint256 sharesToBurn = claim * RAY / supplyFactor; + sharesToBurn = sharesToBurn * supplyFactor < claim * RAY ? sharesToBurn + 1 : sharesToBurn; + + assertEq(normalizedAmt, sharesToBurn); + } } contract VaultWithYieldAndFee_Fuzz is VaultSharedSetup { @@ -158,3 +171,101 @@ contract VaultWithYieldAndFee_Fuzz is VaultSharedSetup { function testFuzz_MaxDepositAndMaxMint(uint256 assets) public { } } + +contract VaultInflationAttack is VaultSharedSetup { + address immutable ATTACKER = newAddress("attacker"); + address immutable USER = newAddress("user"); + + function setUp() public override { + super.setUp(); + + IIonPool[] memory market = new IIonPool[](1); + market[0] = IDLE; + + uint256[] memory allocationCaps = new uint256[](1); + allocationCaps[0] = type(uint256).max; + + IIonPool[] memory queue = new IIonPool[](4); + queue[0] = IDLE; + queue[1] = weEthIonPool; + queue[2] = rsEthIonPool; + queue[3] = rswEthIonPool; + + vm.prank(OWNER); + vault.addSupportedMarkets(market, allocationCaps, queue, queue); + + vm.prank(ATTACKER); + IERC20(address(BASE_ASSET)).approve(address(vault), type(uint256).max); + vm.prank(USER); + IERC20(address(BASE_ASSET)).approve(address(vault), type(uint256).max); + } + + function testFuzz_InflationAttackNotProfitable(uint256 assets) public { + // 1. The vault has not been used. + // - no shares minted and no assets deposited. + // - but the initial conversion is dictated by virtual shares. + assertEq(vault.totalSupply(), 0, "initial total supply"); + assertEq(vault.totalAssets(), 0, "initial total assets"); + + // 2. The attacker makes a first deposit. + uint256 firstDepositAmt = bound(assets, 0, type(uint128).max); + setERC20Balance(address(BASE_ASSET), ATTACKER, firstDepositAmt); + + vm.prank(ATTACKER); + vault.mint(firstDepositAmt, ATTACKER); + + uint256 attackerClaimAfterMint = vault.previewRedeem(vault.balanceOf(ATTACKER)); + + // check that the mint amount and transfer amount was the same + assertEq(BASE_ASSET.balanceOf(ATTACKER), 0, "mint amount equals transfer amount"); + + // 3. The attacker donates. + // - In this case, transfers to vault to increase IDLE deposits. + // - Check that the attacker loses a portion of the donated funds. + uint256 donationAmt = bound(assets, 0, type(uint128).max); + setERC20Balance(address(BASE_ASSET), ATTACKER, donationAmt); + + vm.prank(ATTACKER); + IERC20(address(BASE_ASSET)).transfer(address(vault), donationAmt); + + uint256 attackerClaimAfterDonation = vault.previewRedeem(vault.balanceOf(ATTACKER)); + uint256 attackerLossFromDonation = donationAmt - (attackerClaimAfterDonation - attackerClaimAfterMint); + + uint256 totalAssetsBeforeDeposit = vault.totalAssets(); + uint256 totalSupplyBeforeDeposit = vault.totalSupply(); + + // 4. A user makes a deposit where the shares truncate to zero. + // - sharesToMint = depositAmt * (newTotalSupply + 1) / (newTotalAssets + 1) + // - The sharesToMint must be less than 1 to round down to zero + // - depositAmt * (newTotalSupply + 1) / (newTotalAssets + 1) < 1 + // - depositAmt < 1 * (newTotalAssets + 1) / (newTotalSupply + 1) + uint256 maxDepositAmt = (vault.totalAssets() + 1) / (vault.totalSupply() + 1); + uint256 userDepositAmt = bound(assets, 0, maxDepositAmt); + + vm.startPrank(USER); + setERC20Balance(address(BASE_ASSET), USER, userDepositAmt); + IERC20(address(BASE_ASSET)).approve(address(vault), userDepositAmt); + vault.deposit(userDepositAmt, USER); + vm.stopPrank(); + + assertEq(vault.balanceOf(USER), 0, "user minted shares must be zero"); + + uint256 attackerClaimAfterUser = vault.previewRedeem(vault.balanceOf(ATTACKER)); + uint256 attackerGainFromUser = attackerClaimAfterUser - attackerClaimAfterDonation; + + // loss = donationAmt / (1 + firstDepositAmt) + uint256 expectedAttackerLossFromDonation = donationAmt / (1 + firstDepositAmt); + assertLe( + attackerLossFromDonation - expectedAttackerLossFromDonation, + 1, + "attacker loss from donation as expected with rounding error" + ); + + // INVARIANT: The money gained from the user must be less than or equal to the attacker's loss from the + // donation. + // assertLe(attackerGainFromUser, attackerLossFromDonation, "attacker must not profit from user"); + assertLe(userDepositAmt, attackerLossFromDonation, "loss must be ge to user deposit"); + } + + function testFuzz_InflationAttackSmallerDegree() public { } +} From aade327cce25ac4c84c712bfc963a165ecd7a9c4 Mon Sep 17 00:00:00 2001 From: Jun Kim <64379343+junkim012@users.noreply.github.com> Date: Sun, 12 May 2024 01:45:08 -0400 Subject: [PATCH 3/3] fix: revert the non-rebasing model of RewardToken to rebasing; changes must only be renaming function names --- src/interfaces/IIonPool.sol | 1 - src/token/RewardToken.sol | 8 --- src/vault/Vault.sol | 16 +---- test/unit/concrete/vault/Vault.t.sol | 103 --------------------------- 4 files changed, 1 insertion(+), 127 deletions(-) diff --git a/src/interfaces/IIonPool.sol b/src/interfaces/IIonPool.sol index b7b9e587..b28d071f 100644 --- a/src/interfaces/IIonPool.sol +++ b/src/interfaces/IIonPool.sol @@ -234,5 +234,4 @@ interface IIonPool { function getTotalUnderlyingClaims() external view returns (uint256); function getUnderlyingClaimOf(address user) external view returns (uint256); function extsload(bytes32 slot) external view returns (bytes32); - function balanceOfUnaccrued(address user) external view returns (uint256); } diff --git a/src/token/RewardToken.sol b/src/token/RewardToken.sol index c1887128..aa9c3120 100644 --- a/src/token/RewardToken.sol +++ b/src/token/RewardToken.sol @@ -459,14 +459,6 @@ abstract contract RewardToken is return $._normalizedBalances[user].rayMulDown($.supplyFactor + totalSupplyFactorIncrease); } - /** - * @dev Current claim of the underlying token without accounting for interest to be accrued. - */ - function balanceOfUnaccrued(address user) public view returns (uint256) { - RewardTokenStorage storage $ = _getRewardTokenStorage(); - return $._normalizedBalances[user].rayMulDown($.supplyFactor); - } - /** * @dev Accounting is done in normalized balances * @param user to get normalized balance of diff --git a/src/vault/Vault.sol b/src/vault/Vault.sol index a3569fcd..6b42ebc7 100644 --- a/src/vault/Vault.sol +++ b/src/vault/Vault.sol @@ -115,7 +115,6 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy */ function updateFeePercentage(uint256 _feePercentage) external onlyRole(OWNER_ROLE) { if (_feePercentage > RAY) revert InvalidFeePercentage(); - _accrueFee(); feePercentage = _feePercentage; } @@ -344,8 +343,6 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy // to the user from the previous function scope. if (pool != IDLE) { pool.withdraw(address(this), transferAmt); - } else { - currentIdleDeposits -= transferAmt; } totalWithdrawn += transferAmt; @@ -375,8 +372,6 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy // contract. if (pool != IDLE) { pool.supply(address(this), transferAmt, new bytes32[](0)); - } else { - currentIdleDeposits += transferAmt; } totalSupplied += transferAmt; @@ -660,16 +655,7 @@ contract Vault is ERC4626, Multicall, AccessControlDefaultAdminRules, Reentrancy for (uint256 i; i != _supportedMarketsLength;) { IIonPool pool = IIonPool(supportedMarkets.at(i)); - uint256 assetsInPool; - if (pool == IDLE) { - assetsInPool = BASE_ASSET.balanceOf(address(this)); - } else { - if (pool.paused()) { - assetsInPool = pool.balanceOfUnaccrued(address(this)); - } else { - assetsInPool = pool.balanceOf(address(this)); - } - } + uint256 assetsInPool = pool == IDLE ? BASE_ASSET.balanceOf(address(this)) : pool.balanceOf(address(this)); assets += assetsInPool; diff --git a/test/unit/concrete/vault/Vault.t.sol b/test/unit/concrete/vault/Vault.t.sol index 60c3168a..31ad2d14 100644 --- a/test/unit/concrete/vault/Vault.t.sol +++ b/test/unit/concrete/vault/Vault.t.sol @@ -1425,109 +1425,6 @@ contract VaultERC4626ExternalViews is VaultSharedSetup { super.setUp(); } - function test_TotalAssetsWithSinglePausedIonPool() public { - weEthIonPool.updateSupplyCap(type(uint256).max); - weEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); - - supply(address(this), weEthIonPool, 1000e18); - borrow(address(this), weEthIonPool, weEthGemJoin, 100e18, 70e18); - - uint256[] memory allocationCaps = new uint256[](3); - allocationCaps[0] = 20e18; - allocationCaps[1] = 0; - allocationCaps[2] = 0; - - vm.prank(OWNER); - vault.updateAllocationCaps(markets, allocationCaps); - - uint256 depositAmt = 10e18; - setERC20Balance(address(BASE_ASSET), address(this), depositAmt); - vault.deposit(depositAmt, address(this)); - - assertEq(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool balance"); - // pause the weEthIonPool - weEthIonPool.pause(); - - vm.warp(block.timestamp + 365 days); - - assertGt(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool accrues interest"); - assertLt( - weEthIonPool.balanceOfUnaccrued(address(vault)), - weEthIonPool.balanceOf(address(vault)), - "weEthIonPool unaccrued balance" - ); - assertTrue(weEthIonPool.paused(), "weEthIonPool is paused"); - - uint256 totalAssets = vault.totalAssets(); - assertEq(totalAssets, depositAmt, "total assets with paused IonPool does not include interest"); - } - - function test_TotalAssetsWithMultiplePausedIonPools() public { - // Make sure every pool has debt to accrue interest from - uint256 initialSupplyAmt = 1000e18; - weEthIonPool.updateSupplyCap(type(uint256).max); - rsEthIonPool.updateSupplyCap(type(uint256).max); - rswEthIonPool.updateSupplyCap(type(uint256).max); - - weEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); - rsEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); - rswEthIonPool.updateIlkDebtCeiling(0, type(uint256).max); - - supply(address(this), weEthIonPool, initialSupplyAmt); - borrow(address(this), weEthIonPool, weEthGemJoin, 100e18, 70e18); - - supply(address(this), rsEthIonPool, initialSupplyAmt); - borrow(address(this), rsEthIonPool, rsEthGemJoin, 100e18, 70e18); - - supply(address(this), rswEthIonPool, initialSupplyAmt); - borrow(address(this), rswEthIonPool, rswEthGemJoin, 100e18, 70e18); - - uint256[] memory allocationCaps = new uint256[](3); - uint256 weEthIonPoolAmt = 10e18; - uint256 rsEthIonPoolAmt = 20e18; - uint256 rswEthIonPoolAmt = 30e18; - allocationCaps[0] = weEthIonPoolAmt; - allocationCaps[1] = rsEthIonPoolAmt; - allocationCaps[2] = rswEthIonPoolAmt; - - vm.prank(OWNER); - vault.updateAllocationCaps(markets, allocationCaps); - - uint256 depositAmt = 60e18; - setERC20Balance(address(BASE_ASSET), address(this), depositAmt); - vault.deposit(depositAmt, address(this)); - - assertEq(weEthIonPool.balanceOf(address(vault)), weEthIonPoolAmt, "weEthIonPool balance"); - assertEq(rsEthIonPool.balanceOf(address(vault)), rsEthIonPoolAmt, "rsEthIonPool balance"); - assertEq(rswEthIonPool.balanceOf(address(vault)), rswEthIonPoolAmt, "rswEthIonPool balance"); - - weEthIonPool.pause(); - // NOTE rsEthIonPool is not paused - rswEthIonPool.pause(); - - assertTrue(weEthIonPool.paused(), "weEthIonPool is paused"); - assertFalse(rsEthIonPool.paused(), "rsEthIonPool is not paused"); - assertTrue(rswEthIonPool.paused(), "rswEthIonPool is paused"); - - vm.warp(block.timestamp + 365 days); - - assertGt(weEthIonPool.balanceOf(address(vault)), weEthIonPoolAmt, "weEthIonPool balance increases"); - assertGt(rsEthIonPool.balanceOf(address(vault)), rsEthIonPoolAmt, "rsEthIonPool balance does not change"); - assertGt(rswEthIonPool.balanceOf(address(vault)), rswEthIonPoolAmt, "rswEthIonPool balance increases"); - - // The 'unaccrued' values should not change - assertEq(weEthIonPool.balanceOfUnaccrued(address(vault)), weEthIonPoolAmt, "weEthIonPool balance"); - assertEq(rsEthIonPool.balanceOfUnaccrued(address(vault)), rsEthIonPoolAmt, "rsEthIonPool balance"); - assertEq(rswEthIonPool.balanceOfUnaccrued(address(vault)), rswEthIonPoolAmt, "rswEthIonPool balance"); - - uint256 expectedTotalAssets = weEthIonPool.balanceOfUnaccrued(address(vault)) - + rsEthIonPool.balanceOf(address(vault)) + rswEthIonPool.balanceOfUnaccrued(address(vault)); - - assertEq( - vault.totalAssets(), expectedTotalAssets, "total assets without accounting for interest in paused IonPools" - ); - } - // --- Max --- // Get max and submit max transactions