Skip to content

feat(irm): accrue interest before changing borrow rate bounds - #228

Merged
qingyang-lista merged 2 commits into
masterfrom
feat/irm-accrue-before-rate-bounds-update
Aug 18, 2026
Merged

feat(irm): accrue interest before changing borrow rate bounds#228
qingyang-lista merged 2 commits into
masterfrom
feat/irm-accrue-before-rate-bounds-update

Conversation

@razww

@razww razww commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Problem

Changing a market's rate cap, rate floor, or fixed borrow rate took effect immediately, retroactively repricing all interest that had accrued since the market's last update at the new value. Lowering a cap re-priced past interest downward; raising a floor re-priced it upward.

Change

Both IRMs now call Moolah.accrueInterest for the market before writing the new value, so elapsed interest is settled at the bounds that were actually in effect and only future interest uses the new ones.

Contract Entry points
InterestRateModel updateRateCap, updateRateFloor
FixedRateIrm updateRateCap, updateRateFloor, setBorrowRate
function _accrueInterest(Id id) private {
  if (IMoolah(MOOLAH).market(id).lastUpdate == 0) return;
  IMoolah(MOOLAH).accrueInterest(IMoolah(MOOLAH).idToMarketParams(id));
}

Two details worth reviewing closely:

  • Placement is load-bearing. The accrual runs after the validation requires (so an invalid update doesn't pay for it) and before the storage write. Moolah calls back into the IRM's borrowRate during accrual, so the callback must still observe the old value — moving the call below the assignment would silently invert the fix.
  • No-op when the market doesn't exist (lastUpdate == 0). Moolah.accrueInterest reverts with MARKET_NOT_CREATED otherwise, which would break pre-setting bounds ahead of market creation. FixedRateIrm.setBorrowRate needs this specifically — it is documented as running before the market is created.

updateMinCap is deliberately untouched: it is global across all markets, so there is no single market to accrue. Giving it the same guarantee needs a different design.

Breaking: FixedRateIrm constructor

FixedRateIrm held no reference to Moolah, so it gains a MOOLAH immutable and a constructor argument, matching InterestRateModel. All five construction sites were updated:

Site Moolah
script/deploy_fixedRateIrm_impl.sol BSC 0x8F73…5D8C
script/eth/deploy_fixedRateIrm.sol ETH 0xf820…Fd70
test/interest-rate-model/AlphaIrmTest.sol BSC mainnet (fork)
test/utils/PositionManager.t.sol local moolah
test/interest-rate-model/FixedRateIrmTest.sol address(this) + market(Id) stub

This ships as a fresh implementation deploy via the updated scripts, not an in-place proxy upgrade. The immutable lives in runtime bytecode, so it consumes no storage slot and does not disturb the existing layout — but a wrong address is only fixable by another upgrade. Please sanity-check the ETH address, taken from script/eth/deploy_irm_impl.sol.

Testing

  • forge build — compiler run successful
  • FixedRateIrmTest — 13/13 pass
  • InterestRateModelTest — 23/23 pass

Not exercised: AlphaIrmTest and PositionManager.t.sol are fork tests — they compile, but running them needs an RPC.

🤖 Generated with Claude Code

Changing a market's rate cap, rate floor, or fixed borrow rate used to
take effect immediately, retroactively repricing all interest that had
accrued since the market's last update at the new value. Both IRMs now
call Moolah.accrueInterest for the market first, so elapsed interest is
settled at the bounds that were actually in effect and only future
interest uses the new ones.

Affected entry points:
- InterestRateModel: updateRateCap, updateRateFloor
- FixedRateIrm: updateRateCap, updateRateFloor, setBorrowRate

The accrual runs after the validation requires and before the storage
write. Ordering matters: Moolah calls back into the IRM's borrowRate
during accrual, so the callback must still observe the old value.

Accrual is a no-op when the market does not exist yet (lastUpdate == 0),
which keeps bounds settable ahead of market creation. FixedRateIrm needs
this in particular, since setBorrowRate is documented as running before
the market is created.

FixedRateIrm held no reference to Moolah, so it gains a MOOLAH immutable
and a constructor argument. This is a breaking constructor change: the
new implementation must be deployed via the updated scripts rather than
upgraded in place, and the address is baked into bytecode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hashdit-bot

hashdit-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

Pull Request Review

This Solidity lending-protocol PR updates both interest-rate models to accrue pending market interest before changing rate caps, floors, or fixed borrow rates, preventing retroactive repricing. It also adds an immutable Moolah address to FixedRateIrm, updates deployment and test construction sites, and introduces Moolah stubs in unit tests.

Sensitive Content

No sensitive content detected.

Security Issues

No serious security issues detected.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

The suite forked mainnet at latest (no pinned block) and asserted live
config values, which made it a time bomb: EthUpgradeForkTest's
test_postUpgrade_liquidatorHasReflowBlacklist asserted that the ETH
Liquidator's fundSource is address(0) on the reasoning that an upgrade
with empty data leaves it at the default. fundSource is storage, so
upgrading the implementation never resets it -- the assertion only held
while nobody had set it on mainnet.

It is now 0xC1D8F6072D8a987BCbB675dc4805E8A2dE604899 on-chain, so the
test failed on every PR and on master regardless of the change under
test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@razww
razww force-pushed the feat/irm-accrue-before-rate-bounds-update branch from 7dc8574 to 29a4067 Compare August 17, 2026 08:53

@qingyang-lista qingyang-lista left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@qingyang-lista
qingyang-lista merged commit 9736182 into master Aug 18, 2026
8 checks passed
@qingyang-lista
qingyang-lista deleted the feat/irm-accrue-before-rate-bounds-update branch August 18, 2026 02:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants