Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions UMIPs/umip-179.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ In addition to the description [UMIP-157](https://github.com/UMAprotocol/UMIPs/b
- Proposers may opt to reduce the size of the proposal block range for each chain in the event that RPC provider data inconsistencies are detected, and
- A "soft pause" of a chain is permitted in the event that the proposer cannot safely increment the bundle block range, or has no events to propose beyond the previous bundle block range. In this case, the proposer may repeat the procedure for
DISABLED_CHAINS by proposing from and to the previous bundle end block.
- When a chain is removed from the `DISABLED_CHAINS` list (i.e. re-enabled), the proposer shall resume that chain's `Bundle Block Range` from the block at which it was re-enabled. The range shall not span the interval during which the chain was in `DISABLED_CHAINS`. Consequently, any `Fill` that occurred on the chain while it was in `DISABLED_CHAINS` is never included in a `Bundle Block Range` and is not eligible for repayment. This ensures that a depositor refund issued for a disabled destination (see [Finding Abandoned-Destination Deposits](#finding-abandoned-destination-deposits)) can never be accompanied by a later relayer repayment for the same `Deposit`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Cover active deposits that expire while the destination is disabled

If a deposit is made while the destination is still active and the chain is then disabled before a bundle covers its fillDeadline, this skip makes the re-enabled destination range start after the disabled gap, so the deadline is never processed by the normal expired-deposit rule. The new abandoned-destination rule also excludes it because DISABLED_CHAINS was not set at the deposit timestamp, leaving the origin funds stranded for exactly the class of deposits that become unprocessable during a later disablement.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You've correctly identified a real class, but declining to auto-refund it here is deliberate rather than an oversight — and I've added an explicit scope note in f56614b so the boundary is stated rather than silent.

The class you describe is: destination active at deposit, added to DISABLED_CHAINS later, fillDeadline elapses inside the disablement gap. The reason it can't be folded into this rule is the same reason the disabled arm is anchored at deposit time in the first place: a deposit made while the destination was active may already have been validly filled and repaid before the chain was disabled (that pre-disable Fill lands in an active window and is bundled normally). Refunding such a deposit on the origin clock without a destination FillStatus check would double-pay against that repaid Fill. And a FillStatus check is exactly what this rule cannot rely on — the whole premise is that a disabled/orphaned destination's SpokePool may be unresolvable (dead RPC, or never existed). So this class is provably unsafe to handle under the "zero destination reads" constraint that makes the rest of the rule sound; it is not merely unhandled.

Note also this is narrower than "origin funds stranded whenever a destination is later disabled": deposits whose fillDeadline is still in the future when the chain is re-enabled settle normally on the live destination. Only deposits whose deadline falls inside the gap are affected, and only when the chain is disabled (not re-enabled) across that deadline.

Those deposits remain recoverable via a discretionary admin refund, exactly as today — this PR's goal is to prevent recurrence of the deposit-into-an-already-disabled-destination shape (the Scroll/ZeroWay incident), not to auto-recover every stranding path. Extending coverage to the active-at-deposit class would need a separate, destination-reading mechanism and is out of scope here.


#### SVM support

Expand Down Expand Up @@ -512,6 +513,7 @@ For the purpose of computing relayer repayments, the following procedures are co
- Validating Fills
- Validating Pre-Fills
- Finding Expired Deposits
- Finding Abandoned-Destination Deposits
- Finding Unfillable Deposits

#### Note
Expand Down Expand Up @@ -559,6 +561,25 @@ For the purpose of computing depositor refunds, each `Deposit` shall be consider

When evaluating if the `fillDeadline` / `fill_deadline` timestamp elapsed within the `Bundle Block Range` on the SVM destination SpokePool, one can use `getBlock` RPC method for the destination chain's bundle start and end slot and use the `blockTime` field to compare against the `fillDeadline` / `fill_deadline` timestamp. End slot must always have a corresponding block produced, but if there is none for the start block, one should get the timestamp from the last produced block before such empty start slot.

### Finding Abandoned-Destination Deposits
A `Deposit` whose `destinationChainId` does not correspond to an active protocol chain cannot be evaluated against a destination `Bundle Block Range`, because no destination `SpokePool` block range or `FillStatus` can be resolved for it. To ensure such funds cannot be permanently stranded on the origin `SpokePool`, these `Deposits` shall be refunded to the `depositor` on the origin chain.

A `Deposit` shall be considered to have an abandoned destination when either of the following holds:
1. The `destinationChainId` is **not** present in the `CHAIN_ID_INDICES` list as of the HubPool block resolved from the `Deposit`'s `fillDeadline` timestamp (i.e. the destination was never onboarded through the `fillDeadline`). Because `CHAIN_ID_INDICES` is append-only, absence at the `fillDeadline` implies absence for the entire interval between the `Deposit` and its `fillDeadline`.
2. The `destinationChainId` is present in the `DISABLED_CHAINS` list **continuously** over the HubPool block interval from the block resolved from the `Deposit`'s origin `block.timestamp` through the block resolved from the `Deposit`'s `fillDeadline`. Concretely, this requires that the `destinationChainId` is present in the `DISABLED_CHAINS` list at the deposit-time block, is present at the `fillDeadline` block, **and** is not removed from `DISABLED_CHAINS` (re-enabled) by any `DISABLED_CHAINS` update occurring within that interval. Unlike `CHAIN_ID_INDICES`, the `DISABLED_CHAINS` list is **not** append-only — chains may be re-enabled — so membership at the two endpoints alone does **not** establish continuous disablement: a destination that was disabled at the deposit, re-enabled, and disabled again before the `fillDeadline` would satisfy both endpoint checks yet expose an interval during which a `Fill` could be included in a `Bundle Block Range` and repaid. The proposer shall therefore confirm that every `DISABLED_CHAINS` value effective during the interval contains the `destinationChainId`.

An abandoned-destination `Deposit` shall be considered refundable by verifying that:
1. The `fillDeadline` timestamp elapsed within the `Bundle Block Range` on the **origin** `SpokePool` (i.e. the `fillDeadline` expired between the `block.timestamp` of the origin chain's bundle start and end block).

#### Note
- Abandoned-destination refunds shall be issued to the `depositor` address on the origin `SpokePool` as part of the relayer refund procedure, identically to expired-deposit refunds.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add abandoned refunds to the bundle accounting

Although this new section says abandoned-destination deposits are refunded via the relayer refund procedure, the actual accounting rules still only add deposit refunds for deposits that “expired or were deemed unfillable” and the procedure list above likewise only names expired/unfillable deposits. In bundles where an abandoned-destination deposit expires, a proposer following the spec can identify it here but never add its inputAmount/inputToken to the origin-chain relayer refund root, so the intended refund is omitted unless the relayer-refund/running-balance rules are updated to include abandoned-destination deposits explicitly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f56614b. You're right that the section declared abandoned-destination deposits are refunded "identically to expired-deposit refunds" but the accounting rules only named expired/unfillable deposits, so a proposer following the letter of §Computing Running Balances would never add the inputAmount/inputToken. Wired them in explicitly:

  • §Computing Relayer Repayments & Depositor Refunds procedure list — added Finding Abandoned-Destination Deposits.
  • §Computing Deposit Refunds — abandoned-destination deposits refund inputAmount units of inputToken on the origin chain, identically to an expired deposit.
  • §Computing Running Balances & Net Send Amounts, step 3 (Add deposit refunds) — the sum now includes deposits "deemed abandoned-destination refundable," with a note that these are evaluated against the origin Bundle Block Range (their fillDeadline is resolved on the origin clock), matching the reference-clock rule in the new section. In all other respects they are accounted for identically to an expired deposit — consistent with the SDK implementation, which reuses the existing updateExpiredDepositsV3 path (no new leaf type).

- The reference clock is the **origin** chain. No destination `SpokePool` exists or is resolvable for these `Deposits`, so — unlike [Finding Expired Deposits](#finding-expired-deposits) — the `fillDeadline` is resolved to a block on the origin chain, and no destination `FillStatus` is consulted.
- No destination `FillStatus` check is required because no repayable `Fill` can exist for an abandoned-destination `Deposit`: a destination absent from `CHAIN_ID_INDICES` has never had a `SpokePool`, and a destination that was in `DISABLED_CHAINS` for the whole `Deposit`-to-`fillDeadline` interval can only have `Fills` inside a disabled interval, which are never included in a `Bundle Block Range` (see [Identifying Bundle Block Ranges](#identifying-bundle-block-ranges)).
- Membership of `CHAIN_ID_INDICES` and `DISABLED_CHAINS` is resolved using the HubPool chain block corresponding to the referenced timestamp. The `Deposit`'s origin `block.timestamp` is an origin-chain clock; where it falls near a `DISABLED_CHAINS` transition, it shall be resolved conservatively — the destination is treated as disabled at deposit time only when the origin `block.timestamp` resolves to a HubPool block at or after the `DISABLED_CHAINS` addition. The `quoteTimestamp` is intentionally **not** used for these determinations, so that the rule is independent of `depositQuoteTimeBuffer`.
- Consistent with [Finding Expired Deposits](#finding-expired-deposits), only `Deposits` whose `fillDeadline` elapses within the current origin `Bundle Block Range` are refunded; `Deposits` whose `fillDeadline` elapsed before the origin bundle start are assumed to have been resolved in a prior bundle. This rule therefore applies to `Deposits` from the activation of this clause onward and does not retroactively settle `Deposits` whose `fillDeadline` elapsed before activation.
- This procedure is gated on the `AcrossConfigStore` `VERSION` global; proposers shall apply it only for bundles proposed at or after the `VERSION` at which it is introduced.
- **Scope — deposits made while the destination was active are intentionally excluded.** This rule refunds only `Deposits` whose destination was orphaned (never in `CHAIN_ID_INDICES`) or was **already** in `DISABLED_CHAINS` at the deposit (and stayed so continuously through the `fillDeadline`). A `Deposit` made while its destination was active whose destination is *subsequently* added to `DISABLED_CHAINS` before a `Bundle Block Range` covers its `fillDeadline` is **not** treated as abandoned-destination refundable, because such a `Deposit` may have been validly filled and repaid before the destination was disabled. Distinguishing that case would require reading the destination `SpokePool`'s `FillStatus`, which cannot be relied upon for a disabled destination (its `SpokePool` may be unresolvable), so refunding it on the origin clock without that check could double-pay against an already-repaid `Fill`. Such `Deposits` remain resolvable through a discretionary admin refund and are outside the scope of this automated rule.

### Finding Unfillable Deposits
For the purpose of computing depositor refunds, each duplicate `Deposit` shall be considered unfillable by verifying that:
1. The `Deposit` is identical with another `Deposit`.
Expand Down Expand Up @@ -642,7 +663,7 @@ The rules above enforce that a `repaymentToken` for a successful relayer repayme
All the rules for computing relayer repayments as described in the generic [Computing Relayer Repayments](#computing-relayer-repayments) section above apply to SVM chains, except there is no `Fill` `msg.sender` address fallback logic when the `Fill` or resolved `repaymentChainId` is SVM chain. When the resolved `repaymentChainId` is SVM chain, the applied repayment token can be distributed to the associated token account derived from the `relayer` refund address. The relayer can also claim their refunds to any custom token account using the `Fill` `relayer` account as the signer. It is the responsibility of the relayer to ensure it passes the correct `Fill` `relayer` address that they control and that is valid for the resolved `repaymentChainId`.

### Computing Deposit Refunds
For an expired `Deposit` event, the depositor refund amount shall be computed as `inputAmount` units of `inputToken`.
For an expired `Deposit` event, the depositor refund amount shall be computed as `inputAmount` units of `inputToken`. An abandoned-destination `Deposit` (see [Finding Abandoned-Destination Deposits](#finding-abandoned-destination-deposits)) shall be refunded identically: the depositor refund amount shall be computed as `inputAmount` units of `inputToken` on the origin chain.

### Computing Slow Fill updated output amounts
For the purpose of computing the amount to issue to a recipient for a SlowFill, the relayer fee shall be nulled by applying the following procedure:
Expand Down Expand Up @@ -672,7 +693,7 @@ The procedure for computing running balances for an `l1Token` and `chainId` pair
- For each group of validated `Fill` and `Pre-fill` events, initialize a running balance at 0 and add the add the relayer repayment.

3. Add deposit refunds:
- For each group of `Deposit` events that expired or were deemed unfillable within the `Bundle Block Range`, sum the total deposit refunds on the origin chain. Add the amount to the exsting relayer refunds for that chain.
- For each group of `Deposit` events that expired, were deemed unfillable, or were deemed abandoned-destination refundable within the `Bundle Block Range`, sum the total deposit refunds on the origin chain. Add the amount to the exsting relayer refunds for that chain. Abandoned-destination `Deposits` are evaluated for inclusion against the **origin** `Bundle Block Range` (their `fillDeadline` is resolved on the origin chain), consistent with [Finding Abandoned-Destination Deposits](#finding-abandoned-destination-deposits); in all other respects their refund is accounted for identically to an expired `Deposit`.

4. Add slow fills:
- For each group of validated `Slow Fill Requests`, add each slow relay's `updatedOutputAmount` to the group's running balance.
Expand Down