Skip to content

Make tx max fee reimbursement best effort#968

Open
mswilkison wants to merge 8 commits into
mainfrom
codex/best-effort-tx-fee-reimbursement
Open

Make tx max fee reimbursement best effort#968
mswilkison wants to merge 8 commits into
mainfrom
codex/best-effort-tx-fee-reimbursement

Conversation

@mswilkison
Copy link
Copy Markdown

@mswilkison mswilkison commented May 20, 2026

Summary

Updates AbstractL1BTCDepositor.finalizeDeposit so the deposit transaction max fee reimbursement is best-effort instead of mandatory.

When reimburseTxMaxFee is enabled, the depositor now:

  • forwards the base finalized tBTC amount as before
  • adds the depositTxMaxFee reimbursement only if the contract balance can cover the fully reimbursed amount
  • emits DepositTxMaxFeeReimbursementSkipped when the reimbursement is skipped because the shared depositor balance is short

This keeps finalization live when a small reimbursement shortfall would otherwise block forwarding tBTC to the user's final destination address.

Review follow-ups also:

  • mirror the same best-effort reimbursement logic into the separate StarkNet package copy
  • mirror the same guard into the standalone Wormhole V2 depositor contracts
  • update NatSpec so reimburseTxMaxFee = true no longer reads as a guaranteed reimbursement
  • add Wormhole skip-path coverage for the inherited abstract depositor flow

Root cause

The current code always adds depositTxMaxFee to the amount forwarded when reimbursement is enabled. If earlier finalizations consume enough of the shared depositor contract balance, a later finalization can have enough tBTC to cover the base deposit amount but not enough to also cover the reimbursement. In that case the final transfer reverts and the depositor waits until unrelated deposits top up the contract.

Tradeoff

This favors liveness over exact per-deposit reimbursement. A depositor may miss the depositTxMaxFee reimbursement when the contract balance is short, but they still receive the base finalized deposit amount immediately instead of being blocked behind later deposits.

That means reimbursement is no longer strictly guaranteed by reimburseTxMaxFee = true; it becomes conditional on available contract balance. The new event makes skipped reimbursements visible for monitoring or possible off-chain reconciliation. If the contract cannot cover even the base amount, finalization can still revert; this PR only prevents the extra reimbursement amount from blocking otherwise-forwardable deposits.

Validation

cd solidity
npm run test -- test/cross-chain/wormhole/L1BTCDepositorWormhole.test.ts test/cross-chain/wormhole/L1BTCDepositorWormholeV2Arbitrum.test.ts
npm run test -- test/depositor/NativeBTCDepositor.test.ts test/cross-chain/wormhole/BTCDepositorWormhole.test.ts
npm run format

cd ../cross-chain/starknet
L1_CHAIN_SEPOLIA_API_URL=http://127.0.0.1:8545 L1_CHAIN_MAINNET_API_URL=http://127.0.0.1:8545 npm run build
npx solhint contracts/cross-chain/AbstractL1BTCDepositor.sol
npx prettier --check contracts/cross-chain/AbstractL1BTCDepositor.sol

Results:

  • 99 passing for L1BTCDepositorWormhole + L1BTCDepositorWormholeV2Arbitrum
  • 149 passing for NativeBTCDepositor + BTCDepositorWormhole
  • Solidity package npm run format exits successfully; existing repo warnings remain, with no errors
  • StarkNet package build succeeds with dummy local RPC URLs; direct solhint/prettier checks pass for the touched StarkNet Solidity file, with existing solhint warnings only

@mswilkison mswilkison changed the title [codex] Make tx max fee reimbursement best effort Make tx max fee reimbursement best effort May 20, 2026
@mswilkison mswilkison marked this pull request as ready for review May 20, 2026 16:34
The flag-doc block in AbstractL1BTCDepositor (solidity and starknet
copies) still described the pre-patch behavior ("Add txMaxFee to the
minted tBTC amount"), which contradicts the post-patch code path that
only adds txMaxFee when the contract balance covers it. The Wormhole V2
variants already carry the corrected wording; this commit mirrors that
phrasing into both abstract copies for documentation congruence with the
implementation in finalizeDeposit.
The V2Base depositor previously had no dedicated test file; coverage
relied implicitly on the V2Arbitrum twin's tests. The new file covers
the four reimbursement scenarios exercised by finalizeDeposit:

- reimburseTxMaxFee is false: contract pays only the base tbtcAmount.
- reimburseTxMaxFee is true with balance >= reimbursedAmount: contract
  pays base + txMaxFee; no skip event.
- reimburseTxMaxFee is true with balance one wei short of
  reimbursedAmount: contract pays only base, emits
  DepositTxMaxFeeReimbursementSkipped with the available balance.
- reimburseTxMaxFee is true with zero balance: contract pays only base,
  emits the skip event with availableBalance == 0, and the L2 receiver
  payload is preserved through the skip branch.

All assertions read the post-call tBTC allowance to the Wormhole token
bridge and the event log; the contract source itself is unchanged.
The existing finalizeDeposit suite only exercised the
reimburseTxMaxFee=false path. Three additional contexts now cover the
balance-gated branches added by the best-effort patch:

- balance covers reimbursedAmount: contract transfers base + txMaxFee
  and emits no skip event.
- balance is one wei below reimbursedAmount: contract transfers only
  base and emits DepositTxMaxFeeReimbursementSkipped with the available
  balance.
- proxy holds zero tBTC: contract transfers only base, emits the skip
  event with availableBalance == 0, and the L2 receiver payload survives
  through the skip branch.

Assertions read the post-call tBTC allowance to the Wormhole token
bridge plus the event log; contract source is unchanged.
Two NatSpec defects shared by the solidity and starknet copies:
- "gas dgasd" garbled token in the initializeDeposit doc block
  (corrected to "gas refund").
- Empty backticks (`\`\`` contract.) inside the Bitcoin deposit script
  description where the contract name was meant to be inlined
  (filled in with `AbstractL1BTCDepositor`).

Documentation only; no behavioral change.
External review flagged two gaps in the prior reimbursement-branch
tests:

1. The "proxy holds zero tBTC" context was a false positive. In
   production, a real Wormhole Token Bridge pulls baseTbtcAmount from
   the proxy via transferFrom during finalizeDeposit; with zero balance
   that transfer reverts and rolls back the skip event together with
   the state transition. The smock fake in the unit test does not pull
   tokens, so the test passed against behavior that does not exist on
   mainnet. The context is replaced with "balance equals baseTbtcAmount"
   in both V2Base and V2Arbitrum suites; this matches the real Rafael /
   Base recipient scenarios where the Bridge sweep delivered base but
   not the full base + txMaxFee, and the skip event arg
   availableBalance is now asserted as baseTbtcAmount.

2. The reimburseTxMaxFee=true contexts only asserted the allowance and
   the presence/absence of the skip event. A regression that transferred
   the correct amount but emitted a wrong tbtcAmount in DepositFinalized
   would have slipped through. Every reimbursement-branch context now
   asserts DepositFinalized with the exact expected initialAmountWei
   and tbtcAmount (reimbursedAmount for the covered path, baseTbtcAmount
   for both skip-paths).

Contract source is unchanged; only the two new V2 test files are
modified.
Copy link
Copy Markdown
Member

@lrsaturnino lrsaturnino left a comment

Choose a reason for hiding this comment

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

LGTM

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.

3 participants