From 085f745d4c188e02bf1ec736478cd9d54d9e7e1a Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Mon, 7 Apr 2025 20:13:22 +0200 Subject: [PATCH 1/7] l2tol2cdm-gasreceipt --- ecosystem/l2tol2cdm-gasreceipt.md | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 ecosystem/l2tol2cdm-gasreceipt.md diff --git a/ecosystem/l2tol2cdm-gasreceipt.md b/ecosystem/l2tol2cdm-gasreceipt.md new file mode 100644 index 00000000..231188d8 --- /dev/null +++ b/ecosystem/l2tol2cdm-gasreceipt.md @@ -0,0 +1,64 @@ +# [Project Name]: Design Doc + +| | | +| ------------------ | -------------------------------------------------- | +| Author | _Author Name_ | +| Created at | _YYYY-MM-DD_ | +| Initial Reviewers | _Reviewer Name 1, Reviewer Name 2_ | +| Need Approval From | _Reviewer Name_ | +| Status | _Draft / In Review / Implementing Actions / Final_ | + +## Purpose + + + + + +## Summary + + + +## Problem Statement + Context + + + +## Proposed Solution + + + +### Resource Usage + + + +### Single Point of Failure and Multi Client Considerations + + + +## Failure Mode Analysis + + + +## Impact on Developer Experience + + +## Alternatives Considered + + + +## Risks & Uncertainties + + From 4e62dbbd6ee422b039e0d7fa55978cd344268abe Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 16 Apr 2025 11:41:07 -0400 Subject: [PATCH 2/7] finished --- ecosystem/l2tol2cdm-gasreceipt.md | 124 +++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/ecosystem/l2tol2cdm-gasreceipt.md b/ecosystem/l2tol2cdm-gasreceipt.md index 231188d8..c9f9067a 100644 --- a/ecosystem/l2tol2cdm-gasreceipt.md +++ b/ecosystem/l2tol2cdm-gasreceipt.md @@ -1,12 +1,12 @@ -# [Project Name]: Design Doc +# [L2ToL2CrossDomainMessenger Gas Receipt]: Design Doc -| | | -| ------------------ | -------------------------------------------------- | -| Author | _Author Name_ | -| Created at | _YYYY-MM-DD_ | -| Initial Reviewers | _Reviewer Name 1, Reviewer Name 2_ | -| Need Approval From | _Reviewer Name_ | -| Status | _Draft / In Review / Implementing Actions / Final_ | +| | | +| ------------------ | -------------------------------------------------------- | +| Author | _Hamdi Allam_ | +| Created at | _2025-04-14_ | +| Initial Reviewers | _Wonderland, Mark Tyneway, Harry Markley, Karl Floersch_ | +| Need Approval From | _Wonderland, Ben Clabby, Mark Tyneway_ | +| Status | _Draft / In Review / Implementing Actions / Final_ | ## Purpose @@ -15,12 +15,16 @@ +As more contracts are built that natively integrate with superchain interop, several single-action experiences can span up to N+1 op-stack chains to achieve their desired outcome. It is important we include the needed features & data to preserve a single-tx experience in the Superchain to not lose developers or users due to either a poor developer or user experience. + ## Summary +The general mental model for a transaction is that the sending account pays for the gas used. This mental model is the backbone for 4337 or 7702 sponsored transactions where tx.origin is decoupled from the calling account. By providing a mechanism for which the accumulated gas used for all cross domain calls with the initial `tx.origin` can be verifiably tracked, it provides the foundation for a single-tx experience. + ## Problem Statement + Context +Today, a single transaction cross-chain experience is achieved by pushing gas from the source -> destination chain with the message. Relayers are incentivized as long as the pushed funds for gas is sufficiently enough to cover the cost. This works in primitive usecases, where there's a single message from A -> B such as bridging funds. However even this has several drawbacks: + +- **Gas Estimation**. Doing on-chain estimation is very inaccurate, resulting in overestimation for high odds of delivery. Some protocols/relayers even pocket this difference instead of refunding the account. And if the account is refunded, it is dust on the remote chain for the user. +- **Relayer Vendor lock-in**. Contract developers must lock into a crosschain provider (Hyperlane,LayerZero,Wormhole) in order to make use of these services. This tight coupling makes it nearly impossible to switch without introducing complexity (upgradable smart contracts and provider-agonstic interfaces). + +Stepping outside this simple usecase, we can enumerate more scenarios in which pushing gas from source -> destination quickly falls apart. + +1. **Transitive cross-domain calls**. A -> B -> C. Gas estmation from the source chain (A), is already inefficient in the single hop. This scheme must also additionally overestimate and provide enough gas for each cross-domain call, B -> C. If a cross-domain call was to fail in an intermediate hop, it's unclear how to fund that transaction through from A. + +2. **Cross-Domain Composability**. A single transaction can fan out calls to multiple chains which then calls back to the originating contract, (A -> B -> A) & (A->C). For example, a Swap -> Bridge & Swap -> Bridge. Or two cross-chain DeFi protocols integrating each other. This type of cross-chain communication does not work in the world of relayers today. + + - For applications composing each other (A & B), we can not gaurantee the same relayer vendor is used. Hop A->B might leverage Hyperlane to send the message, while B might transitively use Wormhole to send a message to C. **This is why it's important to solve this problem natively**, such that as long as the providers (LayerZero/Wormhole/Hyperlane) use native message passing, everything works. + ## Proposed Solution + Providing a mechanism for which the accumulated gas used for all cross domain calls can be correlated with the original `tx.origin`. + +By natively providing this in the messenger, anyone can charge tx senders exactly what was used for all incurred cross domain messages. It is important to note here that this design doc does not prescribe a solution for charging this gas. This can happen within shared block building, enshrined in a app-level relay bounties framework, or used the 3rdparty providers in their own accounting systems. + +Support can be enumerated in 2 parts. + +### 1. Propogated SentMessage "Headers" or "Context". + +With nested cross domain calls, there's no way to correlate the subsequent messages with the original `tx.origin`, as each message has a unique message hash associated with it. By introducing a new field, "headers" or "context" that are appropriately propogated as nested cross domain calls are made, we can introduce contextual information unrelated to the cross domain message itself. + + event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message, bytes context); + +This added context should be versioned such that the propogated information can evolve over time to fit various needs. In this first iteration, we propose encoding the root-most `messageHash`, the `tx.origin` of that root call, and `call depth` of the cross domain message. This information is made availble in transient storage when relaying a message so that any further outbound messages simply forwards the appropriate context, rather than repopoulate an entirely new one. + +Some pseudocode describing the above, the exact api will be fleshed out in a specs PR. + +```solidity +function sendMessage(...) { + (,,bytes ctx memory) = crossDomainMessageContext(); + if (ctx.length == 0) { + // new "top-level" cross domain call (messageHash_ == outbound message) + ctx = abi.encodePacked(version, abi.encode(messageHash_, tx.origin, 0)); + } else { + // propogate, incrementing call dpeth + (bytes32 rootMessageHash_, address txorigin, uint256 depth) = _decodeContext(ctx); + ctx = abi.encodePacked(version, abi.encode(rootMessageHash_, txorigin, depth+1)) + } + + ... + + emit SentMessage(..., ctx) +} + +function relayMessage(...) { + (uint256 source, address sender, bytes memory ctx) = decodeSentMessage(...) + _storeMessageMetaData(source, sender, ctx); +} +``` + +Although first use of this propogated context is for gas receipts, this versioned abstraction can prove useful for much more information in the future without breaking the signature of `SentMessage`, critical for not breaking the call flow between `sendMessage` and `relayMessage`. + +### 2. Gas Receipt + +All superchain interop cross domain messages are relayed via the `L2ToL2CrossDomainMessenger#relayMessage` function. We can leverage this single-entrypoint to track the gas consumption and emit relevant execution information. + +We include the `block.basefee` and not the `tx.gasprice`, so that the priority fee (included in gasPrice) set by the relaying entity is not included and only the minimum execution costs are computed. + +- **Question**: _Should we simply emit the computed cost here rather than the individual parts? Would emitting the gasPrice be useful for someone else building on top?_ + +New Event: + + event RelayedMessageGasReceipt(bytes32 indexed msgHash, bytes32 indexed rootMsgHash, uint256 indexed depth, address indexed txOrigin, uint256 baseFee, uint256 gasUsed) + +```solidity +function relayMessage(...) { + uint256 _gasLeft = gasLeft() + _ = target.call{ value: msg.value }(message); + uint256 gasUsed = _gasLeft - gasLeft(); + + // there will always be populated context when relaying. + (,,bytes ctx memory) = crossDomainMessageContext(); + (bytes32 rootMsgHash, address txorigin, uint256 depth) = _decodeContext(ctx); + emit RelayedMessageGasReceipt(msgHash, rootMsgHash, depth, txorigin, block.basefee, gasUsed + RELAY_MESSAGE_OVERHEAD); +} +``` + +Thus this receipt can be used to reliably track `tx.origin` reponsible for all cross domain calls that occur from the first transaction. And with `rootMessageHash`, obtaining a unique identifier to tie all nested cross domain calls with their root-most outbound message. + +- **Question**: _Might the parent message hash be useful here too? It could easily be fetched via the a gas receipt where the rootMsgHash matches and the depth is 1 behind. These are indexed topics making it an efficient getLogs query._ + ### Resource Usage +There is an increase in gas cost to the `relayMessage` function: + +1. Additional encoded bytes for the `SentMessage` event, used to relay the message. +2. Emission of a new event, `RelayedMessageGasReceipt`. + +However the added event fields to `SentMessage` and fields of the new event, `RelayedMessageGasReceipt`, are encodings of fixed sized data fields bounding the added overhead. + ### Single Point of Failure and Multi Client Considerations +The added gas cost, if unbounded, could be a single point of failure to relaying messages since we are attaching auxilliary data to each outbound message. However as noted in the [Resource Usage](#resource-usage) section, the encoding of this data is fixed is size and cannot be exploited to include more. + ## Failure Mode Analysis +- + ## Impact on Developer Experience + +Will any Superchain developer tools (like Supersim, templates, etc.) break as a result of this change? ---> + +The contract API of the L2ToL2CrossDomainMessenger does not change with this introduction. Although any relayers operating on the devnet must update the their abi for the `SentMessage` event to relay. ## Alternatives Considered +1. Rely on 3rdparty Bridge/Relay providers. As listed in the problem statement, this can work well for single cross-chain messages but quickly falls apart to any complex use case. +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 + +`RelayedMessageGasReceipt` does not end up used in any meaningful gas payment system. In this world, the event can be ignored and even removed in a future hardfork. The serialized context propogated with every SentMessage can also be versioned to empty bytes to reduced the overhead that was added. It is important to note here that serialized context is an added feature irrespective of the gas receipt that can be re-purposed to propogate _any_ useful context. From 6ef55ddbc5bf15b8b4d9cc739fc1a6de46b0ef41 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 16 Apr 2025 11:45:18 -0400 Subject: [PATCH 3/7] wrap event block in solidity markdown --- ecosystem/l2tol2cdm-gasreceipt.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ecosystem/l2tol2cdm-gasreceipt.md b/ecosystem/l2tol2cdm-gasreceipt.md index c9f9067a..d0834773 100644 --- a/ecosystem/l2tol2cdm-gasreceipt.md +++ b/ecosystem/l2tol2cdm-gasreceipt.md @@ -63,7 +63,9 @@ Support can be enumerated in 2 parts. With nested cross domain calls, there's no way to correlate the subsequent messages with the original `tx.origin`, as each message has a unique message hash associated with it. By introducing a new field, "headers" or "context" that are appropriately propogated as nested cross domain calls are made, we can introduce contextual information unrelated to the cross domain message itself. - event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message, bytes context); +```solidity +event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message, bytes context); +``` This added context should be versioned such that the propogated information can evolve over time to fit various needs. In this first iteration, we propose encoding the root-most `messageHash`, the `tx.origin` of that root call, and `call depth` of the cross domain message. This information is made availble in transient storage when relaying a message so that any further outbound messages simply forwards the appropriate context, rather than repopoulate an entirely new one. @@ -104,7 +106,9 @@ We include the `block.basefee` and not the `tx.gasprice`, so that the priority f New Event: - event RelayedMessageGasReceipt(bytes32 indexed msgHash, bytes32 indexed rootMsgHash, uint256 indexed depth, address indexed txOrigin, uint256 baseFee, uint256 gasUsed) +```solidity +event RelayedMessageGasReceipt(bytes32 indexed msgHash, bytes32 indexed rootMsgHash, uint256 indexed depth, address indexed txOrigin, uint256 baseFee, uint256 gasUsed) +``` ```solidity function relayMessage(...) { From 4612168959cd6fba5fab10294fce2fb0776fb083 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Tue, 22 Apr 2025 14:30:27 -0400 Subject: [PATCH 4/7] compute cost instead & emit the relayer address --- ecosystem/l2tol2cdm-gasreceipt.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ecosystem/l2tol2cdm-gasreceipt.md b/ecosystem/l2tol2cdm-gasreceipt.md index d0834773..31c3e9ae 100644 --- a/ecosystem/l2tol2cdm-gasreceipt.md +++ b/ecosystem/l2tol2cdm-gasreceipt.md @@ -102,24 +102,28 @@ All superchain interop cross domain messages are relayed via the `L2ToL2CrossDom We include the `block.basefee` and not the `tx.gasprice`, so that the priority fee (included in gasPrice) set by the relaying entity is not included and only the minimum execution costs are computed. -- **Question**: _Should we simply emit the computed cost here rather than the individual parts? Would emitting the gasPrice be useful for someone else building on top?_ +- **Question**: _Should we simply emit individual parts instead of the computed cost? Would emitting the baseFee & gasPrice be useful for someone else building on top?_ New Event: ```solidity -event RelayedMessageGasReceipt(bytes32 indexed msgHash, bytes32 indexed rootMsgHash, uint256 indexed depth, address indexed txOrigin, uint256 baseFee, uint256 gasUsed) +event RelayedMessageGasReceipt(bytes32 indexed msgHash, bytes32 indexed rootMsgHash, uint256 indexed depth, address indexed txOrigin, address relayer, uint256 cost) ``` ```solidity function relayMessage(...) { uint256 _gasLeft = gasLeft() _ = target.call{ value: msg.value }(message); - uint256 gasUsed = _gasLeft - gasLeft(); + uint256 gasUsed = (_gasLeft - gasLeft()) + RELAY_MESSAGE_OVERHEAD; + + address relayer = msg.sender; + uint256 cost = block.basefee * gasUsed; // there will always be populated context when relaying. (,,bytes ctx memory) = crossDomainMessageContext(); (bytes32 rootMsgHash, address txorigin, uint256 depth) = _decodeContext(ctx); - emit RelayedMessageGasReceipt(msgHash, rootMsgHash, depth, txorigin, block.basefee, gasUsed + RELAY_MESSAGE_OVERHEAD); + + emit RelayedMessageGasReceipt(msgHash, rootMsgHash, depth, txorigin, relayer, cost); } ``` From 45cb4cf2170bae1b786229fd5b1f542cbc18a562 Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Wed, 7 May 2025 13:58:15 -0400 Subject: [PATCH 5/7] scope to sendmsg ctx --- ecosystem/l2tol2cdm-gasreceipt.md | 178 ----------------------------- ecosystem/l2tol2cdm-sentmsg-ctx.md | 92 +++++++++++++++ 2 files changed, 92 insertions(+), 178 deletions(-) delete mode 100644 ecosystem/l2tol2cdm-gasreceipt.md create mode 100644 ecosystem/l2tol2cdm-sentmsg-ctx.md diff --git a/ecosystem/l2tol2cdm-gasreceipt.md b/ecosystem/l2tol2cdm-gasreceipt.md deleted file mode 100644 index 31c3e9ae..00000000 --- a/ecosystem/l2tol2cdm-gasreceipt.md +++ /dev/null @@ -1,178 +0,0 @@ -# [L2ToL2CrossDomainMessenger Gas Receipt]: Design Doc - -| | | -| ------------------ | -------------------------------------------------------- | -| Author | _Hamdi Allam_ | -| Created at | _2025-04-14_ | -| Initial Reviewers | _Wonderland, Mark Tyneway, Harry Markley, Karl Floersch_ | -| Need Approval From | _Wonderland, Ben Clabby, Mark Tyneway_ | -| Status | _Draft / In Review / Implementing Actions / Final_ | - -## Purpose - - - - - -As more contracts are built that natively integrate with superchain interop, several single-action experiences can span up to N+1 op-stack chains to achieve their desired outcome. It is important we include the needed features & data to preserve a single-tx experience in the Superchain to not lose developers or users due to either a poor developer or user experience. - -## Summary - - - -The general mental model for a transaction is that the sending account pays for the gas used. This mental model is the backbone for 4337 or 7702 sponsored transactions where tx.origin is decoupled from the calling account. By providing a mechanism for which the accumulated gas used for all cross domain calls with the initial `tx.origin` can be verifiably tracked, it provides the foundation for a single-tx experience. - -## Problem Statement + Context - - - -Today, a single transaction cross-chain experience is achieved by pushing gas from the source -> destination chain with the message. Relayers are incentivized as long as the pushed funds for gas is sufficiently enough to cover the cost. This works in primitive usecases, where there's a single message from A -> B such as bridging funds. However even this has several drawbacks: - -- **Gas Estimation**. Doing on-chain estimation is very inaccurate, resulting in overestimation for high odds of delivery. Some protocols/relayers even pocket this difference instead of refunding the account. And if the account is refunded, it is dust on the remote chain for the user. -- **Relayer Vendor lock-in**. Contract developers must lock into a crosschain provider (Hyperlane,LayerZero,Wormhole) in order to make use of these services. This tight coupling makes it nearly impossible to switch without introducing complexity (upgradable smart contracts and provider-agonstic interfaces). - -Stepping outside this simple usecase, we can enumerate more scenarios in which pushing gas from source -> destination quickly falls apart. - -1. **Transitive cross-domain calls**. A -> B -> C. Gas estmation from the source chain (A), is already inefficient in the single hop. This scheme must also additionally overestimate and provide enough gas for each cross-domain call, B -> C. If a cross-domain call was to fail in an intermediate hop, it's unclear how to fund that transaction through from A. - -2. **Cross-Domain Composability**. A single transaction can fan out calls to multiple chains which then calls back to the originating contract, (A -> B -> A) & (A->C). For example, a Swap -> Bridge & Swap -> Bridge. Or two cross-chain DeFi protocols integrating each other. This type of cross-chain communication does not work in the world of relayers today. - - - For applications composing each other (A & B), we can not gaurantee the same relayer vendor is used. Hop A->B might leverage Hyperlane to send the message, while B might transitively use Wormhole to send a message to C. **This is why it's important to solve this problem natively**, such that as long as the providers (LayerZero/Wormhole/Hyperlane) use native message passing, everything works. - -## Proposed Solution - - - - Providing a mechanism for which the accumulated gas used for all cross domain calls can be correlated with the original `tx.origin`. - -By natively providing this in the messenger, anyone can charge tx senders exactly what was used for all incurred cross domain messages. It is important to note here that this design doc does not prescribe a solution for charging this gas. This can happen within shared block building, enshrined in a app-level relay bounties framework, or used the 3rdparty providers in their own accounting systems. - -Support can be enumerated in 2 parts. - -### 1. Propogated SentMessage "Headers" or "Context". - -With nested cross domain calls, there's no way to correlate the subsequent messages with the original `tx.origin`, as each message has a unique message hash associated with it. By introducing a new field, "headers" or "context" that are appropriately propogated as nested cross domain calls are made, we can introduce contextual information unrelated to the cross domain message itself. - -```solidity -event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message, bytes context); -``` - -This added context should be versioned such that the propogated information can evolve over time to fit various needs. In this first iteration, we propose encoding the root-most `messageHash`, the `tx.origin` of that root call, and `call depth` of the cross domain message. This information is made availble in transient storage when relaying a message so that any further outbound messages simply forwards the appropriate context, rather than repopoulate an entirely new one. - -Some pseudocode describing the above, the exact api will be fleshed out in a specs PR. - -```solidity -function sendMessage(...) { - (,,bytes ctx memory) = crossDomainMessageContext(); - if (ctx.length == 0) { - // new "top-level" cross domain call (messageHash_ == outbound message) - ctx = abi.encodePacked(version, abi.encode(messageHash_, tx.origin, 0)); - } else { - // propogate, incrementing call dpeth - (bytes32 rootMessageHash_, address txorigin, uint256 depth) = _decodeContext(ctx); - ctx = abi.encodePacked(version, abi.encode(rootMessageHash_, txorigin, depth+1)) - } - - ... - - emit SentMessage(..., ctx) -} - -function relayMessage(...) { - (uint256 source, address sender, bytes memory ctx) = decodeSentMessage(...) - _storeMessageMetaData(source, sender, ctx); -} -``` - -Although first use of this propogated context is for gas receipts, this versioned abstraction can prove useful for much more information in the future without breaking the signature of `SentMessage`, critical for not breaking the call flow between `sendMessage` and `relayMessage`. - -### 2. Gas Receipt - -All superchain interop cross domain messages are relayed via the `L2ToL2CrossDomainMessenger#relayMessage` function. We can leverage this single-entrypoint to track the gas consumption and emit relevant execution information. - -We include the `block.basefee` and not the `tx.gasprice`, so that the priority fee (included in gasPrice) set by the relaying entity is not included and only the minimum execution costs are computed. - -- **Question**: _Should we simply emit individual parts instead of the computed cost? Would emitting the baseFee & gasPrice be useful for someone else building on top?_ - -New Event: - -```solidity -event RelayedMessageGasReceipt(bytes32 indexed msgHash, bytes32 indexed rootMsgHash, uint256 indexed depth, address indexed txOrigin, address relayer, uint256 cost) -``` - -```solidity -function relayMessage(...) { - uint256 _gasLeft = gasLeft() - _ = target.call{ value: msg.value }(message); - uint256 gasUsed = (_gasLeft - gasLeft()) + RELAY_MESSAGE_OVERHEAD; - - address relayer = msg.sender; - uint256 cost = block.basefee * gasUsed; - - // there will always be populated context when relaying. - (,,bytes ctx memory) = crossDomainMessageContext(); - (bytes32 rootMsgHash, address txorigin, uint256 depth) = _decodeContext(ctx); - - emit RelayedMessageGasReceipt(msgHash, rootMsgHash, depth, txorigin, relayer, cost); -} -``` - -Thus this receipt can be used to reliably track `tx.origin` reponsible for all cross domain calls that occur from the first transaction. And with `rootMessageHash`, obtaining a unique identifier to tie all nested cross domain calls with their root-most outbound message. - -- **Question**: _Might the parent message hash be useful here too? It could easily be fetched via the a gas receipt where the rootMsgHash matches and the depth is 1 behind. These are indexed topics making it an efficient getLogs query._ - -### Resource Usage - - - -There is an increase in gas cost to the `relayMessage` function: - -1. Additional encoded bytes for the `SentMessage` event, used to relay the message. -2. Emission of a new event, `RelayedMessageGasReceipt`. - -However the added event fields to `SentMessage` and fields of the new event, `RelayedMessageGasReceipt`, are encodings of fixed sized data fields bounding the added overhead. - -### Single Point of Failure and Multi Client Considerations - - - -The added gas cost, if unbounded, could be a single point of failure to relaying messages since we are attaching auxilliary data to each outbound message. However as noted in the [Resource Usage](#resource-usage) section, the encoding of this data is fixed is size and cannot be exploited to include more. - -## Failure Mode Analysis - - - -- - -## Impact on Developer Experience - - - -The contract API of the L2ToL2CrossDomainMessenger does not change with this introduction. Although any relayers operating on the devnet must update the their abi for the `SentMessage` event to relay. - -## Alternatives Considered - - - -1. Rely on 3rdparty Bridge/Relay providers. As listed in the problem statement, this can work well for single cross-chain messages but quickly falls apart to any complex use case. -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 - - - -`RelayedMessageGasReceipt` does not end up used in any meaningful gas payment system. In this world, the event can be ignored and even removed in a future hardfork. The serialized context propogated with every SentMessage can also be versioned to empty bytes to reduced the overhead that was added. It is important to note here that serialized context is an added feature irrespective of the gas receipt that can be re-purposed to propogate _any_ useful context. diff --git a/ecosystem/l2tol2cdm-sentmsg-ctx.md b/ecosystem/l2tol2cdm-sentmsg-ctx.md new file mode 100644 index 00000000..1a3b8d56 --- /dev/null +++ b/ecosystem/l2tol2cdm-sentmsg-ctx.md @@ -0,0 +1,92 @@ +# [L2ToL2CrossDomainMessenger SentMessage context]: Design Doc + +| | | +| ------------------ | -------------------------------------------------------- | +| Author | _Hamdi Allam_ | +| Created at | _2025-04-14_ | +| Initial Reviewers | _Wonderland, Mark Tyneway, Harry Markley, Karl Floersch_ | +| Need Approval From | _Wonderland, Ben Clabby, Mark Tyneway_ | +| Status | _Draft / In Review / Implementing Actions / Final_ | + +## Purpose + + + + + +As more contracts are built that natively integrate with superchain interop, several single-action experiences can span up to N+1 op-stack chains to achieve their desired outcome. As multiple cross domain message are spawned from single transactions, being able to propogate contextual information unrelated to the core message, like headers in HTTP, is important to be able to tie subsequent cross domain messages together. + +## Summary + + + +We make space for internal contextual information by adding opaque bytes to each `SentMessage` event. The `L2ToL2CrossDomainMessenger` can internally take care of populating and propogating this context on nested cross domain messages. + +## Problem Statement + Context + + + +As is, the `SentMessage` event explictly emits some contextual information along with the core message, i.e `messageNonce`. If additional contextual information is required, a breaking abi change must be made in order to include it. + +This leaves no room for flexibilty for including new contextual information without making a more complex breaking change to the messenger. + +## Proposed Solution + + + +Introudce opaque context bytes field in `SentMessage`. This field MUST be versioned such that it can evolve with new or removed fields. + +```solidity +event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message, bytes context); +``` + +This proposal simply includes this field without specifying the first versioned format. By leaving it as empty for now, we can populate the first version in a later +design that requires it. + +- See [incentivized message delivery](https://github.com/ethereum-optimism/design-docs/pull/272). + +**Note**: The context itself **MUST** be included in the pre-image of the cross domain message hash. While `CrossL2Inbox` validation ensures log integrity, we must include the context in the preimage in order for `resendMessage` which re-emits stale `SentMessage` event to gaurantee that context hasn't been tampered with. + +## Resource Usage + + + +There's a small added gas cost to emitting the `SentMessage` event with an extra field. In this proposal, the field is empty bytes so the overhead is most minimal. As further designs encode information into this context, resource usage must be bounded as the gas overhead applies to every outbound message and executing relay transaction. + +### Single Point of Failure and Multi Client Considerations + + + +1. Resource Usage +2. Appropriate versioning on the context to not break in-flight messages + +## Failure Mode Analysis + + + +- + +## Alternatives Considered + + + +To avoid breaking the abi, the context could be encoded within the `message` field. However the `message` is not currently version and convolutes the purpose of the field (raw target calldata). Keeping these fields explictly seperate makes the overall cross domain message much more readable and decodable. + +## Risks & Uncertainties + + + +No meaningful usage of any metadata in context. In the setting the context can be versioned such that it is always empty, minimizing gas overhead. This flexibility in this field is a strength, as the parameter can always be re-purposed or extended to new usecases. From 45d65ad44aa5a0e14aeb5e3bf5b874b2a69a06ab Mon Sep 17 00:00:00 2001 From: Hamdi Allam Date: Fri, 9 May 2025 11:57:56 -0400 Subject: [PATCH 6/7] add reference to gas receipt pr --- ecosystem/l2tol2cdm-sentmsg-ctx.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ecosystem/l2tol2cdm-sentmsg-ctx.md b/ecosystem/l2tol2cdm-sentmsg-ctx.md index 1a3b8d56..13b20b50 100644 --- a/ecosystem/l2tol2cdm-sentmsg-ctx.md +++ b/ecosystem/l2tol2cdm-sentmsg-ctx.md @@ -53,6 +53,7 @@ event SentMessage(uint256 indexed destination, address indexed target, uint256 i This proposal simply includes this field without specifying the first versioned format. By leaving it as empty for now, we can populate the first version in a later design that requires it. +- See [RelayedMessageGasReceipt](https://github.com/ethereum-optimism/design-docs/pull/282). - See [incentivized message delivery](https://github.com/ethereum-optimism/design-docs/pull/272). **Note**: The context itself **MUST** be included in the pre-image of the cross domain message hash. While `CrossL2Inbox` validation ensures log integrity, we must include the context in the preimage in order for `resendMessage` which re-emits stale `SentMessage` event to gaurantee that context hasn't been tampered with. From 2582fbbea6145ed16d81df7857e01039629b8424 Mon Sep 17 00:00:00 2001 From: Hex <165055168+hexshire@users.noreply.github.com> Date: Thu, 29 May 2025 13:33:08 -0300 Subject: [PATCH 7/7] feat: update context (#286) --- ecosystem/l2tol2cdm-sentmsg-ctx.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ecosystem/l2tol2cdm-sentmsg-ctx.md b/ecosystem/l2tol2cdm-sentmsg-ctx.md index 13b20b50..d378ae04 100644 --- a/ecosystem/l2tol2cdm-sentmsg-ctx.md +++ b/ecosystem/l2tol2cdm-sentmsg-ctx.md @@ -47,7 +47,7 @@ is likely too low level. --> Introudce opaque context bytes field in `SentMessage`. This field MUST be versioned such that it can evolve with new or removed fields. ```solidity -event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message, bytes context); +event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message, bytes originContext); ``` This proposal simply includes this field without specifying the first versioned format. By leaving it as empty for now, we can populate the first version in a later @@ -56,7 +56,7 @@ design that requires it. - See [RelayedMessageGasReceipt](https://github.com/ethereum-optimism/design-docs/pull/282). - See [incentivized message delivery](https://github.com/ethereum-optimism/design-docs/pull/272). -**Note**: The context itself **MUST** be included in the pre-image of the cross domain message hash. While `CrossL2Inbox` validation ensures log integrity, we must include the context in the preimage in order for `resendMessage` which re-emits stale `SentMessage` event to gaurantee that context hasn't been tampered with. +**Note**: The context itself **MUST** be included in the pre-image of the cross domain message hash. While `CrossL2Inbox` validation ensures log integrity, we must include the context in the preimage in order for `resendMessage` which re-emits stale `SentMessage` event to gaurantee that context hasn't been tampered with. The pre-image hashing process begins by computing the messagePayloadHash from the core message payload. The originContext is then constructed by encoding the ORIGIN_CONTEXT_ENCODING_VERSION alongside the messagePayloadHash. Finally, the messageHash is derived by hashing the messagePayloadHash and the originContext together. ## Resource Usage