feat: add CashRiskSteward for bounded, rate-limited Aave v4 drawCap tuning#228
Draft
seongyun-ko wants to merge 2 commits into
Draft
feat: add CashRiskSteward for bounded, rate-limited Aave v4 drawCap tuning#228seongyun-ko wants to merge 2 commits into
seongyun-ko wants to merge 2 commits into
Conversation
Bounds-enforcing wrapper around HubConfigurator.updateSpokeDrawCap so a low-trust keeper can tune the Cash spoke's borrow cap within an on-chain band it can never leave. Closes the Aave v4 gap where the drawCap value is unbounded: a raw role-holder could set it to 0 (freeze borrows) or MAX_ALLOWED_SPOKE_CAP (uncap -> 100% utilization). Grant the AccessManager drawCap role to this contract instead of the keeper. Includes unit + fuzz (10k runs) + stateful invariant tests with an adversarial ratchet handler; the invariant is mutation-verified (removing the ceiling check fails invariant_DrawCapStaysInBand).
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
…t cap Replace the absolute maxStep with maxStepBps (basis points of the current drawCap), so the allowed per-update move scales with volume as the cap grows — easier to interpret and operate (e.g. 500 = +/-5% per cooldown) and matches Aave's RiskSteward convention. Bounds validation now requires maxStepBps in (0, MAX_BPS]. Adds test_Keeper_StepScalesWithCap; invariant handler ratchet/in-band math updated to the percentage form and remains mutation-verified (ceiling removed still breaches invariant_DrawCapStaysInBand).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/aave-v4/CashRiskSteward.sol— a bounds-enforcing wrapper around Aave v4'sHubConfigurator.updateSpokeDrawCap, so a low-trust keeper (EOA / 1-of-N) can tune the ether.fi Cash spoke's borrow cap to track live supply (keeping utilization stable and deposits withdrawable) while governance keeps the cap inside an on-chain band the keeper can never leave.drawCapvalue is unbounded on-chain (Hub._updateSpokeConfigdoes a raw write with no ceiling/floor check, aave-v4 @cdacec50Hub.sol:710). A key granted that selector directly could set drawCap to0(freeze all Cash borrows) orMAX_ALLOWED_SPOKE_CAP(uncap → 100% utilization → rate spike + LP withdrawals block). Granting the AccessManager drawCap role to this contract instead of the keeper confines every keeper move to[floor, ceiling]throttled by a cooldown and a per-update step.Protections
Governance sets the band; the keeper can only move within it, throttled:
minDrawCapFloor/maxDrawCapCeiling, enforced on every path. The bounds setter forcesceiling < MAX_ALLOWED_SPOKE_CAP, so the spoke can never be uncapped — even by governance.cooldown(min seconds between updates) plusmaxStepBps, a percentage of the current cap (e.g.500= ±5% per cooldown). Expressing the step in BPS makes it scale with volume as the book grows (no governance retune) and matches Aave's RiskSteward convention.onlyKeepertunes within bounds;onlyOwner(governance) sets bounds, rotates the keeper, pauses, and has an incident-response setter that skips the cooldown/step throttle but is still boxed by[floor, ceiling].drawCap) on a single(hub, assetId, spoke)fixed at deploy; every check reads the live cap from the Hub as the source of truth.Design notes
EtherFiSpokeInstance,AaveV4RevenueSplitter); mirrors the splitter's shape (peripheralsrc/aave-v4/contract, immutable config, inline minimal-interface slice, OZ access).feat/lendper the base-branch workflow in feat: ether.fi Lend #153.Test plan
FOUNDRY_PROFILE=lend forge test --match-contract CashRiskSteward— 21 passing (19 unit/fuzz + 2 stateful invariant)[floor, ceiling], never the uncapped sentineltest_Keeper_StepScalesWithCap: the same bps permits a 10M move at a 50M cap but only 2M at a 10M cap — proves the step scales with volumedrawCapnever leaves the bandinvariant_DrawCapStaysInBand(the geometric ratchet breaches> 90M) — proves the suite isn't vacuoustest_RawRoleHolder_CanUncapToUnlimited/CanFreezeBorrowsdemonstrate the raw-selector danger this wrapper removessetTargetFunctionRole+grantRole) — follow-updeployments/dev/10/aave-v4-test.json) — follow-up