From ee6b73f89e1438d65c91fa7df78134b388154e36 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Mon, 18 Nov 2024 18:04:22 -0300 Subject: [PATCH 01/14] feat: added entrypoints.md --- protocol/messaging/entrypoints.md | 156 ++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 protocol/messaging/entrypoints.md diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md new file mode 100644 index 00000000..51dcd3c4 --- /dev/null +++ b/protocol/messaging/entrypoints.md @@ -0,0 +1,156 @@ +## Summary + +This design document introduces `Entrypoint` contracts as a new primitive that allows anyone to add custom logic on top of the `L2ToL2CrossDomainMessenger`. +It generalizes the `L2ToL2CrossDomainMessenger` design and unlocks other interop primitives such as message batching and expiring. +To do so, the `L2ToL2CrossDomainMessenger` will allow to authorize a single address to relay a message. The authorized address can then add custom logic before executing the call. + +## Problem Statement + Context + +Building functionality on top of the `L2ToL2CrossDomainMessenger` is not simple. For example, to allow message recovery, the contract would need to track expired/failed messages to prevent someone from relaying them after recovery. This would require adding code on the `L2ToL2CrossDomainMessenger`. If another feature were required, we would need to add more code, which doesn’t scale. + +The `Entrypoint` contract primitive solves this issue and unlocks new powerful cross-chain actions. The integration with `Entrypoint` contracts is simple, and the code difference is small. + +## `Entrypoint` Contracts + +### Main idea + +Anyone can deploy `Entrypoint` contracts with any logic. Users can indicate, explicitly or through a contract, which `Entrypoint` contract can process their messages in the destination chain. `relayMessage` will check if the message has an `Entrypoint` associated, and if it does, it will validate that this contract is the `msg.sender`; otherwise, it will revert. + +The key modification is the `msg.sender` binding feature on the `relayMessage` call. If `msg.sender` has logic, the design is equivalent to binding `relayMessage` to include a pre-hook with that logic. + +Notice that post-hook design is already possible with the current design using logic on `target`. + +The single authorized address design can also work to permission relaying for an EOA. There is no check that the calling address is a contract or not. + +### Intuition Example + +This section will describe a simple example to showcase the power of `Entrypoint` contracts. Let’s consider the situation where Alice wants to send `AliceSuperToken` from Chain A to Chain B and then swap them. + +1. Alice (or anyone) deploys a `SwapperEntrypoint` as an `Entrypoint` contract in Chain B that can: + - Receive `AliceSuperToken` tokens. + - Swap for a target token. +2. Alice (or anyone) deploys a `CrosschainSwapper` contract in chain A that will have a `sendAndSwap()` function. + - We assume the `CrosschainSwapper` is allowed to call `crosschainBurn()` on `AliceSuperToken`. +3. Alice calls `sendAndSwap()` . The `CrosschainSwapper` burns tokens on behalf of Alice, and calls `sendMessage()` in `L2ToL2CrossDomainMessenger` with the `SwapperEntrypoint` in destination as `entrypoint`. +4. `Alice` (or anyone) goes to the `SwapperEntrypoint` in Chain B, and calls `relaySendAndSwap()` with her message. The contract will a. Call `relayMessage()` in the `L2ToL2CrossDomainMessenger` and process her message, which relays the `AliceSuperToken` to the `SwapperEntrypoint` contract. b. Perform a swap in a Uniswap pool, for a target token. c. Optionally, the `SwapperEntrypoint` could bridge the target token back to Alice on chain A. + +The message cannot be relayed by any other `msg.sender` besides the `SwapperEntrypoint`, or it will revert. This is what we mean by binding. + +This will allow Alice to perform complex but safe cross-chain actions with a simple interaction in the source chain. + +## `entrypointContext` + +### Main idea + +Following the intuition example above, we could notice a problem in 4b: the `SwapperEntrypoint` doesn’t know what pool to use, how much of the total tokens given to it to swap, or who to give the tokens to after the swap. Information is missing. `SwapperEntrypoint` could ask for these values in the `swapTokens` function, but anyone could call it with Alice’s event and provide bad values. + +To solve this, `Entrypoint` contracts can define a decoding method and expect a second set of data that we will call `entrypointContext`. The context is binded into the message, meaning that its not possible to frontrun the `SentMessage` event with a different context. + +Entrypoints that wish to use `entrypointContext` will need to implement the `onRelayMessage` function. The `L2ToL2CrossDomainMessenger` will perform a callback to `onRelayMessage` inside the `relayMessage` call and before calling the target. + +`Entrypoint` can use the context however they want. It’s important to notice the `relayMessage()` is non reentrant, so the callback cannot call it back. Some possible ways to use the context are the following: + +**Optimistic** + +Context is assumed to be true, but revert on callback if not. + +1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. +2. On destination, the `Entrypoint` will ask for the context as a user input, in addition to the message and id. `Entrypoint` will assume this input context is true (optimistic). + 1. `Entrypoint` will include an `onRelayMessage` callback. +3. The `Entrypoint` will check that the context is valid. +4. If so, it will call `relayMessage`, which will + 1. validate usual properties of the message (domain binding, non-replayability). + 2. check `entrypoint == msg.sender`. + 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` will check that the context that was inputed matches the `entrypointContext`, and revert if not. + 4. finally, call `target` with the `message`. + +**Store and use later** + +Some applications might not require the context at the time of the message (expire messages for instance). In that case, the `Entrypoint` can ignore the context parameter and just store it on the callback for later use. + +1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. +2. On destination, the `Entrypoint` will ask for the message and id. + 1. `Entrypoint` will include an `onRelayMessage` callback. +3. `Entrypoint` will call `relayMessage`, which will + 1. validate usual properties of the message (domain binding, non-replayability). + 2. check `entrypoint == msg.sender`. + 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` the `Entrypoint` will store the context for later use. + 4. finally, call `target` with the `message`. + +## Summary of required changes + +The following changes are required: + +- Overload `sendMessage` to + - include `address entrypoint` and `bytes entrypointContext`. + - modify `SentMessage` event to include `address entrypoint` and `bytes entrypointContext`. +- Overload `relayMessage` to + - check if `msg.sender == entrypoint` and reverts if not. + - checks if `entrypointContext.length > 0` , and if so, it performs a callback to the `entrypoint` address with `entrypointContext` data. +- `_decodeSentMessagePayload` will decode the `entrypoint` and `entrypointContext`. +- `hashL2toL2CrossDomainMessage` will include `entrypoint` and `entrypointContext`. + +## Full example: Expire messages + +Here’s an example of how `Entrypoint` contracts with `entrypointContext` can enable powerful features like the expiration of messages: + +Suppose an `ExpirableTokenBridge` contract exists, that uses an `Entrypoint` with the added functionality of expiring a failed message in destination. `ExpirableTokenBridge` has minting and burning rights over `SuperchainERC20`. + +- Alice will transfer `AliceSuperToken` from Chain A to Chain B through the `ExpirableTokenBridge`, alongside some optional expiring parameters that will get encoded in the `entrypointContext` (these could be expire time window for instance). +- If `AliceSuperToken` has not yet been deployed, relaying the message from the `Entrypoint` will fail. + - If the message has `entrypointContext`, `Entrypoint` can store it for later use. +- The user will then use the `Entrypoint` functionality to expire the message in chain B, making it non-processable by adding it to a mapping. This will emit a message that the contract in Chain A can consume to handle the expired message. This call might optionally check the stored context as well for time windows or allowed senders. +Any call to the `Entrypoint` to relay the expired message will revert. As the `Entrypoint` is the only contract that can process this message in the `L2ToL2CrossDomainMessenger`, the expired message is effectively non-processable in Chain B. + +The sequence diagrams will be divided in three: + +1. Shows the initiating interaction of the user with the `ExpirableTokenBridge` to send the message to Chain B. +2. Shows the interaction of the user in Chain B with the `Entrypoint` to process the expirable message, and shows how it fails to be relayed, and how the `Entrypoint` expires it. +3. Shows the interaction of the user back with the `ExpirableTokenBridge` in Chain A to recover the lost tokens. + +```mermaid +sequenceDiagram + +title Message creation (Chain A) + +Alice->>ExpirableTokenBridge_A: sendERC20(to) +ExpirableTokenBridge_A->>L2ToL2CrossDomainMessenger: sendMessage(..., entrypointAddr, entrypointContext) +L2ToL2CrossDomainMessenger-->L2ToL2CrossDomainMessenger: emit SentMessage(...params, entrypointAddr, message, entrypointContext) + +``` + +```mermaid +sequenceDiagram + +title Message failed and expired (Chain B) + +Anyone->>Entrypoint: processMessage(Identifier messageId, Message message, Identifier contextId, Message context) +Entrypoint->>L2ToL2CrossDomainMessenger: relayMessage(id, message) +L2ToL2CrossDomainMessenger ->> Entrypoint: onRelayMessage(entrypointContext) +L2ToL2CrossDomainMessenger->>ExpirableTokenBridge_B: relayERC2O(to, amount) +ExpirableTokenBridge_B->>SuperchainERC20_B: FAILS +Anyone->>Entrypoint: expireMessage(messageHash) +Entrypoint-->Entrypoint: checks if the deadline of the message is due +Entrypoint-->L2ToL2CrossDomainMessenger: checks if the message has been successfully relayed before +Entrypoint-->Entrypoint: adds it to expiredMessages mapping to avoid future processing +Entrypoint-->Entrypoint: emit ExpiredMessage(destination, messageHash, receiverOfExpiredMessage) + +``` + +```mermaid +sequenceDiagram + +title Expired message handling and funds recovery (Chain A) + +Anyone->>ExpirableTokenBridge_A: handleExpired(Identifier id, ExpiredMessage expiredMessage) +ExpirableTokenBridge_A->>CrossL2Inbox: validateMessage +ExpirableTokenBridge_A-->ExpirableTokenBridge_A: adds to expired messages mapping +ExpirableTokenBridge_A->>SuperchainERC20_A: crosschainMint(receiverOfExpiredTokens) + +``` + +## Notes and Open Questions + +- Notice the modifications basically unlock permissioned relaying. `Entrypoints` are the most clear and useful way to take benefit of this permission control, but it could potentially be used for other actions. +- Names are all up for debate and change. +- For `EntrypointContext`, should we emit the full struct, an encoded bytes or a hash? \ No newline at end of file From ab1f414b428e325df358210df4752891b432c7e6 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Tue, 19 Nov 2024 10:18:12 -0300 Subject: [PATCH 02/14] feat: improved required changes --- protocol/messaging/entrypoints.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 51dcd3c4..836f9d9d 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -84,11 +84,12 @@ The following changes are required: - Overload `sendMessage` to - include `address entrypoint` and `bytes entrypointContext`. - modify `SentMessage` event to include `address entrypoint` and `bytes entrypointContext`. -- Overload `relayMessage` to - - check if `msg.sender == entrypoint` and reverts if not. - - checks if `entrypointContext.length > 0` , and if so, it performs a callback to the `entrypoint` address with `entrypointContext` data. +- Modify `relayMessage` to + - check if `msg.sender != address(0)` to check if an entrypoint has been defined. + - if entrypoint has been defined, check if `msg.sender == entrypoint` and reverts if not. + - if entrypoint has been defined and if `entrypointContext.length > 0` , performs a callback to the `entrypoint` address with `entrypointContext` data to the `onRelayMessage` function. - `_decodeSentMessagePayload` will decode the `entrypoint` and `entrypointContext`. -- `hashL2toL2CrossDomainMessage` will include `entrypoint` and `entrypointContext`. + ## Full example: Expire messages From d2b5244291f1919fe207b9b81357babd07892df4 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Wed, 20 Nov 2024 11:22:40 -0300 Subject: [PATCH 03/14] feat: rollback to first Context design --- protocol/messaging/entrypoints.md | 133 ++++++++++++++++++------------ 1 file changed, 80 insertions(+), 53 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 836f9d9d..f7760c0e 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -44,64 +44,42 @@ This will allow Alice to perform complex but safe cross-chain actions with a sim Following the intuition example above, we could notice a problem in 4b: the `SwapperEntrypoint` doesn’t know what pool to use, how much of the total tokens given to it to swap, or who to give the tokens to after the swap. Information is missing. `SwapperEntrypoint` could ask for these values in the `swapTokens` function, but anyone could call it with Alice’s event and provide bad values. -To solve this, `Entrypoint` contracts can define a decoding method and expect a second set of data that we will call `entrypointContext`. The context is binded into the message, meaning that its not possible to frontrun the `SentMessage` event with a different context. +To solve this, `Entrypoint` contracts can define a decoding method and expect a second event. Then, `Entrypoint` can recover the required parameters from a second validated message, and then perform its logic as expected. -Entrypoints that wish to use `entrypointContext` will need to implement the `onRelayMessage` function. The `L2ToL2CrossDomainMessenger` will perform a callback to `onRelayMessage` inside the `relayMessage` call and before calling the target. +In origin, Alice would emit the `EntrypointContext` as a separate event, and send her message with her designed `Entrypoint` contract. This way, the `Entrypoint` contract can validate the `EntrypointContext` event from origin, decode it, and perform the required actions. -`Entrypoint` can use the context however they want. It’s important to notice the `relayMessage()` is non reentrant, so the callback cannot call it back. Some possible ways to use the context are the following: +Therefore, the processing message function in the `Entrypoint` context will validate and consume two messages: the regular `SentMessage` event and the `EntrypointContext` event. -**Optimistic** +An alternative design could be to enshrine the `entrypointContext` as a `bytes` field from within the `SentMessage` event. We discuss this approach in the Appendix. -Context is assumed to be true, but revert on callback if not. +Notice that, in the intuition example, the `SwapperEntrypoint` is used as both pre and post-hook for the message relay. The `EntrypointContext` is used here in the post-action, but it would be needed beforehand in other cases (like batching). -1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. -2. On destination, the `Entrypoint` will ask for the context as a user input, in addition to the message and id. `Entrypoint` will assume this input context is true (optimistic). - 1. `Entrypoint` will include an `onRelayMessage` callback. -3. The `Entrypoint` will check that the context is valid. -4. If so, it will call `relayMessage`, which will - 1. validate usual properties of the message (domain binding, non-replayability). - 2. check `entrypoint == msg.sender`. - 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` will check that the context that was inputed matches the `entrypointContext`, and revert if not. - 4. finally, call `target` with the `message`. +### Context Binding -**Store and use later** +It is important to note that `EntrypointContext` is not binding to its connected initiating message. This means that, in theory, anyone could execute the message alongside any other valid `EntrypointContext`. +Each `Entrypoint` and initiating message contract will be responsible for handling authentication logic. -Some applications might not require the context at the time of the message (expire messages for instance). In that case, the `Entrypoint` can ignore the context parameter and just store it on the callback for later use. +`Entrypoints` can follow the model of the `L2ToL2CrossDomainMessenger` and decode additional parameters. +Of course, this would require the `EntrypointContext` event to encode this information. +Some parameters that can be used for binding are +- `L2ToL2CrossDomainMessenger` current `nonce`. The `Entrypoint` can then check that the `EntrypointContext` has the same `nonce` than the `SentMessage`. +- Identifier's `origin` from `EntrypointContext` event matches `sender` from `SentMessage`. This could show that the context was created on the same contract than the connected message. +- Identifier's `chainId` from `EntrypointContext` event matches `source` from `SentMessage`. Should match with the `SentMessage`. -1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. -2. On destination, the `Entrypoint` will ask for the message and id. - 1. `Entrypoint` will include an `onRelayMessage` callback. -3. `Entrypoint` will call `relayMessage`, which will - 1. validate usual properties of the message (domain binding, non-replayability). - 2. check `entrypoint == msg.sender`. - 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` the `Entrypoint` will store the context for later use. - 4. finally, call `target` with the `message`. - -## Summary of required changes - -The following changes are required: - -- Overload `sendMessage` to - - include `address entrypoint` and `bytes entrypointContext`. - - modify `SentMessage` event to include `address entrypoint` and `bytes entrypointContext`. -- Modify `relayMessage` to - - check if `msg.sender != address(0)` to check if an entrypoint has been defined. - - if entrypoint has been defined, check if `msg.sender == entrypoint` and reverts if not. - - if entrypoint has been defined and if `entrypointContext.length > 0` , performs a callback to the `entrypoint` address with `entrypointContext` data to the `onRelayMessage` function. -- `_decodeSentMessagePayload` will decode the `entrypoint` and `entrypointContext`. +However, the most efficient way of binding both events is to check who emitted the `EntrypointContext` event against an allowed-list. This might seem restricting, but will be more than enough for many applications. ## Full example: Expire messages -Here’s an example of how `Entrypoint` contracts with `entrypointContext` can enable powerful features like the expiration of messages: +Here’s an example of how `Entrypoint` contracts with `EntrypointContext` events can enable powerful features like the expiration of messages: Suppose an `ExpirableTokenBridge` contract exists, that uses an `Entrypoint` with the added functionality of expiring a failed message in destination. `ExpirableTokenBridge` has minting and burning rights over `SuperchainERC20`. -- Alice will transfer `AliceSuperToken` from Chain A to Chain B through the `ExpirableTokenBridge`, alongside some optional expiring parameters that will get encoded in the `entrypointContext` (these could be expire time window for instance). -- If `AliceSuperToken` has not yet been deployed, relaying the message from the `Entrypoint` will fail. - - If the message has `entrypointContext`, `Entrypoint` can store it for later use. -- The user will then use the `Entrypoint` functionality to expire the message in chain B, making it non-processable by adding it to a mapping. This will emit a message that the contract in Chain A can consume to handle the expired message. This call might optionally check the stored context as well for time windows or allowed senders. -Any call to the `Entrypoint` to relay the expired message will revert. As the `Entrypoint` is the only contract that can process this message in the `L2ToL2CrossDomainMessenger`, the expired message is effectively non-processable in Chain B. +- Alice will transfer `AliceSuperToken` from Chain A to Chain B through the `ExpirableTokenBridge`. +- If `AliceSuperToken` has not yet been deployed, relaying the message will fail. +- The user will then use the `Entrypoint` functionality to expire the message, making it non-processable by adding it to a mapping. Any call to the `Entrypoint` to relay the expired message will revert. As the `Entrypoint` is the only contract that can process this message in the `L2ToL2CrossDomainMessenger`, the expired message is effectively non-processable in Chain B. + - (Optional) The expiring function can also check `EntrypointContext` parameters, such as time window. +- Lastly, after the relaying failure, the user will call the `Entrypoint` again, on chain B, to expire a message. This will emit a message that the contract in Chain A can consume to handle the expired message. The sequence diagrams will be divided in three: @@ -115,9 +93,9 @@ sequenceDiagram title Message creation (Chain A) Alice->>ExpirableTokenBridge_A: sendERC20(to) -ExpirableTokenBridge_A->>L2ToL2CrossDomainMessenger: sendMessage(..., entrypointAddr, entrypointContext) -L2ToL2CrossDomainMessenger-->L2ToL2CrossDomainMessenger: emit SentMessage(...params, entrypointAddr, message, entrypointContext) - +ExpirableTokenBridge_A->>L2ToL2CrossDomainMessenger: sendMessage(..., entrypointAddr) +L2ToL2CrossDomainMessenger-->L2ToL2CrossDomainMessenger: emit SentMessage(...params, entrypointAddr, message) +ExpirableTokenBridge_A-->ExpirableTokenBridge_A: emit EntrypointContext(expirerAddr, token, deadline, receiverOfExpiredMessage, receiverOfExpiredTokens) ``` ```mermaid @@ -126,8 +104,8 @@ sequenceDiagram title Message failed and expired (Chain B) Anyone->>Entrypoint: processMessage(Identifier messageId, Message message, Identifier contextId, Message context) -Entrypoint->>L2ToL2CrossDomainMessenger: relayMessage(id, message) -L2ToL2CrossDomainMessenger ->> Entrypoint: onRelayMessage(entrypointContext) +Entrypoint->>CrossL2Inbox: Validate message and EntrypointContext event +Entrypoint->>L2ToL2CrossDomainMessenger: relayMessage() L2ToL2CrossDomainMessenger->>ExpirableTokenBridge_B: relayERC2O(to, amount) ExpirableTokenBridge_B->>SuperchainERC20_B: FAILS Anyone->>Entrypoint: expireMessage(messageHash) @@ -135,7 +113,6 @@ Entrypoint-->Entrypoint: checks if the deadline of the message is due Entrypoint-->L2ToL2CrossDomainMessenger: checks if the message has been successfully relayed before Entrypoint-->Entrypoint: adds it to expiredMessages mapping to avoid future processing Entrypoint-->Entrypoint: emit ExpiredMessage(destination, messageHash, receiverOfExpiredMessage) - ``` ```mermaid @@ -143,15 +120,65 @@ sequenceDiagram title Expired message handling and funds recovery (Chain A) -Anyone->>ExpirableTokenBridge_A: handleExpired(Identifier id, ExpiredMessage expiredMessage) +Anyone-->ExpirableTokenBridge_A: handleExpired(Identifier id, ExpiredMessage expiredMessage) ExpirableTokenBridge_A->>CrossL2Inbox: validateMessage ExpirableTokenBridge_A-->ExpirableTokenBridge_A: adds to expired messages mapping -ExpirableTokenBridge_A->>SuperchainERC20_A: crosschainMint(receiverOfExpiredTokens) - +ExpirableTokenBridge_A-->SuperchainERC20_A: crosschainMint(receiverOfExpiredTokens) ``` ## Notes and Open Questions - Notice the modifications basically unlock permissioned relaying. `Entrypoints` are the most clear and useful way to take benefit of this permission control, but it could potentially be used for other actions. - Names are all up for debate and change. -- For `EntrypointContext`, should we emit the full struct, an encoded bytes or a hash? \ No newline at end of file +- For `EntrypointContext`, should we emit the full struct, an encoded bytes or a hash? + +## Appendix + +### Enshrined `entrypointContext` + +To solve this, `Entrypoint` contracts can define a decoding method and expect a second set of data that we will call `entrypointContext`. The context is binded into the message, meaning that its not possible to frontrun the `SentMessage` event with a different context. + +Entrypoints that wish to use `entrypointContext` will need to implement the `onRelayMessage` function. The `L2ToL2CrossDomainMessenger` will perform a callback to `onRelayMessage` inside the `relayMessage` call and before calling the target. + +`Entrypoint` can use the context however they want. It’s important to notice the `relayMessage()` is non reentrant, so the callback cannot call it back. Some possible ways to use the context are the following: + + +**Optimistic** + +Context is assumed to be true, but revert on callback if not. + +1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. +2. On destination, the `Entrypoint` will ask for the context as a user input, in addition to the message and id. `Entrypoint` will assume this input context is true (optimistic). + 1. `Entrypoint` will include an `onRelayMessage` callback. +3. The `Entrypoint` will check that the context is valid. +4. If so, it will call `relayMessage`, which will + 1. validate usual properties of the message (domain binding, non-replayability). + 2. check `entrypoint == msg.sender`. + 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` will check that the context that was inputed matches the `entrypointContext`, and revert if not. + 4. finally, call `target` with the `message`. + +**Store and use later** + +Some applications might not require the context at the time of the message (expire messages for instance). In that case, the `Entrypoint` can ignore the context parameter and just store it on the callback for later use. + +1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. +2. On destination, the `Entrypoint` will ask for the message and id. + 1. `Entrypoint` will include an `onRelayMessage` callback. +3. `Entrypoint` will call `relayMessage`, which will + 1. validate usual properties of the message (domain binding, non-replayability). + 2. check `entrypoint == msg.sender`. + 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` the `Entrypoint` will store the context for later use. + 4. finally, call `target` with the `message`. + +## Summary of required changes + +The following changes are required: + +- Overload `sendMessage` to + - include `address entrypoint` and `bytes entrypointContext`. + - modify `SentMessage` event to include `address entrypoint` and `bytes entrypointContext`. +- Modify `relayMessage` to + - check if `msg.sender != address(0)` to check if an entrypoint has been defined. + - if entrypoint has been defined, check if `msg.sender == entrypoint` and reverts if not. + - if entrypoint has been defined and if `entrypointContext.length > 0` , performs a callback to the `entrypoint` address with `entrypointContext` data to the `onRelayMessage` function. +- `_decodeSentMessagePayload` will decode the `entrypoint` and `entrypointContext`. From 5095ebf4d11c7a37a9ca8091c8f69daf35740d97 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Wed, 20 Nov 2024 11:25:34 -0300 Subject: [PATCH 04/14] feat: restored required changes --- protocol/messaging/entrypoints.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index f7760c0e..900848c2 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -68,6 +68,15 @@ Some parameters that can be used for binding are However, the most efficient way of binding both events is to check who emitted the `EntrypointContext` event against an allowed-list. This might seem restricting, but will be more than enough for many applications. +### Summary of required changes + +The following changes are required: + +- `sendMessage` includes `address entrypoint`. +- `SentMessage` event includes `address entrypoint`. +- `relayMessage` checks if `entrypoint!=0`. If so, it checks if `msg.sender == entrypoint` and reverts if not. +- `_decodeSentMessagePayload` will decode the `entrypoint`. +- `hashL2toL2CrossDomainMessage` will include `entrypoint`. ## Full example: Expire messages From 098ac67bf2fd1c92244f60c65f93830b1ee15612 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Wed, 20 Nov 2024 11:27:39 -0300 Subject: [PATCH 05/14] chore: add links and clean text --- protocol/messaging/entrypoints.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 900848c2..873040c5 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -50,7 +50,7 @@ In origin, Alice would emit the `EntrypointContext` as a separate event, and sen Therefore, the processing message function in the `Entrypoint` context will validate and consume two messages: the regular `SentMessage` event and the `EntrypointContext` event. -An alternative design could be to enshrine the `entrypointContext` as a `bytes` field from within the `SentMessage` event. We discuss this approach in the Appendix. +An alternative design could be to enshrine the `entrypointContext` as a `bytes` field from within the `SentMessage` event. We discuss this approach in the [Appendix](#enshrined-entrypointcontext). Notice that, in the intuition example, the `SwapperEntrypoint` is used as both pre and post-hook for the message relay. The `EntrypointContext` is used here in the post-action, but it would be needed beforehand in other cases (like batching). @@ -179,7 +179,7 @@ Some applications might not require the context at the time of the message (expi 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` the `Entrypoint` will store the context for later use. 4. finally, call `target` with the `message`. -## Summary of required changes +### Changes in this case The following changes are required: From e80d1705d35505261818a9190b2798b32d31b7c0 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Wed, 20 Nov 2024 11:31:47 -0300 Subject: [PATCH 06/14] feat: improve enshrined context explanation --- protocol/messaging/entrypoints.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 873040c5..64c99e84 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -145,9 +145,11 @@ ExpirableTokenBridge_A-->SuperchainERC20_A: crosschainMint(receiverOfExpiredToke ### Enshrined `entrypointContext` -To solve this, `Entrypoint` contracts can define a decoding method and expect a second set of data that we will call `entrypointContext`. The context is binded into the message, meaning that its not possible to frontrun the `SentMessage` event with a different context. +This second design binds the context to the message and makes the `L2ToL2CrossDomainMessenger` perform a callback to the entrypoint with this data. For the moment, we decided to let this feature outside of the `L2ToL2CrossDomainMessenger` to keep it minimal, but it can be considered. It is also possible to implement into into a second `L2ToL2CrossDomainMessengerWithContext` that extends from `L2ToL2CrossDomainMessenger`. -Entrypoints that wish to use `entrypointContext` will need to implement the `onRelayMessage` function. The `L2ToL2CrossDomainMessenger` will perform a callback to `onRelayMessage` inside the `relayMessage` call and before calling the target. +The context is binded into the message, meaning that its not possible to frontrun the `SentMessage` event with a different context. + +Entrypoints that wish to use enshrined `entrypointContext` would need to implement the `onRelayMessage` function. The `L2ToL2CrossDomainMessenger` will perform a callback to `onRelayMessage` inside the `relayMessage` call and before calling the target. `Entrypoint` can use the context however they want. It’s important to notice the `relayMessage()` is non reentrant, so the callback cannot call it back. Some possible ways to use the context are the following: @@ -179,7 +181,7 @@ Some applications might not require the context at the time of the message (expi 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` the `Entrypoint` will store the context for later use. 4. finally, call `target` with the `message`. -### Changes in this case +### Changes for enshrinement The following changes are required: From d8027008582744d9f3ba9ef68a031906d5b9c584 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Fri, 22 Nov 2024 10:55:43 -0300 Subject: [PATCH 07/14] feat: changed appendix for future considerations --- protocol/messaging/entrypoints.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 64c99e84..4e9b5181 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -50,7 +50,7 @@ In origin, Alice would emit the `EntrypointContext` as a separate event, and sen Therefore, the processing message function in the `Entrypoint` context will validate and consume two messages: the regular `SentMessage` event and the `EntrypointContext` event. -An alternative design could be to enshrine the `entrypointContext` as a `bytes` field from within the `SentMessage` event. We discuss this approach in the [Appendix](#enshrined-entrypointcontext). +An alternative design could be to enshrine the `entrypointContext` as a `bytes` field from within the `SentMessage` event. We discuss this approach in [Future Considerations](#enshrined-entrypointcontext). Notice that, in the intuition example, the `SwapperEntrypoint` is used as both pre and post-hook for the message relay. The `EntrypointContext` is used here in the post-action, but it would be needed beforehand in other cases (like batching). @@ -141,7 +141,7 @@ ExpirableTokenBridge_A-->SuperchainERC20_A: crosschainMint(receiverOfExpiredToke - Names are all up for debate and change. - For `EntrypointContext`, should we emit the full struct, an encoded bytes or a hash? -## Appendix +## Future Considerations ### Enshrined `entrypointContext` From 86de4037a42e103e19a0b09a8942a7761d362044 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Mon, 25 Nov 2024 13:53:45 -0300 Subject: [PATCH 08/14] refactor: simplified design doc, improved examples and clarity --- protocol/messaging/entrypoints.md | 188 ++++++++---------------------- 1 file changed, 51 insertions(+), 137 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 4e9b5181..26738ca8 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -1,72 +1,57 @@ ## Summary -This design document introduces `Entrypoint` contracts as a new primitive that allows anyone to add custom logic on top of the `L2ToL2CrossDomainMessenger`. -It generalizes the `L2ToL2CrossDomainMessenger` design and unlocks other interop primitives such as message batching and expiring. -To do so, the `L2ToL2CrossDomainMessenger` will allow to authorize a single address to relay a message. The authorized address can then add custom logic before executing the call. +This design document introduces a new primitive called `entrypoint` that enables users to specify a custom relaying address on the destination chain when sending cross-chain messages. +By allowing users to choose a contract as their `entrypoint`, they can incorporate custom logic before and after the message is relayed. This generalizes the functionality of the `L2ToL2CrossDomainMessenger` and unlocks advanced interoperability features such as message batching and message expiration handling. + +Integrating the entrypoint primitive requires minimal changes to the existing `L2ToL2CrossDomainMessenger`. Specifically, the `sendMessage` function is extended to include an optional `address entrypoint` parameter, which is emitted in the `SentMessage `event. On the destination chain, the `relayMessage` function is modified to permit only the specified `entrypoint` address to process the message, if one is assigned. ## Problem Statement + Context Building functionality on top of the `L2ToL2CrossDomainMessenger` is not simple. For example, to allow message recovery, the contract would need to track expired/failed messages to prevent someone from relaying them after recovery. This would require adding code on the `L2ToL2CrossDomainMessenger`. If another feature were required, we would need to add more code, which doesn’t scale. -The `Entrypoint` contract primitive solves this issue and unlocks new powerful cross-chain actions. The integration with `Entrypoint` contracts is simple, and the code difference is small. +The `entrypoint` primitive solves this issue and unlocks new powerful cross-chain actions. The integration with `entrypoint` is simple, and the code difference is small. -## `Entrypoint` Contracts +## `entrypoint` ### Main idea -Anyone can deploy `Entrypoint` contracts with any logic. Users can indicate, explicitly or through a contract, which `Entrypoint` contract can process their messages in the destination chain. `relayMessage` will check if the message has an `Entrypoint` associated, and if it does, it will validate that this contract is the `msg.sender`; otherwise, it will revert. +When sending a message through the `L2ToL2CrossDomainMessenger`, users can indicate which `entrypoint` address can process their messages in the destination chain. `relayMessage` will check if the message has an `entrypoint` associated, and if it does, it will validate that this contract is the `msg.sender`; otherwise, it will revert. The key modification is the `msg.sender` binding feature on the `relayMessage` call. If `msg.sender` has logic, the design is equivalent to binding `relayMessage` to include a pre-hook with that logic. - Notice that post-hook design is already possible with the current design using logic on `target`. -The single authorized address design can also work to permission relaying for an EOA. There is no check that the calling address is a contract or not. +The integration to the `L2ToL2CrossDomainMessenger` will not require any specific interface for the `entrypoint` address. As a matter of fact, the `entrypoint` can be a custom contract (that we will call `Entrypoint`) or an EOA. Anyone can deploy `Entrypoint` contracts. -### Intuition Example +### Use cases -This section will describe a simple example to showcase the power of `Entrypoint` contracts. Let’s consider the situation where Alice wants to send `AliceSuperToken` from Chain A to Chain B and then swap them. +The `entrypoint` primitive simplifies cross-chain interactions and enables powerful new functionalities by allowing custom logic during message relaying. Below are some key use cases illustrating its potential: -1. Alice (or anyone) deploys a `SwapperEntrypoint` as an `Entrypoint` contract in Chain B that can: - - Receive `AliceSuperToken` tokens. - - Swap for a target token. -2. Alice (or anyone) deploys a `CrosschainSwapper` contract in chain A that will have a `sendAndSwap()` function. - - We assume the `CrosschainSwapper` is allowed to call `crosschainBurn()` on `AliceSuperToken`. -3. Alice calls `sendAndSwap()` . The `CrosschainSwapper` burns tokens on behalf of Alice, and calls `sendMessage()` in `L2ToL2CrossDomainMessenger` with the `SwapperEntrypoint` in destination as `entrypoint`. -4. `Alice` (or anyone) goes to the `SwapperEntrypoint` in Chain B, and calls `relaySendAndSwap()` with her message. The contract will a. Call `relayMessage()` in the `L2ToL2CrossDomainMessenger` and process her message, which relays the `AliceSuperToken` to the `SwapperEntrypoint` contract. b. Perform a swap in a Uniswap pool, for a target token. c. Optionally, the `SwapperEntrypoint` could bridge the target token back to Alice on chain A. +**Additional Validation Checks** -The message cannot be relayed by any other `msg.sender` besides the `SwapperEntrypoint`, or it will revert. This is what we mean by binding. +Alice wants to enforce certain conditions before her message is relayed on the destination chain. For example, she might require the message to be relayable only within a specific time window since its origination, or only if a certain asset's price is below a target threshold. -This will allow Alice to perform complex but safe cross-chain actions with a simple interaction in the source chain. +Instead of embedding complex conditions into the message or the `target` contract, Alice can delegate this logic to the `entrypoint`. Separating validation logic from the message payload and target contract makes it easier to compose and reuse components. The `target` contract and the initiating message remain simple and focused on its core functionality. -## `entrypointContext` - -### Main idea +**Storage** -Following the intuition example above, we could notice a problem in 4b: the `SwapperEntrypoint` doesn’t know what pool to use, how much of the total tokens given to it to swap, or who to give the tokens to after the swap. Information is missing. `SwapperEntrypoint` could ask for these values in the `swapTokens` function, but anyone could call it with Alice’s event and provide bad values. +Alice might want to register every cross-chain transfer it performs. The `target` might be EOAs or might not include logic to store these transfers. -To solve this, `Entrypoint` contracts can define a decoding method and expect a second event. Then, `Entrypoint` can recover the required parameters from a second validated message, and then perform its logic as expected. +If this logic was made on an intermediate `target` instead (kind of an aggregator), Alice would need to be aware of this flow every time she does a cross-chain transfer and encode the final `to` into the message, for the intermediate contract to decode. -In origin, Alice would emit the `EntrypointContext` as a separate event, and send her message with her designed `Entrypoint` contract. This way, the `Entrypoint` contract can validate the `EntrypointContext` event from origin, decode it, and perform the required actions. +An entrypoint can allow Alice to store each in a centralized place without adding additional trust or logic on `origin`. -Therefore, the processing message function in the `Entrypoint` context will validate and consume two messages: the regular `SentMessage` event and the `EntrypointContext` event. +This type of flow will be fundamental for expired messages, where storing the `msgHash` is necessary to prevent anyone from relaying recovered messages (see [here](#full-example-expire-messages)). -An alternative design could be to enshrine the `entrypointContext` as a `bytes` field from within the `SentMessage` event. We discuss this approach in [Future Considerations](#enshrined-entrypointcontext). +**Ordered relays** -Notice that, in the intuition example, the `SwapperEntrypoint` is used as both pre and post-hook for the message relay. The `EntrypointContext` is used here in the post-action, but it would be needed beforehand in other cases (like batching). +Entrypoints allow multiple messages to be executed in a specific order on the destination chain. By binding messages together, users can ensure that two initiating messages get executed in the correct order only. -### Context Binding +This flow is the key to enabling message batching. -It is important to note that `EntrypointContext` is not binding to its connected initiating message. This means that, in theory, anyone could execute the message alongside any other valid `EntrypointContext`. -Each `Entrypoint` and initiating message contract will be responsible for handling authentication logic. +**Composable** -`Entrypoints` can follow the model of the `L2ToL2CrossDomainMessenger` and decode additional parameters. -Of course, this would require the `EntrypointContext` event to encode this information. -Some parameters that can be used for binding are -- `L2ToL2CrossDomainMessenger` current `nonce`. The `Entrypoint` can then check that the `EntrypointContext` has the same `nonce` than the `SentMessage`. -- Identifier's `origin` from `EntrypointContext` event matches `sender` from `SentMessage`. This could show that the context was created on the same contract than the connected message. -- Identifier's `chainId` from `EntrypointContext` event matches `source` from `SentMessage`. Should match with the `SentMessage`. +By moving the extra logic to the `entrypoint` instead of the targets, developers can create modular cross-chain interactions without deploying complex target contracts. -However, the most efficient way of binding both events is to check who emitted the `EntrypointContext` event against an allowed-list. This might seem restricting, but will be more than enough for many applications. ### Summary of required changes @@ -80,116 +65,45 @@ The following changes are required: ## Full example: Expire messages -Here’s an example of how `Entrypoint` contracts with `EntrypointContext` events can enable powerful features like the expiration of messages: - -Suppose an `ExpirableTokenBridge` contract exists, that uses an `Entrypoint` with the added functionality of expiring a failed message in destination. `ExpirableTokenBridge` has minting and burning rights over `SuperchainERC20`. +Suppose an `ExpirableTokenBridge` contract exists, that uses an `Entrypoint` with the added functionality of expiring a failed message in destination. Let's suppose that `ExpirableTokenBridge` has minting and burning rights over `SuperchainERC20` and that only the original sender can expire a message. - Alice will transfer `AliceSuperToken` from Chain A to Chain B through the `ExpirableTokenBridge`. - If `AliceSuperToken` has not yet been deployed, relaying the message will fail. -- The user will then use the `Entrypoint` functionality to expire the message, making it non-processable by adding it to a mapping. Any call to the `Entrypoint` to relay the expired message will revert. As the `Entrypoint` is the only contract that can process this message in the `L2ToL2CrossDomainMessenger`, the expired message is effectively non-processable in Chain B. - - (Optional) The expiring function can also check `EntrypointContext` parameters, such as time window. -- Lastly, after the relaying failure, the user will call the `Entrypoint` again, on chain B, to expire a message. This will emit a message that the contract in Chain A can consume to handle the expired message. - -The sequence diagrams will be divided in three: - -1. Shows the initiating interaction of the user with the `ExpirableTokenBridge` to send the message to Chain B. -2. Shows the interaction of the user in Chain B with the `Entrypoint` to process the expirable message, and shows how it fails to be relayed, and how the `Entrypoint` expires it. -3. Shows the interaction of the user back with the `ExpirableTokenBridge` in Chain A to recover the lost tokens. - -```mermaid -sequenceDiagram +- Alice (and only her) can use the `Entrypoint` functionality to expire the message, making it non-processable by adding it to a mapping. Any call to the `Entrypoint` to relay the expired message after this will revert. As the `Entrypoint` is the only contract that can process this message in the `L2ToL2CrossDomainMessenger`, the expired message is effectively non-processable in Chain B. +- On the same call, the `Entrypoint` will emit a message that the contract in Chain A can consume to handle the expired message (mint the tokens back). -title Message creation (Chain A) -Alice->>ExpirableTokenBridge_A: sendERC20(to) -ExpirableTokenBridge_A->>L2ToL2CrossDomainMessenger: sendMessage(..., entrypointAddr) -L2ToL2CrossDomainMessenger-->L2ToL2CrossDomainMessenger: emit SentMessage(...params, entrypointAddr, message) -ExpirableTokenBridge_A-->ExpirableTokenBridge_A: emit EntrypointContext(expirerAddr, token, deadline, receiverOfExpiredMessage, receiverOfExpiredTokens) -``` +The first diagram shows a failed transfer (in red), and then the expiry flow (in blue), all happening in Chain B. +The second diagram corresponds to the funds recovery on Chain A. ```mermaid sequenceDiagram - -title Message failed and expired (Chain B) - -Anyone->>Entrypoint: processMessage(Identifier messageId, Message message, Identifier contextId, Message context) -Entrypoint->>CrossL2Inbox: Validate message and EntrypointContext event -Entrypoint->>L2ToL2CrossDomainMessenger: relayMessage() -L2ToL2CrossDomainMessenger->>ExpirableTokenBridge_B: relayERC2O(to, amount) -ExpirableTokenBridge_B->>SuperchainERC20_B: FAILS -Anyone->>Entrypoint: expireMessage(messageHash) -Entrypoint-->Entrypoint: checks if the deadline of the message is due -Entrypoint-->L2ToL2CrossDomainMessenger: checks if the message has been successfully relayed before -Entrypoint-->Entrypoint: adds it to expiredMessages mapping to avoid future processing -Entrypoint-->Entrypoint: emit ExpiredMessage(destination, messageHash, receiverOfExpiredMessage) +title Transfer expiration (Chain B) + +rect rgb(160, 0, 0) +Note over Alice: 1st failed transfer +Alice ->> ExpirerEntrypoint: startRelayERC20(id, message) +ExpirerEntrypoint --> ExpirerEntrypoint: check expiredMsg[mshHash] == false +ExpirerEntrypoint ->> L2ToL2: relayMessage() +L2ToL2 ->> SuperchainTokenBridge: relayERC20() +SuperchainTokenBridge ->> SuperchainERC20: call fails +end + +rect rgb(0, 0, 200) +Note over Alice: 2nd transfer, expire message +Alice ->> ExpirerEntrypoint: expireRelay(message) +ExpirerEntrypoint --> ExpirerEntrypoint: check decoded sender matches msg.sender +ExpirerEntrypoint --> L2ToL2: checks succesfulMessages[msgHash] == false +ExpirerEntrypoint --> ExpirerEntrypoint: sets expiredMessages[msgHash] == true +ExpirerEntrypoint ->> L2ToL2: sendMessage() to relayERC20() in chain A +end ``` ```mermaid sequenceDiagram +title Transfer recovery (Chain A) -title Expired message handling and funds recovery (Chain A) - -Anyone-->ExpirableTokenBridge_A: handleExpired(Identifier id, ExpiredMessage expiredMessage) -ExpirableTokenBridge_A->>CrossL2Inbox: validateMessage -ExpirableTokenBridge_A-->ExpirableTokenBridge_A: adds to expired messages mapping -ExpirableTokenBridge_A-->SuperchainERC20_A: crosschainMint(receiverOfExpiredTokens) +Anyone ->> L2ToL2: relayMessage() +L2ToL2 ->> SuperchainTokenBridge: relayERC20() +SuperchainTokenBridge ->> SuperchainERC20: crosschainMint(sender, amount) ``` - -## Notes and Open Questions - -- Notice the modifications basically unlock permissioned relaying. `Entrypoints` are the most clear and useful way to take benefit of this permission control, but it could potentially be used for other actions. -- Names are all up for debate and change. -- For `EntrypointContext`, should we emit the full struct, an encoded bytes or a hash? - -## Future Considerations - -### Enshrined `entrypointContext` - -This second design binds the context to the message and makes the `L2ToL2CrossDomainMessenger` perform a callback to the entrypoint with this data. For the moment, we decided to let this feature outside of the `L2ToL2CrossDomainMessenger` to keep it minimal, but it can be considered. It is also possible to implement into into a second `L2ToL2CrossDomainMessengerWithContext` that extends from `L2ToL2CrossDomainMessenger`. - -The context is binded into the message, meaning that its not possible to frontrun the `SentMessage` event with a different context. - -Entrypoints that wish to use enshrined `entrypointContext` would need to implement the `onRelayMessage` function. The `L2ToL2CrossDomainMessenger` will perform a callback to `onRelayMessage` inside the `relayMessage` call and before calling the target. - -`Entrypoint` can use the context however they want. It’s important to notice the `relayMessage()` is non reentrant, so the callback cannot call it back. Some possible ways to use the context are the following: - - -**Optimistic** - -Context is assumed to be true, but revert on callback if not. - -1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. -2. On destination, the `Entrypoint` will ask for the context as a user input, in addition to the message and id. `Entrypoint` will assume this input context is true (optimistic). - 1. `Entrypoint` will include an `onRelayMessage` callback. -3. The `Entrypoint` will check that the context is valid. -4. If so, it will call `relayMessage`, which will - 1. validate usual properties of the message (domain binding, non-replayability). - 2. check `entrypoint == msg.sender`. - 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` will check that the context that was inputed matches the `entrypointContext`, and revert if not. - 4. finally, call `target` with the `message`. - -**Store and use later** - -Some applications might not require the context at the time of the message (expire messages for instance). In that case, the `Entrypoint` can ignore the context parameter and just store it on the callback for later use. - -1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. -2. On destination, the `Entrypoint` will ask for the message and id. - 1. `Entrypoint` will include an `onRelayMessage` callback. -3. `Entrypoint` will call `relayMessage`, which will - 1. validate usual properties of the message (domain binding, non-replayability). - 2. check `entrypoint == msg.sender`. - 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` the `Entrypoint` will store the context for later use. - 4. finally, call `target` with the `message`. - -### Changes for enshrinement - -The following changes are required: - -- Overload `sendMessage` to - - include `address entrypoint` and `bytes entrypointContext`. - - modify `SentMessage` event to include `address entrypoint` and `bytes entrypointContext`. -- Modify `relayMessage` to - - check if `msg.sender != address(0)` to check if an entrypoint has been defined. - - if entrypoint has been defined, check if `msg.sender == entrypoint` and reverts if not. - - if entrypoint has been defined and if `entrypointContext.length > 0` , performs a callback to the `entrypoint` address with `entrypointContext` data to the `onRelayMessage` function. -- `_decodeSentMessagePayload` will decode the `entrypoint` and `entrypointContext`. From c92d635e85164d7da4b04b2f8c75ae0f5ff853a9 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Mon, 25 Nov 2024 14:03:27 -0300 Subject: [PATCH 09/14] feat: added reference to expired messages PR --- protocol/messaging/entrypoints.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 26738ca8..3e3b9353 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -65,6 +65,8 @@ The following changes are required: ## Full example: Expire messages +You can check the full example and rationale in the dedicated [design doc](https://github.com/ethereum-optimism/design-docs/pull/170) + Suppose an `ExpirableTokenBridge` contract exists, that uses an `Entrypoint` with the added functionality of expiring a failed message in destination. Let's suppose that `ExpirableTokenBridge` has minting and burning rights over `SuperchainERC20` and that only the original sender can expire a message. - Alice will transfer `AliceSuperToken` from Chain A to Chain B through the `ExpirableTokenBridge`. From 7dd772f43dc316cdf7cc301a8f687c439c9961a0 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Mon, 25 Nov 2024 14:09:21 -0300 Subject: [PATCH 10/14] feat: added paymaster use case --- protocol/messaging/entrypoints.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 3e3b9353..fbb8cf0f 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -42,6 +42,13 @@ An entrypoint can allow Alice to store each in a centralized place without addin This type of flow will be fundamental for expired messages, where storing the `msgHash` is necessary to prevent anyone from relaying recovered messages (see [here](#full-example-expire-messages)). +**Paymasters** + +A regular cross-chain message requires two actions to be completed: the initiating message and the execution. It is very likely that the execution can be sold as a service, akin to the concept of Paymasters. With an `entrypoint`, and a user approval on destination, this can easily be built. + +With the user's prior approval on the destination chain, the entrypoint can automatically deduct a precomputed service fee or receive compensation through predefined mechanisms upon successful execution. + + **Ordered relays** Entrypoints allow multiple messages to be executed in a specific order on the destination chain. By binding messages together, users can ensure that two initiating messages get executed in the correct order only. From 8e8fe1752d79f8a5001a776386a2ed39cb5d2e09 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Mon, 25 Nov 2024 16:32:26 -0300 Subject: [PATCH 11/14] chore: lighter blue in mermaid --- protocol/messaging/entrypoints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index fbb8cf0f..073ae791 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -98,7 +98,7 @@ L2ToL2 ->> SuperchainTokenBridge: relayERC20() SuperchainTokenBridge ->> SuperchainERC20: call fails end -rect rgb(0, 0, 200) +rect rgb(0, 0, 300) Note over Alice: 2nd transfer, expire message Alice ->> ExpirerEntrypoint: expireRelay(message) ExpirerEntrypoint --> ExpirerEntrypoint: check decoded sender matches msg.sender From 921f485ac28b45f27b2170effd9d8f4dc18f5d66 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Tue, 26 Nov 2024 14:50:50 -0300 Subject: [PATCH 12/14] feat: add context again, add comparison to using target, improve use case explanations --- protocol/messaging/entrypoints.md | 111 ++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 073ae791..4951d44e 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -26,39 +26,29 @@ The integration to the `L2ToL2CrossDomainMessenger` will not require any specifi The `entrypoint` primitive simplifies cross-chain interactions and enables powerful new functionalities by allowing custom logic during message relaying. Below are some key use cases illustrating its potential: -**Additional Validation Checks** +**Message bundles** -Alice wants to enforce certain conditions before her message is relayed on the destination chain. For example, she might require the message to be relayable only within a specific time window since its origination, or only if a certain asset's price is below a target threshold. +Entrypoints allow multiple messages to be executed in a specific order on the destination chain. By binding messages together, users can ensure that two initiating messages are not executed isolated or out of order. -Instead of embedding complex conditions into the message or the `target` contract, Alice can delegate this logic to the `entrypoint`. Separating validation logic from the message payload and target contract makes it easier to compose and reuse components. The `target` contract and the initiating message remain simple and focused on its core functionality. -**Storage** +**Additional Validation Checks** -Alice might want to register every cross-chain transfer it performs. The `target` might be EOAs or might not include logic to store these transfers. +Users might want to enforce certain conditions before her message is relayed on the destination chain. For example, they might require the message to be relayable only within a specific time window since its origination, or only if a certain asset's price is below a target threshold. -If this logic was made on an intermediate `target` instead (kind of an aggregator), Alice would need to be aware of this flow every time she does a cross-chain transfer and encode the final `to` into the message, for the intermediate contract to decode. +For more complex logic, users can use `Context` events (see the [appendix section](#context)). +**Storage** + +Users might want to store information about cross-chain transfers. An entrypoint can allow Alice to store each in a centralized place without adding additional trust or logic on `origin`. -This type of flow will be fundamental for expired messages, where storing the `msgHash` is necessary to prevent anyone from relaying recovered messages (see [here](#full-example-expire-messages)). +This type of flow will be useful for expired messages, where storing the `msgHash` is necessary to prevent anyone from relaying recovered messages (see [here](#full-example-expire-messages)). **Paymasters** -A regular cross-chain message requires two actions to be completed: the initiating message and the execution. It is very likely that the execution can be sold as a service, akin to the concept of Paymasters. With an `entrypoint`, and a user approval on destination, this can easily be built. - -With the user's prior approval on the destination chain, the entrypoint can automatically deduct a precomputed service fee or receive compensation through predefined mechanisms upon successful execution. - - -**Ordered relays** - -Entrypoints allow multiple messages to be executed in a specific order on the destination chain. By binding messages together, users can ensure that two initiating messages get executed in the correct order only. - -This flow is the key to enabling message batching. - -**Composable** - -By moving the extra logic to the `entrypoint` instead of the targets, developers can create modular cross-chain interactions without deploying complex target contracts. +A regular cross-chain message requires two actions to be completed: the initiating message and the execution. It is very likely that the execution can be sold as a service so that users interact only once, or can relay without the gas token. This is akin to the concept of Paymasters. +With the user's prior approval on the destination chain, the `entrypoint` can automatically deduct a precomputed service fee or receive compensation through predefined mechanisms upon successful execution. ### Summary of required changes @@ -116,3 +106,82 @@ Anyone ->> L2ToL2: relayMessage() L2ToL2 ->> SuperchainTokenBridge: relayERC20() SuperchainTokenBridge ->> SuperchainERC20: crosschainMint(sender, amount) ``` + +## Using `target` instead of `entrypoint` + +All of the use cases listed above can be achieved by encoding additional information (on an `Initiator` contract or manually) on the origin chain and then sending a message to a dedicated `target` (that we will call `Receiver` here) for decoding and crafting the final calls in destination. + +The advantage of using `entrypoints` is that, instead of embedding complex conditions into the message or the `target` contract, users can delegate this logic to the `entrypoint`. Separating validation logic from the message payload and target contract makes it easier to compose and reuse components. The `target` contract and the initiating message remain simple and focused on its core functionality. + +Moreover, for message bundles, `entrypoint` have a considerably better user flow. +Even though it is possible for bundles to be processed using `Receiver` contracts, these would need to receive each relayed message, wait for the bundle to be completed, and finally require a final transaction to execute them all (N+1 relay transactions vs 1). + +## Appendix +### `Context` +Entrypoints might require additional information to relay an event. This information might be additional checks or variables to use. As the current `L2ToL2CrossDomainMessenger` does not allow to encode additional information (the call to `target` passes the full message), we need a second executing message that we will call `Context`. + + +In origin, Alice would emit the `EntrypointContext` as a separate event, and send her message with her designed `Entrypoint` contract. This way, the `Entrypoint` contract can validate the `EntrypointContext` event from origin, decode it, and perform the required actions. + +Therefore, the processing message function in the `Entrypoint` context will validate and consume two messages: the regular `SentMessage` event and the `EntrypointContext` event. + +An alternative design could be to enshrine the `entrypointContext` as a `bytes` field from within the `SentMessage` event. We discuss this approach [here](#future-considerations-enshrined-entrypointcontext). + + +#### Context Binding + +It is important to note that `EntrypointContext` is not binding to its connected initiating message. This means that, in theory, anyone could execute the message alongside any other valid `EntrypointContext`. +Each `Entrypoint` and initiating message contract will be responsible for handling authentication logic. + +The most efficient way of binding both events is to check who emitted the `EntrypointContext` event against an allowed-list. This might seem restricting, but will be more than enough for many applications. + +### Future considerations: Enshrined `entrypointContext` + +This second design binds the context to the message and makes the `L2ToL2CrossDomainMessenger` perform a callback to the entrypoint with this data. For the moment, we decided to let this feature outside of the `L2ToL2CrossDomainMessenger` to keep it minimal, but it can be considered. It is also possible to implement into into a second `L2ToL2CrossDomainMessengerWithContext` that extends from `L2ToL2CrossDomainMessenger`. + +The context is binded into the message, meaning that its not possible to frontrun the `SentMessage` event with a different context. + +Entrypoints that wish to use enshrined `entrypointContext` would need to implement the `onRelayMessage` function. The `L2ToL2CrossDomainMessenger` will perform a callback to `onRelayMessage` inside the `relayMessage` call and before calling the target. + +`Entrypoint` can use the context however they want. It’s important to notice the `relayMessage()` is non reentrant, so the callback cannot call it back. Some possible ways to use the context are the following: + + +**Optimistic** + +Context is assumed to be true, but revert on callback if not. + +1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. +2. On destination, the `Entrypoint` will ask for the context as a user input, in addition to the message and id. `Entrypoint` will assume this input context is true (optimistic). + 1. `Entrypoint` will include an `onRelayMessage` callback. +3. The `Entrypoint` will check that the context is valid. +4. If so, it will call `relayMessage`, which will + 1. validate usual properties of the message (domain binding, non-replayability). + 2. check `entrypoint == msg.sender`. + 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` will check that the context that was inputed matches the `entrypointContext`, and revert if not. + 4. finally, call `target` with the `message`. + +**Store and use later** + +Some applications might not require the context at the time of the message (expire messages for instance). In that case, the `Entrypoint` can ignore the context parameter and just store it on the callback for later use. + +1. In origin, Alice will encode the `entrypointContext` as a single `bytes` parameter into the `sendMessage` call. +2. On destination, the `Entrypoint` will ask for the message and id. + 1. `Entrypoint` will include an `onRelayMessage` callback. +3. `Entrypoint` will call `relayMessage`, which will + 1. validate usual properties of the message (domain binding, non-replayability). + 2. check `entrypoint == msg.sender`. + 3. do a callback to the `Entrypoint` for `onRelayMessage`, passing the encoded `entrypointContext`. `onRelayMessage` the `Entrypoint` will store the context for later use. + 4. finally, call `target` with the `message`. + +#### Changes for enshrinement + +The following changes are required: + +- Overload `sendMessage` to + - include `address entrypoint` and `bytes entrypointContext`. + - modify `SentMessage` event to include `address entrypoint` and `bytes entrypointContext`. +- Modify `relayMessage` to + - check if `msg.sender != address(0)` to check if an entrypoint has been defined. + - if entrypoint has been defined, check if `msg.sender == entrypoint` and reverts if not. + - if entrypoint has been defined and if `entrypointContext.length > 0` , performs a callback to the `entrypoint` address with `entrypointContext` data to the `onRelayMessage` function. +- `_decodeSentMessagePayload` will decode the `entrypoint` and `entrypointContext`. \ No newline at end of file From af3031424bc6685005f36d3cadd8aa0ba73db1d1 Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Tue, 26 Nov 2024 15:55:31 -0300 Subject: [PATCH 13/14] feat: removed comparison with target --- protocol/messaging/entrypoints.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index 4951d44e..b39acd00 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -11,6 +11,9 @@ Building functionality on top of the `L2ToL2CrossDomainMessenger` is not simple. The `entrypoint` primitive solves this issue and unlocks new powerful cross-chain actions. The integration with `entrypoint` is simple, and the code difference is small. +The advantage of using `entrypoints` over wrapper contracts is that, instead of embedding complex conditions into the message or the `target` contract, users can delegate this logic to the `entrypoint`. Separating validation logic from the message payload and target contract makes it easier to compose and reuse components. The `target` contract and the initiating message remain simple and focused on its core functionality. + + ## `entrypoint` ### Main idea @@ -107,14 +110,7 @@ L2ToL2 ->> SuperchainTokenBridge: relayERC20() SuperchainTokenBridge ->> SuperchainERC20: crosschainMint(sender, amount) ``` -## Using `target` instead of `entrypoint` - -All of the use cases listed above can be achieved by encoding additional information (on an `Initiator` contract or manually) on the origin chain and then sending a message to a dedicated `target` (that we will call `Receiver` here) for decoding and crafting the final calls in destination. - -The advantage of using `entrypoints` is that, instead of embedding complex conditions into the message or the `target` contract, users can delegate this logic to the `entrypoint`. Separating validation logic from the message payload and target contract makes it easier to compose and reuse components. The `target` contract and the initiating message remain simple and focused on its core functionality. -Moreover, for message bundles, `entrypoint` have a considerably better user flow. -Even though it is possible for bundles to be processed using `Receiver` contracts, these would need to receive each relayed message, wait for the bundle to be completed, and finally require a final transaction to execute them all (N+1 relay transactions vs 1). ## Appendix ### `Context` From 7e4788feb5fc5bb65afb546c30fa1c844e9d8cdd Mon Sep 17 00:00:00 2001 From: 0xParticle Date: Tue, 26 Nov 2024 17:51:38 -0300 Subject: [PATCH 14/14] feat: add Risk & Uncertainties, add Alternatives considered --- protocol/messaging/entrypoints.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/protocol/messaging/entrypoints.md b/protocol/messaging/entrypoints.md index b39acd00..fa3a8993 100644 --- a/protocol/messaging/entrypoints.md +++ b/protocol/messaging/entrypoints.md @@ -110,10 +110,7 @@ L2ToL2 ->> SuperchainTokenBridge: relayERC20() SuperchainTokenBridge ->> SuperchainERC20: crosschainMint(sender, amount) ``` - - -## Appendix -### `Context` +## `Context` Entrypoints might require additional information to relay an event. This information might be additional checks or variables to use. As the current `L2ToL2CrossDomainMessenger` does not allow to encode additional information (the call to `target` passes the full message), we need a second executing message that we will call `Context`. @@ -131,7 +128,22 @@ Each `Entrypoint` and initiating message contract will be responsible for handli The most efficient way of binding both events is to check who emitted the `EntrypointContext` event against an allowed-list. This might seem restricting, but will be more than enough for many applications. -### Future considerations: Enshrined `entrypointContext` +## Risk & Uncertainties +While the `entrypoint` primitive introduces powerful capabilities and flexibility in cross-chain messaging, it also brings certain risks and uncertainties that need to be carefully considered: + +- Entrypoint Failure: If an `entrypoint` contract fails during the relaying process—due to bugs, insufficient gas, or intentional reverts—the message may remain unprocessed, potentially causing funds or state changes to be locked or delayed. + - Mitigation: Users should ensure that `entrypoint` contracts are thoroughly tested and audited. +- Security: Since `entrypoint` contracts can contain arbitrary logic and may be developed by third parties, they could introduce security vulnerabilities or malicious behavior, such as unauthorized fund transfers, message manipulation or poor security checks. + - Mitigation: Users should interact only with trusted and audited `entrypoint` contracts. +- Compatibility with existing protocols: Introducing `entrypoints` may affect compatibility with existing wallets, or protocols not designed to handle the additional logic. `entrypoint` are optional, and not including them leads to the current flow. Still, some flows might require two separate integrators, so it might happen that only one party uses it and the other is not aware. + - Mitigation: Raising awareness is crucial. Developers and users should ensure all parties involved are informed about `entrypoint` and verify compatibility to prevent integration issues. + + +This design space is relatively unexplored territory and may lead to the creation of new standards in cross-chain messaging. + +## Alternatives considered + +### Enshrined `entrypointContext` This second design binds the context to the message and makes the `L2ToL2CrossDomainMessenger` perform a callback to the entrypoint with this data. For the moment, we decided to let this feature outside of the `L2ToL2CrossDomainMessenger` to keep it minimal, but it can be considered. It is also possible to implement into into a second `L2ToL2CrossDomainMessengerWithContext` that extends from `L2ToL2CrossDomainMessenger`.