Skip to content

feat: ether.fi Lend#153

Open
nikkaroraa wants to merge 121 commits into
masterfrom
feat/lend
Open

feat: ether.fi Lend#153
nikkaroraa wants to merge 121 commits into
masterfrom
feat/lend

Conversation

@nikkaroraa

@nikkaroraa nikkaroraa commented Jun 22, 2026

Copy link
Copy Markdown

This is the long-lived base branch for the ether.fi Lend project. The project replaces the in-house DebtManager with an Aave v4 money market and routes Cash spending through it.

This pull request is a draft and should not be merged. It stays open so the full set of changes is visible in one place. It can merge only after the re-audit and cutover tracked in COR-954.

How work lands

Every Lend change goes on a branch cut from feat/lend, and each branch opens a pull request back into feat/lend. Nothing goes straight to dev or master.

What the branch adds

Capabilities

  • Safes can be migrated onto Aave v4: an admin call moves a safe's legacy debt and collateral onto Aave and flips it to gateway routing in one transaction; safes not yet migrated keep the legacy DebtManager path.
  • Debit spend, credit spend, repay, auto-supply, signed borrow, and withdrawal sourcing all work through the gateway for migrated safes.
  • Safes can opt out of Lend after a delay.
  • Other modules can withdraw from Aave, act, and re-supply through a sandwich helper.

Infrastructure

  • Price feeds added for Chainlink, Pyth, and Veda-priced assets.
  • A lens contract added for reading Aave positions.
  • A test Aave v4 instance deployed on Optimism dev, with deploy scripts and recorded addresses.
  • CI runs fork-based lend tests under a dedicated build profile.
  • Certora patches updated for the new gateway paths.

Note

High Risk
Touches migration, borrow/repay routing, and Aave position management—core credit and collateral flows—with a large scripted upgrade surface on dev; mistakes could strand debt, mis-route Safes, or break health checks.

Overview
Introduces ether.fi Lend infrastructure so Cash can route migrated Safes through Aave v4 instead of the in-house DebtManager, while legacy Safes keep the old engine until migration.

DebtManager gains migrateToLendGateway (admin, ETHER_FI_WALLET_ROLE): clear legacy debt, supply collateral via LendGateway, re-borrow on Aave to fund repayment, then flip Cash routing with markUsesLendGateway. Legacy borrow / repay / liquidate paths are gated with onlyLegacySafe; ensureHealth is a no-op when usesLendGateway is true.

Build & CI: adds lib/aave-v4, Foundry lend and aave-deploy profiles (skips fork tests in default build; pins LiquidationLogic), remappings, and a workflow step that runs test/safe/modules/cash/lend/** under FOUNDRY_PROFILE=lend. README documents fork test env vars.

On-chain ops & manifests: deploy/verify scripts for an Optimism test Aave instance, Summer Lend collateral and oracle feeds (Chainlink/Pyth/Veda/OracleSink), AaveV4Lens, and a dev Cash Lend upgrade path (DeployCashLendDev, rollback/verify, gateway spend-asset setup, module redeploy helpers). Committed deployment JSON includes rollback baseline, cash-lend, and feed address maps.

Formal verification: Certora patches swap EnumerableSet imports to OpenZeppelin; EtherFiSafe rule p01 excludes gateway Safes from DebtManager health assumptions.

Reviewed by Cursor Bugbot for commit 6f46a36. Bugbot is set up for automated code reviews on this repo. Configure here.

@nikkaroraa nikkaroraa changed the title feat: ether.fi Lend — Aave v4 migration (integration branch, DRAFT) feat: ether.fi Lend - Aave v4 migration (integration branch, DRAFT) Jun 22, 2026
@nikkaroraa nikkaroraa changed the title feat: ether.fi Lend - Aave v4 migration (integration branch, DRAFT) Integration branch for the ether.fi Lend project (draft) Jun 22, 2026
@nikkaroraa
nikkaroraa changed the base branch from dev to master June 22, 2026 11:50
@nikkaroraa nikkaroraa changed the title Integration branch for the ether.fi Lend project (draft) feat: ether.fi Lend (WIP) Jun 23, 2026

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: 9f9d94de-271a-41c7-a1eb-7e57ba290b5d
  • Commit: 2356e13
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: d03e52f1-9eb5-4aec-8177-96ec61450db4
  • Commit: fb3fc95
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: b627ced7-1a01-460f-bbff-ef31487a86e4
  • Commit: ac03667
Job Result VERIFIED Link
EtherFiSafe.conf 14 Link
CashModuleCore.conf --rule "p03" 2 Link

* feat: rate adapters for receipt tokens (aave v4) (#154)

* feat: veda price feed for aave v4 (cor-963)

* docs: compact natspec on veda price feed

* test: fork optimism for veda price feed

* feat: add chainlink composite price feed for receipt tokens

* feat: add rate staleness check to veda price feed

* refactor: extract _readFeed helper in chainlink composite feed

Collapse the two duplicated read-and-validate blocks in latestAnswer into
a single private helper. Behavior unchanged; addresses PR review feedback.

* feat: shared Aave withdraw/action/re-supply sandwich helper (#155)

* feat: shared Aave withdraw/action/re-supply sandwich helper

Adds ModuleGatewaySandwich, an abstract base that asset-moving modules inherit once auto-supply has moved an asset into the safe's Aave position. It withdraws the asset back to the safe, guards health, and re-supplies the output as collateral. The module rewrites that consume it are separate tickets.

Guard is a post-withdraw health-factor check, the only price-free guard IGateway v0 allows. Gateway is a constructor param, not a data-provider getter. Both flagged for review. Tested against MockGateway.

* feat: reject zero gateway address in ModuleGatewaySandwich constructor

Matches the zero-address checks in ModuleBase and AaveV3Module. Uses a distinct InvalidGateway error so it does not collide with ModuleBase's InvalidInput when a module inherits both. Also adds a one-line comment to each test.

* feat: rewrite CashLens to read the Aave position (phase one)

Reads a safe's position from the Aave gateway instead of DebtManager plus raw
balances, keeping every external signature and the SafeCashData /
DebitModeMaxSpend shapes. Phase one is the no-borrow world: credit =
availableBorrowsUsd, debit = raw balance + withdrawable supplied amount (capped
by reserve liquidity), no underwater math.

Adds suppliedOf / debtOf / availableCash to IGateway and MockGateway. The token
whitelist is still sourced from DebtManager, and the two DebtManager-coupled
collateral getters stay on the raw balance until the Aave cutover.

* feat: debt-aware debit math in CashLens (phase two)

Phase one covered the no-debt world. This adds the cap that keeps a card
spend from over-withdrawing supplied collateral when the safe has debt.

A debit spend can withdraw supplied collateral only while the leftover
position keeps debt within its borrowing power (LTV). That cap is exactly
Aave's availableBorrowsUsd, which the gateway already returns, so the debit
headroom is availableBorrowsUsd and each token's withdrawable face converts
through its LTV. CashLens reads only the per-asset LTV; the liquidation
threshold and bonus stay in Aave. This mirrors DebtManager's old limit
(debt <= sum of collateral x LTV) and is a safe under-estimate of what Aave
allows on a withdraw, since Aave checks the looser liquidation threshold.

The only new gateway read over phase one is ltvBps(asset). The no-debt path
is unchanged, so phase one stays exact.

Tested on the Optimism fork: CashLensMaxSpend (23), CashLens (12), CanSpend
(40), ModuleGatewaySandwich (4), all pass.

For review: CashLens's LTV limit stays stricter than the sandwich helper's
withdraw guard only while its minHealthFactor <= liqThreshold / LTV. Keep it
in that range or CashLens could over-report.

* fix: preserve DebtManager-visible behavior in CashLens

Addresses the Cursor Bugbot review on PR #158 plus a related underflow:

- maxBorrow: report gross borrowing power again (availableBorrowsUsd +
  debtUsd) instead of net headroom, matching DebtManager.getMaxBorrowAmount
  and keeping it distinct from creditMaxSpend when debt is non-zero.
- pending withdrawals: net them out of the gateway-derived headroom in both
  credit (getMaxSpendCredit, _creditCheck) and debit (_netBorrowHeadroom),
  matching the live DebtManager.ensureHealth path which reads balance minus
  pending.
- debit canSpend: avoid an underflow revert when a pending withdrawal exceeds
  the available balance; decline with a reason instead.
- _withdrawableSupplied: treat a zero-LTV reserve as not withdrawable against
  debt rather than skipping the headroom cap.

Tests added for each in CashLensMaxSpend.t.sol.

* refactor: net pending withdrawals from the loose balance only in CashLens

A pending withdrawal sits against the loose Safe balance, not the supplied
position, so CashLens now reserves it from the raw balance alone and reads the
gateway's figures (collateral, maxBorrow, credit/debit headroom) as-is. This
reverses the earlier conservative approach that subtracted a pending from every
Aave-derived figure, which under-reported spendable when wallet and Aave
balances coexist.

- _creditCheck / getMaxSpendCredit: return availableBorrowsUsd without a pending haircut
- _debitCheck / getMaxSpendDebit: borrow headroom is the gateway's availableBorrowsUsd;
  _debitSpendableAmount reserves pending from raw only
- drop the now-unused _pendingWithdrawalHeadroom and _netBorrowHeadroom helpers
- comments describe the loose-balance vs supplied-position split, no Aave-mechanics framing

Tests: clearer gateway setup via _setGatewayAccount (derives availableBorrowsUsd),
rewritten _mirrorPositionToGateway NatSpec, and removed the redundant
getMaxSpendCredit pending test (passthrough is covered elsewhere).

Verified: TEST_CHAIN=10 forge test, CashLens + MaxSpend + CanSpend, 80 pass.

* fix: reserve pending from raw before charging debit headroom in CashLens

In the multi-token debit check, the borrowing headroom a token consumed was
sized from the full raw balance, ignoring that a pending withdrawal reserves
part of it. So a spend that looked fully covered by raw could actually need a
supplied withdrawal, and a later token could reuse headroom the first token had
already consumed. Net the pending from raw first, then size both the spendable
check and the headroom it consumes off that effective raw.

Adds a regression test: raw reserved by a pending forces a supplied withdrawal
that consumes the full shared headroom, so a later token is correctly declined.

Tests: TEST_CHAIN=10 forge test, CashLens + MaxSpend + CanSpend, 81 pass.

* refactor: rename withdrawable to withdrawableSupplied and add mixed-LTV debit tests

Rename the local in _debitCheck and _debitSpendable to withdrawableSupplied so
it matches the _withdrawableSupplied source and the _debitSpendableAmount param,
making clear it is the supplied (Aave) side, not the loose balance.

Add two getMaxSpendDebit tests: mixed-LTV per-token headroom threading (each
token bills the shared headroom at its own LTV) and mixed collateral where a
non-stable carries the borrowing power so both stables stay face-bound.

* fix: clamp pending withdrawal subtraction in CashLens collateral getters

getUserCollateralForToken and getUserTotalCollateral subtracted the pending
withdrawal from the raw balance unchecked, so they reverted when a pending
withdrawal exceeded the loose Safe balance. The debit canSpend path already
clamps this case to zero; clamp the getters the same way so the whole file
treats pending > balance consistently.

Clamping returns the same DebtManager-visible value in the normal case and
keeps collateral reads (and the liquidation/health paths that depend on them)
from reverting on this state. Repointing or removing these raw-balance getters
at DebtManager cutover is tracked on COR-991.

* fix: cap credit max-spend by liquidity and guard sandwich health post-op

- getMaxSpendCredit: cap borrowing power by the most-liquid borrow token's pool liquidity, matching the reserve-liquidity decline in _creditCheck.

- ModuleGatewaySandwich: move the minHealthFactor buffer off the withdraw bookend into a guardsHealth modifier checked after the whole operation; Aave already enforces its liquidation-threshold floor on the withdraw itself. Rename WithdrawBreachesHealth -> OperationBreachesHealth.

- CashLens: rename the unsupported-token decline to 'Not a supported borrow token' (kept the cash-be-matched liquidity string as-is).

* fix: reword credit liquidity decline string

_creditCheck now declines with 'Insufficient liquidity to cover the loan', dropping the stale 'in debt manager'. Updated the matching test assertion.

NOTE: cash-be authorizations-utils.ts exact-matches this string for a TEMP credit contingency that overrides the decline to authorize; update it to the new string in lockstep before prod, or that contingency stops firing.
Comment thread src/modules/cash/CashLens.sol
…er (#160)

* refactor: remove liquidation callbacks and retire CashLiquidationHelper

On Aave, liquidation is permissionless with no callback, so Cash's preLiquidate/postLiquidate model is dead. Remove both callbacks from CashModule and the now-unused OnlyDebtManager error.

DebtManager called those callbacks to seize collateral, since it is not a module on the Safe and cannot move funds itself. Removing the callbacks therefore also removes DebtManager's liquidation entry point and its Liquidation test suite. This touches DebtManagerCore, which Jacob owns for the migration work.

Retire CashLiquidationHelper, its deploy script, and its bytecode verify test, since it only read DebtManager. Keep liquidatable() and leave the Liquidated event and LiquidationTokenData struct for COR-991 to remove with DebtManager event decoding.

* test: skip bytecode verify for CashModule and DebtManager on Lend

Both contracts changed for Lend, so their compiled bytecode no longer matches the deployed pre-Lend version. Skip the verify like CashLens does, to re-enable after the Lend deployment.
@github-actions

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: 01c19dac-b6bb-4f2c-a35e-8935090ca466
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-53f7f82a9876.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-3dda5a8eaf48.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

* feat: repoint CashModule credit spend and repay to the Aave gateway

Credit spend now borrows through the gateway (gateway.borrow onBehalf, forwarded to the settlement dispatcher) and repay goes through gateway.repay onBehalf, instead of DebtManager. The gateway is injected via CashModule storage plus a controller-gated setGateway/getGateway, so no constructor change was needed.

Debit spend stays on DebtManager for now and moves to the gateway in a follow-up along with the spend-path health check. Token whitelist and USD->token conversion still come from DebtManager until the cutover, same as CashLens.

Tested against MockGateway: the Repay suite is rewritten to the gateway model (green) and credit spend tests assert the gateway.borrow call. Three Spend tests that need dynamic health / blocked-borrow behavior are skipped pending the debit + health repoint.

Decision under uncertainty (worth a second look): repay assumes the gateway pulls the repay token from the safe. How the gateway sources funds is a COR-973 detail owned by Shivam and not built yet; this builds against the interface and reconciles when the real gateway lands.

Part of COR-979 (credit + repay; debit is a follow-up).

* feat: repoint debit spend to the Aave gateway

Debit spend now sources the safe loose balance first, then withdraws the
Aave-supplied balance for any shortfall via the gateway, both routed to the
settlement dispatcher, replacing the DebtManager loose-only transfer. A stale
withdrawal is cancelled up-front against the loose portion, since pending
withdrawals reserve loose balance, not Aave collateral.

Replaces the debtManager.ensureHealth check with a gateway check that runs
only when the spend actually withdrew Aave collateral: a safe that still
carries debt must stay within its LTV-based max borrow, else it reverts
BorrowingsExceedMaxBorrowAfterSpending. Aave gates withdrawals at the looser
liquidation threshold, so this re-imposes the LTV limit; a loose-only spend
never touches Aave and is always allowed.

Also removes the post-op cancel-then-retry from the credit borrow. It is dead
in the Aave model: cancelling a withdrawal only clears a loose-balance
reservation and never re-supplies Aave collateral, so a borrow that reverts
for lack of collateral reverts again on retry (and the whole tx reverts
regardless). Credit now borrows once and lets a failure bubble up.

Adds a borrow-revert toggle to MockGateway and migrates Spend.t.sol: covers
loose-then-supplied ordering, the withdrawal health revert, a loose-only spend
allowed while over the borrow limit, and a blocked credit borrow reverting.

Tested with TEST_CHAIN=10 forge test on Spend, MultiSpend, CanSpend, Mode,
Cashback, ClearPendingCashback, CashbackDispatcher and Repay (all green).

* fix: size debit spends by borrow headroom, share the math with CashLens

The debit path capped withdrawable supplied collateral at min(supplied, reserve
cash) and backstopped it with a post-withdrawal availableBorrows == 0 check.
That check also rejected spends that land exactly at the LTV max borrow (which
canSpend allows), and the loose sizing could plan withdrawals canSpend denies,
including from zero-LTV reserves while the safe carries debt.

_sourceDebit now caps each supplied withdrawal by the borrowing headroom,
threaded across tokens and zero for a zero-LTV reserve, so a debit cannot push
debt past its LTV max borrow by construction. The redundant post-check and its
BorrowingsExceedMaxBorrowAfterSpending error are removed.

The headroom math is extracted to DebitHeadroomLib so CashModuleCore (execution)
and CashLens (canSpend) share one implementation and cannot drift.

Tested: Spend 27/27, CanSpend 40/40, CashLensMaxSpend 32/32.

* fix: move setGateway to CashModuleSetters so CashModuleCore fits EIP-170

Repointing spend/repay to the Aave gateway pushed CashModuleCore past the
24,576 B runtime limit. setGateway is a controller-gated address setter, so
it belongs in CashModuleSetters next to setSettlementDispatcher; it stays
reachable through the fallback delegatecall, so the external ABI is unchanged.
Retyped the one test call site to CashModuleSetters (the repo's idiom for
Setters functions).

* fix: cap CashModule repay at outstanding gateway debt, align tests to gateway borrow/repay

_repay now reads the safe's gateway (Aave) debt and caps the repay
amount to it, emitting the event with the actual repaid amount and its
USD value. MockGateway tracks per-safe debt so repay returns the real
repaid figure.

Tests updated to assert CashModule borrows/repays flow through the
gateway rather than DebtManager: borrows no longer create DebtManager
debt or accrue supplier interest, withdrawals are gated only by loose
Safe funds, and CashLens reads fabricated gateway positions. Skip the
LiquidUSDLiquifier repay tests (COR-988) and the CashModuleSetters
bytecode check until the Lend deployment.

* refactor: unify spend emit path and rename to DebitSourcingLib

* feat: emit GatewayUpdated event on setGateway

* refactor: read gateway from CashModule, gate setGateway to bootstrap

* fix: reject codeless addresses in setGateway

* fix: preserve pending withdrawal when supplied balance covers shortfall

* refactor: clarify pending withdrawal cancellation logic
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: eaa57b3c-0678-4182-9078-da608f2650ea
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-ced7ee732a26.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-616c9ca87cc5.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

Comment thread src/modules/cash/CashModuleCore.sol Outdated
Comment thread src/modules/cash/CashLens.sol Outdated
* add aave gateway

* fix: add reentrancy guard to the set using as collateral

* feat: add debt manager migration and lend disable toggle (#166)

* feat: debt manager migration and add more tests

* feat: add lend disable feature

* fix: use debt manager for spend credit if aave not enabled

* fix: add repay with gateway

* fix: address migration and lend-disable review comments

- migrateToAave reads the gateway from CashModule (single source of truth);
  removes DebtManager's own gateway field, setGateway, and GatewaySet event
- migrateToAave handles lend-disabled safes: skips the Aave supply and just
  marks migrated; reverts LendDisabledSafeHasDebt if such a safe carries debt
- lend events (LendDisableRequested/Executed, LendEnabled, LendGatewaySet) now
  route through CashEventEmitter so indexers and cash-be see them
- _disableLend and _hasOpenBorrows iterate the gateway's registered assets, and
  the open-borrows check uses raw per-asset debtOf (not floored debtUsd) so dust
  debt is caught at request time instead of reverting deep in Aave
- Gateway.setUsingAsCollateral gates only the enable direction; disabling stays
  open for opted-out safes to unwind a residual position
- CashModuleLib.repay emits the USD of the amount actually repaid
- fix a pre-existing infinite loop in processCashback on a zero recipient
- delete unused PositionUnhealthyAfterMigration error; drop a named return

---------

Co-authored-by: Nikhil Arora <narora200@gmail.com>

* fix: align cash tests with the migration-conditional gateway model

After rebasing onto feat/lend, credit spend, repay, and borrow route through
the gateway only for a migrated safe and through the DebtManager otherwise. The
merge kept feat/lend's gateway-only test versions, which assumed the gateway is
always used, so they failed for non-migrated safes.

- Borrow.t.sol, Repay.t.sol, DebtManagerViewFunctions.t.sol: restore the
  DebtManager-path assertions for a non-migrated safe.
- Spend.t.sol: keep the gateway debit-sourcing tests, swap the credit tests to
  assert the DebtManager borrow path, and rename them to name that path.
- Add one-line NatSpec to the reconciled tests; fix a test-name typo (faile).

Migrated/gateway coverage stays in the aave-profile gateway suite.

* test: cover full-repay dust clearance for a migrated safe

Add test_migratedSafe_fullRepay_clearsAaveDebtWithoutDust: after interest
accrues on a migrated Aave position, a repay that covers the debt takes the
max-sentinel path in CashModuleLib.repay, clearing the debt to zero with no
dust and refunding the unused balance to the safe.

* refactor: split lend logic out of CashModuleLib into CashLendLib

* test: align withdrawal and supply tests to the DebtManager path

* fix: make the lend gateway set-once by dropping setLendGateway

* refactor: rename debit sourcing dipped flag to cancelWithdrawal

---------

Co-authored-by: Nikhil Arora <narora200@gmail.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: 6d806597-5d60-4ce2-a3f3-395d8308c171
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-f2463dcd0933.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-cc411793f5c7.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

Comment thread src/modules/cash/CashModuleCore.sol Outdated
Comment thread src/debt-manager/DebtManagerCore.sol
Comment thread src/modules/cash/CashLendLib.sol Outdated
* feat: route each safe between legacy and Aave gateway engines

* refactor: rename engine routing flag family to usesAave
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: a47d488e-f957-4778-9f8e-60a7a5d9905a
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-df436193531d.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-b31da5a70456.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: 3c0bb8c5-9747-41fa-b2c0-1c122303dd5a
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-ee2620ad485e.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-3ff6a632076c.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

Comment thread src/debt-manager/DebtManagerCore.sol
* refactor: rename gateway to lend gateway

* build: skip renamed lend-gateway tests in default profile
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: bdb9649e-a5b0-435f-9149-e1aceef958fc
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-9e3a4902b5f8.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-ab340bf0c6f8.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

Comment thread src/modules/cash/CashModuleCore.sol Outdated
Comment thread src/debt-manager/DebtManagerCore.sol
* test: add real-gateway cash test base, dedupe fork suites

Introduces test/safe/modules/cash/aave/CashGatewayTestSetup, which deploys a
real Aave v4 instance in-test and wires a real LendGateway into the CashModule
as the live lend engine. This is the shared setup for migrating the cash test
suite off MockLendGateway onto the real gateway (first of a series).

The four test/lend-gateway/*.t.sol fork suites now extend this base instead of
each duplicating ~30 lines of Aave + gateway wiring. Suite-specific deltas stay
local via hooks (DebtManagerMigration sets a lower weETH collateral factor and
an empty initial seed; LendGatewayInvariant seeds more liquidity).

Profile plumbing: the new dir is added to the default-profile skip (real Aave
needs via_ir), and a FOUNDRY_PROFILE=aave step is added to CI so the gateway
suite runs there.

Also fixes a pre-existing bug in LendGatewayInvariant: the afterInvariant
"hollow campaign" guard read a handler counter that Foundry rolls back to the
setUp snapshot before afterInvariant, so it always read 0 and the suite always
failed under the aave profile. Replaced with a deterministic test that drives a
real supply + borrow through the handler.

Tested: FOUNDRY_PROFILE=aave TEST_CHAIN=10 TEST_RPC=$OPTIMISM_RPC forge test
--match-path "test/lend-gateway/*.t.sol" -> 69 passed, 0 failed. Default
forge build clean; forge fmt clean on changed files.

* test: convert CanSpend and CashLens gateway paths to real Aave

Moves the gateway-path canSpend and CashLens tests off the MockLendGateway and onto the
real LendGateway backed by real Aave v4, building positions through real supply/borrow
flows instead of injecting them with mock setters.

- New test/safe/modules/cash/aave/CanSpend.t.sol (8 tests): credit borrowing power,
  reserve-liquidity decline, debit sourced from an Aave-supplied balance, pending-withdrawal
  interplay, and single-token credit.
- New test/safe/modules/cash/aave/CashLens.t.sol (3 tests): getUserCollateralForToken on a
  gateway safe, and getSafeCashData with and without borrows, with totals asserted exactly
  against gateway.getAccountData (the same source CashLens reads).
- Base gains position-builder helpers (_supplyToGateway, _borrowOnGateway,
  _buildGatewayPosition, _setAaveCollateralFactor); AaveV4Fixture gains
  _setAaveReserveCollateralFactor plus the updateDynamicReserveConfig role grant.
- Removes those 8 + 3 tests and every _mirrorPositionToGateway / mock-setter call from the
  original CanSpend.t.sol and CashLens.t.sol (drops the now-unused ILendGateway import in
  CashLens). The debit / spending-limit / txId / validation tests stay on the default
  profile with the mock as inert plumbing.

This clears 8 of the 10 _mirrorPositionToGateway call sites; the last two live in
CashLensMaxSpend.t.sol and are removed next, along with the helper itself.

Also records, in CashLens._creditCheck, why the credit gate counts only the already-supplied
position and deliberately excludes the loose collateral the spend-time resupply can pull in
(comment only, no behavior change).

Pre-existing formatting in the two trimmed files is left untouched (running forge fmt over
them would produce a large unrelated diff).

Tested: default forge test on the trimmed CanSpend.t.sol and CashLens.t.sol pass;
FOUNDRY_PROFILE=aave TEST_CHAIN=10 TEST_RPC=$OPTIMISM_RPC forge build && forge test
--match-path "test/safe/modules/cash/aave/*.t.sol" -> 11 passed, 0 failed.

* test: migrate cash gateway tests to real Aave and slim the mock

* test: drop via_ir from the aave profile for faster compiles
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: 4a76a290-5647-40d2-978c-5e78d4ede579
Config Status Link Log File
CashModuleCore.conf --rule "p03" Failed (1) - certora/confs/CashModuleCore.conf-4262f6a8fd69.log
EtherFiSafe.conf Failed (1) - certora/confs/EtherFiSafe.conf-2938dc590975.log

Certora Run Summary

  • Started 0 jobs
  • 2 jobs failed

Download Logs

* test: unify lend gateway tests under one dir and profile

* ci: keep FOUNDRY_PROFILE ci env line
Comment thread scripts/aave-v4/DeployAaveV4TestInstance.s.sol

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: 1b8e85d2-d832-4882-80d6-e3583d99d2a9
  • Commit: b37bd4a
JobResultVERIFIED
EtherFiSafe.…0
CashModuleCo…2

nikkaroraa and others added 3 commits July 22, 2026 08:41
* fix: make Lend supply and collateral atomic

* refactor: remove duplicate Lend supply event

* chore: minimize atomic supply diff
Comment thread scripts/lend/DeployCashLendDev.s.sol

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: 7ac3ce59-4987-4b17-8c87-47381305af70
  • Commit: 0ec3e1c
JobResultVERIFIED
EtherFiSafe.…14
CashModuleCo…2

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: 314a2955-39b2-4619-ac01-a9a06ff0275a
  • Commit: e6669b7
JobResultVERIFIED
EtherFiSafe.…14
CashModuleCo…2

shivam-ef and others added 5 commits July 23, 2026 20:01
…ould clear the spend

A spend blocked by its pending-withdrawal reservation was declined with the
try-again-later Lend-liquidity reason when the Hub was also drained, even though
restored liquidity alone could never approve it. Both shortfall branches now
attribute against the pending-net loose balance plus the full supplied leg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: prod deploy script for summer lend price feeds

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: ab9dac75-1cdf-4deb-8141-20cb254577b4
  • Commit: 6ef2c05
JobResultVERIFIED
EtherFiSafe.…14
CashModuleCo…2

shivam-ef and others added 2 commits July 24, 2026 05:56
* feat: add lend errors for cash lends

* fix: code size

* fix: attribute debit shortfall to Lend liquidity only when recovery would clear the spend

A spend blocked by its pending-withdrawal reservation was declined with the
try-again-later Lend-liquidity reason when the Hub was also drained, even though
restored liquidity alone could never approve it. Both shortfall branches now
attribute against the pending-net loose balance plus the full supplied leg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ev deployment (#233)

* fix: reject a zero-scaled feed price and staleness-check the composite USD leg

* feat: add OracleSinkPriceFeed for relay-priced tokens

Prices a token for the Aave v4 oracle from the OracleSink (the OP-side
store of prices relayed from mainnet over LayerZero), one instance per
token since the sink's latestRoundData is token-keyed. Enforces its own
immutable staleness bound against the source-chain read time the relay
stamped, so a delayed LayerZero delivery cannot present a stale price
as fresh. Two modes like the sibling feeds: USD-quoted, or a rate
composed on an underlying IAaveV4PriceFeed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: address paladin audit info findings in the price feeds

* chore: refresh dev lend feeds onto the audited code and fix the refresh script

* chore: record the prod summer lend feed addresses

---------

Co-authored-by: Shivam Agrawal <shivam@ether.fi>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
uint256 reserveCount = spoke.getReserveCount();
for (uint256 reserveId = 0; reserveId < reserveCount; ++reserveId) {
gateway.setReserveId(spoke.getReserve(reserveId).underlying, reserveId);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deploy registers excluded legacy reserve

Medium Severity

DeployCashLendDev mirrors every Spoke reserve into the gateway, including the legacy liquidRESERVE OFT. VerifyCashLendDev requires that asset to stay unregistered and expects gateway registration count to be Spoke count minus one, so a fresh or rerun deploy fails verification.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4e7514d. Configure here.

@github-actions

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: 9d8ed193-9152-4d0f-a72d-ecbaa843ae06
Config Status Link Log File
CashModuleCore.conf --rule "p03" Compiled - certora/confs/CashModuleCore.conf-94e0da1d07fb.log
EtherFiSafe.conf Compiled - certora/confs/EtherFiSafe.conf-cd7207a26508.log

Certora Run Summary

  • Started 0 jobs
  • 0 jobs failed

Download Logs

@github-actions

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: dab039e9-42ac-466b-82e1-b464f4e3f7de
Config Status Link Log File
CashModuleCore.conf --rule "p03" Compiled - certora/confs/CashModuleCore.conf-55b1f8ec7948.log
EtherFiSafe.conf Compiled - certora/confs/EtherFiSafe.conf-7f031c368635.log

Certora Run Summary

  • Started 0 jobs
  • 0 jobs failed

Download Logs

@github-actions

Copy link
Copy Markdown

Certora Run Started (Certora verification)

  • Group ID: 9d107071-1885-42b1-9e33-72cbda15873d
Config Status Link Log File
CashModuleCore.conf --rule "p03" Compiled - certora/confs/CashModuleCore.conf-440353d1def5.log
EtherFiSafe.conf Compiled - certora/confs/EtherFiSafe.conf-ab760d483d32.log

Certora Run Summary

  • Started 0 jobs
  • 0 jobs failed

Download Logs

@certora-run certora-run Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verification Results

  • Group ID: 7af967fd-3afd-466a-a030-0384b909fbaa
  • Commit: 8a45388
JobResultERRORVERIFIED
EtherFiSafe.…114
CashModuleCo…12

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6f46a36. Configure here.

_list(LIQUID_BTC, _veda(LIQUID_BTC_TELLER, btcUsd, "liquidBTC / USD"), 50_00, 10_500);
_list(LIQUID_USD, _veda(LIQUID_USD_TELLER, usdcUsd, "liquidUSD / USD"), 80_00, 10_200);
_list(EBTC, _veda(EBTC_TELLER, btcUsd, "eBTC / USD"), 52_00, 10_500);
_list(EUSD, _veda(EUSD_TELLER, usdcUsd, "eUSD / USD"), 80_00, 10_200);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dev eUSD feed mismatch

Medium Severity

The test-instance eUSD reserve uses VedaAccountantPriceFeed composed on usdcUsd, but the prod feed script prices eUSD with no underlying leg so the accountant rate is USD outright (matching PriceProviderV2). Dev Aave collateral values for eUSD can diverge from production behavior.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f46a36. Configure here.

// Midas receipt tokens (proxy rate x composed USD leg)
_list(LIQUID_RESERVE, _chainlink(LIQUID_RESERVE_USD_PROXY, usdcUsd, MIDAS_RATE_MAX_STALENESS, false, "liquidRESERVE / USD"), 80_00, 10_100);
_list(WEEUR, _chainlink(WEEUR_EUR_PROXY, eurUsd, MIDAS_RATE_MAX_STALENESS, false, "weEUR / USD"), 70_00, 10_200);
_list(LIQUID_RWA, _chainlink(LIQUID_RWA_USD_PROXY, usdcUsd, MIDAS_RATE_MAX_STALENESS, false, "liquidRWA / USD"), 70_00, 10_400);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Midas USD proxy double leg

Medium Severity

liquidRESERVE and liquidRWA on the test Spoke use Midas USD proxies composed on usdcUsd, while the prod feed deployer treats those proxies as already USD-quoted with no underlying feed. Reserve prices can skew whenever USDC/USD deviates from par.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f46a36. Configure here.

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