Skip to content

Commit

Permalink
evm: Add wormhole transceiver (#1)
Browse files Browse the repository at this point in the history
* forge install: wormhole-solidity-sdk

v0.1.0

* EVM: Add wormhole transceiver

* Code review rework

* Pick up new router interfaces

* evm: Use create2

* evm: Set up compiler version

* Code review rework
  • Loading branch information
bruce-riley authored Oct 29, 2024
1 parent 54bf202 commit 5a271c1
Show file tree
Hide file tree
Showing 21 changed files with 1,347 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/svm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
run: |
ANCHOR_VERSION="$(awk '/anchor_version =/ { print substr($3, 2, length($3)-2) }' Anchor.toml)"
echo "::set-output name=version::${ANCHOR_VERSION}"
- uses: evan-gray/anchor-test@24c04ecece7b484fa1218bab4318818b36436005
- uses: evan-gray/anchor-test@06370fbca011ee48b176211b8f858789d6c33282
with:
anchor-version: "${{steps.anchor.outputs.version}}"
solana-cli-version: "${{steps.solana.outputs.version}}"
Expand Down
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[submodule "evm/lib/forge-std"]
path = evm/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "evm/lib/wormhole-solidity-sdk"]
path = evm/lib/wormhole-solidity-sdk
url = https://github.com/wormhole-foundation/wormhole-solidity-sdk
[submodule "evm/lib/example-gmp-router"]
path = evm/lib/example-gmp-router
url = https://github.com/wormholelabs-xyz/example-gmp-router
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

## Overview

This repository serves as a template monorepo for developing on multiple blockchains. The goal is to provide the generally recommended starting setups for each runtime along with pre-configured CI.
This implementation provides a reference implementation of a transceiver that allows the GMP router to communicate over Wormhole.

A transceiver MUST implement the `ITransceiver` interface defined in the GMP repo.

## Runtime Support

Expand Down
12 changes: 9 additions & 3 deletions evm/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
broadcast
broadcast/*/31337/
broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env

# Code coverage
lcov.info

# Flattened source
flattened
138 changes: 127 additions & 11 deletions evm/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,120 @@
## Foundry
# EVM Documentation

## Design

The WormholeTransceiver is responsible for sending and receiving messages for a [GMP Router](https://github.com/wormholelabs-xyz/example-gmp-router/blob/main/README.md) via the Wormhole core network.

The WormholeTransceiver contract is associated with exactly one Router contract. It may be configured to support multiple peer transceivers with at most one per chain. All peer contracts must implement the WormholeTransceiver message format as defined in `_encodePayload`. The peers are added by an admin contract. Once a peer is set for a chain, it may not be updated.

It is expected that the Router will use this transceiver to send messages.

This transceiver expects to receive messages from external relayers. If a message is valid, the transceiver will attest it to the router.

Note that this transceiver does not do replay protection because that is done by the router.

### Constructor Parameters

All of these parameters are part of the contract state as public attributes so anyone may read them.

#### Chain Identifiers

The `ourChain` parameter specifies the Wormhole chain ID of the chain to which this WormholeTransceiver is deployed.

The `evmChain` parameters specifies the EVM chain ID of this chain. It is used to detect forks when attesting messages.

The chain values are immutable.

#### Router

A Router is the on-chain contract which will call `sendMessage` on the WormholeTransceiver. From the WormholeTransceiver perspective, the Router is the `msg.sender` of `sendMessage`. Additionally, the WormholeTransceiver will call `attestMessage` on the Router contract.

The Router contract must implement that `IRouterTransceiver` interface. The Router address is immutable.

#### Admin

The Admin contract is used to administer the WormholeTransceiver. The admin is the only contract that is allowed to call `setPeer`. Additionally, only the admin may call `updateAdmin`, `transferAdmin` and `discardAdmin` to update the admin contract.

#### Wormhole Contract

The Wormhole contract should be set to the canonical Wormhole Core contract on this chain. The Wormhole address is immutable. The WormholeTransceiver uses the Wormhole contract to quote the delivery price, send messages and validate received messages.

#### Consistency Level

The `consistencyLevel` parameter is passed into the `IWormhole` `publishMessage` call. It configures whether the Wormhole network should publish the message immediately (value 200), when the block is marked safe (value 201) or when the block is finalized (any other value). The consistency level is immutable.

### Contract Administration:

- The admin contract is set at construction time.
- Only the current admin may update the admin.
- The admin privileges may be transferred immediately using `updateAdmin`.
- The admin privileges may be transferred in a two step process using `transferAdmin`. This requires the new admin contract to call `claimAdmin` to actually become the admin contract.
- The current admin contract may cancel a transfer by calling `claimAdmin` with `msg.sender`.
- The current admin may discard the admin privileges by calling `discardAdmin`. This makes the WormholeTransceiver immutable, allowing not further peers to be set. THIS IS NOT REVERSIBLE.

### Other External Interfaces

- Any contract may call `getTransceiverType` to get the string identifier of this transceiver.
- Any contract may call `quoteDeliveryPrice` to compute the cost of delivering a router message to the specified peer.
- The Router may call `sendMessage` to send a message to a given connected peer.
- Any contract may call `receiveMessage` to post a message to the transceiver. If the message is valid, the WormholeTransceiver will call `attestMessage` on the Router.

- Any contract may call `getPeer` to determine if the WormholeTransceiver is connected to a given chain, and if so what is the peer address.
- Any contract may call `getPeers` to get a list of all connected peers.

### Message Format

The payload of the Wormhole messages consists of the following fields encoded using `abi.encodePacked`.

```code
UniversalAddress srcAddr,
uint64 sequence,
uint16 dstChain,
UniversalAddress dstAddr,
bytes32 payloadHash
```

## Deployment and Administration

### Deploying the Wormhole Transceiver Contract

The contract can be deployed using the following command.

```shell
evm$ RPC_URL= MNEMONIC= OUR_CHAIN_ID= EVM_CHAIN_ID= ADMIN= ROUTER= WORMHOLE= CONSISTENCY_LEVEL= ./sh/deployWormholeTransceiver.sh
```

Note that the deploy script uses `create2` to generate a deterministic contract address.

### Generating Flattened Source

If you need to generate flattened source to be used for contract verification, you can use the following command. The results will be in `evm/flattened`.

```shell
evm$ ./sh/flatten.sh
```

### Verifying the Wormhole Transceiver Contract

To verify the WormholeTransceiver contract, do something like this:

```shell
evm$ forge verify-contract --etherscan-api-key $ETHERSCAN_KEY --verifier etherscan --chain sepolia --watch --constructor-args $(cast abi-encode "constructor(uint16,uint256,address,address,address,uint8)" 10002 11155111 0x49887A216375FDED17DC1aAAD4920c3777265614 0xB3375116c00873D3ED5781edFE304aC9cC75eA56 0x4a8bc80Ed5a4067f1CCf107057b8270E0c
C11A78 200) 0xB3375116c00873D3ED5781edFE304aC9cC75eA56 ./src/WormholeTransceiver.sol:WormholeTransceiver
```

### Configuring Peer Transceivers

To configure a peer Wormhole Transceiver for a given chain, you can use the following command.

```shell
evm$ RPC_URL= MNEMONIC= WT_ADDR= PEER_CHAIN_ID= PEER_ADDR= ./sh/setPeer.sh
```

Note that `PEER_CHAIN_ID` is a Wormhole CHAIN ID and the `PEER_ADDR` is a `UniversalAddress` expressed as a `bytes32` beginning with `0x`.

## Development

### Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Expand All @@ -9,55 +125,55 @@ Foundry consists of:
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation
### Documentation

https://book.getfoundry.sh/

## Usage
### Usage

### Build
#### Build

```shell
$ forge build
```

### Test
#### Test

```shell
$ forge test
```

### Format
#### Format

```shell
$ forge fmt
```

### Gas Snapshots
#### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil
#### Anvil

```shell
$ anvil
```

### Deploy
#### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast
#### Cast

```shell
$ cast <subcommand>
```

### Help
#### Help

```shell
$ forge --help
Expand Down
6 changes: 6 additions & 0 deletions evm/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ src = "src"
out = "out"
libs = ["lib"]

solc_version = "0.8.24"
evm_version = "paris"
optimizer = true
optimizer_runs = 200
via_ir = false

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions evm/lib/example-gmp-router
Submodule example-gmp-router added at 633b4d
1 change: 1 addition & 0 deletions evm/lib/wormhole-solidity-sdk
Submodule wormhole-solidity-sdk added at b9e129
12 changes: 0 additions & 12 deletions evm/script/Counter.s.sol

This file was deleted.

49 changes: 49 additions & 0 deletions evm/script/DeployWormholeTransceiver.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {WormholeTransceiver, wormholeTransceiverVersionString} from "../src/WormholeTransceiver.sol";
import "forge-std/Script.sol";

// DeployWormholeTransceiver is a forge script to deploy the WormholeTransceiver contract. Use ./sh/deployWormholeTransceiver.sh to invoke this.
contract DeployWormholeTransceiver is Script {
function test() public {} // Exclude this from coverage report.

function dryRun(
uint16 ourChain,
uint256 evmChain,
address admin,
address router,
address wormhole,
uint8 consistencyLevel
) public {
_deploy(ourChain, evmChain, admin, router, wormhole, consistencyLevel);
}

function run(
uint16 ourChain,
uint256 evmChain,
address admin,
address router,
address wormhole,
uint8 consistencyLevel
) public returns (address deployedAddress) {
vm.startBroadcast();
(deployedAddress) = _deploy(ourChain, evmChain, admin, router, wormhole, consistencyLevel);
vm.stopBroadcast();
}

function _deploy(
uint16 ourChain,
uint256 evmChain,
address admin,
address router,
address wormhole,
uint8 consistencyLevel
) internal returns (address deployedAddress) {
bytes32 salt = keccak256(abi.encodePacked(wormholeTransceiverVersionString));
WormholeTransceiver wormholeTransceiver =
new WormholeTransceiver{salt: salt}(ourChain, evmChain, admin, router, wormhole, consistencyLevel);

return (address(wormholeTransceiver));
}
}
26 changes: 26 additions & 0 deletions evm/script/ReceiveMsg.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {WormholeTransceiver} from "../src/WormholeTransceiver.sol";
import "forge-std/Script.sol";
import "example-gmp-router/libraries/UniversalAddress.sol";

// ReceiveMsg is a forge script to receive a message on the WormholeTransceiver contract. Use ./sh/receiveMsg.sh to invoke this.
contract ReceiveMsg is Script {
function test() public {} // Exclude this from coverage report.

function dryRun(address wormholeTransceiver, bytes calldata vaaBytes) public {
_receiveMsg(wormholeTransceiver, vaaBytes);
}

function run(address wormholeTransceiver, bytes calldata vaaBytes) public {
vm.startBroadcast();
_receiveMsg(wormholeTransceiver, vaaBytes);
vm.stopBroadcast();
}

function _receiveMsg(address wormholeTransceiver, bytes calldata vaaBytes) internal {
WormholeTransceiver wormholeTransceiverContract = WormholeTransceiver(wormholeTransceiver);
wormholeTransceiverContract.receiveMessage(vaaBytes);
}
}
26 changes: 26 additions & 0 deletions evm/script/SetPeer.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {WormholeTransceiver} from "../src/WormholeTransceiver.sol";
import "forge-std/Script.sol";
import "example-gmp-router/libraries/UniversalAddress.sol";

// SetPeer is a forge script to set the peer for a given chain with the WormholeTransceiver contract. Use ./sh/setPeer.sh to invoke this.
contract SetPeer is Script {
function test() public {} // Exclude this from coverage report.

function dryRun(address wormholeTransceiver, uint16 peerChain, bytes32 peerAddress) public {
_setPeer(wormholeTransceiver, peerChain, peerAddress);
}

function run(address wormholeTransceiver, uint16 peerChain, bytes32 peerAddress) public {
vm.startBroadcast();
_setPeer(wormholeTransceiver, peerChain, peerAddress);
vm.stopBroadcast();
}

function _setPeer(address wormholeTransceiver, uint16 peerChain, bytes32 peerAddress) internal {
WormholeTransceiver wormholeTransceiverContract = WormholeTransceiver(wormholeTransceiver);
wormholeTransceiverContract.setPeer(peerChain, peerAddress);
}
}
Loading

0 comments on commit 5a271c1

Please sign in to comment.