Skip to content

Commit 81af36b

Browse files
committed
incentivized relays
1 parent c4b947e commit 81af36b

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

ecosystem/incentivized-relays-mvp.md

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# [Incentivized Relays MVP]: Design Doc
2+
3+
| | |
4+
| ------------------ | -------------------------------------------------- |
5+
| Author | _Hamdi Allam_ |
6+
| Created at | _2025-04-21_ |
7+
| Initial Reviewers | _Reviewer Name 1, Reviewer Name 2_ |
8+
| Need Approval From | _Reviewer Name_ |
9+
| Status | _Draft / In Review / Implementing Actions / Final_ |
10+
11+
## Purpose
12+
13+
<!-- This section is also sometimes called “Motivations” or “Goals”. -->
14+
15+
<!-- It is fine to remove this section from the final document,
16+
but understanding the purpose of the doc when writing is very helpful. -->
17+
18+
We want to preserve a single transaction experience in the Superchain event when a transaction spawns asynchronous cross chain invocations.
19+
20+
## Summary
21+
22+
<!-- Most (if not all) documents should have a summary.
23+
While the length will likely be proportional to the length of the full document,
24+
the summary should be as succinct as possible. -->
25+
26+
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.
27+
28+
This settlement system is built outside of 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.
29+
30+
## Problem Statement + Context
31+
32+
<!-- Describe the specific problem that the document is seeking to address as well
33+
as information needed to understand the problem and design space.
34+
If more information is needed on the costs of the problem,
35+
this is a good place to that information. -->
36+
37+
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 many hops, i.e A->B->C or A->B->A, 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.
38+
39+
## Proposed Solution
40+
41+
<!-- A high level overview of the proposed solution.
42+
When there are multiple alternatives there should be an explanation
43+
of why one solution was picked over other solutions.
44+
As a rule of thumb, including code snippets (except for defining an external API)
45+
is likely too low level. -->
46+
47+
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.
48+
49+
The changes proposed in [#266](https://github.com/ethereum-optimism/design-docs/pull/266) & [#282](https://github.com/ethereum-optimism/design-docs/pull/282) provide the foundation for 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`) upon nested cross domain messages that can be used to appropriately charge `tx.origin`.
50+
51+
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.
52+
53+
```solidity
54+
contract L2ToL2CrossDomainGasTank {
55+
uint256 constant MAX_DEPOSIT = 0.01 ether;
56+
57+
event Deposit(address depositor, uint256 amount);
58+
59+
mapping(address => uint256) public balanceOf;
60+
61+
function deposit() nonReentrant external payable {
62+
uint256 amount = msg.value;
63+
require(amount > 0);
64+
65+
uint256 newBalance = balanceOf[msg.sender] + amount;
66+
require(newBalance < MAX_DEPOSIT);
67+
68+
balanceOf[msg.sender] = newBalance;
69+
emit Deposit(msg.sender, amount)
70+
}
71+
}
72+
```
73+
74+
We cap the deposits in order to cut scope in supporting withdrawals. This makes our first iteration 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.
75+
76+
Relayers that deliver 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 incentive exists. The user must either relay messages themselves or rely on a 3rdparty provider.
77+
78+
```solidity
79+
contract L2ToL2CrossDomainGasTank {
80+
81+
event Claimed(bytes32 msgHash, address relayer, uint256 amount);
82+
83+
mapping(bytes32 => bool) claimed;
84+
85+
function claim(Identifier calldata id, bytes calldata payload) nonReentrant external {
86+
require(id.origin == address(messenger));
87+
ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(id, keccak256(payload));
88+
89+
// parse the receipt
90+
require(payload[:32] == RelayedMessageGasReceipt.selector);
91+
(bytes32 msgHash, bytes32 rootMsgHash, address relayer, address txOrigin, uint256 relayCost) = _decode(payload);
92+
93+
// ensure the original outbound message was sent from this chain
94+
require(messenger.sentMessages[rootMsgHash]);
95+
96+
// ensure unclaimed, and mark the claim
97+
require(!claimed[msgHash]);
98+
claimed[msgHash] = true
99+
100+
// compute total cost (adding the overhead of this claim)
101+
uint256 claimCost = CLAIM_OVERHEAD * block.basefee;
102+
uint256 cost = relayCost + claimCost;
103+
require(balanceOf[txOrigin] >= cost);
104+
105+
// compensate the relayer (whatever is left if there is not enough)
106+
uint256 amount = SafeMath.min(balanceOf[txOrigin], cost)
107+
balanceOf[txOrigin] -= amount;
108+
109+
new SafeSend{ value: amount }(payable(relayer));
110+
111+
emit Claimed(msgHash, relayer, cost)
112+
}
113+
}
114+
```
115+
116+
As relayers deliver messages, they can claim the cost against the original tx sender's deposit. This design introduces some off-chain complexity for the relayer.
117+
118+
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.
119+
2. The relayer should also track the pending claimable `RelayedMessageGasReceipt` of the `rootMsgHash` callstack to maximize the likelihood that the held deposit is sufficient.
120+
3. The relayer should claim the receipt within a reasonable time frame to ensure they are compensated.
121+
- An unrelated relayer not checking (2) might claim newer transitive message that depletes the funds. A version of the gas tank where claims are ordered would fix this.
122+
- There are restrictions for the max age of any event pulled in with interop -- applying to claim flow with `RelayedMessageGasReceipt`
123+
124+
### Resource Usage
125+
126+
<!-- What is the resource usage of the proposed solution?
127+
Does it consume a large amount of computational resources or time? -->
128+
129+
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.
130+
131+
### Single Point of Failure and Multi Client Considerations
132+
133+
<!-- Details on how this change will impact multiple clients. Do we need to plan for changes to both op-geth and op-reth? -->
134+
135+
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.
136+
137+
## Failure Mode Analysis
138+
139+
<!-- Link to the failure mode analysis document, created from the fma-template.md file. -->
140+
141+
_pending_.
142+
143+
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.
144+
145+
## Impact on Developer Experience
146+
147+
<!-- Does this proposed design change the way application developers interact with the protocol?
148+
Will any Superchain developer tools (like Supersim, templates, etc.) break as a result of this change? -->
149+
150+
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.
151+
152+
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.
153+
154+
## Alternatives Considered
155+
156+
<!-- List out a short summary of each possible solution that was considered.
157+
Comparing the effort of each solution -->
158+
159+
1. Rely on 3rdparty Bridge/Relay providers. See the problem statement in [#266](https://github.com/ethereum-optimism/design-docs/pull/266).
160+
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.
161+
162+
### Permissioned Gas Tank
163+
164+
We can create an intermediate version of the gas tank to build the entire flow end to end. The deployment of this gas tank is scoped to specific relayers which can unliterally charge depositors without needing a validated receipt. By having the same user-facing deposit api, we can get to a faster implementation to test any UX flows ahead of time.
165+
166+
```solidity
167+
contract PermissionedL2ToL2CrossDomainGasTank {
168+
constructor(address[] relayers) {}
169+
170+
function deposit() payable;
171+
function claim(address txSender, uint256 amount) onlyRelayers {}
172+
}
173+
```
174+
175+
## Risks & Uncertainties
176+
177+
<!-- An overview of what could go wrong.
178+
Also any open questions that need more work to resolve. -->
179+
180+
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.
181+
182+
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.

0 commit comments

Comments
 (0)