From 875ebaa455e4f5f6f2fd67fc2474e04d358c9d0a Mon Sep 17 00:00:00 2001 From: droplet-rl <284132418+droplet-rl@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:15:45 +0000 Subject: [PATCH 1/2] UMIP-179: refund deposits to abandoned (orphaned/disabled) destinations Adds a "Finding Abandoned-Destination Deposits" rule so that a Deposit whose destinationChainId is not an active protocol chain (never onboarded, or in DISABLED_CHAINS) is refunded to the depositor on the origin SpokePool once its fillDeadline elapses, measured on the ORIGIN chain clock. Today such deposits are filtered out of bundle consideration and stranded indefinitely. Safety: - Orphan destination: absence from CHAIN_ID_INDICES resolved at the fillDeadline HubPool block; append-only CHAIN_ID_INDICES => absent through the whole window => no SpokePool, no repayable fill can exist. - Disabled destination: in DISABLED_CHAINS at both the deposit block.timestamp and the fillDeadline; combined with the new bundle-range rule that a re-enabled chain resumes from its re-enablement block (disabled-gap fills never bundled), a depositor refund can never be double-paid against a relayer repayment. - Membership uses on-chain event timestamps (deposit block / fillDeadline), never quoteTimestamp, so the rule is independent of depositQuoteTimeBuffer. - Forward-only: gated behind a ConfigStore VERSION bump; no retroactive sweep. Co-Authored-By: Claude Opus 4.8 --- UMIPs/umip-179.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/UMIPs/umip-179.md b/UMIPs/umip-179.md index 4bd6e3ba..4cdb8037 100644 --- a/UMIPs/umip-179.md +++ b/UMIPs/umip-179.md @@ -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`. #### SVM support @@ -559,6 +560,24 @@ 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 as of the HubPool block resolved from the `Deposit`'s origin `block.timestamp`, **and** remains present in the `DISABLED_CHAINS` list as of the HubPool block resolved from the `Deposit`'s `fillDeadline` (i.e. the destination was disabled for the entire interval between the `Deposit` and its `fillDeadline`). + +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. +- 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. + ### 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`. From f56614b221b41a30cee7a0fe42eba6edeaaddf6e Mon Sep 17 00:00:00 2001 From: droplet-rl <284132418+droplet-rl@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:29:41 +0000 Subject: [PATCH 2/2] =?UTF-8?q?UMIP-179:=20address=20Codex=20review=20?= =?UTF-8?q?=E2=80=94=20continuous-disablement=20test=20+=20accounting=20wi?= =?UTF-8?q?ring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Disabled arm (P1): the abandoned-destination test previously checked DISABLED_CHAINS membership only at the deposit block and the fillDeadline block. Because DISABLED_CHAINS is not append-only (chains can be re-enabled), a disable -> re-enable -> disable sequence passes both endpoint checks while exposing a window in which a Fill can be bundled and repaid -> double-pay. Tighten the condition to require CONTINUOUS membership across the whole [deposit block, fillDeadline block] HubPool interval (no re-enablement in between), matching the design's stated "disabled across the whole window". - Accounting wiring (P2): abandoned-destination refunds flow through the same path as expired-deposit refunds but were not named in the procedure list, Computing Deposit Refunds, or the running-balance deposit-refund step. Add them explicitly so a proposer following the accounting rules includes their inputAmount/inputToken (evaluated against the ORIGIN Bundle Block Range). - Scope note: document that deposits made while the destination was active and only later disabled are intentionally excluded (would require a destination FillStatus check the design forbids); these remain covered by discretionary admin refund. Co-Authored-By: Claude Opus 4.8 --- UMIPs/umip-179.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/UMIPs/umip-179.md b/UMIPs/umip-179.md index 4cdb8037..f2d50b4e 100644 --- a/UMIPs/umip-179.md +++ b/UMIPs/umip-179.md @@ -513,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 @@ -565,7 +566,7 @@ A `Deposit` whose `destinationChainId` does not correspond to an active protocol 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 as of the HubPool block resolved from the `Deposit`'s origin `block.timestamp`, **and** remains present in the `DISABLED_CHAINS` list as of the HubPool block resolved from the `Deposit`'s `fillDeadline` (i.e. the destination was disabled 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). @@ -577,6 +578,7 @@ An abandoned-destination `Deposit` shall be considered refundable by verifying t - 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: @@ -661,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: @@ -691,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.