Skip to content

feat(vault): optimistic minting exposure cap and per-minter rate limits - #1035

Draft
mswilkison wants to merge 6 commits into
devfrom
feat/om-throughput-controls
Draft

feat(vault): optimistic minting exposure cap and per-minter rate limits#1035
mswilkison wants to merge 6 commits into
devfrom
feat/om-throughput-controls

Conversation

@mswilkison

@mswilkison mswilkison commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds native, governance-tunable limits to the optimistic minting path in TBTCOptimisticMinting, bounding the worst-case TBTC issuance through optimistic minting. Optimistic minting is currently paused on mainnet; these controls are part of the hardening required before it can be re-enabled. They apply to future deployments of TBTCVault — the currently deployed vault is not upgradeable in place.

Today, a single compromised Minter key can request optimistic mints for the bridge's entire TVL, with Guardian cancellation as the only backstop during the optimisticMintingDelay window. With these limits, the worst-case unauthorized issuance is bounded by the caps instead of being unbounded.

Design

The primary control is a cap on outstanding optimistic minting exposure rather than a time-windowed rate: at any moment, the TBTC that could turn out to be unbacked is exactly the optimistic minting debt not yet repaid by swept deposits, plus the value of in-flight (requested, not yet finalized) mints. The contract now tracks both quantities globally (optimisticMintingDebtTotal, mirroring the per-depositor optimisticMintingDebt ledger, and optimisticMintingPendingTotal) and rejects requests that would push their sum above the cap.

This shape has three properties a pure rate limit lacks:

  • The bound holds regardless of detection time. Debt of deposits that never get swept — the signature of a fraudulent mint — is never repaid, so it permanently consumes the cap. A rate limit refills and keeps feeding a slow attacker; the debt cap is an automatic circuit breaker that trips exactly when unverified issuance accumulates, and stays tripped until governance deliberately resolves the situation (e.g. raises the cap through the delayed update).
  • Capacity recycles at the speed of settlement. Legitimate mints return their capacity as soon as the backing deposits are swept and the debt is repaid. Conversely, if sweeping stalls, optimistic minting throttles itself rather than growing exposure against a settlement path that is not confirming.
  • In-flight requests count. The check includes the value of pending requests, so the cap cannot be overshot by submitting many requests in one block and finalizing them together. Cancellation by a Guardian releases the pending value (nothing was minted), and finalization converts it into outstanding debt.

Four limits, each disabled by setting it to zero:

Parameter Default Meaning
optimisticMintingDebtCap 10 BTC Max outstanding + in-flight optimistic minting exposure at any moment
optimisticMintingCapPerMinter 10 BTC Max deposit value requested by a single Minter per rolling 24h (continuously refilling token bucket)
optimisticMintingMaxDepositSize 5 BTC Max size of a single optimistically minted deposit; larger deposits follow the standard sweep flow
optimisticMintingRequestLimitPerMinter 100 Max requests per Minter per rolling 24h; bounds the number of requests Guardians may need to validate and cancel, independent of value

The per-Minter limits stay time-based deliberately. Debt repayment reaches the vault as per-depositor amounts with no per-Minter attribution, so a per-Minter debt cap is not cleanly implementable — and a per-Minter rate limit is independently valuable because it bounds how much value can enter each optimisticMintingDelay cancellation window, keeping the Guardians' verification workload bounded per window. Per-Minter caps are meant to overlap: their sum may exceed the debt cap, which remains the binding total.

Other semantics worth noting:

  • Value is measured at request time from Bridge.deposits().amount (satoshi), the same value optimistic minting is accountable for via optimisticMintingDebt.
  • Cancellation releases the in-flight value but does not restore the per-Minter bucket, so request/cancel cycles from a misbehaving Minter stay bounded by that Minter's own limits. Guardians should also cancel requests that can no longer be finalized (e.g. deposits swept before finalization) to release their in-flight value.
  • Deposits above the size limit mint through the standard sweep flow instead. Historically, deposits above the default 5 BTC limit are roughly the p95 of individual deposit sizes; the limit also keeps any single deposit at half the default debt cap, so one large deposit cannot monopolize the optimistic minting pipeline while it settles.
  • Extension seam. _isOptimisticMintingThrottleExempt(address) is an internal virtual hook (always false here) allowing derived vault implementations to exempt classes of requesters whose issuance is bounded by separate, dedicated exposure limits. Exempt requests bypass the checks but still count toward the measured totals; overriding contracts must account for that overlap.

Defaults were derived from historical mainnet optimistic minting activity (typical outstanding exposure given observed daily volume and reveal-to-sweep settlement times, and the per-deposit size distribution) and are set deliberately conservative for the initial re-enablement: hitting a cap costs only latency (deposits fall back to the sweep flow) while raising one takes the 24-hour governance delay, so the intended posture is to start tight and ratchet up based on observed cap-utilization telemetry.

Governance

The four limits are updated together via the existing two-step idiom (beginOptimisticMintingCapsUpdate / finalizeOptimisticMintingCapsUpdate) with the same 24-hour GOVERNANCE_DELAY used by the fee and delay updates, mirroring the Bridge's grouped-parameter governance.

Monitoring

  • OptimisticMintingAllowanceConsumed is emitted on every request with the amount, the Minter's remaining bucket, and the remaining debt-cap headroom (sentinel type(uint64).max when a limit is disabled) — cap-utilization alerts double as an anomaly detector for a misbehaving Minter.
  • getOptimisticMintingAllowance(minter) returns live remaining allowances (including accrued bucket refill) for Minter clients and dashboards; optimisticMintingDebtTotal and optimisticMintingPendingTotal are public for exposure monitoring.

Testing

New suite TBTCVault.OptimisticMintingCaps.test.ts (35 tests, self-contained against a mocked Bridge): defaults; all four limits; cross-minter bucket independence; the debt cap binding across Minters; cancellation releasing headroom while per-Minter allowance stays consumed; the full settle-and-recycle cycle (in-flight → outstanding debt on finalization → capacity recycled on repayment via receiveBalanceIncrease); the cap lowered below current exposure clamping to zero headroom; continuous bucket refill and clamping; count-limit exhaustion and recovery; disabled-limit sentinels; finalization not double-consuming; governance update flow (authorization, delay, event, state reset); the enable-transition semantics (a limit enabled after being disabled starts every Minter from a full bucket, exercised with an exact-boundary request); fractional refill accrual for small request limits; and the exemption seam via a test stub, including exempt in-flight exposure still being measured.

The pre-existing vault suites are unchanged; default limits sit far above the fixture deposit sizes, so their behavior is unaffected.

Compatibility

  • New state variables change the storage layout relative to the deployed vault. This is intentional and safe: TBTCVault is deployed fresh (the deployed instance cannot be upgraded in place), and these controls target the next vault deployment.
  • requestOptimisticMint gains new revert paths (size cap, per-Minter limits, debt cap). Minter clients should consult getOptimisticMintingAllowance before submitting.
  • optimisticMintingDebtTotal maintenance adds one storage write to finalizeOptimisticMint and to debt-repaying receiveBalanceIncrease calls; behavior of finalization, cancellation, Minter/Guardian management, and the pause switch is otherwise unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AnrPAUEuN6uKDVeASMxb4M

Bound the worst-case TBTC issuance through the optimistic minting path
ahead of re-enabling it. The primary control is a cap on outstanding
optimistic minting exposure: the sum of debt not yet repaid by swept
deposits (tracked globally as optimisticMintingDebtTotal) and the value
of in-flight requests (optimisticMintingPendingTotal). Debt of deposits
that never settle permanently consumes the cap, acting as an automatic
circuit breaker; capacity recycles as deposits are swept.

Complementary per-Minter limits are time-based: a rolling 24h value
bucket and a request count bucket bounding Guardians' per-window
validation workload, plus a per-deposit size limit routing large
deposits through the standard sweep flow. Consumed per-Minter allowance
is not restored on cancellation, so caught request bursts consume the
requesting Minter's own capacity. All limits are governance-tunable via
the existing two-step 24h-delay idiom; zero disables a limit. An
internal virtual exemption hook lets derived vaults exempt requesters
governed by separate, dedicated exposure limits.

Applies to future TBTCVault deployments; the deployed vault is not
upgradeable in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnrPAUEuN6uKDVeASMxb4M
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/om-throughput-controls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

mswilkison and others added 2 commits July 17, 2026 12:34
Set the initial defaults to a deliberately tight posture for the
re-enablement period: 10 BTC debt cap, 10 BTC per-Minter 24h bucket,
and 5 BTC per-deposit size limit (roughly the p95 of historical deposit
sizes, and half the debt cap so a single deposit cannot monopolize the
optimistic minting pipeline while it settles). Hitting a cap costs only
latency, while raising one takes the 24-hour governance delay, so the
intended operating posture is to start tight and ratchet up based on
observed cap-utilization telemetry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnrPAUEuN6uKDVeASMxb4M
- Delete the accidentally committed root pnpm-lock.yaml that broke the
  prettier --check code-format gate (the repo uses yarn).
- Suppress two slither findings in TBTCOptimisticMinting with the repo's
  established annotations:
  - incorrect-equality on the refill sentinel checks (valueRefilledAt /
    requestsRefilledAt == 0 detect a never-initialized timestamp, not a
    manipulable balance equality).
  - costly-loop on the optimisticMintingDebtTotal decrements in
    repayOptimisticMintingDebt (called per-depositor from
    receiveBalanceIncrease); the aggregate must be updated as debt is
    repaid, matching the existing costly-loop suppression in this file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AxCWMXfUdFygLaGaS2SPhg
@piotr-roslaniec
piotr-roslaniec changed the base branch from main to dev August 26, 2026 15:08
Advance each refill checkpoint only by the time represented by credited whole tokens so partial accrual is retained. Use ceiling division to prevent the retained timestamp remainder from making tokens available early at non-divisible rates.
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.

1 participant