feat(BundleDataClient): refund deposits to abandoned destinations (UMIP-179)#1477
Draft
droplet-rl wants to merge 1 commit into
Draft
feat(BundleDataClient): refund deposits to abandoned destinations (UMIP-179)#1477droplet-rl wants to merge 1 commit into
droplet-rl wants to merge 1 commit into
Conversation
…IP-179)
A deposit whose destinationChainId is not an active protocol chain — never
onboarded, or in DISABLED_CHAINS across its fillable window — is currently
filtered out of bundle consideration (it never enters the origin/destination
loops, which iterate only `allChainIds`) and stranded on the origin SpokePool
indefinitely, because expired-deposit refunds are defined against the
destination clock which doesn't exist for these deposits.
This adds an origin-clock refund for such "abandoned destination" deposits,
per UMIP-179 "Finding Abandoned-Destination Deposits":
- Iterate each origin chain's deposits whose destination is not in the active
set, gated by the same forward-only rule as expired deposits (fillDeadline
within the origin bundle block range).
- Classify using on-chain event timestamps, never quoteTimestamp, so the rule
is independent of depositQuoteTimeBuffer:
* orphan: destination absent from CHAIN_ID_INDICES at the fillDeadline
HubPool block (append-only => absent for the whole window => no fill).
* disabled: destination in DISABLED_CHAINS at BOTH the deposit block
timestamp and the fillDeadline (disabled across the window; combined with
the re-enablement bundle-range rule, no repayable fill can be double-paid).
- Refund matching deposits to the depositor on origin via updateExpiredDepositsV3.
Gated behind ABANDONED_DESTINATION_REFUND_CONFIG_STORE_VERSION so proposers
switch deterministically; the placeholder version keeps it inert until the
UMIP-179 clause is ratified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A
DepositwhosedestinationChainIdis not an active protocol chain — never onboarded (typo / unsupported id) or currently inDISABLED_CHAINS(e.g. Scroll) — is accepted on-chain (SpokePool._depositV3does not validatedestinationChainId) but then silently dropped by the dataworker:loadDataonly iteratesallChainIds(active chains), so these deposits never enter the origin/destination loops, and expired-deposit refunds are defined against a destination clock that doesn't exist for them. The input funds sit on the origin SpokePool indefinitely. Hit in production (Base→Scroll via a third-party integrator; funds stranded, manual admin recovery required).Change
After the existing deposit/fill/expiry processing in
loadData, add an origin-clock refund pass for abandoned-destination deposits (gated onABANDONED_DESTINATION_REFUND_CONFIG_STORE_VERSION):destinationChainId ∉ allChainIds.fillDeadlinefalls within[originStart, originEnd]. Older deadlines are assumed resolved in a prior bundle → no retroactive sweep.quoteTimestamp(so it's independent ofdepositQuoteTimeBuffer, which may be raised to ~1 week):∉ CHAIN_ID_INDICESat thefillDeadlineHubPool block.CHAIN_ID_INDICESis append-only ⇒ absent at the deadline ⇒ absent across the whole window ⇒ no SpokePool ever existed ⇒ no fill.∈ DISABLED_CHAINSat both the depositblock.timestampand thefillDeadlineHubPool blocks. Combined with the re-enablement bundle-range rule (UMIP PR), disabled-interval fills are never bundled, so the depositor refund can't be double-paid.updateExpiredDepositsV3→ flows into the origin relayer-refund root. No new leaf type, no contract change.Safety
No destination
FillStatuscheck (the chain is unresolvable) — safety is structural: an orphan destination never had a SpokePool, and a disabled-throughout destination's fills are never in any bundle range. Membership resolves from deposit/fillDeadlineblock timestamps viaHubPoolClient.getBlockNumber(); near aDISABLED_CHAINSboundary the origin→mainnet resolution should be taken conservatively (design note in the UMIP).Test plan (TODO before un-drafting)
BundleDataClientloadData suite (MockConfigStoreClientversion ≥ gate) to assert:fillDeadlinein window; not before.DISABLED_CHAINSdestination disabled at both deposit & deadline → refunded; disabled only after deposit (active at deposit) → not refunded (double-pay guard).fillDeadlinepredates the origin window → not refunded (forward-only).ABANDONED_DESTINATION_REFUND_CONFIG_STORE_VERSIONis set to the ratified activation VERSION.🤖 Generated with Claude Code