Skip to content

Commit

Permalink
fix: handle interest accrual while paused in calculateRewardAndDebtDi…
Browse files Browse the repository at this point in the history
…stributionForIlk
  • Loading branch information
junkim012 committed May 15, 2024
1 parent c39ad6c commit 597dbf9
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 21 deletions.
5 changes: 4 additions & 1 deletion src/IonPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,10 @@ contract IonPool is PausableUpgradeable, RewardToken {
Ilk storage ilk = $.ilks[ilkIndex];

uint256 _totalNormalizedDebt = ilk.totalNormalizedDebt;
if (_totalNormalizedDebt == 0 || block.timestamp == ilk.lastRateUpdate) {
// Because all interest that would have accrued during a pause is
// cancelled upon `unpause`, we return zero interest while markets are
// paused.
if (_totalNormalizedDebt == 0 || block.timestamp == ilk.lastRateUpdate || paused()) {
// Unsafe cast OK
// block.timestamp - ilk.lastRateUpdate will almost always be 0
// here. The exception is on first borrow.
Expand Down
11 changes: 1 addition & 10 deletions src/vault/Vault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -655,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;

Expand Down
61 changes: 61 additions & 0 deletions test/unit/concrete/IonPool.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,67 @@ contract IonPool_InterestTest is IonPoolSharedSetup, IIonPoolEvents {
previousRates[i] = rate;
}
}

function test_AccrueInterestWhenPaused() public {
uint256 collateralDepositAmount = 10e18;
uint256 normalizedBorrowAmount = 5e18;

for (uint8 i = 0; i < lens.ilkCount(iIonPool); i++) {
vm.prank(borrower1);
ionPool.depositCollateral(i, borrower1, borrower1, collateralDepositAmount, new bytes32[](0));

uint256 rate = ionPool.rate(i);
uint256 liquidityBefore = lens.liquidity(iIonPool);

assertEq(ionPool.collateral(i, borrower1), collateralDepositAmount);
assertEq(underlying.balanceOf(borrower1), normalizedBorrowAmount.rayMulDown(rate) * i);

vm.prank(borrower1);
ionPool.borrow(i, borrower1, borrower1, normalizedBorrowAmount, new bytes32[](0));

uint256 liquidityRemoved = normalizedBorrowAmount.rayMulDown(rate);

assertEq(ionPool.normalizedDebt(i, borrower1), normalizedBorrowAmount);
assertEq(lens.totalNormalizedDebt(iIonPool, i), normalizedBorrowAmount);
assertEq(lens.liquidity(iIonPool), liquidityBefore - liquidityRemoved);
assertEq(underlying.balanceOf(borrower1), normalizedBorrowAmount.rayMulDown(rate) * (i + 1));
}

vm.warp(block.timestamp + 1 hours);

ionPool.pause();

uint256 rate0AfterPause = ionPool.rate(0);
uint256 rate1AfterPause = ionPool.rate(1);
uint256 rate2AfterPause = ionPool.rate(2);

uint256 supplyFactorAfterPause = ionPool.supplyFactor();
uint256 lenderBalanceAfterPause = ionPool.balanceOf(lender2);

vm.warp(block.timestamp + 365 days);

(
uint256 totalSupplyFactorIncrease,
uint256 treasuryMintAmount,
uint104[] memory rateIncreases,
uint256 totalDebtIncrease,
uint48[] memory timestampIncreases
) = ionPool.calculateRewardAndDebtDistribution();

assertEq(totalSupplyFactorIncrease, 0, "no supply factor increase");
assertEq(treasuryMintAmount, 0, "no treasury mint amount");
for (uint8 i = 0; i < lens.ilkCount(iIonPool); i++) {
assertEq(rateIncreases[i], 0, "no rate increase");
assertEq(timestampIncreases[i], 365 days, "no timestamp increase");
}
assertEq(totalDebtIncrease, 0, "no total debt increase");

assertEq(ionPool.balanceOf(lender2), lenderBalanceAfterPause, "lender balance doesn't change");
assertEq(ionPool.supplyFactor(), supplyFactorAfterPause, "supply factor doesn't change");
assertEq(ionPool.rate(0), rate0AfterPause, "rate 0 doesn't change");
assertEq(ionPool.rate(1), rate1AfterPause, "rate 1 doesn't change");
assertEq(ionPool.rate(2), rate2AfterPause, "rate 2 doesn't change");
}
}

contract IonPool_AdminTest is IonPoolSharedSetup {
Expand Down
45 changes: 37 additions & 8 deletions test/unit/concrete/vault/Vault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1445,21 +1445,35 @@ contract VaultERC4626ExternalViews is VaultSharedSetup {
vault.deposit(depositAmt, address(this));

assertEq(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool balance");
// pause the weEthIonPool

// Pause the weEthIonPool, stop accruing interest
weEthIonPool.pause();
assertTrue(weEthIonPool.paused(), "weEthIonPool is paused");

vm.warp(block.timestamp + 365 days);

assertGt(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool accrues interest");
assertLt(
assertEq(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool accrues interest");
assertEq(
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");

// When unpaused, should now accrue interest
weEthIonPool.unpause();
vm.warp(block.timestamp + 365 days);

assertGt(weEthIonPool.balanceOf(address(vault)), depositAmt, "weEthIonPool accrues interest");
assertGt(
weEthIonPool.balanceOf(address(vault)),
weEthIonPool.balanceOfUnaccrued(address(vault)),
"weEthIonPool unaccrued balance"
);

assertGt(vault.totalAssets(), depositAmt, "total assets with paused IonPool does not include interest");
}

function test_TotalAssetsWithMultiplePausedIonPools() public {
Expand Down Expand Up @@ -1511,15 +1525,30 @@ contract VaultERC4626ExternalViews is VaultSharedSetup {

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");

// When paused, the unaccrued and accrued balanceOf should be the same
assertEq(
weEthIonPool.balanceOf(address(vault)),
weEthIonPool.balanceOfUnaccrued(address(vault)),
"weEthIonPool balance increases"
);
assertEq(
rswEthIonPool.balanceOf(address(vault)),
rswEthIonPool.balanceOfUnaccrued(address(vault)),
"rswEthIonPool balance increases"
);

// When not paused, the accrued balanceOf should be greater
assertGt(
rsEthIonPool.balanceOf(address(vault)),
rsEthIonPool.balanceOfUnaccrued(address(vault)),
"rsEthIonPool balance does not change"
);

uint256 expectedTotalAssets = weEthIonPool.balanceOfUnaccrued(address(vault))
+ rsEthIonPool.balanceOf(address(vault)) + rswEthIonPool.balanceOfUnaccrued(address(vault));

Expand Down
5 changes: 3 additions & 2 deletions test/unit/fuzz/vault/Vault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ contract VaultWithYieldAndFee_Fuzz is VaultSharedSetup {
// expected resulting state

uint256 expectedFeeAssets = interestAccrued.mulDiv(feePerc, RAY);
uint256 expectedFeeShares =
expectedFeeAssets.mulDiv(vault.totalSupply(), newTotalAssets - expectedFeeAssets, Math.Rounding.Floor);
uint256 expectedFeeShares = expectedFeeAssets.mulDiv(
vault.totalSupply() + 1, newTotalAssets - expectedFeeAssets + 1, Math.Rounding.Floor
);

uint256 expectedUserAssets = prevUserAssets + interestAccrued.mulDiv(RAY - feePerc, RAY);

Expand Down

0 comments on commit 597dbf9

Please sign in to comment.