Skip to content

Commit e84c0d8

Browse files
committed
separate out two more examples
1 parent d071126 commit e84c0d8

File tree

6 files changed

+127
-126
lines changed

6 files changed

+127
-126
lines changed

docs/src/specs/contracts/bridging/interop/bundles_calls.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ your call).
4141
On the destination chain, you can execute the call using the execute method:
4242

4343
```solidity
44-
contract InteropCenter {
44+
contract InteropHandler {
4545
// Executes a given bundle.
4646
// interopMessage is the message that contains your bundle as payload.
4747
// If it fails, it can be called again.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Interop Message Simple Use Case
2+
3+
Before we dive into the details of how the system works, let’s look at a simple use case for a DApp that decides to use
4+
InteropMessage.
5+
6+
For this example, imagine a basic cross-chain contract where the `signup()` method can be called on chains B, C, and D
7+
only if someone has first called `signup_open()` on chain A.
8+
9+
```solidity
10+
// Contract deployed on chain A.
11+
contract SignupManager {
12+
public bytes32 sigup_open_msg_hash;
13+
function signup_open() onlyOwner {
14+
// We are open for business
15+
signup_open_msg_hash = InteropCenter(INTEROP_CENTER_ADDRESS).sendInteropMessage("We are open");
16+
}
17+
}
18+
19+
// Contract deployed on all other chains.
20+
contract SignupContract {
21+
public bool signupIsOpen;
22+
// Anyone can call it.
23+
function openSignup(InteropMessage message, InteropProof proof) {
24+
InteropCenter(INTEROP_CENTER_ADDRESS).verifyInteropMessage(keccak(message), proof);
25+
require(message.sourceChainId == CHAIN_A_ID);
26+
require(message.sender == SIGNUP_MANAGER_ON_CHAIN_A);
27+
require(message.data == "We are open");
28+
signupIsOpen = true;
29+
}
30+
31+
function signup() {
32+
require(signupIsOpen);
33+
signedUpUser[msg.sender] = true;
34+
}
35+
}
36+
```
37+
38+
In the example above, the `signupManager` on chain A calls the `signup_open` method. After that, any user on other
39+
chains can retrieve the `signup_open_msg_hash`, obtain the necessary proof from the Gateway (or another source), and
40+
call the `openSignup` function on any destination chain.
Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,81 @@
1-
# InteropCenter requestL2TransactionTwoBridges
1+
# InteropCenter requestL2TransactionTwoBridges
2+
3+
## Generic usage of `BridgeHub.requestL2TransactionTwoBridges`
4+
5+
`L1AssetRouter` is the only bridge that can handle base tokens. However, the `BridgeHub.requestL2TransactionTwoBridges` could be used by `secondBridgeAddress` on L1. A notable example of how it is done is how our [CTMDeploymentTracker](../../../l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol) uses it to register the correct CTM address on Gateway. You can read more about how Gateway works in [its documentation](../../gateway/overview.md).
6+
7+
Let’s do a quick recap on how it works:
8+
9+
When calling `BridgeHub.requestL2TransactionTwoBridges` the following struct needs to be provided:
10+
11+
```solidity
12+
struct L2TransactionRequestTwoBridgesOuter {
13+
uint256 chainId;
14+
uint256 mintValue;
15+
uint256 l2Value;
16+
uint256 l2GasLimit;
17+
uint256 l2GasPerPubdataByteLimit;
18+
address refundRecipient;
19+
address secondBridgeAddress;
20+
uint256 secondBridgeValue;
21+
bytes secondBridgeCalldata;
22+
}
23+
```
24+
25+
- `secondBridgeAddress` is the address of the L1 contract that needs to perform the L1->L2 transaction.
26+
- `secondBridgeValue` is the `msg.value` to be sent to the `secondBridgeAddress`.
27+
- `secondBridgeCalldata` is the data to pass to the `secondBridgeAddress`. This can be interpreted any way it wants.
28+
29+
1. Firstly, the Bridgehub will deposit the `request.mintValue` the same way as during a simple L1→L2 transaction. These funds will be used for funding the `l2Value` and the fee to the operator.
30+
2. After that, the `secondBridgeAddress.bridgehubDeposit` with the following signature is called
31+
32+
```solidity
33+
struct L2TransactionRequestTwoBridgesInner {
34+
// Should be equal to a constant `keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1`
35+
bytes32 magicValue;
36+
// The L2 contract to call
37+
address l2Contract;
38+
// The calldata to call it with
39+
bytes l2Calldata;
40+
// The factory deps to call it with
41+
bytes[] factoryDeps;
42+
// Just some 32-byte value that can be used for later processing
43+
// It is called `txDataHash` as it *should* be used as a way to facilitate
44+
// reclaiming failed deposits.
45+
bytes32 txDataHash;
46+
}
47+
48+
function bridgehubDeposit(
49+
uint256 _chainId,
50+
// The actual user that does the deposit
51+
address _prevMsgSender,
52+
// The msg.value of the L1->L2 transaction to be created
53+
uint256 _l2Value,
54+
// Custom bridge-specific data
55+
bytes calldata _data
56+
) external payable returns (L2TransactionRequestTwoBridgesInner memory request);
57+
```
58+
59+
Now the job of the contract will be to “validate” whether they are okay with the transaction to come. For instance, the `CTMDeploymentTracker` checks that the `_prevMsgSender` is the owner of `CTMDeploymentTracker` and has the necessary rights to perform the transaction out of the name of it.
60+
61+
Ultimately, the correctly processed `bridgehubDeposit` function basically grants `BridgeHub` the right to create an L1→L2 transaction out of the name of the `secondBridgeAddress`. Since it is so powerful, the first returned value must be a magical constant that is equal to `keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1`. The fact that it was a somewhat non standard signature and a struct with the magical value is the major defense against “accidental” approvals to start a transaction out of the name of an account.
62+
63+
Aside from the magical constant, the method should also return the information an L1→L2 transaction will start its call with: the `l2Contract` , `l2Calldata`, `factoryDeps`. It also should return the `txDataHash` field. The meaning `txDataHash` will be needed in the next paragraphs. But generally it can be any 32-byte value the bridge wants.
64+
65+
1. After that, an L1→L2 transaction is invoked. Note, that the “trusted” `L1AssetRouter` has enforced that the baseToken was deposited correctly (again, the step (1) can _only_ be handled by the `L1AssetRouter`), while the second bridge can provide any data to call its L2 counterpart with.
66+
2. As a final step, following function is called:
67+
68+
```solidity
69+
function bridgehubConfirmL2Transaction(
70+
// `chainId` of the ZKChain
71+
uint256 _chainId,
72+
// the same value that was returned by `bridgehubDeposit`
73+
bytes32 _txDataHash,
74+
// the hash of the L1->L2 transaction
75+
bytes32 _txHash
76+
) external;
77+
```
78+
79+
This function is needed for whatever actions are needed to be done after the L1→L2 transaction has been invoked.
80+
81+
On `L1AssetRouter` it is used to remember the hash of each deposit transaction, so that later on, the funds could be returned to user if the `L1->L2` transaction fails. The `_txDataHash` is stored so that the whenever the users will want to reclaim funds from a failed deposit, they would provide the token and the amount as well as the sender to send the money to.

docs/src/specs/contracts/bridging/interop/interop_center.md

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -58,85 +58,6 @@ This call will return the parameters to call the l2 contract with (the address o
5858

5959
![requestL2TransactionTwoBridges (SharedBridge) (1).png](../img/requestL2TransactionTwoBridges_depositEthToUSDC.png)
6060

61-
## Generic usage of `BridgeHub.requestL2TransactionTwoBridges`
62-
63-
`L1AssetRouter` is the only bridge that can handle base tokens. However, the `BridgeHub.requestL2TransactionTwoBridges` could be used by `secondBridgeAddress` on L1. A notable example of how it is done is how our [CTMDeploymentTracker](../../../l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol) uses it to register the correct CTM address on Gateway. You can read more about how Gateway works in [its documentation](../../gateway/overview.md).
64-
65-
Let’s do a quick recap on how it works:
66-
67-
When calling `BridgeHub.requestL2TransactionTwoBridges` the following struct needs to be provided:
68-
69-
```solidity
70-
struct L2TransactionRequestTwoBridgesOuter {
71-
uint256 chainId;
72-
uint256 mintValue;
73-
uint256 l2Value;
74-
uint256 l2GasLimit;
75-
uint256 l2GasPerPubdataByteLimit;
76-
address refundRecipient;
77-
address secondBridgeAddress;
78-
uint256 secondBridgeValue;
79-
bytes secondBridgeCalldata;
80-
}
81-
```
82-
83-
- `secondBridgeAddress` is the address of the L1 contract that needs to perform the L1->L2 transaction.
84-
- `secondBridgeValue` is the `msg.value` to be sent to the `secondBridgeAddress`.
85-
- `secondBridgeCalldata` is the data to pass to the `secondBridgeAddress`. This can be interpreted any way it wants.
86-
87-
1. Firstly, the Bridgehub will deposit the `request.mintValue` the same way as during a simple L1→L2 transaction. These funds will be used for funding the `l2Value` and the fee to the operator.
88-
2. After that, the `secondBridgeAddress.bridgehubDeposit` with the following signature is called
89-
90-
```solidity
91-
struct L2TransactionRequestTwoBridgesInner {
92-
// Should be equal to a constant `keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1`
93-
bytes32 magicValue;
94-
// The L2 contract to call
95-
address l2Contract;
96-
// The calldata to call it with
97-
bytes l2Calldata;
98-
// The factory deps to call it with
99-
bytes[] factoryDeps;
100-
// Just some 32-byte value that can be used for later processing
101-
// It is called `txDataHash` as it *should* be used as a way to facilitate
102-
// reclaiming failed deposits.
103-
bytes32 txDataHash;
104-
}
105-
106-
function bridgehubDeposit(
107-
uint256 _chainId,
108-
// The actual user that does the deposit
109-
address _prevMsgSender,
110-
// The msg.value of the L1->L2 transaction to be created
111-
uint256 _l2Value,
112-
// Custom bridge-specific data
113-
bytes calldata _data
114-
) external payable returns (L2TransactionRequestTwoBridgesInner memory request);
115-
```
116-
117-
Now the job of the contract will be to “validate” whether they are okay with the transaction to come. For instance, the `CTMDeploymentTracker` checks that the `_prevMsgSender` is the owner of `CTMDeploymentTracker` and has the necessary rights to perform the transaction out of the name of it.
118-
119-
Ultimately, the correctly processed `bridgehubDeposit` function basically grants `BridgeHub` the right to create an L1→L2 transaction out of the name of the `secondBridgeAddress`. Since it is so powerful, the first returned value must be a magical constant that is equal to `keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1`. The fact that it was a somewhat non standard signature and a struct with the magical value is the major defense against “accidental” approvals to start a transaction out of the name of an account.
120-
121-
Aside from the magical constant, the method should also return the information an L1→L2 transaction will start its call with: the `l2Contract` , `l2Calldata`, `factoryDeps`. It also should return the `txDataHash` field. The meaning `txDataHash` will be needed in the next paragraphs. But generally it can be any 32-byte value the bridge wants.
122-
123-
1. After that, an L1→L2 transaction is invoked. Note, that the “trusted” `L1AssetRouter` has enforced that the baseToken was deposited correctly (again, the step (1) can _only_ be handled by the `L1AssetRouter`), while the second bridge can provide any data to call its L2 counterpart with.
124-
2. As a final step, following function is called:
125-
126-
```solidity
127-
function bridgehubConfirmL2Transaction(
128-
// `chainId` of the ZKChain
129-
uint256 _chainId,
130-
// the same value that was returned by `bridgehubDeposit`
131-
bytes32 _txDataHash,
132-
// the hash of the L1->L2 transaction
133-
bytes32 _txHash
134-
) external;
135-
```
136-
137-
This function is needed for whatever actions are needed to be done after the L1→L2 transaction has been invoked.
138-
139-
On `L1AssetRouter` it is used to remember the hash of each deposit transaction, so that later on, the funds could be returned to user if the `L1->L2` transaction fails. The `_txDataHash` is stored so that the whenever the users will want to reclaim funds from a failed deposit, they would provide the token and the amount as well as the sender to send the money to.
14061

14162
## Claiming failed deposits
14263

docs/src/specs/contracts/bridging/interop/interop_messages.md

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ This `interopHash` serves as a globally unique identifier that can be used on an
5353
#### How do I get the proof
5454

5555
You’ll notice that **verifyInteropMessage** has a second argument — a proof that you need to provide. This proof is a
56-
Merkle tree proof (more details below). You can obtain it by querying the
57-
[chain](https://docs.zksync.io/build/api-reference/zks-rpc#zks_getl2tol1msgproof) , or generate it off-chain - by
56+
Merkle tree proof (more details [here](./message_root.md)). You can obtain it by querying the chain using the
57+
[api](https://docs.zksync.io/build/api-reference/zks-rpc#zks_getl2tol1msgproof), or generate it off-chain - by
5858
looking at the chain's state on L1.
5959

6060
#### How does the interop message differ from other layers (InteropTransactions, InteropCalls)
@@ -65,48 +65,8 @@ destination chains, nullifiers/replay, cancellation, and more.
6565
If you need these capabilities, consider integrating with a higher layer of interop, such as Call or Bundle, which
6666
provide these additional functionalities.
6767

68-
## Simple Use Case
6968

70-
Before we dive into the details of how the system works, let’s look at a simple use case for a DApp that decides to use
71-
InteropMessage.
72-
73-
For this example, imagine a basic cross-chain contract where the `signup()` method can be called on chains B, C, and D
74-
only if someone has first called `signup_open()` on chain A.
75-
76-
```solidity
77-
// Contract deployed on chain A.
78-
contract SignupManager {
79-
public bytes32 sigup_open_msg_hash;
80-
function signup_open() onlyOwner {
81-
// We are open for business
82-
signup_open_msg_hash = InteropCenter(INTEROP_CENTER_ADDRESS).sendInteropMessage("We are open");
83-
}
84-
}
85-
86-
// Contract deployed on all other chains.
87-
contract SignupContract {
88-
public bool signupIsOpen;
89-
// Anyone can call it.
90-
function openSignup(InteropMessage message, InteropProof proof) {
91-
InteropCenter(INTEROP_CENTER_ADDRESS).verifyInteropMessage(keccak(message), proof);
92-
require(message.sourceChainId == CHAIN_A_ID);
93-
require(message.sender == SIGNUP_MANAGER_ON_CHAIN_A);
94-
require(message.data == "We are open");
95-
signupIsOpen = true;
96-
}
97-
98-
function signup() {
99-
require(signupIsOpen);
100-
signedUpUser[msg.sender] = true;
101-
}
102-
}
103-
```
104-
105-
In the example above, the `signupManager` on chain A calls the `signup_open` method. After that, any user on other
106-
chains can retrieve the `signup_open_msg_hash`, obtain the necessary proof from the Gateway (or another source), and
107-
call the `openSignup` function on any destination chain.
108-
109-
## Deeper Technical Dive
69+
<!-- ## Deeper Technical Dive
11070
11171
Let’s break down what happens inside the InteropCenter when a new interop message is created:
11272
@@ -127,4 +87,4 @@ As you can see, it populates the necessary data and then calls the `sendToL1` me
12787
12888
- In ElasticChain, older messages become increasingly difficult to validate as it becomes harder to gather the data
12989
required to construct a Merkle proof. Expiration is also being considered for this reason, but the specifics are yet
130-
to be determined.
90+
to be determined. -->

docs/src/specs/contracts/bridging/interop/interop_transactions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,4 @@ information can still be constructed off-chain using data available on L1.
193193
### How it Works Under the hood
194194

195195
We’ll modify the default account to accept interop proofs as signatures, seamlessly integrating with the existing ZKSync
196-
native **Account Abstraction** model.
196+
native **Account Abstraction** model. See [Interop handler](./interop_handler.md) for more details.

0 commit comments

Comments
 (0)