Skip to content

Interop testing tutorial #1654

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pages/interop/tutorials/unit-tests.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Interop and unit tests
description: Learn how to write unit tests for interop
lang: en-US
content_type: landing-page
topic: interop-tutorial-unit-tests
personas:
- app-developer
categories:
- protocol
- interoperability
- cross-chain-messaging
- tutorial
- testing
is_imported_content: 'false'
---

import { Card, Cards } from 'nextra/components'

# Interop and unit tests

Documentation covering Interop related tutorials.

<Cards>
<Card title="Interop unit tests in Foundry" href="/interop/tutorials/unit-tests/foundry" icon={<img src="/img/icons/shapes.svg" />} />

<Card title="Interop unit tests in Hardhat" href="/interop/tutorials/unit-tests/hardhat" icon={<img src="/img/icons/shapes.svg" />} />

</Cards>
4 changes: 4 additions & 0 deletions pages/interop/tutorials/unit-tests/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"foundry": "Foundry",
"hardhat": "Hardhat"
}
147 changes: 147 additions & 0 deletions pages/interop/tutorials/unit-tests/foundry.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
title: Interop and unit tests using Foundry
description: Learn how to write unit tests for interop when using Foundry
lang: en-US
content_type: tutorial
topic: interop-tutorial-unit-tests-foundry
personas:
- app-developer
categories:
- protocol
- interoperability
- cross-chain-messaging
- tutorial
- testing
- foundry
is_imported_content: 'false'
---

import { Callout, Steps } from 'nextra/components'
import { InteropCallout } from '@/components/WipCallout'

<InteropCallout />

# Interop and unit tests using Foundry

Most parts of the contracts can be [tested normally](/app-developers/testing-apps).
This tutorial teaches you how to verify that a message has been sent successfully and to simulate receiving a message, the two functions that tie directly into interop.
To show how this works, we test [the `Greeter` and `GreetingSender` contracts](/interop/tutorials/message-passing).

## Setup

This script creates a Foundry project (in `testing/forge`) and a Hardhat project (in `testing/hardhat`) with the necessary files.
Execute it in a UNIX or Linux environment.

The results of this step are similar to what the [message passing tutorial](/interop/tutorials/message-passing) produces.

```sh file=<rootDir>/public/tutorials/setup-for-testing.sh hash=4d0fa175564131911ab6a12fb110294c
```

## Test sending a message

The easiest way to test sending a message is to see [the event emitted by `L2ToL2CrossDomainMessenger.sendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L160).

To see this in action, run these commands:

```sh
cd testing/forge
forge test GreetingSender --fork-url https://interop-alpha-0.optimism.io
```

The default [`anvil`](https://book.getfoundry.sh/anvil/overview) instance created by `forge test` does not contain the interop contracts, so we need to fork [a blockchain that does](/interop/tools/devnet).

The test is implemented by `tests/GreetingSender.t.sol`.

<details>
<summary>Explanation</summary>

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L109-L117 hash=e8c77299b01dd6f03d3e36d2e9d82608
```

The definition for the event we expect to see, [from the `L2ToL2CrossDomainMessenger` source code](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L160).

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L119-L121 hash=645c522fafccb282df37b7d3df27bd3f
```

Create a new `GreetingSender` instance for each test.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L124-L129 hash=5e79ce67c4e5f54eee5b8d09ace39860
```

Calculate the message to be sent, the same way that `GreetingSender` does.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L132-L132 hash=028411291cccc262557abde834549fc2
```

Out of the indexed topics, verify the first (destination chain) and second (address on the destination chain).
Ignore the third topic, the nonce, because it can vary.
Finally, verify that the unindexed data of the log entry (the sender address and the message) is correct.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L133-L133 hash=37203df9264e95e763befaf2e5dbbfab
```

Emit the message we expect to see.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L135-L135 hash=9322933b2f965ddc75bb8d3b2ab4c95d
```

Call the code being tested, which should emit a similar log entry to the one we just emitted.
</details>

## Test receiving a message

To simulate receiving a message, we need to ensure two conditions are fulfilled:

* The tested code is called by [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol).
* The tested code can receive additional information through simulations of:
* [`L2ToL2CrossDomainMessenger.crossDomainMessageSender`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L102-L108)
* [`L2ToL2CrossDomainMessenger.crossDomainMessageSource`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L110-L116)
* [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126)

To see this in action, run these commands:

```
cd testing/forge
forge test Greeter
```

The test is implemented by `tests/Greeter.t.sol`.

<details>
<summary>Explanation</summary>

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L171 hash=d9bb7b8ca88e8856bd86c256934a0b0b
```

This function sets up the environment for a pretend interop call.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L172-L182 hash=65aae51c8573ecb296b942c7073a3379
```

Use [`vm.mockCall`](https://book.getfoundry.sh/reference/cheatcodes/mock-call) to specify the responses to calls to `L2ToL2CrossDomainMessenger`.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L183-L187 hash=478229c219493adbbfea088cf266dec5
```

At writing `crossDomainMessageContext` is not available in the [contracts npm package](https://www.npmjs.com/package/@eth-optimism/contracts-bedrock), so we calculate the selector directly using `bytes4(keccak256("crossDomainMessageContext()"))`.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L188-L189 hash=18498ce1031cf749a13f6207d0828ca7
```

Use [`vm.prank`](https://book.getfoundry.sh/reference/cheatcodes/prank) to make it appear the tested code is called by `L2ToL2CrossDomainMessenger`.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L191-L201 hash=035afa3348edfa18a0be06b9f9991c38
```

Test how `Greeter` acts when the greeting is set from another chain.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L203-L210 hash=ea83b0fcf27566b5ae9221d1091bfc6b
```

Test how `Greeter` acts when the greeting is set from this chain.
</details>

## Next steps

* Write a revolutionary app that uses multiple blockchains within the Superchain.
* Write tests to make sure it works correctly.
185 changes: 185 additions & 0 deletions pages/interop/tutorials/unit-tests/hardhat.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
---
title: Interop and unit tests using Hardhat
description: Learn how to write unit tests for interop when using Hardhat
lang: en-US
content_type: tutorial
topic: interop-tutorial-unit-tests-hardhat
personas:
- app-developer
categories:
- protocol
- interoperability
- cross-chain-messaging
- tutorial
- testing
- hardhat
is_imported_content: 'false'
---

import { Callout, Steps } from 'nextra/components'
import { InteropCallout } from '@/components/WipCallout'

<InteropCallout />

# Interop and unit tests using Hardhat

Most parts of the contracts can be [tested normally](/app-developers/testing-apps).
This tutorial teaches you how to verify that a message has been sent successfully and to simulate receiving a message, the two functions that tie directly into interop.
To show how this works, we test [the `Greeter` and `GreetingSender` contracts](/interop/tutorials/message-passing).

## Setup

This script creates a Foundry project (in `testing/forge`) and a Hardhat project (in `testing/hardhat`) with the necessary files.
Execute it in a UNIX or Linux environment.

The results of this step are similar to what the [message passing tutorial](/interop/tutorials/message-passing) produces.

```sh file=<rootDir>/public/tutorials/setup-for-testing.sh hash=4d0fa175564131911ab6a12fb110294c
```

## Test sending a message

The easiest way to test sending a message is to see [the event emitted by `L2ToL2CrossDomainMessenger.sendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L160).

To see this in action, run these commands:

```sh
cd testing/hardhat
npx hardhat test test/GreetingSender.js
```

Of course, Hardhat does not include the interop contracts by default.
To have `L2ToL2CrossDomainMessenger.sendMessage` we fork [a blockchain that does have it](/interop/tools/devnet).
This is specified in `hardhat.config.js`.

The test is implemented by `test/GreetingSender.js`.

<details>
<summary>Explanation</summary>

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L258-L264 hash=3f669191f745ca77f1186d5d6b8dd062
```

Deploy a `GreetingSender`.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L266-L270 hash=40785947670a88582c43f31e37d7cb28
```

Create a contract object for `L2ToL2CrossDomainMessenger`.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L280-L286 hash=80f81c9dafbea8a958f0bfacb12e9389
```

Calculate the calldata that will be sent by `L2ToL2CrossDomainMessenger`.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L288 hash=ea3c2728d499ef1536178d53fdb5497a
```

The operation we're testing, `greetingSender.setGreeting(greeting)`.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L289 hash=76eeeefe7b7605474ab8e43105e7dc0d
```

The expected result, to have the `messenger` contract emit a `SentMessage` log entry.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L290-L296 hash=14bf71dc909944de148fda7bcc4d0f2d
```

The parameters in the log entry.
We use `anyValue` for the nonce because we do not know in advance what value it would be.
</details>

## Test receiving a message

To simulate receiving a message, we need to ensure two conditions are fulfilled:

* The tested code is called by [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol).
* The tested code can receive additional information through simulations of:
* [`L2ToL2CrossDomainMessenger.crossDomainMessageSender`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L102-L108)
* [`L2ToL2CrossDomainMessenger.crossDomainMessageSource`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L110-L116)
* [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126)

To see this in action, run these commands:

```
cd testing/hardhat
npx hardhat test test/Greeter.js
```

This test is implemented by two files, `contracts/MockL2ToL2Messenger.sol` which replaces `L2ToL2CrossDomainMessenger` with a contract we control, and `test/Greeter.js` which actually has the tests.

<details>
<summary>Explanation of `contracts/MockL2ToL2Messenger.sol`</summary>

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L374-L380 hash=870b143463550d8f0915a4db9d584cea
```

When the tested code receive a message, these are the source chain and address where it originates.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L382-L392 hash=7f9cf709fa17ce391ec40728267b82a0
```

These are the three functions we need to implement for tested code, such as `Greeter`, to call.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L394-L408 hash=13b29fd8bbcccfc6c7a5201f5d316d0a
```

Because we are replacing the `L2ToL2CrossDomainMessenger` with this contract, and the order in which tests are executed is not deterministic, we need this contract to work for the `GreetingSender` test.
The code here is copied from [`L2ToL2CrossDomainMessenger.sendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L128-L161), with the irrelevant parts removed.

```solidity file=<rootDir>/public/tutorials/setup-for-testing.sh#L410-L413 hash=0797417fc01fcc3a27511f55729e1cbe
```

The interop message `Greeter` gets has to come from the `L2ToL2CrossDomainMessenger` address.
We can either send a transaction into `MockL2ToL2Messenger` that then calls `Greeter`, or give the `L2ToL2CrossDomainMessenger` address some ETH and send the transaction directly from it.
This function enables us to give the contract ETH.
</details>

<details>
<summary>Explanation of `test/Greeter.js`</summary>

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L310 hash=6fd176fc44208d2f92f0210015c8e806
```

This function deploys the fixtures we need to run the tests.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L312-L319 hash=7d218ce8fead9bc8412e79303d026c16
```

This is the code that first deploys `MockL2ToL2Messenger` and then tells Hardhat to direct calls to the `L2ToL2CrossDomainMessenger` contract to it instead.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L321-L322 hash=b6df4166a44f7b5f006d269f97a4e34a
```

Deploy a `Greeter` to test.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L324-L328 hash=ee7023cfaf1b9672914aa77b75e77285
```

Create a contract object for `L2ToL2CrossDomainMessenger`.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L334-L340 hash=d44100c903a4fd42aedadce81455226a
```

Run the test for local messages.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L347-L352 hash=b37199ac43cee36514ab508d763049f8
```

Create an object for `MockL2ToL2Messenger` and give it some ETH to be able to send transactions.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L354-L358 hash=7ae6ae561d95426670ea1d0311bc9711
```

Test that when receiving an interop message, `Greeter` still emits `SetGreeting` correctly.

```js file=<rootDir>/public/tutorials/setup-for-testing.sh#L360-L364 hash=e0b49ce1ca70cc58378abcfba8d1d2ca
```

Test that when receiving an interop message, `Greeter` also emits `CrossDomainSetGreeting`.
</details>

## Next steps

* Write a revolutionary app that uses multiple blockchains within the Superchain.
* Write tests to make sure it works correctly.
Loading