diff --git a/UMIPs/umip-179.md b/UMIPs/umip-179.md index b194c76f..624aaab6 100644 --- a/UMIPs/umip-179.md +++ b/UMIPs/umip-179.md @@ -39,25 +39,50 @@ All global constants from UMIP-157 will be retained for use in Across v3. The gl * This shall be a stringified list of chain ID numbers. Each chain in this list shall also appear in the `CHAIN_ID_INDICES` list. There shall be no duplicates in this list; any `LITE_CHAIN_ID_INDICES` update containing duplicates may be ignored. Chains may be removed from the `LITE_CHAIN_ID_INDICES` list in order to remove their "Lite" chain designation. ## Data Types -Across v3 introduces the following new data types: +Across v3 defines the following data types: ### V3RelayData -The V3RelayData type underpins the transfer of funds in or out of a SpokePool instance. V3RelayData is defined as follows: +The `V3RelayData` type underpins the transfer of funds in or out of a SpokePool instance. `V3RelayData` is defined as follows: | Name | Type | Description | | :--- |:---- | :---------- | -| depositor | address | The address that made the deposit on the origin chain. | -| recipient | address | The recipient address on the destination chain. | -| exclusiveRelayer | address | The optional exclusive relayer who can fill the deposit before the exclusivity deadline. | -| inputToken | address | The token that is deposited on the origin chain by the depositor. | -| outputToken | address | The token that is received on the destination chain by the recipient. | +| depositor | bytes32 | The address that made the deposit on the origin chain. | +| recipient | bytes32 | The recipient address on the destination chain. | +| exclusiveRelayer | bytes32 | The optional exclusive relayer who can fill the deposit before the exclusivity deadline. | +| inputToken | bytes32 | The token that is deposited on the origin chain by the depositor. | +| outputToken | bytes32 | The token that is received on the destination chain by the recipient. | | inputAmount | uint256 | The amount of inputToken that is deposited by the depositor. | | outputAmount | uint256 | The amount of outputToken that is received by the recipient. | | originChainId | uint256 | The chain ID of the origin SpokePool. | -| depositId | uint32 | The ID uniquely identifying a deposit on the origin chain. | +| depositId | uint256 | The ID identifying a deposit on the origin chain. | | fillDeadline | uint32 | The Unix timestamp on the destination chain after which the deposit can no longer be filled. | | exclusivityDeadline | uint32 | The optional Unix timestamp on the destination chain after which any relayer can fill the deposit. | | message | bytes | Optional data that is forwarded to the recipient as part of a relay. | +#### Note +- `V3RelayData` specifies `bytes32` representation for addresses (`depositor`, `recipient`, ...) in order to interface with non-EVM chains. The EVM address supplied shall be promoted to type `bytes32` with the upper 12 bytes zeroed. + +### V3RelayDataLegacy +The `V3RelayDataLegacy` type is supported for backwards compatibility, but is slated for deprecation. `V3RelayDataLegacy` has the following delta to the `V3RelayData` type: +| Name | Type | +| :--- |:---- | +| depositor | address | +| recipient | address | +| exclusiveRelayer | address | +| inputToken | address | +| outputToken | address | +| depositId | uint32 | + +## V3RelayExecutionParams +The `V3RelayExecutionParams` type is supplied by a relayer or executor when completing a fill. `V3RelayExecutionParams` is defined as follows: +| Name | Type | Description | +| :--- |:---- | :---------- | +| relay | V3RelayData | The V3RelayData object corresponding to the origin chain deposit to be filled. | +| relayHash | bytes32 | The keccak256 hash of the V3RelayData object. See also [Computing RelayData hashes](#computing-relaydata-hashes). | +| updatedOutputAmount | uint256 | The effective amount to be received by the recipient. This may be different to the deposit outputAmount. | +| updatedRecipient | bytes32 | The effective recipient address. This may be different to the deposit recipient. | +| updatedMessage | bytes | The effective message (if any) to be executed by the destination SpokePool. | +| repaymentChainId | uint256 | The repayment chain ID requested by the relayer completing the fill. This field is not relevant for slow fills. | + ### FillStatus A mapping of `RelayData` -> `FillStatus` is stored within each SpokePool instance. This mapping can be queried with the hashed `V3RelayData` for a deposit, allowing the status for the corresponding fill to be queried. @@ -81,7 +106,7 @@ A V3RelayExecutionEventInfo instance is emitted with each `FilledV3Relay` event | Name | Type | Description | | :--- |:---- | :---------- | -| updatedRecipient | address | The recipient of the funds being transferred. This may be the `recipient` identified in the original deposit, or an updated `recipient` following a `RequestedSpeedUpV3Deposit` event. | +| updatedRecipient | bytes32 | The recipient of the funds being transferred. This may be the `recipient` identified in the original deposit, or an updated `recipient` following a `RequestedSpeedUpV3Deposit` event. | | updatedOutputAmount | uint256 | The amount sent to `updatedRecipient` by the relayer completing the fill. | | updatedMessage | bytes | Data that is forwarded to the recipient as part of a relay. | | repaymentChainId | uint256 | The chain specified by the depositor for fill repayment. | @@ -102,20 +127,49 @@ A V3SlowFill instance is emitted with each `FilledV3Relay` event (see below). The `updatedRecipient` field is normally set to the `recipient` from the corresponding `V3FundsDeposited` event. In the event that the relayer completes the fill with an accompanying `RequestedSpeedUpV3Deposit` event, `updatedRecipient` will be set to the address approved by the update. ## Events -Across V3 defines the following new events: +Across V3 defines the following events: +- FundsDeposited - V3FundsDeposited +- RequestedSpeedUpDeposit - RequestedSpeedUpV3Deposit +- FilledRelay +- FilledV3Relay +- RequestedSlowFill - RequestedV3SlowFill +- ClaimedRelayerRefund + +### Event Deprecation +The following events are marked for deprecation. See [Migration](#migration) for more information. +- V3FundsDeposited +- RequestedSpeedUpV3Deposit - FilledV3Relay +- RequestedV3SlowFill -### V3FundsDeposited -The `V3FundsDeposited` event emits the unique `V3RelayData` for an individual deposit. No additional fields are defined. Consumers of this event should append the `originChainId` in order to avoid unintentionally mixing events from different chains. +### FundsDeposited, V3FundsDeposited +The `FundsDeposited` event emits the unique `V3RelayData` for an individual deposit. No additional fields are defined. +The `V3FundsDeposited` event emits the unique `V3RelayDataLegacy` for an individual deposit. No additional fields are defined. -Note: -- The `V3FundsDeposited` `outputToken` is not required to be a known HubPool `l1Token`. In-protocol arbitrary token swaps are technically supported by Across v3. +#### Note +- Consumers of these events should append the `originChainId` in order to avoid unintentionally mixing events from different chains. +- The `FundsDeposited` and `V3FundsDeposited` `outputToken` field is not required to be a known HubPool `l1Token`. In-protocol arbitrary token swaps are supported by Across v3. +- The address identified by `exclusiveRelayer` has exclusive right to complete the relay on the destination chain until `exclusivityDeadline` has elapsed. +- If `exclusivityDeadline` is set to a past timestamp, any address is eligible to fill the relay. +- Any deposit that remains unfilled after the specified `fillDeadline` shall be refunded to the `depositor` address via the origin SpokePool in a subsequent settlement bundle. + +### RequestedSpeedUpDeposit, RequestedSpeedUpV3Deposit +The `RequestedSpeedUpDeposit` event emits the following data. + +| Name | Type | Description | +| :--- | :--- | :---------- | +| depositId | uint256 | The depositId of the corresponding `FundsDeposited` event to be updated. | +| depositor | bytes32 | The depositor of the corresponding `FundsDeposited` event to be updated. | +| updatedOutputAmount | uint256 | The new outputAmount approved by the depositor. This should be _lower_ than the original deposit `outputAmount`. | +| updatedRecipient | bytes32 | The new recipient to receive the funds. | +| updatedMessage | bytes | The new message to be supplied to the recipient. | +| depositorSignature | bytes | A signature by the depositor authorizing the above updated fields. | + +The `RequestedSpeedUpV3Deposit` event emits the following data: -### RequestedSpeedUpV3Deposit -The `RequestedSpeedUpV3Deposit` emits specifies the following fields: | Name | Type | Description | | :--- | :--- | :---------- | | depositId | uint32 | The depositId of the corresponding `V3FundsDeposited` event to be updated. | @@ -125,29 +179,42 @@ The `RequestedSpeedUpV3Deposit` emits specifies the following fields: | updatedMessage | bytes | The new message to be supplied to the recipient. | | depositorSignature | bytes | A signature by the depositor authorizing the above updated fields. | -Note: -- Relayers may optionally append the updated request from a `RequestedSpeedUpV3Deposit` when filling a relay, but have no obligation to use the updated request. -- The address identified by `exclusiveRelayer` has exclusive right to complete the relay on the destination chain until `exclusivityDeadline` has elapsed. -- If `exclusivityDeadline` is set to a past timestamp, any address is eligible to fill the relay. -- Any attempted fill that occurs after `fillDeadline` has elapsed shall be rejected by the destination SpokePool. The corresponding deposit shall be refunded via the origin SpokePool in the relevant settlement bundle. +#### Note +- Relayers may optionally append the updated request from a `RequestedSpeedUpDeposit` or `RequestedSpeedUpV3Deposit` event when filling a relay, but have no obligation to use the updated request. + +### RequestedSlowFill, RequestedV3SlowFill +The `RequestedSlowFill` event extends the `V3RelayData` type by applying the following adjustments: + +| Name | Type | Description | +| :--- | :--- | :---------- | +| message | omitted | This field is omitted in favour of the `messageHash` field. | +| messageHash | bytes32 | The keccak256 hash of the `V3RelayData` message field where the message is non-empty, or `bytes32(0)` for an empty message. This field is included in place of the `V3RelayData` message field. | + +The `RequestedV3SlowFill` event emits an `V3RelayDataLegacy` instance. + +#### Note +- These events are emitted on the destination chain and signal to proposers that a slow fill has been requested for a specific deposit. -### RequestedV3SlowFill -The `RequestedV3SlowFill` event emits `V3RelayData`. This event is emitted on the destination chain and is intended to signal to proposers that a slow fill has been requested. +### FilledRelay, FilledV3Relay -Note: -- `RequestedV3SlowFill` events cannot occur until the `exclusivityDeadline` timestamp has elapsed on the destination chain. -- `RequestedV3SlowFill` events cannot be emitted once the `fillDeadline` timestamp has elapsed on the destination chain. +The `FilledRelay` event extends the `V3RelayData` type by applying the following adjustments: +| Name | Type | Description | +| :--- | :--- | :---------- | +| message | omitted | This field is omitted from the `FilledRelay` event in favour of the `messageHash` field. | +| messageHash | bytes32 | The keccak256 hash of the `V3RelayData` message field where the message is non-empty, or `bytes32(0)` for an empty message. This field is included in place of the `V3RelayData` message field. | +| relayer | bytes32 | The address completing relay on the destination SpokePool. | +| repaymentChainId | uint256 | The depositId of the corresponding `V3FundsDeposited` event to be updated. | +| relayExecutionInfo | V3RelayExecutionEventInfo | The effective `recipient`, `message` and `outputAmount`, as well as the `FillType` performed (FastFill, ReplacedSlowFill, SlowFill). | -### FilledV3Relay -The `FilledV3Relay` event extends the `V3RelayData` type by adding the following fields: +The `FilledV3Relay` event extends the `V3RelayDataLegacy` type by adding the following fields: | Name | Type | Description | | :--- | :--- | :---------- | | relayer | address | The address completing relay on the destination SpokePool. | | repaymentChainId | uint256 | The depositId of the corresponding `V3FundsDeposited` event to be updated. | -| relayExecutionInfo | V3RelayExecutionInfo | The effective `recipient`, `message` and `outputAmount`, as well as the `FillType` performed (FastFill, ReplacedSlowFill, SlowFill). | +| relayExecutionInfo | V3RelayExecutionEventInfo | The effective `recipient`, `message` and `outputAmount`, as well as the `FillType` performed (FastFill, ReplacedSlowFill, SlowFill). | -Note: -- Consumers of this event should append the `destinationChainId` attribute in order to avoid unintentioanlly mixing events from different chains. +#### Note +- Consumers of these events should append the `destinationChainId` attribute in order to avoid unintentioanlly mixing events from different chains. # Root Bundle Proposals @@ -174,7 +241,7 @@ A `PoolRebalanceLeaf` shall consist of the following: | leafId | uint8 | Index of the `PoolRebalanceLeaf` within the ordered array of `PoolRebalanceLeaves`. | | l1Tokens | address[] | Ordered array of HubPool `l1Token` addresses. -Note: +#### Note - The format of Pool Rebalance leaves is unchanged from Across v2. ### Relayer Refund Leaves @@ -187,27 +254,55 @@ Note: | refundAddresses | uint256[] | Ordered array of addresses to be refunded by this `RelayerRefundLeaf`. | | refundAmounts | uint256[] | Ordered array of amounts of `l2TokenAddress`to be refunded to the corresponding `refundAddress`. | -Note: +#### Note - The format of Relayer Refund leaves is unchanged from Across v2. - Across v3 expands the utility of the `RelayerRefundLeaf` to include issuing depositor refunds on origin chains in the event of an expired `V3FundsDeposited` `fillDeadline`. ### Slow Relay Leaves Across v3 `SlowRelayLeaf` objects are defined by the `V3SlowFill` [data type](#data-types). -Note: +#### Note - The format of Slow Relay leaves is updated from Across v2. +## Definitions + +### Deposits +A `Deposit` is defined as an instance of either of the following events: +- `FundsDeposited`. +- `V3FundsDeposited`. + +### Fills +A `Fill` is defined as an instance of either of the following events: +- `FilledRelay`. +- `FilledV3Relay`. + +### Slow Fill Requests +A `Slow Fill` is defined as an instance of either of the following events: +- `RequestedSlowFill`. +- `RequestedV3SlowFill`. + +### RelayData +`RelayData` is defined as an instance of etiher of the following data types: +- `V3RelayData`. +- `V3RelayDataLegacy`. + +### Bundle Block Range +The `Bundle Block Range` is the pair of start and end blocks for a given proposal. See [Identifying Bundle Block Ranges](#identifying-bundle-block-ranges) for guidance on identifying the `Bundle Block Range`. + +### Fill Status +A `Deposit` is considered to be `Filled` on the destination chain when the destination `SpokePool` `FillStatus` mapping shows state `Filled` for the `Deposit` `RelayData` hash. + ## Method ### Identifying SpokePool Contracts The current SpokePool address for a specific chain is available by querying `HubPool.crossChainContracts()`. The chainId must be specified in the query. In case of SpokePool migations, historical SpokePool addresses can be identified by scraping HubPool `CrossChainContractsSet` events. -### Identifying a chains inclusion as a "Lite" Chain +### Identifying "Lite" deployments We consider a deposit to "originate" or be "destined for" a "Lite chain" if the `LITE_CHAIN_ID_INDICES` value in the AcrossConfigStore includes the deposit's origin chain or destination chain respectively as of the deposit's `quoteTimestamp` field. These chains impose constraints on relayer repayments and slow fills. ### Resolving SpokePool tokens to their HubPool equivalent For the purpose of identifying the equivalent HubPool token given a SpokePool token, the following shall be followed: 1. Find the latest `SetRebalanceRoute` event with a block timestamp at or before the relevant HubPool block number, where the relevant SpokePool chain ID and token address match the `SetRebalanceRoute` `destinationChainId` and `destinationToken` fields. - - In the case of a `V3FundsDeposited` event, the relevant HubPool block number is identified by resolving the `quoteTimestamp` to a HubPool block number. + - In the case of a `Deposit` event, the relevant HubPool block number is identified by resolving the `quoteTimestamp` to a HubPool block number. 3. From the resulting `SetRebalanceRoute` event, select the associated `l1Token` field. 4. Search the `SetPoolRebalanceRoute` events for the same `l1Token` and `destinationChainId` at or before the applicable HubPool block number. 5. Using the `l1Token` value found in step 2, search for the latest `SetRebalanceRoute` event at or before the applicable HubPool block number where `l1Token` and `destinationChainId` that match the extracted `l1Token` and SpokePool chain ID. If a match is found, the addresses match and are considered cross-chain equivalents. @@ -218,67 +313,146 @@ In addition to the description [UMIP-157](https://github.com/UMAprotocol/UMIPs/b - 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. -### Finding Valid Relays -For the purpose of computing relayer repayments, each `FilledV3Relay` event emitted within the target block range on a destination SpokePool shall be considered valid by verifying that: -1. The `FilledV3Relay` `FillType` field is not set to `SlowFill`, -2. The component `V3RelayData` maps exactly to a corresponding `V3FundsDeposited` event emitted on the relevant `originChainId`. This may be compared by comparing the hashes of the two objects. +### Reconstructing FilledRelay messages +The `FilledRelay` event emits the `messsageHash` field. This field is set as follows: +- When the `RelayData` `message` field is empty (`0x`): `bytes32(0)`, OR +- When the `RelayData` `message` field is non-empty (`0x...`): `keccak256(message)`. + +### Computing RelayData Hashes +A `RelayData` hash is computed as the `keccak256` hash over the ABI-encoded representation of the arguments `relayData`, `destinationChainId`, where: +- `relayData` is of type `V3RelayData` or `V3RelayDataLegacy`. +- `destinationChainId` is of type `uint256`. + +- When the `FilledRelay` event data omits the `message` field, the `message` field shall be populated according to the procedure specified in [Reconstructing FilledRelay messages](#resonstructing-filledrelay-messages). + +#### Note +- This method produces the identical hashes for `V3RelayData` and `V3RelayDataLegacy` for the same input data due to `address` promotion to `bytes32` prior to hashing. + +### Computing Relayer Repayments & Depositor Refunds +For the purpose of computing relayer repayments, the following procedures are completed: +- Validating Fills +- Validating Pre-Fills +- Finding Expired Deposits +- Finding Unfillable Deposits + +#### Note +- Depositor refunds are issued via the Relayer Repayment workflow. + +### Validating Fills +Each of the `Fills` emitted within the `Bundle Block Range` on a destination SpokePool shall be considered valid by verifying that: +1. The `Fill` event `FillType` field is not set to `SlowFill`, AND +2. The component `RelayData` maps exactly to one or more corresponding `Deposit` events emitted on the relevant `originChainId`, AND +3. The corresponding `Deposit` event occurred within or before the `Bundle Block Range` on the origin chain SpokePool. + +#### Note +- If the `Deposit` event specifies `outputToken` `bytes32(0)` (i.e. the Zero Address), the equivalent SpokePool token on the destination chain shall be substituted in. For the purpose of determining `RelayData` equivalency, the updated/substituted `outputToken` shall be used in place of the Zero Address. +- `RelayData` equality can be determined by comparing the keccak256 hashes of two `RelayData` objects. +- Fills of type `SlowFill` are valid, but are not relevant for computing relayer repayments. -If the `V3FundsDeposited` event specifies `outputToken` 0x0 (i.e. the Zero Address), the equivalent SpokePool token on the destination chain shall be substituted in. For the purpose of determining `V3RelayData` equivalency, the updated/substituted `outputToken` shall be used in place of 0x0. +### Validating Pre-fills +For each of the `Deposits` emitted within the `Bundle Block Range` where no corresponding `Fill` is identified on the destination chain within the `Bundle Block Range`, identify the valid `Fill` according to the following criteria: +1. Verify that the destination chain `FillStatus` for the `Deposit` `RelayData` is `Filled` as at the destination chain end block number for the proposal. +2. Resolve the corresponding `Fill` on the destination chain. +3. Verify that the `FillType` is `FastFill` or `ReplacedSlowFill` AND that the `Fill` occurred prior to the current the `Bundle Block Range` on the destination chain SpokePool. + +#### Note +- No specific method is prescribed for resolving the `Fill` on the destination chain. An `eth_getLogs` request can facilitate this, and if required, the `Bundle Block Range` could be narrowed by a binary search over the `FillStatus` field. This is left as an implementation decision. ### Finding Expired Deposits -For the purpose of computing depositor refunds, each `V3FundsDeposited` event shall be considered expired by verifying that: -1. The `fillDeadline` timestamp elapsed within the target block range on the destination SpokePool (i.e. the `fillDeadline` expired between the `block.timestamp` of the destination chain's bundle start and end block), -2. The `FillStatus` on the destination SpokePool is set to `Unfilled` or `SlowFillRequested`. +For the purpose of computing depositor refunds, each `Deposit` shall be considered expired by verifying that: +1. The `fillDeadline` timestamp elapsed within the `Bundle Block Range` on the destination SpokePool (i.e. the `fillDeadline` expired between the `block.timestamp` of the destination chain's bundle start and end block), +2. The `FillStatus` on the destination SpokePool is set to `Unfilled` or `SlowFillRequested` as at the end of the `Bundle Block Range`. -Note: +#### Note - Expired deposits shall be refunded to the `depositor` address on the origin SpokePool. - Depositor refunds are to be issued as part of the relayer refund procedure. -- The `fillDeadline` timestamp shall be resolved to a block number on the destination chain in order to determine inclusion within the target block range. +- The `fillDeadline` timestamp shall be resolved to a block number on the destination chain in order to determine inclusion within the `Bundle Block Range`. + +### 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`. +2. The destination chain `FillStatus` for the `Deposit` is `Filled`. +3. The destination chain `Fill` `FillType` was `SlowFill`. +4. The destination chain `Fill` occurred within the current `BundleBlockRange` or the `Deposit` occurred within the current `BundleBlockRange`. + +#### Note +- `Deposits` are considered identical when their `RelayData` matches. +- `Deposits` are considered duplicate when they are emitted after their initial identical instance. ### Finding Slow Fill Requests -For the purpose of computing slow fills to be issued to recipients, each `RequestedV3SlowFill` event emitted within the target block range on a destination SpokePool shall be considered valid by verifying that: -1. The `inputToken` and `outputToken` addresses are equivalent at the deposit `quoteTimestamp`, -2. The `fillDeadline` has not already elapsed relative to the `destinationChainId` bundle end block number, -3. The destination SpokePool `FillStatus` mapping for the relevant `V3RelayData` hash is `SlowFillRequested`, -4. The `RequestedV3SlowFill` `V3RelayData` is matched by a corresponding `V3FundsDeposited` event on the origin SpokePool, +For the purpose of computing slow fills to be issued to recipients, each `Slow Fill Request` emitted within the `Bundle Block Range` on a destination SpokePool shall be considered valid by verifying that: +1. The `fillDeadline` is greater than `destinationChainId` bundle end block's `block.timestamp`, +2. The `Slow Fill Request` `RelayData` is matched by one or more corresponding `Deposit` events that occurred within or before the `Bundle Block Range` on the origin SpokePool, +3. The `inputToken` and `outputToken` addresses are equivalent at the earliest matching deposit's `quoteTimestamp`, +4. The destination SpokePool `FillStatus` mapping for the relevant `RelayData` hash is `SlowFillRequested` at the end of the `Bundle Block Range`, 5. The `originChainId` and `destinationChainId` are not Lite chains. -Note: -- A slow fill request is made by supplying a complete copy of the relevant `V3RelayData` emitted by a `V3FundsDeposited` event. -- The resulting set of validated `RequestedV3SlowFill` events shall be included as SlowFills in the subsequent root bundle proposal. +#### Note +- A `Slow Fill Request` is made by supplying a complete copy of the relevant `RelayData` emitted by a `Deposit` event. +- The resulting set of validated `Slow Fill Requests` shall be included as SlowFills in the subsequent root bundle proposal. +- A `Slow Fill Request` may correspond to a `Deposit` from previous bundles. + +### Finding Early Slow Fill Requests +When an early `Slow Fill Request` is implied, the `Slow Fill Request` shall be validated as follows: +1. The `fillDeadline` has not already elapsed relative to the `destinationChainId` bundle end block number, +2. The `inputToken` and `outputToken` addresses are equivalent at the earliest matching deposit's `quoteTimestamp`, +3. Neither the`originChainId` nor the `destinationChainId` is a `Lite` chain. + +#### Note +- An early `Slow Fill Request` is implied where a `Deposit` emitted within the current `Bundle Block Range` has a `FillStatus` of `SlowFillRequested` as at the end of the current `Bundle Block Range` on the destination chain, but where no `Slow Fill Request` is identified within the current `Bundle Block Range`. This may occur where the `Slow Fill Request` was submitted prior to the current bundle. ### Computing LP Fees -Each valid `FilledV3Relay` event is subject to an LP fee. The procedure for computing LP fees is as defined in [UMIP-136 Add IS_RELAY_VALID as a supported price identifier](https://github.com/UMAprotocol/UMIPs/blob/7b1a046098d3e2583abd0372c5e9c6003b46ad92/UMIPs/umip-136.md), with the following amendments: +Each valid `Fill` is subject to an LP fee. The procedure for computing LP fees is as defined in [UMIP-136 Add IS_RELAY_VALID as a supported price identifier](https://github.com/UMAprotocol/UMIPs/blob/7b1a046098d3e2583abd0372c5e9c6003b46ad92/UMIPs/umip-136.md), with the following amendments: - The AcrossConfigStore contract shall be used to identify the correct rate model, instead of a `RateModelStore` contract. - The `HubPool` `liquidityUtilizationCurrent()` and `liquidityUtilizationPostRelay()` functions shall be used instead of the `BridgePool` variant. - The event `inputToken` shall be mapped from the SpokePool address to a HubPool `l1Token` address by following the matching procedure outlined above. -- The LP fee is computed between the `originChainId` and `FilledV3Relay.repaymentChainId` where the `relayExecutionInfo.FillType != SlowFill` and `FilledV3Relay.destinationChainId` otherwise. +- The LP fee is computed between the `originChainId` specified by the `Deposit` and `repaymentChainId` specified by the relayer, where the `relayExecutionInfo.FillType != SlowFill` and the Fill `destinationChainId` otherwise. -Note: -- The LP fee is typically referenced as a multiplier of the `V3FundsDeposited` `inputAmount`, named `realizedLpFeePct` elsewhere in this document. +#### Note +- The LP fee is typically referenced as a multiplier of the `Deposit` `inputAmount`, named `realizedLpFeePct` elsewhere in this document. ### Computing Bundle LP Fees -The bundle LP fee for a target block range on a SpokePool and token pair shall be determined by summing the applicable LP fees for each of the following validated events: -- `FilledV3Relay` +The bundle LP fee for a `Bundle Block Range` on a SpokePool and token pair shall be determined by summing the applicable LP fees for each of the following validated events: +- `FilledRelay`. +- `FilledV3Relay`. + +#### Note + +Each `FilledRelay` or `FilledV3Relay` can have multiple associated deposit events. In the event of multiple matching deposit events, there will be multiple LP fees paid per event in the case of a non slow fill. ### Computing Relayer Repayments -For a validated `FilledV3Relay` event, the relayer repayment amount shall be computed as follows: -- `(inputAmount * (1 - realizedLpFeePct)) / 1e18`, where `realizedLpFeePct` is computed over the set of HubPool `l1Token`, `originChainId` and `repaymentChainId` at the HubPool block number corresponding to the relevant `V3FundsDeposited` `quoteTimestamp`. +For each validated matching `Deposit` event, the relayer repayment amount shall be computed as follows: +- `(inputAmount * (1 - realizedLpFeePct)) / 1e18`, where `realizedLpFeePct` is computed over the set of HubPool `l1Token`, `originChainId` and `repaymentChainId` at the HubPool block number corresponding to the relevant `Deposit` `quoteTimestamp`. - The applicable rate model shall be sourced from the AcrossConfigStore contract for the relevant `l1Token`. -- Deposits where the `originChainId` is a "Lite" chain in the AcrossConfigStore as of the `quoteTimestamp` shall always be repaid on the deposit's origin chain. This means the protocol overrides the relayer's requested `repaymentChainId` with the `originChainId` instead. +- For a given `Fill` that satisfies the requirements for relayer repayment, each matching `Deposit` generates a distinct repayment computed against its `quoteTimestamp`. + +The applied `repaymentChainId` shall be determined as follows: +- When the `originChainId` is a `Lite chain` as at the `Deposit` `quoteTimestamp`: `originChainId`, ELSE +- When no `PoolRebalanceRoute` exists for the `repaymentToken` on the `Fill` `repaymentChainId`: `destinationChainId`, ELSE +- The `repaymentChainId` as specified in the `Fill`. + +The applied `repayment address` shall be determined as follows: +- When the `Fill` `relayer` address is valid for the applied `repaymentChainId`: `relayer`, ELSE +- The `Fill` `msg.sender` address. + +If the applied `repayment address` is not valid for the applied `repaymentChainId`, the repayment shall be discarded. + +#### Note +- Examples of an invalid `relayer` address include: + - An SVM address on an EVM chain. ### Computing Deposit Refunds -For an expired `V3FundsDeposited` 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`. ### 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: -- `updatedOutputAmount = (inputAmount * (1 - realizedLpFeePct)) / 1e18`, where `realizedLpFeePct` is computed at the deposit `quoteTimestamp` between `originChainId` and `destinationChainId`. +- `updatedOutputAmount = (inputAmount * (1 - realizedLpFeePct)) / 1e18`, where `realizedLpFeePct` is computed at the earliest matching deposit's `quoteTimestamp` between `originChainId` and `destinationChainId`. -Constraint: -- The `V3FundsDeposited` `outputAmount` shall _not_ be considered when determining SlowFill amounts. +#### Constraint +- The `Deposit` `outputAmount` shall _not_ be considered when determining SlowFill amounts. -Note: -- The `V3FundsDeposited` `outputAmount` specifies the exact amount to be received by the `recipient` for a standard fill, and is therefore exclusive of any relayer or LP fees paid. +#### Note +- The `Deposit` `outputAmount` specifies the exact amount to be received by the `recipient` for a standard fill, and is therefore exclusive of any relayer or LP fees paid. ### Finding the Opening Running Balance The Opening Running Balance is defined as the cumulative running balance as at the previous successful (undisputed) Root Bundle Proposal. @@ -295,17 +469,17 @@ The procedure for computing running balances for an `l1Token` and `chainId` pair 1. Initialize the running balance to 0. 2. Add relayer refunds: - - For each group of validated `FilledV3Relay` events, initialize a running balance at 0 and add the add the relayer repayment. + - 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 `V3FundsDeposited` events that expired within the target 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 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. 4. Add slow fills: - - For each group of validated `RequestedV3SlowFill` events, add each slow relay's `updatedOutputAmount` to the group's running balance. + - For each group of validated `Slow Fill Requests`, add each slow relay's `updatedOutputAmount` to the group's running balance. 5. Subtract excesses from unexecuted slow fills: - - For each group of validated `FilledV3Relay` events where the `FillType` is `ReplacedSlowFill` and where there is no valid `RequestedV3SlowFill` event with an identical relay data hash in the current bundle data, subtract the SlowFill `updatedOutputAmount` from the running balance in recognition that the SlowFill will never be executed because the fill amount has already been transferred. - - For each expired deposit refund identified above where the `FillStatus` on the deposit destination chain for the deposit's relay data is `RequestedSlowFill` and the matching slow fill request is not in the current bundle range, subtract the associated SlowFill `updatedOutputAmount` from the running balance in recognition that the SlowFill cannot be executed past the `fillDeadline`. + - For each group of validated `Fills` where the `FillType` is `ReplacedSlowFill` and where there is no valid `Slow Fill Request` with an identical relay data hash in the current bundle data, subtract the SlowFill `updatedOutputAmount` from the running balance in recognition that the SlowFill will never be executed because the fill amount has already been transferred. + - For each expired deposit refund identified above where the `FillStatus` on the deposit destination chain is `RequestedSlowFill` and the matching slow fill request is not in the current bundle range, subtract the associated SlowFill `updatedOutputAmount` from the running balance in recognition that the SlowFill cannot be executed past the `fillDeadline`. 6. Add the Opening Running Balance for the selected `l1Token` and `chainId` pair. @@ -325,13 +499,13 @@ else if abs(running_balance) >= spoke_balance_threshold: running_balance = running_balance - net_send_amount ``` -Note: +#### Note The referenced `SpokeTargetBalances` is as specified by [UMIP-157 Token Constants](https://github.com/UMAprotocol/UMIPs/blob/pxrl/umip179/UMIPs/umip-157.md#token-constants): ## Constructing Root Bundles ### Constructing the Pool Rebalance Root -One Pool Rebalance Leaf shall be produced per unique `chainId` & `l1Token` pair, where the corresponding SpokePool emitted `V3FundsDeposited`, `FilledV3Relay` or`RequestedV3SlowFill` events within the target block range. +One Pool Rebalance Leaf shall be produced per unique `chainId` & `l1Token` pair, where the corresponding `Deposit`, `Fill` or`Slow Fill Request` events were emitted by the relevant SpokePool within the [Bundle Block Range](#identifying-bundle-block-ranges). Each Pool Rebalance Leaf shall be constructed as follows: 1. For each unique `chainId` and `l1Token` pair: @@ -356,13 +530,14 @@ The hash for each Pool Rebalance Leaf shall be constructed by using Solidity's s The Pool Rebalance Merkle Tree shall be constructed such that it is verifyable using [OpenZeppelin's MerkleProof](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/742e85be7c08dff21410ba4aa9c60f6a033befb8/contracts/utils/cryptography/MerkleProof.sol) library. -Note: +#### Note - See examples [here](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/742e85be7c08dff21410ba4aa9c60f6a033befb8/test/utils/cryptography/MerkleProof.test.js) for how to construct these types of trees. ### Constructing the Relayer Refund Root At least one Relayer Refund Leaf shall be produced for each unique combination of SpokePool and `l1Token` for any of the following conditions: -- Valid `FilledV3Relay` events, OR -- Expired `V3Fundsdeposited` events, OR +- Valid `Fills`, OR +- Expired `Deposits`, OR +- Unfillable `Deposits`, OR - A negative running balance net send amount. Each Relayer Refund Leaf shall be constructed as follows: @@ -372,8 +547,9 @@ Each Relayer Refund Leaf shall be constructed as follows: - If no relays are present, then the relevant token mapping from the previous successful proposal shall be used. - Each element of the `refundAddresses` and `refundAmounts` arrays shall be produced according to the defined procedure for computing relayer repayments. - One entry shall exist per unique address, containing the sum of any outstanding: - - Relayer repayments, and/or - - Expired deposits. + - Relayer repayments, and + - Expired deposits, and + - Pre-filled deposits where the `FillType` is `SlowFill`. - The `refundAddresses` and `refundAmounts` arrays shall be ordered according to the following criteria: 1. `refundAmount` descending order, then 2. `relayerAddress` ascending order (in case of duplicate `refundAmount` values). @@ -391,42 +567,43 @@ The set of relayer refund leaves shall be ordered according to: The Relayer Refund Leaf `leafId` field shall be numbered according to the ordering established above, starting at 0. -Note: +#### Note - Once these leaves are constructed, they can be used to form a merkle root as described in the previous section. ### Constructing the Slow Relay Root -One Slow Relay Leaf shall be produced per valid `RequestedV3SlowFill` event emitted within the target block range for a destination SpokePool. +One Slow Relay Leaf shall be produced per valid `Slow Fill Request` emitted within the `Bundle Block Range` for a destination SpokePool. +One Slow Relay Leaf shall be produced per valid early `Slow Fill Request` where the corresponding `Deposit` was emitted within the `Bundle Block Range` for an origin SpokePool. -A Slow Relay Leaf shall not be produced if the `RequestedV3SlowFill` event's `inputAmount` is equal to 0 and the `message` is a zero bytes string. +A Slow Relay Leaf shall not be produced if the relevant `Slow Fill Request` `inputAmount` is equal to 0 and the `message` is a zero bytes string. + +When a `Slow Fill Request` corresponds to multiple identical `Deposits`, the applied `quoteTimestamp` shall be sourced from the earliest identical `Deposit`. Each Slow Relay Leaf shall be constructed as follows: -1. Set `relayData` to the `V3RelayData` data emitted by the validated `RequestedV3SlowFill` event. -2. Set `chainId` to `destinationChainId` from the corresponding validated `RequestedV3SlowFill` event. +1. Set `relayData` to the `RelayData` emitted by the validated `Slow Fill Request`. +2. Set `chainId` to `destinationChainId` from the corresponding validated `Slow Fill Request`. 3. Set `updatedOutputAmount` to the updated amount computed for the SlowFill. The array of Slow Relay Leaf instances shall be sorted according to; 1. `originChainId`, then 2. `depositId`. -Note: +#### Note - Once these leaves are constructed, they can be used to form a merkle root as described in the previous section. -- Deposits with disparate output tokens (i.e. where the outputToken is not the equivalent of inputToken on the destination chain) are explicitly not eligible for slow fills. Any instances of `RequestedV3SlowFill` events for non-equivalent tokens shall be ignored. +- Deposits with disparate output tokens (i.e. where the outputToken is not the equivalent of inputToken on the destination chain) are explicitly not eligible for slow fills. Any instances of `Slow Fill Requests` for non-equivalent tokens shall be ignored. # Recommendations - Proposers are responsible for detecting and mitigating incorrect or inconsistent RPC data. Proposers should take steps to validate the correctness of their RPC data before proposing. - Proposers should avoid relying on the deposit `outputAmount`, even for deposits where the `outputToken` is a known HubPool token. When computing fees, ensure that the `realizedLpFee` is _always_ subtracted from the `inputAmount`, rather than trying to infer them based on the spread between `inputAmount` and `outputAmount`. - Relayers are advised to factor in origin chain finality guarantees when making fills on destination chains. Origin chain re-organisation can lead to deposit re-ordering and can thus invalidate fills. -# Across v2 -> V3 Transition -Across v3 is supplementary to Across v2, adding extra logic support new types of deposits, fills and slow fill requests. At the point of upgrading to Across v3, it will no longer be possible for Across v2 `FundsDeposited` events to be emitted. In order to support continuity of service and to minimise disruption for third-party integrators, it will be possible for pre-existing `FundsDeposited` events to be filled via `FilledRelay` events. During this period, RootBundleProposals will contain relayer repayment and slow fill leaves for both Across v2 and v3. - -The V3 rules defined in this UMIP will apply beginning when the VERSION field in the ConfigStore is updated to 3 or higher. The ability for Across bundles to support V2 events may cease in a future VERSION increment. +# Migration +- Support for the logic described above (BUT NOT the updated events with `bytes32` types, like `FundsDeposited`, `FilledRelay`, ...) is required as of ConfigStore [VERSION](https://github.com/UMAprotocol/UMIPs/blob/7b1a046098d3e2583abd0372c5e9c6003b46ad92/UMIPs/umip-157.md#versions) 5. +- To ensure pre-fills are not double-refunded, the `Bundle Block Range` containing the version bump from 4 to 5 will follow the rules of this UMIP, but will not consider any `Fill` events from prior bundles for the purposes of generating relayer repayments. Similarly, no `Slow Fill Request` that was included in any prior bundle will be considered for the generation of a `Slow Relay Leaf`. All subsequent bundles will perform the logic exactly as described above. +- Support for Across events with `bytes32` types (`FundsDeposited`, `FilledRelay`, ...) is required as of ConfigStore [VERSION](https://github.com/UMAprotocol/UMIPs/blob/7b1a046098d3e2583abd0372c5e9c6003b46ad92/UMIPs/umip-157.md#versions) 6. +- The `Legacy` events defined in this UMIP are marked as deprecated 7 days after the ConfigStore [VERSION](https://github.com/UMAprotocol/UMIPs/blob/7b1a046098d3e2583abd0372c5e9c6003b46ad92/UMIPs/umip-157.md#versions) migration from 5 to 6. # Implementation -The Across v3 implementation is available in the Across [contracts-v2](https://github.com/across-protocol/contracts-v2) repository. +The Across v3 implementation is available in the Across [contracts-v2](https://github.com/across-protocol/contracts) repository. # Security considerations Across v3 has been audited by OpenZeppelin. - -Note: -- If a particular relayer refund is known to be unexecutable, it can be removed from the bundle by the proposer if a sufficient public justification is made before the proposal. This is intended to deal with unlikely situations, such as ag centralized token issuer blacklisting an address that is due a refund. If this leaf were to remain unaltered, this blacklisted address could block other addresses from recieving refunds.