From fa45cecfc54db41c52ca6e5d0084be3bd87d3fb9 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Tue, 22 Apr 2025 14:31:09 -0400 Subject: [PATCH 1/3] incentivized relays --- ecosystem/incentivized-relays-mvp.md | 155 +++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 ecosystem/incentivized-relays-mvp.md diff --git a/ecosystem/incentivized-relays-mvp.md b/ecosystem/incentivized-relays-mvp.md new file mode 100644 index 00000000..6bf219d1 --- /dev/null +++ b/ecosystem/incentivized-relays-mvp.md @@ -0,0 +1,155 @@ +# [Incentivized Relays MVP]: Design Doc + +| | | +| ------------------ | -------------------------------------------------- | +| Author | _Hamdi Allam_ | +| Created at | _2025-04-21_ | +| Initial Reviewers | _Reviewer Name 1, Reviewer Name 2_ | +| Need Approval From | _Reviewer Name_ | +| Status | _Draft / In Review / Implementing Actions / Final_ | + +## Purpose + + + + + +We want to preserve a single transaction experience in the Superchain event when a transaction spawns asynchronous cross chain invocations. + +## Summary + + + +With an incentivation framework, we can ensure permissionless delivery of cross domain messages by any relayer. Very similar to solvers fulfilling cross chain [intents](https://www.erc7683.org/) that are retroactively paid by the settlement system. + +This this settlement system can built build outside of the core protocol contracts, this iteration is meant to serve as a functional MVP to start from, leaving some open questions to be answered later. + +## Problem Statement + Context + + + +In order to pay relayers for delivering cross chain messages, relayers need to reimburse themselves for funds used during delivery. Since cross domain messages can span N hops, i.e A->B->C, this settlement system must ensure all costs are paid by the same fee payer. Thus all callbacks and transitive messages are implicitly incentivized by the originating transaction. + +## Proposed Solution + + + +An initial invariant that works well in establishing the fee payer is the `tx.origin` of the originating transaction. This is the same invariant that holds for 4337 and 7702 sponsored transaction. + +The changes proposed in [#266](https://github.com/ethereum-optimism/design-docs/pull/266), provides the foundation creating the first iteration of this settlement system, without enshrinment in the core protocol contracts. The `RelayedMessageGasReceipt` includes contextual information on the gas consumed, and the propogated (`tx.origin`, `rootMessageHash`,`callDepth`) upon nested cross domain messages that can be used to appropriately charge `tx.origin`. + +This settlement system is implemented via a new CREATE2 contract, `L2ToL2CrossDomainGasTank`, where fee payers can hold an ETH deposit, used to asynchronously pay relayers for delivered messages. + +```solidity +contract L2ToL2CrossDomainGasTank { + uint256 constant MAX_DEPOSIT = 0.01 ether; + + mapping(address => uint256) public balanceOf; + + function deposit() nonReentrant external payable { + uint256 amount = msg.value; + require(amount > 0); + + balanceOf[msg.sender] += amount; + + if (balanceOf[msg.sender] > MAX_DEPOSIT) { + uint256 refund = balanceOf[msg.sender] - MAX_DEPOSIT; + balanceOf[msg.sender] = MAX_DEPOSIT; + new SafeSend{ value: refund }(payable(msg.sender)); + } + } +} +``` + +We cap the deposits in order to cut scope in supporting withdrawals from the gas tank. This makes our first interation super simple since supporting withdrawals introduces a race between deposited funds and a relayer compensating themselves for message delivery. With a relatively low max deposit, we eliminate the risk of a large amount of stuck funds for a given account whilst the amount being sufficient enough to cover hundreds of sub-cent transactions. + +Relayers that have delivered messages can compensate themselves by pushing through the `RelayedMessageGasReceipt` event, validated with interop. Holding a deposit of 0 makes this feature a no-op. The user but either relay messages themselves, or can rely on a 3rdparty to ensure delivery. + +```solidity +contract L2ToL2CrossDomainGasTank { + + function claim(Identifier calldata id, bytes calldata payload) nonReentrant external { + require(id.origin == address(messenger)); + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(id, keccak256(payload)); + + // parse the receipt + require(payload[:32] == RelayedMessageGasReceipt.selector); + (bytes32 rootMsgHash, address txOrigin, address relayer, uint256 relayCost) = _decode(payload); + + // ensure message was sent from this chain + require(messenger.sentMessages[rootMsgHash]); + + // compute total cost + uint256 claimCost = CLAIM_OVERHEAD * block.basefee; + uint256 cost = relayCost + claimCost; + require(balanceOf[txOrigin] >= cost); + + // compensate the relayer + balanceOf[txOrigin] -= cost; + new SafeSend{ value: cost }(payable(relayer)); + } +} +``` + +As relayers deliver messages, they can claim the cost against the original tx originator's deposit with the gas tank. This design introduces some off-chain complexity for the relayer. + +1. The relayer must simulate the call and ensure the emitted tx origin has a deposit that will cover the cost +2. The relayer should also track the "claimable" `RelayedMessageGasReceipt` of the `rootMsgHash` callstack to maximize the likelihood that the deposit is sufficient. +3. The relayer should claim the receipt within a reasonable time frame to ensure they are compensated. An unrelated relayer not checking (2) might claim an older transitive message that depletes the funds. + +Integrating the `callDepth` is left out of scope in this first iteration to keep things simple. Later iterations may incorporate this to allow for a fee payer to set or have a global constant `max_call_depth` to cap how much a single call stack can claim against a deposit. + +### Resource Usage + + + +As a CREATE2 deployment, this settlement framework does not affect the core protocol contracts. All contract operations are on fixed-sized fields bounding the gas consumption of this contract. + +### Single Point of Failure and Multi Client Considerations + + + +No external contract calls are made during the deposit and claim pathways, with the `nonReentrant` modifier also applied to protect against re-entrancy attacks as ETH is transferred between the gas tank and the caller. + +## Failure Mode Analysis + + + +_pending_. Important to note that holding no deposit is makes this feature a no-op. Users leveraging 3rdparty relayers or different infrastructure can continue to do so without affect. The failure mode here is simply an unused gas tank. The `RelayedMessageGasReceipt` emmitted by the messenger can be ignored or consumed by a different party for their own purposes. + +## Impact on Developer Experience + + + +When sending a transaction that involves cross chain interactions, the frontend should simulate these interactions and ensure the gas tank has appropriate funds to pay. With `multicall`, a single transaction can bundle together the funding operation with the transaction if neededed. + +The infrastructure required for make cross chain tx simulation as simple as possible must also be taken into consideration. Since a cross chain tx requires a valid `CrossL2Inbox.Identifier`, there's already added complexity here in simulating side effects where their dependencies have not yet been executed. However, frontends can liberally make deposits without full simulation as the settlement system only charges what was used. + +## Alternatives Considered + + + +1. Rely on 3rdparty Bridge/Relay providers. See the problem statement in [#266](https://github.com/ethereum-optimism/design-docs/pull/266). +2. Offchain attribution. Schemes can be derived with web2-esque approaches such as API keys. With a special tx-submission endpoint, being able to tag cross chain messages with off-chain accounts to thus charge for gas used. This might a good fallback mechanism to have in place. + +## Risks & Uncertainties + + + +1. This incentive framework is insufficient and unused. This is not a problem as this settlement framework is not enshrined in the protocol and is a simple CREATE2 deployment. Entirely new frameworks can be derived to replace this or this feature can be deprecated out of protocol. + +2. Also important to note that holding no deposit is makes this feature a no-op. Users leveraging 3rdparty relayers or different infrastructure can continue to do so without affect. From 1fd10aeab9cdaf15452ff0272e7d51597fe452b6 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Tue, 22 Apr 2025 17:49:22 -0400 Subject: [PATCH 2/3] nits --- ecosystem/incentivized-relays-mvp.md | 35 +++++++++++++--------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/ecosystem/incentivized-relays-mvp.md b/ecosystem/incentivized-relays-mvp.md index 6bf219d1..1fb90259 100644 --- a/ecosystem/incentivized-relays-mvp.md +++ b/ecosystem/incentivized-relays-mvp.md @@ -15,7 +15,7 @@ -We want to preserve a single transaction experience in the Superchain event when a transaction spawns asynchronous cross chain invocations. +We want to preserve a single transaction experience in the Superchain event event when a transaction spawns asynchronous cross chain invocations. ## Summary @@ -25,7 +25,7 @@ the summary should be as succinct as possible. --> With an incentivation framework, we can ensure permissionless delivery of cross domain messages by any relayer. Very similar to solvers fulfilling cross chain [intents](https://www.erc7683.org/) that are retroactively paid by the settlement system. -This this settlement system can built build outside of the core protocol contracts, this iteration is meant to serve as a functional MVP to start from, leaving some open questions to be answered later. +This settlement system is built outside external to the core protocol contracts, with this iteration serving as a functional MVP to start from. Any cut scope significantly simplifies implementation and can be re-added as improvements in further versions. ## Problem Statement + Context @@ -34,7 +34,7 @@ as information needed to understand the problem and design space. If more information is needed on the costs of the problem, this is a good place to that information. --> -In order to pay relayers for delivering cross chain messages, relayers need to reimburse themselves for funds used during delivery. Since cross domain messages can span N hops, i.e A->B->C, this settlement system must ensure all costs are paid by the same fee payer. Thus all callbacks and transitive messages are implicitly incentivized by the originating transaction. +In order to pay relayers for delivering cross chain messages, relayers need to reimburse themselves for gas used during delivery. Since cross domain messages can span N hops, i.e A->B->C, this settlement system must ensure all costs are paid by the same fee payer. Thus all callbacks and transitive messages are implicitly incentivized by the originating transaction. ## Proposed Solution @@ -46,9 +46,9 @@ is likely too low level. --> An initial invariant that works well in establishing the fee payer is the `tx.origin` of the originating transaction. This is the same invariant that holds for 4337 and 7702 sponsored transaction. -The changes proposed in [#266](https://github.com/ethereum-optimism/design-docs/pull/266), provides the foundation creating the first iteration of this settlement system, without enshrinment in the core protocol contracts. The `RelayedMessageGasReceipt` includes contextual information on the gas consumed, and the propogated (`tx.origin`, `rootMessageHash`,`callDepth`) upon nested cross domain messages that can be used to appropriately charge `tx.origin`. +The changes proposed in [#266](https://github.com/ethereum-optimism/design-docs/pull/266), provides the foundation creating the first iteration of this settlement system -- without enshrinment in the core protocol contracts. The `RelayedMessageGasReceipt` includes contextual information on the gas consumed, and the propogated (`tx.origin`, `rootMessageHash`,`callDepth`) upon nested cross domain messages that can be used to appropriately charge `tx.origin`. -This settlement system is implemented via a new CREATE2 contract, `L2ToL2CrossDomainGasTank`, where fee payers can hold an ETH deposit, used to asynchronously pay relayers for delivered messages. +This settlement system is a permissionless CREATE2 deployment, `L2ToL2CrossDomainGasTank`, where tx senders can hold an ETH deposit, used to asynchronously pay relayers for delivered messages. ```solidity contract L2ToL2CrossDomainGasTank { @@ -60,20 +60,17 @@ contract L2ToL2CrossDomainGasTank { uint256 amount = msg.value; require(amount > 0); - balanceOf[msg.sender] += amount; + uint256 newBalance = balanceOf[msg.sender] + amount; + require(newBalance < MAX_DEPOSIT); - if (balanceOf[msg.sender] > MAX_DEPOSIT) { - uint256 refund = balanceOf[msg.sender] - MAX_DEPOSIT; - balanceOf[msg.sender] = MAX_DEPOSIT; - new SafeSend{ value: refund }(payable(msg.sender)); - } + balanceOf[msg.sender] = newBalance; } } ``` -We cap the deposits in order to cut scope in supporting withdrawals from the gas tank. This makes our first interation super simple since supporting withdrawals introduces a race between deposited funds and a relayer compensating themselves for message delivery. With a relatively low max deposit, we eliminate the risk of a large amount of stuck funds for a given account whilst the amount being sufficient enough to cover hundreds of sub-cent transactions. +We cap the deposits in order to cut scope in supporting withdrawals. This makes our first interation super simple since as withdrawal support introduces a race between deposited funds and a relayer compensating themselves for message delivery. With a relatively low max deposit, we eliminate the risk of a large amount of stuck funds for a given account whilst the amount being sufficient enough to cover hundreds of sub-cent transactions. -Relayers that have delivered messages can compensate themselves by pushing through the `RelayedMessageGasReceipt` event, validated with interop. Holding a deposit of 0 makes this feature a no-op. The user but either relay messages themselves, or can rely on a 3rdparty to ensure delivery. +Relayers that delivered messages can compensate themselves by pushing through the `RelayedMessageGasReceipt` event, validated with Superchain interop. A tx sender with no deposit makes this feature a no-op as no relayer will auto-deliver the message. The user but either relay messages themselves or can rely on a 3rdparty provider. ```solidity contract L2ToL2CrossDomainGasTank { @@ -101,11 +98,11 @@ contract L2ToL2CrossDomainGasTank { } ``` -As relayers deliver messages, they can claim the cost against the original tx originator's deposit with the gas tank. This design introduces some off-chain complexity for the relayer. +As relayers deliver messages, they can claim the cost against the original tx sender deposit with the gas tank. This design introduces some off-chain complexity for the relayer. -1. The relayer must simulate the call and ensure the emitted tx origin has a deposit that will cover the cost -2. The relayer should also track the "claimable" `RelayedMessageGasReceipt` of the `rootMsgHash` callstack to maximize the likelihood that the deposit is sufficient. -3. The relayer should claim the receipt within a reasonable time frame to ensure they are compensated. An unrelated relayer not checking (2) might claim an older transitive message that depletes the funds. +1. The relayer must simulate the call and ensure the emitted tx origin has a deposit that will cover the cost on the originating chain. +2. The relayer should also track the pending claimable `RelayedMessageGasReceipt` of the `rootMsgHash` callstack to maximize the likelihood that the held deposit is sufficient. +3. The relayer should claim the receipt within a reasonable time frame to ensure they are compensated. An unrelated relayer not checking (2) might claim newer transitive message that depletes the funds. Integrating the `callDepth` is left out of scope in this first iteration to keep things simple. Later iterations may incorporate this to allow for a fee payer to set or have a global constant `max_call_depth` to cap how much a single call stack can claim against a deposit. @@ -135,7 +132,7 @@ Will any Superchain developer tools (like Supersim, templates, etc.) break as a When sending a transaction that involves cross chain interactions, the frontend should simulate these interactions and ensure the gas tank has appropriate funds to pay. With `multicall`, a single transaction can bundle together the funding operation with the transaction if neededed. -The infrastructure required for make cross chain tx simulation as simple as possible must also be taken into consideration. Since a cross chain tx requires a valid `CrossL2Inbox.Identifier`, there's already added complexity here in simulating side effects where their dependencies have not yet been executed. However, frontends can liberally make deposits without full simulation as the settlement system only charges what was used. +The infrastructure required for make cross chain tx simulation as simple as possible must also be taken into consideration. Since a cross chain tx requires a valid `CrossL2Inbox.Identifier`, there's already added complexity here in simulating side effects where dependencies have not yet been executed. However, frontends can liberally make deposits without full simulation as the settlement system only charges what was used. ## Alternatives Considered @@ -150,6 +147,6 @@ Comparing the effort of each solution --> -1. This incentive framework is insufficient and unused. This is not a problem as this settlement framework is not enshrined in the protocol and is a simple CREATE2 deployment. Entirely new frameworks can be derived to replace this or this feature can be deprecated out of protocol. +1. This incentive framework is insufficient and unused. This is not a problem as this settlement framework is not enshrined in the protocol and is a simple CREATE2 deployment. Entirely new frameworks can be derived to replace this, leaving this unused. 2. Also important to note that holding no deposit is makes this feature a no-op. Users leveraging 3rdparty relayers or different infrastructure can continue to do so without affect. From a1b54bf1070b5029e67dddaae6995aca44ef2824 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 23 Apr 2025 14:03:56 -0400 Subject: [PATCH 3/3] claimId to ensure claims can only happen once --- ecosystem/incentivized-relays-mvp.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ecosystem/incentivized-relays-mvp.md b/ecosystem/incentivized-relays-mvp.md index 1fb90259..737d3c10 100644 --- a/ecosystem/incentivized-relays-mvp.md +++ b/ecosystem/incentivized-relays-mvp.md @@ -75,17 +75,25 @@ Relayers that delivered messages can compensate themselves by pushing through th ```solidity contract L2ToL2CrossDomainGasTank { + mapping(bytes32 => bool) claimed; + function claim(Identifier calldata id, bytes calldata payload) nonReentrant external { require(id.origin == address(messenger)); ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(id, keccak256(payload)); // parse the receipt require(payload[:32] == RelayedMessageGasReceipt.selector); - (bytes32 rootMsgHash, address txOrigin, address relayer, uint256 relayCost) = _decode(payload); + (bytes32 rootMsgHash, address txOrigin, uint256 callDepth, address relayer, uint256 relayCost) = _decode(payload); // ensure message was sent from this chain require(messenger.sentMessages[rootMsgHash]); + // ensure unclaimed, and mark the claim + bytes32 claimId = keccak256(abi.encode(rootMsgHash, callDepth)); + require(!claimed[claimId]); + + claimed[claimId] = true + // compute total cost uint256 claimCost = CLAIM_OVERHEAD * block.basefee; uint256 cost = relayCost + claimCost; @@ -98,6 +106,8 @@ contract L2ToL2CrossDomainGasTank { } ``` +A claim is uniquely identified by the `(rootMsgHash, callDepth)` tuple. + As relayers deliver messages, they can claim the cost against the original tx sender deposit with the gas tank. This design introduces some off-chain complexity for the relayer. 1. The relayer must simulate the call and ensure the emitted tx origin has a deposit that will cover the cost on the originating chain.