Skip to content

Commit

Permalink
fix: formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
kaymomin committed Nov 24, 2024
1 parent 36483f1 commit ceaabae
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 206 deletions.
3 changes: 2 additions & 1 deletion docs/src/guides/build-docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Install prerequisites: see

## Build docker files

You may build all images with [Makefile](../../docker/Makefile) located in [docker](../../docker) directory in this repository
You may build all images with [Makefile](../../docker/Makefile) located in [docker](../../docker) directory in this
repository

> All commands should be run from the root directory of the repository
Expand Down
2 changes: 1 addition & 1 deletion docs/src/specs/interop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
- [Overview](./overview.md)
- [Interop Messages](./interopmessages.md)
- [Bundles and Calls](./bundlesandcalls.md)
- [Interop Transactions](./interoptransactions.md)
- [Interop Transactions](./interoptransactions.md)
180 changes: 107 additions & 73 deletions docs/src/specs/interop/bundlesandcalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

## Basics Calls

Interop Calls are the next level of interfaces, built on top of Interop Messages, enabling you to call contracts on other chains.
Interop Calls are the next level of interfaces, built on top of Interop Messages, enabling you to call contracts on
other chains.

![interopcall.png](../img/interopcall.png)

At this level, the system handles replay protection—once a call is successfully executed, it cannot be executed again (eliminating the need for your own nullifiers or similar mechanisms).
At this level, the system handles replay protection—once a call is successfully executed, it cannot be executed again
(eliminating the need for your own nullifiers or similar mechanisms).

Additionally, these calls originate from aliased accounts, simplifying permission management (more details on this below).
Additionally, these calls originate from aliased accounts, simplifying permission management (more details on this
below).

Cancellations and retries are managed at the next level (Bundles), which are covered in the following section.

Expand All @@ -22,7 +25,7 @@ struct InteropCall {
address destinationAddress,
uint256 destinationChainId,
calldata data,
uint256 value
uint256 value
}
contract InteropCenter {
// On source chain.
Expand All @@ -32,8 +35,8 @@ contract InteropCenter {
}
```


In return, you receive a `bundleId` (we’ll explain bundles later, but for now, think of it as a unique identifier for your call).
In return, you receive a `bundleId` (we’ll explain bundles later, but for now, think of it as a unique identifier for
your call).

On the destination chain, you can execute the call using the execute method:

Expand All @@ -42,32 +45,41 @@ contract InteropCenter {
// Executes a given bundle.
// interopMessage is the message that contains your bundle as payload.
// If it fails, it can be called again.
function executeInteropBundle(interopMessage, proof);
// If the bundle didn't execute succesfully yet, it can be marked as cancelled.
// See details below.
function executeInteropBundle(interopMessage, proof);
// If the bundle didn't execute succesfully yet, it can be marked as cancelled.
// See details below.
function cancelInteropBundle(interopMessage, proof);
}
```

You can retrieve the `interopMessage` (which contains your entire payload) from the Gateway, or you can construct it yourself using L1 data.
You can retrieve the `interopMessage` (which contains your entire payload) from the Gateway, or you can construct it
yourself using L1 data.

Under the hood, this process calls the `destinationAddress` with the specified calldata.

This leads to an important question: **Who is the msg.sender for this call?**

## `msg.sender` of the Destination Call

The `msg.sender` on the destination chain will be the **AliasedAccount** — an address created as a hash of the original sender and the original source chain.
The `msg.sender` on the destination chain will be the **AliasedAccount** — an address created as a hash of the original
sender and the original source chain.

(Normally, we’d like to use `sourceAccount@sourceChain`, but since Ethereum limits the size of addresses to 20 bytes, we compute the Keccak hash of the string above and use this as the address.)
(Normally, we’d like to use `sourceAccount@sourceChain`, but since Ethereum limits the size of addresses to 20 bytes, we
compute the Keccak hash of the string above and use this as the address.)

One way to think about it is this: You (as account `0x5bFF1...` on chain A) can send a call to a contract on a destination chain, and for that contract, it will appear as if the call came locally from the address `keccak(0x5bFF1 || A)`. This means you are effectively "controlling" such an account address on **every ZK Chain** by sending interop messages from the `0x5bFF1...` account on chain A.
One way to think about it is this: You (as account `0x5bFF1...` on chain A) can send a call to a contract on a
destination chain, and for that contract, it will appear as if the call came locally from the address
`keccak(0x5bFF1 || A)`. This means you are effectively "controlling" such an account address on **every ZK Chain** by
sending interop messages from the `0x5bFF1...` account on chain A.

![msgdotsender.png](../img/msgdotsender.png)

## Simple Example

Imagine you have contracts on chains B, C, and D, and you’d like them to send "reports" to the Headquarters (HQ) contract on chain A every time a customer makes a purchase.
Imagine you have contracts on chains B, C, and D, and you’d like them to send "reports" to the Headquarters (HQ)
contract on chain A every time a customer makes a purchase.

```solidity
// Deployed on chains B, C, D.
Expand Down Expand Up @@ -95,23 +107,24 @@ contract HQ {
// Adding aliased accounts.
shops[address(keccak(addressOnChain || chainId))] = true;
}
function reportSales(uint256 itemPrice) {
// only allow calls from our shops (their aliased accounts).
require(shops[msg.sender]);
sales[msg.sender] += itemPrice;
}
}
}
```

#### Who is paying for gas? How does this Call get to the destination chain?

At this level, the **InteropCall** acts like a hitchhiker — it relies on someone (anyone) to pick it up, execute it, and pay for the gas!
At this level, the **InteropCall** acts like a hitchhiker — it relies on someone (anyone) to pick it up, execute it, and
pay for the gas!

![callride.png](../img/callride.png)

While any transaction on the destination chain can simply call `InteropCenter.executeInteropBundle`, if you don’t want to rely on hitchhiking, you can create one yourself. We’ll discuss this in the section about **Interop Transactions**.

While any transaction on the destination chain can simply call `InteropCenter.executeInteropBundle`, if you don’t want
to rely on hitchhiking, you can create one yourself. We’ll discuss this in the section about **Interop Transactions**.

## Bundles

Expand All @@ -121,18 +134,20 @@ Before we proceed to discuss **InteropTransactions**, there is one more layer in

**Bundles Offer:**

- **Shared Fate**: All calls in the bundle either succeed or fail together.
- **Retries**: If a bundle fails, it can be retried (e.g., with more gas).
- **Cancellations**: If a bundle has not been successfully executed yet, it can be cancelled.

- **Shared Fate**: All calls in the bundle either succeed or fail together.
- **Retries**: If a bundle fails, it can be retried (e.g., with more gas).
- **Cancellations**: If a bundle has not been successfully executed yet, it can be cancelled.

If you look closely at the interface we used earlier, you’ll notice that we were already discussing the execution of **Bundles** rather than single calls. So, let’s dive into what bundles are and the role they fulfill.
If you look closely at the interface we used earlier, you’ll notice that we were already discussing the execution of
**Bundles** rather than single calls. So, let’s dive into what bundles are and the role they fulfill.

The primary purpose of a bundle is to ensure that a given list of calls is executed in a specific order and has a shared fate (i.e., either all succeed or all fail).
The primary purpose of a bundle is to ensure that a given list of calls is executed in a specific order and has a shared
fate (i.e., either all succeed or all fail).

In this sense, you can think of a bundle as a **"multicall"**, but with two key differences:

1. You cannot "unbundle" items—an individual `InteropCall` cannot be run independently; it is tightly tied to the bundle.
1. You cannot "unbundle" items—an individual `InteropCall` cannot be run independently; it is tightly tied to the
bundle.

2. Each `InteropCall` within a bundle can use a different aliased account, enabling separate permissions for each call.

Expand All @@ -142,13 +157,13 @@ contract InteropCenter {
// Calls have to be done in this order.
InteropCall calls[];
uint256 destinationChain;
// If not set - anyone can execute it.
address executionAddresses[];
address executionAddresses[];
// Who can 'cancel' this bundle.
address cancellationAddress;
}
// Starts a new bundle.
// All the calls that will be added to this bundle (potentially by different contracts)
// will have a 'shared fate'.
Expand All @@ -168,23 +183,25 @@ Imagine you want to perform a swap on chain B, exchanging USDC for PEPE, but all

This process would typically involve four steps:

1. Transfer USDC from chain A to chain B.
2. Set allowance for the swap.
3. Execute the swap.
4. Transfer PEPE back to chain A.
1. Transfer USDC from chain A to chain B.
2. Set allowance for the swap.
3. Execute the swap.
4. Transfer PEPE back to chain A.

Each of these steps is a separate "call," but you need them to execute in exactly this order and, ideally, atomically. If the swap fails, you wouldn’t want the allowance to remain set on the destination chain.
Each of these steps is a separate "call," but you need them to execute in exactly this order and, ideally, atomically.
If the swap fails, you wouldn’t want the allowance to remain set on the destination chain.

Below is an example of how this process could look (note that the code is pseudocode; we’ll explain the helper methods required to make it work in a later section).
Below is an example of how this process could look (note that the code is pseudocode; we’ll explain the helper methods
required to make it work in a later section).

```solidity
bundleId = InteropCenter(INTEROP_CENTER).startBundle(chainD);
// This will 'burn' the 1k USDC, create the special interopCall
// when this call is executed on chainD, it will mint 1k USDC there.
// BUT - this interopCall is tied to this bundle id.
USDCBridge.transferWithBundle(
bundleId,
chainD,
bundleId,
chainD,
aliasedAccount(this(account), block.chain_id),
1000);
Expand All @@ -194,40 +211,47 @@ InteropCenter.addToBundle(bundleId,
USDCOnDestinationChain,
createCalldata("approve", 1000, poolOnDestinationChain),
0);
// This will create interopCall to do the swap.
InteropCenter.addToBundle(bundleId,
poolOnDestinationChain,
// This will create interopCall to do the swap.
InteropCenter.addToBundle(bundleId,
poolOnDestinationChain,
createCalldata("swap", "USDC_PEPE", 1000, ...),
0)
// And this will be the interopcall to transfer all the assets back.
InteropCenter.addToBundle(bundleId,
pepeBridgeOnDestinationChain,
createCalldata("transferAll", block.chain_id, this(account)),
0)
bundleHash = interopCenter.finishAndSendBundle(bundleId);
```

In the code above, we created a bundle that anyone can execute on the destination chain. This bundle will handle the entire process: minting, approving, swapping, and transferring back.
In the code above, we created a bundle that anyone can execute on the destination chain. This bundle will handle the
entire process: minting, approving, swapping, and transferring back.

### Bundle Restrictions

When starting a bundle, if you specify the `executionAddress`, only that account will be able to execute the bundle on the destination chain. If no `executionAddress` is specified, anyone can trigger the execution.
When starting a bundle, if you specify the `executionAddress`, only that account will be able to execute the bundle on
the destination chain. If no `executionAddress` is specified, anyone can trigger the execution.

## Retries and Cancellations

If bundle execution fails — whether due to a contract error or running out of gas—none of its calls will be applied. The bundle can be re-run on the **destination chain** without requiring any updates or notifications to the source chain. More details about retries and gas will be covered in the next level, **Interop Transactions**.
If bundle execution fails — whether due to a contract error or running out of gas—none of its calls will be applied. The
bundle can be re-run on the **destination chain** without requiring any updates or notifications to the source chain.
More details about retries and gas will be covered in the next level, **Interop Transactions**.

This process can be likened to a "hitchhiker" (or in the case of a bundle, a group of hitchhikers) — if the car they’re traveling in doesn’t reach the destination, they simply find another ride rather than returning home.
This process can be likened to a "hitchhiker" (or in the case of a bundle, a group of hitchhikers) — if the car they’re
traveling in doesn’t reach the destination, they simply find another ride rather than returning home.

However, there are cases where the bundle should be cancelled. Cancellation can be performed by the `cancellationAddress` specified in the bundle itself.
However, there are cases where the bundle should be cancelled. Cancellation can be performed by the
`cancellationAddress` specified in the bundle itself.

#### For our cross chain swap example:

1. Call `cancelInteropBundle(interopMessage, proof)` on the destination chain.
- A helper method for this will be introduced in the later section.
2. When cancellation occurs, the destination chain will generate an `InteropMessage` containing cancellation information.
2. When cancellation occurs, the destination chain will generate an `InteropMessage` containing cancellation
information.
3. Using the proof from this method, the user can call the USDC bridge to recover their assets:

```solidity
Expand All @@ -243,62 +267,72 @@ Superchain offers the `L2ToL2CrossDomainMessenger` contract.
Similar to **InteropCall**, it allows you to send a message to the target contract:

```solidity
function sendMessage(uint256 _destination, address _target, bytes calldata _message) external returns (bytes32);
function sendMessage(
uint256 _destination,
address _target,
bytes calldata _message
) external returns (bytes32);
```

This contract appends a unique nonce and the sender’s address, resulting in a message structure like this:

```solidity
event SentMessage(
uint256 indexed destination,
address indexed target,
uint256 indexed messageNonce,
address sender,
uint256 indexed destination,
address indexed target,
uint256 indexed messageNonce,
address sender,
bytes message
);
```

The user is then responsible for calling the `L2ToL2CrossDomainMessenger` on the destination chain. This verifies the message and calls the target contract.
The user is then responsible for calling the `L2ToL2CrossDomainMessenger` on the destination chain. This verifies the
message and calls the target contract.

The target contract must "trust" messages originating from the CrossDomainMessenger contract, as it can directly inspect the source sender, which is not aliased.
The target contract must "trust" messages originating from the CrossDomainMessenger contract, as it can directly inspect
the source sender, which is not aliased.

#### Message Expiration
Superchain allows messages to expire (a.k.a. be canceled), but only after a predefined EXPIRY_WINDOW. Anyone can call `sendExpire` on the destination chain, and then they must call `relayExpire` to propagate this information back to the source chain.

Superchain allows messages to expire (a.k.a. be canceled), but only after a predefined EXPIRY_WINDOW. Anyone can call
`sendExpire` on the destination chain, and then they must call `relayExpire` to propagate this information back to the
source chain.

### Differences in Approaches

TL;DR: Superchain provides a "lower-level" interface with fewer features compared to ElasticChain.

#### Destination Contract
- **Superchain:**
The destination contract must be aware that it is being called by Superchain messages and must trust calls originating from the pre-deployed `L2ToL2CrossDomainMessenger`.

- **ElasticChain:**
The destination contract does not need to know it is being called via an interop call. Requests arrive from `aliased accounts'.
- **Superchain:** The destination contract must be aware that it is being called by Superchain messages and must trust
calls originating from the pre-deployed `L2ToL2CrossDomainMessenger`.

- **ElasticChain:** The destination contract does not need to know it is being called via an interop call. Requests
arrive from `aliased accounts'.

#### Batching
- **Superchain:**
Supports single calls with no guarantee of ordering.

- **ElasticChain:**
Supports bundling of messages, ensuring shared fate and strict order.
- **Superchain:** Supports single calls with no guarantee of ordering.

- **ElasticChain:** Supports bundling of messages, ensuring shared fate and strict order.

#### Execution Permissions
- **Superchain:**
No restrictions — anyone can forward or execute a message on the destination chain.

- **ElasticChain:**
Allows restricting who can execute the call or bundle on the destination chain.
- **Superchain:** No restrictions — anyone can forward or execute a message on the destination chain.

- **ElasticChain:** Allows restricting who can execute the call or bundle on the destination chain.

#### Cancellations
- **Superchain:**
Messages can be canceled by anyone, but only after a fixed EXPIRY_TIME has passed.

- **ElasticChain:**
Supports restricting who can cancel. Cancellation can happen at any time.
- **Superchain:** Messages can be canceled by anyone, but only after a fixed EXPIRY_TIME has passed.

- **ElasticChain:** Supports restricting who can cancel. Cancellation can happen at any time.

### How to Bring Them Closer

ElasticChain could introduce an option to be "SuperChain" compatible by adding a special mode to the `call`. This mode would specify that the destination contract should be called from a pre-deployed address, allowing anyone to execute or cancel the message.
ElasticChain could introduce an option to be "SuperChain" compatible by adding a special mode to the `call`. This mode
would specify that the destination contract should be called from a pre-deployed address, allowing anyone to execute or
cancel the message.

Additionally, ElasticChain would need to implement a **cancellation cutoff timestamp**.
Additionally, ElasticChain would need to implement a **cancellation cutoff timestamp**.
Loading

0 comments on commit ceaabae

Please sign in to comment.