Skip to content

Commit

Permalink
Merge pull request #17 from euler-xyz/cantina-331
Browse files Browse the repository at this point in the history
Cantina 331: EulerSavingsRate: Lack of early interest accruals due to precision loss
  • Loading branch information
dglowinski authored Jul 30, 2024
2 parents 6d8747e + b25a795 commit 47188d2
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 8 deletions.
19 changes: 11 additions & 8 deletions src/Synths/EulerSavingsRate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft;
if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function

esrSlotCache.lastInterestUpdate = uint40(block.timestamp);
esrSlotCache.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR);
esrSlotCache.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168

Expand All @@ -157,15 +158,17 @@ contract EulerSavingsRate is EVCUtil, ERC4626 {
ESRSlot memory esrSlotCache = esrSlot;
uint256 accruedInterest = interestAccruedFromCache(esrSlotCache);

// it's safe to down-cast because the accrued interest is a fraction of interest left
esrSlotCache.interestLeft -= uint168(accruedInterest);
esrSlotCache.lastInterestUpdate = uint40(block.timestamp);
// write esrSlotCache back to storage in a single SSTORE
esrSlot = esrSlotCache;
// Move interest accrued to totalAssets
_totalAssets = _totalAssets + accruedInterest;
if (accruedInterest > 0) {
// it's safe to down-cast because the accrued interest is a fraction of interest left
esrSlotCache.interestLeft -= uint168(accruedInterest);
esrSlotCache.lastInterestUpdate = uint40(block.timestamp);
// write esrSlotCache back to storage in a single SSTORE
esrSlot = esrSlotCache;
// Move interest accrued to totalAssets
_totalAssets = _totalAssets + accruedInterest;

emit InterestUpdated(accruedInterest, esrSlotCache.interestLeft);
emit InterestUpdated(accruedInterest, esrSlotCache.interestLeft);
}

return esrSlotCache;
}
Expand Down
36 changes: 36 additions & 0 deletions test/unit/esr/ESR.Fuzz.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,42 @@ contract ESRFuzzTest is ESRTest {
}
}

function testFuzz_conditionalAccruedInterestUpdate(uint32 interestAmount) public {
// min deposit requirement before gulp
doDeposit(user, 1e7);

// mint some interest to be distributed
asset.mint(address(esr), interestAmount);

uint256 balance = asset.balanceOf(address(esr));
uint256 totalAssets = esr.totalAssets();

esr.gulp();
skip(1);

if (interestAmount < esr.INTEREST_SMEAR()) {
assertEq(esr.totalAssets(), totalAssets);
assertEq(esr.totalAssets() + interestAmount, balance);
} else {
uint256 accruedInterest = interestAmount / esr.INTEREST_SMEAR();
assertEq(esr.totalAssets() + interestAmount - accruedInterest, balance);
vm.expectEmit();
emit EulerSavingsRate.InterestUpdated(accruedInterest, interestAmount - accruedInterest);
}

vm.recordLogs();
esr.gulp();

Vm.Log[] memory logs = vm.getRecordedLogs();
if (interestAmount < esr.INTEREST_SMEAR()) {
assertEq(logs.length, 1);
assertNotEq(logs[0].topics[0], EulerSavingsRate.InterestUpdated.selector);
} else {
assertEq(logs.length, 2);
assertEq(logs[0].topics[0], EulerSavingsRate.InterestUpdated.selector);
}
}

// fuzz test that any deposits added are added to the totalAssetsDeposited
function testFuzz_deposit(uint256 depositAmount, uint256 depositAmount2) public {
depositAmount = bound(depositAmount, 0, type(uint112).max);
Expand Down

0 comments on commit 47188d2

Please sign in to comment.