Skip to content

[midnight adapter] proportional cross-market rate reduction - #1301

Merged
MathisGD merged 34 commits into
mainfrom
devin/1793802318-proportional-rate-reduction
Nov 7, 2026
Merged

MathisGD merged 34 commits into
mainfrom
devin/1793802318-proportional-rate-reduction

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Note

On main, when the Midnight adapter sells credit below its amortized value, only the sold market's rate absorbs the shortfall, and the sale reverts once that market's remaining interest runs out. #1084 lets other markets absorb it too, but one at a time in an order the allocator picks. This PR spreads the shortfall over all markets in proportion to each one's remaining interest, so every market's remaining interest drops by the same fraction and nobody has to choose an order. It's an alternative to #1084, branched from main.

interest_i = netCredit_i - amortizedValue_i        // every market
require(shortfall <= Σ interest_i, ShortfallTooHigh)
for each market i with interest_i > 0:
    reduction_i = ceil(shortfall * interest_i / Σ interest)
    netRate_i   = rateFor(netCredit_i, amortizedValue_i + reduction_i)   // rounds down
    emit ReduceRate(id_i, netRate_i)

Context

  • This came out of a design call. Cross-market absorption keeps per-market rates and doesn't pass the loss on to future buys, but choosing or ordering the absorbing markets ([midnight adapter] cross-market rate reduction #1084's swapMarkets) felt unnatural. Proportional reduction was the alternative raised.
  • Force-sellable markets absorb their share like any other market ([midnight adapter] cross-market rate reduction #1084 skips them).
  • Each market's net credit is read as in realAssets(): markets without bad debt (lossFactor == 0) use their stored net credit, and markets with bad debt use their current net credit. The sale reverts with OtherSellInProgress if any other market has a sale in progress (liquidationLocked), since that market's stored net credit is stale until its own onSell runs. Matured and zero-rate markets have no interest left, so they absorb nothing.

Impact of merging

  • A non-force-sellable sale with a shortfall can now cut the rates of every market, force-sellable ones included, not just the sold one. A full sale below amortized value goes through as long as the other markets hold enough interest.
  • The adapter's amortized value drops only by the assets it receives. The sale reverts with ShortfallTooHigh when the shortfall is larger than the remaining interest of all markets combined.
  • A shortfall sale nested inside a sale of the adapter's credit in another market therefore reverts with OtherSellInProgress. Nested sales at or above amortized value are unaffected.
  • Event change: Sell no longer carries the rate. Each market whose rate changes emits ReduceRate(marketId, newRate) instead. Sell gains a last field, absorbedShortfall: the shortfall the markets absorbed, i.e. the increase of their amortized values as realAssets computes them, which is what the sale consumed from shortfallAllowance. It is at least the shortfall, and 0 for forceSellable sales and sales without a shortfall.

Special things to note

  • Every sale with a shortfall now loops over all marketIds twice (up to 250), with a lossFactor call on each market (plus liquidationLocked and updatePositionView for markets with bad debt). Sales with no shortfall skip the loop.
  • Each share rounds up and each new rate rounds down, so the markets' amortized values go up by more than the shortfall: under 1 wei per market from the shares, and up to amortizedValue * ttm / 1e18 per market from the rate truncation. shortfallAllowance is charged with that increase, measured with the same rounding as realAssets, so it bounds exactly what the vault's valuation gains and repeated small sales cannot consume more than it. Each market's amortized value rounds down in the loop, as in realAssets. A share never exceeds its market's interest, so no rate goes negative.
  • MidnightAdapterFactory is 23,748 B at runtime, 828 B under the EIP-170 limit (solc).

Change outline

  • Behavior change, base main, no parent PR.
  • onSell: the first pass records each market's net credit and amortized value in two uint256[] and sums their interest. The second pass skips markets with no interest and applies each market's proportional share. The sold market goes through the same lossFactor net-credit path as the others.
  • marketData.netCredit is updated before the loop.
  • Certora MarketIdsMidnightAdapter.spec: added NONDET summaries for liquidationLocked and updatePositionView.

Evidence

  • New tests in MidnightAdapterTest cover splits across 2–3 markets with the same interest-reduction fraction, markets with bad debt, full sales absorbed by other markets, force-sellable markets absorbing their share, nested shortfall sales reverting, matured markets, insufficient total interest, and the accounting of realAssets.
  • MidnightAdapterTest passes on adf7169 (288 tests, Solar build). Gas for a sale whose shortfall is spread over 1–250 markets is in this thread. forge build --sizes passes on adf7169. Certora wasn't run locally.

Related: PRO-257

Link to Devin session: https://app.devin.ai/sessions/34b1025e5fb04ad5a530c4099c4d08b8
Open in Devin Desktop: https://app.devin.ai/desktop/session/34b1025e5fb04ad5a530c4099c4d08b8?variant=devin

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
@albert-llimos
albert-llimos marked this pull request as ready for review November 5, 2026 11:32
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T10:13:33.525870Z adf7169 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@albert-llimos
albert-llimos requested a review from peyha November 5, 2026 11:32
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
[midnight adapter] consumable shortfallAllowance instead of noSalesWithShortfall
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5b995d74d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
Comment thread src/adapters/MidnightAdapter.sol Resolved
@MathisGD
MathisGD merged commit cfe5a98 into main Nov 7, 2026
53 checks passed
@MathisGD
MathisGD deleted the devin/1793802318-proportional-rate-reduction branch November 7, 2026 11:31
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.

4 participants