Skip to content

Commit

Permalink
Refactored the Messaging tutorial to use templating (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
fadeev authored Aug 23, 2023
1 parent ab208c7 commit d2f9e38
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 62 deletions.
255 changes: 198 additions & 57 deletions docs/developers/cross-chain-messaging/examples/your-first-message.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,156 @@ id: hello-world
title: Your First Cross-Chain Message
---

# Your First Cross-Chain Message
# First Cross-Chain Message

In this tutorial we will create a simple contract that allows sending a message
from one chain to another using the
[Connector API](/developers/cross-chain-messaging/connector/).

## Set up your environment
## Prerequisites

- [Node.js](https://nodejs.org/en/) v12 or later
- [Yarn](https://yarnpkg.com/) v1 or later
- [Git](https://git-scm.com/)

## Set Up Your Environment

```
git clone https://github.com/zeta-chain/template
```

Install the dependencies:
Install dependencies:

```
cd template
yarn add --dev @openzeppelin/contracts
```

## Create a new contract
## Create the Contract

```solidity title="contracts/CrossChainMessage.sol" reference
https://github.com/zeta-chain/example-contracts/blob/feat/import-toolkit/messaging/message/contracts/CrossChainMessage.sol
To create a new cross-chain messaging contract you will use the `messaging`
Hardhat task available by default in the template.

```
npx hardhat messaging CrossChainMessage message
```

The `sendHelloWorld` function is used to send a "Hello, Cross-Chain World!"
message to another chain. It first checks the validity of the destination chain
ID. Then it fetches the required ZETA from the native chain token provided in
the transaction value, and approves the connector for the calculated Zeta value.
Then, it sends the message using the connector's `send` function.
The `messaging` task accepts one or more arguments: the name of the contract and
a list of arguments (optionally with types). The arguments define the contents
of the message that will be sent across chains.

In the example above the message will have only one field: `message` of type
`bytes32`. If the type is not specified it is assumed to be `bytes32`.

The `messaging` task has created:

- `contracts/CrossChainMessage.sol`: a Solidity cross-chain messaging contract
- `tasks/deploy.ts`: a Hardhat task to deploy the contract on one or more chains
- `tasks/interact.ts`: a Hardhat task to interact with the contract

It also modified `hardhat.config.ts` to import both `deploy` and `interact`
tasks.

## Cross-Chain Messaging Contract

Let's review the contents of the `CrossChainMessage` contract:

The `onZetaMessage` function is used to handle incoming cross-chain messages. It
verifies the message signature and the message type. If it's a valid "Hello,
Cross-Chain World!" message, it emits a HelloWorldEvent with the message data.
```solidity title="contracts/CrossChainMessage.sol" reference
https://github.com/zeta-chain/example-contracts/blob/main/messaging/message/contracts/CrossChainMessage.sol
```

The `onZetaRevert` function is triggered if a message fails to send. It decodes
the message similarly to `onZetaMessage` and emits a `RevertedHelloWorldEvent`
in case of failure.
The contract:

- inherits from
[`ZetaInteractor`](https://github.com/zeta-chain/protocol-contracts/blob/main/contracts/evm/tools/ZetaInteractor.sol),
which provides two modifiers that are used to validate the message and revert
calls: `isValidMessageCall` and `isValidRevertCall`.
- implements
[`ZetaReceiver`](https://github.com/zeta-chain/protocol-contracts/blob/main/contracts/evm/interfaces/ZetaInterfaces.sol),
which defines two functions: `onZetaMessage` and `onZetaRevert`.

State Variables:

- `CROSS_CHAIN_MESSAGE_MESSAGE_TYPE`: a public constant state variable which
defines the message type. If your contract supports more than one message
type, it's useful to define a constant for each one.
- `_zetaConsumer`: a private immutable state variable that stores the address of
`ZetaTokenConsumer`, which is used amond other things for getting ZETA tokens
from native tokens to pay for gas when sending a message.
- `_zetaToken`: an internal immutable state variable that stores the address of
the ZETA token contract.

The contract defines two events: `CrossChainMessageEvent` emitted when a message
is processed on the destination chain and `CrossChainMessageRevertedEvent`
emitted when a message is reverted on the destination chain.

The constructor passes `connectorAddress` to the `ZetaInteractor` constructor
and initializes both `_zetaToken` and `_zetaConsumer` state variables.

The `sendMessage` function is used to send a message to a recipient contract on
the destination chain. It first checks that the destination chain ID is valid.
Then it uses ZETA consumer to get the needed amount of ZETA tokens from the
provided `msg.value` (amount of native gas assets sent with the function call),
and approves the `ZetaConnector` to spend the `zetaValueAndGas` amount of ZETA
tokens.

The `sendMessage` function uses `connector.send` to send a crosss-chain message
with the following arguments wrapped in a struct:

- `destinationChainId`: chain ID of the destination chain
- `destinationAddress`: address of the contract receiving the message on the
destination chain (expressed in bytes since it can be non-EVM)
- `destinationGasLimit`: gas limit for the destination chain's transaction
- `message`: arbitrary message to be parsed by the receiving contract on the
destination chain
- `zetaValueAndGas`: amount of ZETA tokens to be sent to the destination chain,
ZetaChain gas fees, and destination chain gas fees (expressed in ZETA tokens)
- `zetaParams`: optional ZetaChain parameters.

The `onZetaMessage` function processes incoming cross-chain messages. The
function decodes the message to identify its type and content. If the message
type matches a predefined constant, the message's reception is logged through
the `CrossChainMessageEvent`. However, if the type is unrecognized, the function
reverts to ensure that only specific message types are handled. The function
also uses a `isValidMessageCall` modifier to verify the message's authenticity,
ensuring it comes from a trusted source.

The `onZetaRevert` function handles the reversal of cross-chain messages. Taking
in a `ZetaInterfaces.ZetaRevert` parameter, the function decodes this reverted
message to identify its type and content. If the message type aligns with a
predefined constant, the function logs the reversal through the
`CrossChainMessageRevertedEvent`. On the other hand, if the type is not
recognized, the function reverts the transaction. The function also uses the
`isValidRevertCall` modifier to ensure that the revert message is genuine and
originates from the trusted source.

## Deploy Task

The `messaging` task has created a Hardhat task to deploy the contract.

## Create a deployment task
```ts title="tasks/deploy.ts" reference
https://github.com/zeta-chain/example-contracts/blob/main/messaging/message/tasks/deploy.ts
```

To establish cross-chain messaging between blockchains via ZetaChain, you should
deploy identical contracts on two or more connected chains.
To establish cross-chain messaging between blockchains via ZetaChain, you need
to deploy contracts capable of sending and receiving cross-chain messages to two
or more blockchains connected to ZetaChain.

You can specify the desired chains by adding a `--networks` parameter to the
deploy task, which accepts a list of network names separated by commas. For
instance, `--networks goerli,bsc-testnet`.
You can specify the desired chains by using a `--networks` parameter of the
`deploy` task, which accepts a list of network names separated by commas. For
instance, `--networks goerli_testnet,bsc_testnet`.

The main function maintains a mapping of network names to their corresponding
The `main` function maintains a mapping of network names to their corresponding
deployed contract addresses, iterating over the networks to deploy the contract
on each one.

The contract's constructor requires three arguments: the connector contract's
address, the ZETA token's address, and the ZETA token consumer contract's
address. These addresses are obtained using ZetaChain's `getAddress`.

The main function subsequently sets interactors for each contract. An interactor
is a mapping between a chain ID of the destination and the contract address on
that chain.
The `main` function subsequently sets interactors for each contract. An
interactor is a mapping between a chain ID of the destination and the contract
address on that chain.

When deploying to two chains (like Goerli and BSC testnet), you will invoke
`setInteractorByChainId` on a Goerli contract and pass the BSC testnet chain ID
Expand All @@ -72,10 +164,63 @@ on a BSC testnet contract, passing the Goerli chain ID (5) and the Goerli
contract address. If deploying to more than two chains, you must call
`setInteractorByChainId` for each link between the chains.

```ts title="tasks/deploy.ts" reference
https://github.com/zeta-chain/example-contracts/blob/feat/import-toolkit/messaging/message/tasks/deploy.ts
## Interact Task

The `messaging` task has also created a Hardhat task to interact with the
contract:

```ts title="tasks/interact.ts"
https://github.com/zeta-chain/example-contracts/blob/main/messaging/message/tasks/interact.ts
```

The task accepts the following arguments:

- `contract`: address of the contract on the source chain
- `amount`: amount of native tokens to send with the transaction
- `destination`: name of the destination chain
- `message`: message to be sent to the destination chain

The `main` function uses the `contract` argument to attach to the contract on
the source chain. It then uses the `destination` argument to obtain the
destination chain's chain ID. The function subsequently converts the `message`
argument to bytes and sends a transaction to the contract's `sendMessage`
function, passing the destination chain ID and the message.

Finally, the task uses the `trackCCTX` function from
`@zetachain/toolkit/helpers` to track the token transfer transaction. The
function waits for the transaction to appear on ZetaChain and tracks the status
of the transaction. Transaction tracking is optional, but helpful to know when
the transaction has been processed by ZetaChain.

## Create an Account

To deploy and interact with the contract you will need a wallet with tokens.

Create a new wallet account:

```
npx hardhat account --save
```

This command generates a random wallet, prints information about the wallet to
the terminal, and saves the private key to a `.env` file to make it accessible
to Hardhat.

## Use the Faucet to Request Tokens

To pay for the transaction fees to deploy and interact with the cross-chain
messaging contracts you will need native gas tokens on the connected chains you
are deploying contracts to. You can find a list of recommended faucets
[in the docs](https://www.zetachain.com/docs/reference/get-testnet-zeta/).

## Check Token Balances

```
npx hardhat balances
```

## Deploy the Contract

Clear the cache and artifacts, then compile the contract:

```
Expand All @@ -85,52 +230,38 @@ npx hardhat compile --force
Run the following command to deploy the contract to two networks:

```
npx hardhat deploy --networks bsc-testnet,goerli
npx hardhat deploy --networks bsc_testnet,goerli_testnet
```

```
🚀 Successfully deployed contract on bsc-testnet.
🚀 Successfully deployed contract on bsc_testnet
📜 Contract address: 0x6Fd784c16219026Ab0349A1a8A6e99B6eE579C93
🚀 Successfully deployed contract on goerli.
🚀 Successfully deployed contract on goerli_testnet.
📜 Contract address: 0xf1907bb130feb28D6e1305C53A4bfdb32140d8E6
🔗 Setting interactors for a contract on bsc-testnet
✅ Interactor address for 5 (goerli) is set to 0xf1907bb130feb28d6e1305c53a4bfdb32140d8e6
🔗 Setting interactors for a contract on bsc_testnet
✅ Interactor address for 5 (goerli_testnet) is set to 0xf1907bb130feb28d6e1305c53a4bfdb32140d8e6
🔗 Setting interactors for a contract on goerli
✅ Interactor address for 97 (bsc-testnet) is set to 0x6fd784c16219026ab0349a1a8a6e99b6ee579c93
🔗 Setting interactors for a contract on goerli_testnet
✅ Interactor address for 97 (bsc_testnet) is set to 0x6fd784c16219026ab0349a1a8a6e99b6ee579c93
```

## Send a message
## Interact with the Contract

Create a new task to send a message from one chain to another. The task accepts
two parameters: the contract address and the destination chain ID.
Send a message from BSC testnet to Goerli using the contract address (see the
output of the `deploy` task). Make sure to submit enough native tokens with
`--amount` to pay for the transaction fees.

The task calls sendHelloWorld on the contract, passing the arguments.

```ts title="tasks/message.ts" reference
https://github.com/zeta-chain/example-contracts/blob/feat/import-toolkit/messaging/message/tasks/message.ts
```

Send a message from BSC testnet to Goerli (chain ID: 5) using the contract
address (see the output of the `deploy` task). Make sure to submit enough native
tokens with `--amount` to pay for the transaction fees.

```
npx hardhat message --network bsc-testnet --destination 5 --contract 0x6Fd784c16219026Ab0349A1a8A6e99B6eE579C93 --amount 2
npx hardhat interact --message hello --network bsc_testnet --destination goerli_testnet --contract 0x6Fd784c16219026Ab0349A1a8A6e99B6eE579C93 --amount 2
```

```
🔑 Using account: 0x2cD3D070aE1BD365909dD859d29F387AA96911e1
✅ "sendHelloWorld" transaction has been broadcasted to bsc-testnet
✅ "sendHelloWorld" transaction has been broadcasted to bsc_testnet
📝 Transaction hash: 0xa3a507d34056f4c00b753e7d0b47b16eb6d35b3c5016efa0323beb274725b1a1
Please, refer to ZetaChain's explorer for updates on the progress of
the cross-chain transaction.
🌍 Explorer: https://explorer.zetachain.com/cc/tx/0xa3a507d34056f4c00b753e7d0b47b16eb6d35b3c5016efa0323beb274725b1a1
```

After the cross-chain transaction is processed on ZetaChain, look up the
Expand All @@ -140,8 +271,18 @@ emitted `HelloWorldEvent` event.

![](/img/docs/ccm-message-explorer.png)

Congratulations! 🎉 In this tutorial you have:

- cloned the Hardhat contract template
- used `npx hardhat messaging` to create a new cross-chain messaging contract
- reviewed the contents of the generated contract and the tasks to deploy and
interact with the contract
- successfully deployed the contract to two connected chains and set interactors
on each contract
- sent a message from one chain to another using the `connector.send`

## Source Code

You can find the source code for the example in this tutorial here:

https://github.com/zeta-chain/example-contracts/blob/feat/import-toolkit/messaging/message
https://github.com/zeta-chain/example-contracts/tree/main/messaging/message
13 changes: 8 additions & 5 deletions docs/developers/omnichain/tutorials/hello.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ to create a contract that accepts specific data from a connected chain. You can
learn more about passing arguments in the following tutorials. In this tutorial
you will create a contract that does not accept any arguments.

This `omnichain` task has created:
The `omnichain` task has created:

- `contracts/MyContract.sol`: a Solidity omnichain smart contract
- `tasks/deploy.ts`: a Hardhat task to deploy the contract
- `tasks/interact.ts`: a Hardhat task to interact with the contract

### Omnichain Contract
It also modified `hardhat.config.ts` to import both `deploy` and `interact`
tasks.

## Omnichain Contract

Let's review the contents of the `MyContract` contract:

Expand Down Expand Up @@ -123,7 +126,7 @@ if (msg.sender != address(systemContract)) {
By default, the `onCrossChainCall` function doesn't do anything else. You will
implement the logic yourself based on your use case.

### Deploy Task
## Deploy Task

The `omnichain` task has created a Hardhat task to deploy the contract:

Expand Down Expand Up @@ -165,7 +168,7 @@ get the address of the system contract on ZetaChain.

The task then uses Ethers.js to deploy the contract to ZetaChain.

### Interact Task
## Interact Task

The `omnichain` task has also created a Hardhat task to interact with the
contract:
Expand Down Expand Up @@ -263,7 +266,7 @@ the
## Check Token Balances

Check token balances to ensure you have tokens on ZetaChain and at least one of
the connected chains (Goerli in this example):
the connected chains:

```
npx hardhat balances
Expand Down

0 comments on commit d2f9e38

Please sign in to comment.