Skip to content

fix(staking,payments): token/deposit-unit-consistent amounts for adapter-backed assets#186

Merged
drewstone merged 1 commit into
mainfrom
fix/adapter-unit-mismatch-vault-exposure
Jun 19, 2026
Merged

fix(staking,payments): token/deposit-unit-consistent amounts for adapter-backed assets#186
drewstone merged 1 commit into
mainfrom
fix/adapter-unit-mismatch-vault-exposure

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Summary

Two adapter unit-mismatch bugs that surface only for non-1:1 (rebasing) adapters (StandardAssetAdapter is 1:1, so the existing suites are unaffected — but RebasingAssetAdapter is registerable, so these are live, not latent). Both validated end-to-end against the code before fixing.

HIGH — LiquidDelegationVault mints shares off the raw token amount

deposit()/mint() computed shares from the raw token assets, but totalAssets() is denominated in staking deposit-units (adapter shares), and the delegation pool actually grows by the credited deposit-units that _depositAndDelegate already computes (and then discarded for the mint). For a rebasing adapter raw != credited, so a depositor after a positive rebase minted more shares than they funded, diluting earlier holders.

Fix: snapshot the pre-deposit rate, then mint against the credited deposit-units. mint() converts the required deposit-units to the raw token cost via the adapter (_depositUnitsToAssets) and mints on credited. 1:1 assets are unchanged.

(The finding's headline — "not share-adjusted" — is imprecise; deposit() does call convertToShares. The real defect is the unit of its numerator: raw tokens vs the deposit-unit denomination of totalAssets()/credited.)

MEDIUM — PaymentsEffectiveExposure feeds deposit-units into a token-unit stat

getOperatorStakeForAsset returns deposit-units (adapter shares; _operatorDelegatedAggregate is incremented by credited units), which _calculateEffectiveExposures fed straight into oracle.toUSD(token, amount)toUSD expects token units. Under a rebasing adapter the USD-normalized exposure (and thus the operator's payout weight) was mis-priced.

Fix: convert deposit-units → tokens via adapter.sharesToAssets (_depositUnitsToTokenAmount) before toUSD; 1:1 for native / non-adapter assets; fails open to the input on a missing adapter so a misconfig degrades rather than reverting distribution.

Tests (test/audit/batch3/AdapterUnitMismatch.t.sol)

  • F1 full-stack: real staking diamond + RebasingAssetAdapter + LiquidDelegationVault; user1 deposits at 1:1, the token rebases +100%, user2 deposits the same nominal amount → mints ~half the shares (pre-fix: equal → dilution). The assertLt fails under the bug.
  • F2 harness: exposes _calculateEffectiveExposures with mocked staking/adapter/oracle; only the token-unit toUSD call is mocked, so the pre-fix deposit-unit path reverts → exposure is asserted to be priced on converted token units.

No regressions: LiquidDelegation, AssetAdapter, LiquidVault, AdaptersMedLow, AdapterChange, RebasingShareScalePoC, Payments, PerAssetExposure, RFQ/Quote distribution, Integration, EndToEndSubscription, MultiAssetDelegation (243 tests).

Full ~1855-test suite not run in one shot (full via-IR graph exceeds the box's forge memory cap); coverage run per affected subsystem. CI can run the full suite.

…adapter-backed assets

Two adapter unit-mismatch bugs surface only for non-1:1 (rebasing) adapters
(StandardAssetAdapter is 1:1, so existing tests are unaffected).

HIGH — LiquidDelegationVault share mint:
  deposit()/mint() computed shares from the RAW token `assets`, but totalAssets()
  is denominated in staking DEPOSIT-UNITS (adapter shares) and the delegation pool
  actually grows by the `credited` deposit-units that `_depositAndDelegate` already
  computes (and previously discarded for the mint). For a rebasing adapter raw !=
  credited, so a post-rebase depositor minted more shares than they funded,
  diluting earlier holders. Fix: snapshot the pre-deposit rate, then mint against
  the CREDITED deposit-units. mint() converts the required deposit-units to the raw
  token cost via the adapter (new _depositUnitsToAssets) and mints on credited.
  1:1 assets are unchanged.

MEDIUM — PaymentsEffectiveExposure USD weighting:
  getOperatorStakeForAsset returns deposit-units (adapter shares), which were fed
  straight into oracle.toUSD(token, amount) — toUSD expects TOKEN units. Under a
  rebasing adapter the USD-normalized exposure (and thus the payout weight) was
  mis-priced. Fix: convert deposit-units -> tokens via adapter.sharesToAssets
  (new _depositUnitsToTokenAmount) before toUSD; 1:1 for native / non-adapter.

Tests: test/audit/batch3/AdapterUnitMismatch.t.sol — full-stack rebasing vault
deposit (F1: post-rebase deposit mints ~half the shares, not equal) and a mocked
exposure harness (F2: exposure priced on converted token units). No regressions
across LiquidDelegation, AssetAdapter, LiquidVault, AdaptersMedLow, Payments,
PerAssetExposure, RFQ/Quote distribution, Integration, subscriptions, MultiAsset.

@tangletools tangletools 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.

✅ Auto-approved PR — 0540f157

Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-19T21:39:32Z

@drewstone drewstone merged commit 214518f into main Jun 19, 2026
1 check passed
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