Skip to content

Commit

Permalink
feat: wormhole l1 to l2 to l1 messaging experiments
Browse files Browse the repository at this point in the history
  • Loading branch information
kupermind committed Mar 27, 2024
1 parent 72cfd89 commit 4d7672e
Show file tree
Hide file tree
Showing 11 changed files with 548 additions and 0 deletions.
27 changes: 27 additions & 0 deletions contracts/bridges/test/WormholeL1Receiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @title WormholeL1Receiver - Smart contract for the L1 message receiving via wormhole
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract WormholeL1Receiver {
event MessageReceived(bytes32 indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain);

/// @dev Processes a message received from L1 Wormhole Relayer contract.
/// @notice The sender must be the source contract address.
/// @param data Bytes message sent from L1 Wormhole Relayer contract.
/// @param sourceAddress The (wormhole format) address on the sending chain which requested this delivery.
/// @param sourceChain The wormhole chain Id where this delivery was requested.
/// @param deliveryHash The VAA hash of the deliveryVAA.
function receiveWormholeMessages(
bytes memory data,
bytes[] memory,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external payable {
// Emit received message
emit MessageReceived(sourceAddress, data, deliveryHash, sourceChain);
}
}
72 changes: 72 additions & 0 deletions contracts/bridges/test/WormholeL1Sender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {IBridgeErrors} from "../../interfaces/IBridgeErrors.sol";

interface IWormhole {
function sendPayloadToEvm(
// Chain ID in Wormhole format
uint16 targetChain,
// Contract Address on target chain we're sending a message to
address targetAddress,
// The payload, encoded as bytes
bytes memory payload,
// How much value to attach to the delivery transaction
uint256 receiverValue,
// The gas limit to set on the delivery transaction
uint256 gasLimit
) external payable returns (
// Unique, incrementing ID, used to identify a message
uint64 sequence
);
}

/// @title WormholeL1Sender - Smart contract for sending a message from L2 to L1 via wormhole
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract WormholeL1Sender is IBridgeErrors {
event MessageReceived(bytes32 indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain);

uint256 public constant GAS_LIMIT = 50_000;
// L2 Wormhole Relayer address that receives the message across the bridge from the source L1 network
address public immutable wormholeRelayer;
// Source chain Id
uint16 public immutable sourceChainId;
// Source contract to communicate with on L1
address public sourceSender;

/// @dev WormholeMessenger constructor.
/// @param _wormholeRelayer L2 Wormhole Relayer address.
/// @param _sourceChainId Source wormhole format chain Id.
constructor(address _wormholeRelayer, uint16 _sourceChainId, address _sourceSender) {
// Check for zero addresses
if (_wormholeRelayer == address(0) || _sourceSender == address(0)) {
revert ZeroAddress();
}

// Check source chain Id
if (_sourceChainId == 0) {
revert ZeroValue();
}

wormholeRelayer = _wormholeRelayer;
sourceChainId = _sourceChainId;
sourceSender = _sourceSender;
}

function sendMessage() external payable {
bytes32 message = keccak256(abi.encode("Hello"));

// Send the message
IWormhole(wormholeRelayer).sendPayloadToEvm{value: msg.value}(
sourceChainId,
sourceSender,
abi.encode(message),
0,
GAS_LIMIT
);
}

receive() external payable {}
}
115 changes: 115 additions & 0 deletions contracts/bridges/test/WormholeL2ReceiverL1Sender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {IBridgeErrors} from "../../interfaces/IBridgeErrors.sol";

interface IWormhole {
function quoteEVMDeliveryPrice(
uint16 targetChain,
uint256 receiverValue,
uint256 gasLimit
) external returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused);

function sendPayloadToEvm(
// Chain ID in Wormhole format
uint16 targetChain,
// Contract Address on target chain we're sending a message to
address targetAddress,
// The payload, encoded as bytes
bytes memory payload,
// How much value to attach to the delivery transaction
uint256 receiverValue,
// The gas limit to set on the delivery transaction
uint256 gasLimit
) external payable returns (
// Unique, incrementing ID, used to identify a message
uint64 sequence
);
}

/// @title WormholeL2ReceiverL1Sender - Smart contract for the L1-L2-L1 message relaying via wormhole
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract WormholeL2ReceiverL1Sender is IBridgeErrors {
event MessageReceived(bytes32 indexed sourceMessageSender, bytes data, bytes32 deliveryHash, uint256 sourceChain);

uint256 public constant GAS_LIMIT = 50_000;
// L2 Wormhole Relayer address that receives the message across the bridge from the source L1 network
address public immutable wormholeRelayer;
// Source chain Id
uint16 public immutable sourceChainId;
// Source contract to communicate with on L1
address public sourceSender;
// Delivery hashes
mapping(bytes32 => bool) public mapDeliveryHashes;

/// @dev WormholeMessenger constructor.
/// @param _wormholeRelayer L2 Wormhole Relayer address.
/// @param _sourceChainId Source wormhole format chain Id.
constructor(address _wormholeRelayer, uint16 _sourceChainId) {
// Check for zero addresses
if (_wormholeRelayer == address(0)) {
revert ZeroAddress();
}

// Check source chain Id
if (_sourceChainId == 0) {
revert ZeroValue();
}

wormholeRelayer = _wormholeRelayer;
sourceChainId = _sourceChainId;
}


/// @dev Processes a message received from L2 Wormhole Relayer contract.
/// @notice The sender must be the source contract address.
/// @param data Bytes message sent from L2 Wormhole Relayer contract.
/// @param sourceAddress The (wormhole format) address on the sending chain which requested this delivery.
/// @param sourceChain The wormhole chain Id where this delivery was requested.
/// @param deliveryHash The VAA hash of the deliveryVAA.
function receiveWormholeMessages(
bytes memory data,
bytes[] memory,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external payable {
// Check L2 Wormhole Relayer address
if (msg.sender != wormholeRelayer) {
revert TargetRelayerOnly(msg.sender, wormholeRelayer);
}

// Check the source chain Id
if (sourceChain != sourceChainId) {
revert WrongSourceChainId(sourceChain, sourceChainId);
}

// Check the delivery hash uniqueness
if (mapDeliveryHashes[deliveryHash]) {
revert AlreadyDelivered(deliveryHash);
}
mapDeliveryHashes[deliveryHash] = true;

sourceSender = abi.decode(data, (address));

// Get a quote for the cost of gas for delivery
uint256 cost;
(cost, ) = IWormhole(wormholeRelayer).quoteEVMDeliveryPrice(sourceChain, 0, GAS_LIMIT);

// Send the message
IWormhole(wormholeRelayer).sendPayloadToEvm{value: cost}(
sourceChain,
sourceSender,
abi.encode(keccak256("Hello")),
0,
GAS_LIMIT
);

// Emit received message
emit MessageReceived(sourceAddress, data, deliveryHash, sourceChain);
}

receive() external payable {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*global process*/

const { ethers } = require("hardhat");
const { LedgerSigner } = require("@anders-t/ethers-ledger");

async function main() {
const fs = require("fs");
const globalsFile = "globals.json";
const dataFromJSON = fs.readFileSync(globalsFile, "utf8");
let parsedData = JSON.parse(dataFromJSON);
const useLedger = parsedData.useLedger;
const derivationPath = parsedData.derivationPath;
const providerName = parsedData.providerName;
const gasPriceInGwei = parsedData.gasPriceInGwei;

Check warning on line 14 in scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_03_womholel2receiverl1sender.js

View workflow job for this annotation

GitHub Actions / build

'gasPriceInGwei' is assigned a value but never used
let EOA;

const provider = new ethers.providers.JsonRpcProvider(parsedData.networkURL);
const signers = await ethers.getSigners();

if (useLedger) {
EOA = new LedgerSigner(provider, derivationPath);
} else {
EOA = signers[0];
}
// EOA address
const deployer = await EOA.getAddress();
console.log("EOA is:", deployer);

// Transaction signing and execution
console.log("1. EOA to deploy WormholeL2ReceiverL1Sender contract");
const WormholeL2ReceiverL1Sender = await ethers.getContractFactory("WormholeL2ReceiverL1Sender");
console.log("You are signing the following transaction: WormholeL2ReceiverL1Sender.connect(EOA).deploy()");
const wormholeL2ReceiverL1Sender = await WormholeL2ReceiverL1Sender.connect(EOA).deploy(parsedData.L2WormholeRelayerAddress,
parsedData.sourceChainId, parsedData.wormholeL1ReceiverAddress);
const result = await wormholeL2ReceiverL1Sender.deployed();

// Transaction details
console.log("Contract deployment: WormholeL2ReceiverL1Sender");
console.log("Contract address:", wormholeL2ReceiverL1Sender.address);
console.log("Transaction:", result.deployTransaction.hash);

// Writing updated parameters back to the JSON file
parsedData.wormholeL2ReceiverL1SenderAddress = wormholeL2ReceiverL1Sender.address;
fs.writeFileSync(globalsFile, JSON.stringify(parsedData));

// Contract verification
if (parsedData.contractVerification) {
const execSync = require("child_process").execSync;
execSync("npx hardhat verify --constructor-args scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_03_womholel2receiverl1sender.js --network " + providerName + " " + wormholeL2ReceiverL1Sender.address, { encoding: "utf-8" });
}
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*global process*/

const { ethers } = require("hardhat");
const { LedgerSigner } = require("@anders-t/ethers-ledger");

async function main() {
const fs = require("fs");
const globalsFile = "globals.json";
const dataFromJSON = fs.readFileSync(globalsFile, "utf8");
let parsedData = JSON.parse(dataFromJSON);
const useLedger = parsedData.useLedger;
const derivationPath = parsedData.derivationPath;
const providerName = "sepolia";
const gasPriceInGwei = parsedData.gasPriceInGwei;

Check warning on line 14 in scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_04_womholel1receiver.js

View workflow job for this annotation

GitHub Actions / build

'gasPriceInGwei' is assigned a value but never used
let EOA;

const provider = await ethers.providers.getDefaultProvider(providerName);
const signers = await ethers.getSigners();

if (useLedger) {
EOA = new LedgerSigner(provider, derivationPath);
} else {
EOA = signers[0];
}
// EOA address
const deployer = await EOA.getAddress();
console.log("EOA is:", deployer);

// Transaction signing and execution
console.log("1. EOA to deploy WormholeL1Receiver contract");
const WormholeL1Receiver = await ethers.getContractFactory("WormholeL1Receiver");
console.log("You are signing the following transaction: WormholeL1Receiver.connect(EOA).deploy()");
const wormholeL1Receiver = await WormholeL1Receiver.connect(EOA).deploy();
const result = await wormholeL1Receiver.deployed();

// Transaction details
console.log("Contract deployment: WormholeL1Receiver");
console.log("Contract address:", wormholeL1Receiver.address);
console.log("Transaction:", result.deployTransaction.hash);

// Writing updated parameters back to the JSON file
parsedData.wormholeL1ReceiverAddress = wormholeL1Receiver.address;
fs.writeFileSync(globalsFile, JSON.stringify(parsedData));

// Contract verification
if (parsedData.contractVerification) {
const execSync = require("child_process").execSync;
execSync("npx hardhat verify --network " + providerName + " " + wormholeL1Receiver.address, { encoding: "utf-8" });
}
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*global process*/

const { ethers } = require("hardhat");
const { LedgerSigner } = require("@anders-t/ethers-ledger");

async function main() {
const fs = require("fs");
const globalsFile = "globals.json";
const dataFromJSON = fs.readFileSync(globalsFile, "utf8");
let parsedData = JSON.parse(dataFromJSON);
const useLedger = parsedData.useLedger;
const derivationPath = parsedData.derivationPath;
const providerName = parsedData.providerName;
const gasPriceInGwei = parsedData.gasPriceInGwei;

Check warning on line 14 in scripts/deployment/bridges/wormhole/test/l1_l2_l1/deploy_05_womholel1sender.js

View workflow job for this annotation

GitHub Actions / build

'gasPriceInGwei' is assigned a value but never used
let EOA;

const provider = new ethers.providers.JsonRpcProvider(parsedData.networkURL);
const signers = await ethers.getSigners();

if (useLedger) {
EOA = new LedgerSigner(provider, derivationPath);
} else {
EOA = signers[0];
}
// EOA address
const deployer = await EOA.getAddress();
console.log("EOA is:", deployer);

// Transaction signing and execution
console.log("1. EOA to deploy WormholeL1Sender contract");
const WormholeL1Sender = await ethers.getContractFactory("WormholeL1Sender");
console.log("You are signing the following transaction: WormholeL1Sender.connect(EOA).deploy()");
const wormholeL1Sender = await WormholeL1Sender.connect(EOA).deploy(parsedData.L2WormholeRelayerAddress,
parsedData.sourceChainId, parsedData.wormholeL1ReceiverAddress);
const result = await wormholeL1Sender.deployed();

// Transaction details
console.log("Contract deployment: WormholeL1Sender");
console.log("Contract address:", wormholeL1Sender.address);
console.log("Transaction:", result.deployTransaction.hash);

// Writing updated parameters back to the JSON file
parsedData.wormholeL1SenderAddress = wormholeL1Sender.address;
fs.writeFileSync(globalsFile, JSON.stringify(parsedData));

// Contract verification
if (parsedData.contractVerification) {
const execSync = require("child_process").execSync;
execSync("npx hardhat verify --constructor-args scripts/deployment/bridges/wormhole/test/l1_l2_l1/verify_05_womholel1sender.js --network " + providerName + " " + wormholeL1Sender.address, { encoding: "utf-8" });
}
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"contractVerification":true,"useLedger":false,"derivationPath":"m/44'/60'/2'/0/0","providerName":"optimisticSepolia","gasPriceInGwei":"2","networkURL":"https://sepolia.optimism.io","sourceChainId":"10002","L1WormholeRelayerAddress":"0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470","L2WormholeRelayerAddress":"0x93BAD53DDfB6132b0aC8E37f6029163E63372cEE","wormholeL2ReceiverL1SenderAddress":"0x1d333b46dB6e8FFd271b6C2D2B254868BD9A2dbd","wormholeL1ReceiverAddress":"0xF66E23209074FA7946E41f45f43d765281af2207","wormholeL1SenderAddress":"0x04A0afD079F14D539B17253Ea93563934A024165"}
Loading

0 comments on commit 4d7672e

Please sign in to comment.