diff --git a/packages/evm/contracts/Yaho.sol b/packages/evm/contracts/Yaho.sol index e9efbdb0..6ac940b3 100644 --- a/packages/evm/contracts/Yaho.sol +++ b/packages/evm/contracts/Yaho.sol @@ -149,6 +149,7 @@ contract Yaho is IYaho, MessageHashCalculator, MessageIdCalculator { function _dispatchMessage(uint256 toChainId, address to, bytes calldata data) internal returns (bytes32 messageId) { bool isHeaderReporter = msg.sender == headerReporter; address from = isHeaderReporter ? address(0) : msg.sender; + // NOTE: in case of isHeaderReporter = true -> to = address(0) Message memory message = Message(block.chainid, toChainId, from, to, data); bytes32 messageHash = calculateMessageHash(message, address(this)); bytes32 salt = keccak256( diff --git a/packages/evm/contracts/Yaru.sol b/packages/evm/contracts/Yaru.sol index be802ac7..6e8a9c0c 100644 --- a/packages/evm/contracts/Yaru.sol +++ b/packages/evm/contracts/Yaru.sol @@ -23,6 +23,7 @@ contract Yaru is IYaru, MessageHashCalculator, MessageIdCalculator, ReentrancyGu error AlreadyInitialized(uint256 chainId); /// @param hashi_ Address of the Hashi contract. + /// @param headerVault_ Address of the HeaderVault contract. constructor(address hashi_, address headerVault_) { hashi = hashi_; headerVault = headerVault_; @@ -48,7 +49,7 @@ contract Yaru is IYaru, MessageHashCalculator, MessageIdCalculator, ReentrancyGu executed[messageId] = true; bytes32 reportedHash = IHashi(hashi).getHash(message.fromChainId, messageId, oracleAdapters); - if (reportedHash != messageHash) revert MessageFailure(messageId, abi.encode(reportedHash, messageHash)); + if (reportedHash != messageHash) revert MessageFailure(messageId, abi.encode(keccak256("HashMismatch"))); address to = message.from == address(0) && message.to == address(0) ? headerVault : message.to; try IJushinki(to).onMessage(message.data, messageId, message.fromChainId, message.from) returns ( @@ -56,7 +57,7 @@ contract Yaru is IYaru, MessageHashCalculator, MessageIdCalculator, ReentrancyGu ) { returnDatas[i] = returnData; } catch { - revert MessageFailure(messageId, abi.encode(0)); + revert MessageFailure(messageId, abi.encode(keccak256("CallFailed"))); } emit MessageIdExecuted(message.fromChainId, messageId); diff --git a/packages/evm/contracts/utils/HeaderReporter.sol b/packages/evm/contracts/headers/HeaderReporter.sol similarity index 85% rename from packages/evm/contracts/utils/HeaderReporter.sol rename to packages/evm/contracts/headers/HeaderReporter.sol index 82f2a47e..5a195c3d 100644 --- a/packages/evm/contracts/utils/HeaderReporter.sol +++ b/packages/evm/contracts/headers/HeaderReporter.sol @@ -16,13 +16,13 @@ contract HeaderReporter is IHeaderReporter { /// @param blockNumbers Uint256 array of block number. /// @param yaho Address of the Yaho contract to call. /// @param toChainIds The destination chain ids. - /// @param adapters Array of relay adapter addresses to which hashes should be relayed. - /// @param destinationAdapters Array of oracle adapter addresses to receive hashes. + /// @param messageRelays Array of relay addresses to which hashes should be relayed. + /// @param adapters Array of oracle adapter addresses to receive hashes. function reportHeaders( uint256[] calldata blockNumbers, uint256[] calldata toChainIds, + address[] calldata messageRelays, address[] calldata adapters, - address[] calldata destinationAdapters, address yaho ) external { bytes32[] memory blockHeaders = IHeaderStorage(headerStorage).storeBlockHeaders(blockNumbers); @@ -36,7 +36,7 @@ contract HeaderReporter is IHeaderReporter { } } - IYaho(yaho).dispatchMessagesToAdapters(toChainIds, tos, data, adapters, destinationAdapters); + IYaho(yaho).dispatchMessagesToAdapters(toChainIds, tos, data, messageRelays, adapters); for (uint256 i = 0; i < blockNumbers.length; ) { emit HeaderReported(toChainIds[i], blockNumbers[i], blockHeaders[i]); diff --git a/packages/evm/contracts/utils/HeaderStorage.sol b/packages/evm/contracts/headers/HeaderStorage.sol similarity index 100% rename from packages/evm/contracts/utils/HeaderStorage.sol rename to packages/evm/contracts/headers/HeaderStorage.sol diff --git a/packages/evm/contracts/ownable/HeaderVault.sol b/packages/evm/contracts/headers/HeaderVault.sol similarity index 69% rename from packages/evm/contracts/ownable/HeaderVault.sol rename to packages/evm/contracts/headers/HeaderVault.sol index 44d7e755..eb54d7ba 100644 --- a/packages/evm/contracts/ownable/HeaderVault.sol +++ b/packages/evm/contracts/headers/HeaderVault.sol @@ -6,7 +6,6 @@ import { IHeaderVault } from "../interfaces/IHeaderVault.sol"; contract HeaderVault is IHeaderVault, Ownable { mapping(uint256 => mapping(uint256 => bytes32)) private _blockHeaders; - mapping(uint256 => address) _headerReporters; address public yaru; error NotYaru(address currentYaru, address expectedYaru); @@ -16,28 +15,16 @@ contract HeaderVault is IHeaderVault, Ownable { modifier onlyYaruAndOnlyHeaderReporter(uint256 fromChainId, address from) { if (msg.sender != yaru) revert NotYaru(msg.sender, yaru); - address expectedHeaderReporter = _headerReporters[fromChainId]; - if (expectedHeaderReporter != from) revert InvalidHeaderReporter(fromChainId, from, expectedHeaderReporter); + if (from != address(0)) revert InvalidHeaderReporter(fromChainId, from, address(0)); _; } - function disableBlockHeaderReporter(uint256 fromChainId) external onlyOwner { - address headerReporter = _headerReporters[fromChainId]; - delete _headerReporters[fromChainId]; - emit HeaderReporterDisabled(fromChainId, headerReporter); - } - - function enableBlockHeaderReporter(uint256 fromChainId, address headerReporter) external onlyOwner { - _headerReporters[fromChainId] = headerReporter; - emit HeaderReporterEnabled(fromChainId, headerReporter); - } - function getBlockHeader(uint256 chainId, uint256 blockNumber) external view returns (bytes32) { return _blockHeaders[chainId][blockNumber]; } function initializeYaru(address yaru_) external onlyOwner { - if (yaru == address(0)) revert YaruAlreadyInitialized(yaru); + if (yaru != address(0)) revert YaruAlreadyInitialized(yaru); yaru = yaru_; } @@ -55,6 +42,9 @@ contract HeaderVault is IHeaderVault, Ownable { bytes32 blockHeader = blockHeaders[i]; _blockHeaders[fromChainId][blockNumber] = blockHeader; emit NewBlock(fromChainId, blockNumber, blockHeader); + unchecked { + ++i; + } } // TODO: what to return? diff --git a/packages/evm/contracts/interfaces/IHeaderVault.sol b/packages/evm/contracts/interfaces/IHeaderVault.sol index 27558f90..2b628b7c 100644 --- a/packages/evm/contracts/interfaces/IHeaderVault.sol +++ b/packages/evm/contracts/interfaces/IHeaderVault.sol @@ -8,10 +8,6 @@ interface IHeaderVault is IJushinki { event HeaderReporterEnabled(uint256 fromChainId, address headerReporter); event HeaderReporterDisabled(uint256 fromChainId, address headerReporter); - function disableBlockHeaderReporter(uint256 fromChainId) external; - - function enableBlockHeaderReporter(uint256 fromChainId, address headerReporter) external; - function initializeYaru(address yaru_) external; function getBlockHeader(uint256 chainId, uint256 blockNumber) external view returns (bytes32); diff --git a/packages/evm/contracts/test/PingPong.sol b/packages/evm/contracts/test/PingPong.sol index b2443b1c..e4ea5f36 100644 --- a/packages/evm/contracts/test/PingPong.sol +++ b/packages/evm/contracts/test/PingPong.sol @@ -4,17 +4,20 @@ pragma solidity ^0.8.17; import { IJushinki } from "../interfaces/IJushinki.sol"; contract PingPong is IJushinki { - event Pong(string pong); - uint256 public count; + event Pong(uint256 count); + + error PongError(); + function ping() public returns (string memory pong) { count++; pong = "pong"; - emit Pong(pong); + emit Pong(count); } - function onMessage(bytes calldata, bytes32, uint256, address) external returns (bytes memory) { + function onMessage(bytes calldata data, bytes32, uint256, address) external returns (bytes memory) { + if (uint256(bytes32(data)) == 0) revert PongError(); return abi.encode(ping()); } } diff --git a/packages/evm/test/05_Yaru.spec.ts b/packages/evm/test/05_Yaru.spec.ts index d5e07224..78f77aec 100644 --- a/packages/evm/test/05_Yaru.spec.ts +++ b/packages/evm/test/05_Yaru.spec.ts @@ -1,174 +1,198 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" import { expect } from "chai" -import { ethers, network } from "hardhat" - -const DOMAIN_ID = network.config.chainId -const ID_ZERO = 0 -const ID_ONE = 1 -const ID_TWO = 2 - -const setup = async () => { - const [wallet] = await ethers.getSigners() - const Hashi = await ethers.getContractFactory("Hashi") - const hashi = await Hashi.deploy() - const Yaru = await ethers.getContractFactory("Yaru") - const Yaho = await ethers.getContractFactory("Yaho") - const yaho = await Yaho.deploy() - const yaru = await Yaru.deploy(hashi.address, yaho.address, DOMAIN_ID) - const OracleAdapter = await ethers.getContractFactory("MockOracleAdapter") - const oracleAdapter = await OracleAdapter.deploy() - const PingPong = await ethers.getContractFactory("PingPong") - const pingPong = await PingPong.deploy() - - const message_1 = { - to: pingPong.address, - toChainId: DOMAIN_ID, - data: pingPong.interface.getSighash("ping"), - } - const message_2 = { - to: "0x0000000000000000000000000000000000000002", - toChainId: DOMAIN_ID, - data: 0x02, - } - const hash_one = await yaho.calculateHash(DOMAIN_ID, ID_ZERO, yaho.address, wallet.address, message_1) - const hash_two = await yaho.calculateHash(DOMAIN_ID, ID_ONE, yaho.address, wallet.address, message_2) - const failMessage = { - to: hashi.address, - toChainId: 1, - data: 0x1111111111, - } - const hash_fail = await yaho.calculateHash(DOMAIN_ID, ID_TWO, yaho.address, wallet.address, failMessage) - await oracleAdapter.setHashes(DOMAIN_ID, [ID_ZERO, ID_ONE, ID_TWO], [hash_one, hash_two, hash_fail]) - - return { - wallet, - hashi, - yaru, - oracleAdapter, - yaho, - hash_one, - hash_two, - failMessage, - hash_fail, - pingPong, - message_1, - message_2, - } -} +import { Contract } from "ethers" +import { ethers } from "hardhat" + +import { Chains } from "./constants" +import Message from "./utils/Message" + +let hashi: Contract, + yaho: Contract, + yaru: Contract, + oracleAdapter: Contract, + pingPong: Contract, + headerVault: Contract, + messageRelay: Contract, + headerReporter: Contract, + headerStorage: Contract, + fakeTo1: SignerWithAddress, + fakeTo2: SignerWithAddress describe("Yaru", function () { + this.beforeEach(async function () { + const Hashi = await ethers.getContractFactory("Hashi") + const Yaru = await ethers.getContractFactory("Yaru") + const Yaho = await ethers.getContractFactory("Yaho") + const OracleAdapter = await ethers.getContractFactory("MockOracleAdapter") + const HeaderReporter = await ethers.getContractFactory("HeaderReporter") + const HeaderStorage = await ethers.getContractFactory("HeaderStorage") + const HeaderVault = await ethers.getContractFactory("HeaderVault") + const PingPong = await ethers.getContractFactory("PingPong") + const MessageRelay = await ethers.getContractFactory("MockMessageRelay") + + const signers = await ethers.getSigners() + fakeTo1 = signers[1] + fakeTo2 = signers[2] + + headerStorage = await HeaderStorage.deploy() + headerReporter = await HeaderReporter.deploy(headerStorage.address) + hashi = await Hashi.deploy() + yaho = await Yaho.deploy(headerReporter.address) + headerVault = await HeaderVault.deploy() + yaru = await Yaru.deploy(hashi.address, headerVault.address) + oracleAdapter = await OracleAdapter.deploy() + pingPong = await PingPong.deploy() + messageRelay = await MessageRelay.deploy() + + await headerVault.initializeYaru(yaru.address) + await yaru.initializeForChainId(Chains.Hardhat, yaho.address) + }) + describe("constructor()", function () { it("Successfully deploys contract", async function () { - const { yaru } = await setup() expect(await yaru.deployed()) }) it("Sets hashi address", async function () { - const { yaru, hashi } = await setup() expect(await yaru.hashi()).to.equal(hashi.address) }) - it("Sets yaho address", async function () { - const { yaru, yaho } = await setup() - expect(await yaru.yaho()).to.equal(yaho.address) - }) - }) - - describe("calculateHash()", function () { - it("calculates correct hash of the given message", async function () { - const { yaru, wallet, hash_one, message_1, yaho } = await setup() - const calculatedHash = await yaru.calculateHash(DOMAIN_ID, 0, yaho.address, wallet.address, message_1) - expect(calculatedHash).to.equal(hash_one) + it("Sets header vault address", async function () { + expect(await yaru.headerVault()).to.equal(headerVault.address) }) }) describe("executeMessages()", function () { - it("reverts if messages, messageIds, or senders are unequal lengths", async function () { - const { yaru, wallet, oracleAdapter, message_1, message_2 } = await setup() - - await expect( - yaru.executeMessages( - [message_1, message_2], - [ID_ZERO], - [wallet.address, wallet.address], - [oracleAdapter.address], - ), - ) - .to.be.revertedWithCustomError(yaru, "UnequalArrayLengths") - .withArgs(yaru.address) - await expect( - yaru.executeMessages([message_1], [ID_ZERO, ID_ONE], [wallet.address, wallet.address], [oracleAdapter.address]), - ) - .to.be.revertedWithCustomError(yaru, "UnequalArrayLengths") - .withArgs(yaru.address) - await expect( - yaru.executeMessages([message_1, message_2], [ID_ZERO, ID_ONE], [wallet.address], [oracleAdapter.address]), + it("reverts if messages and messageIds are unequal lengths", async function () { + const tx = await yaho["dispatchMessagesToAdapters(uint256[],address[],bytes[],address[],address[])"]( + [Chains.Gnosis, Chains.Mainnet], + [fakeTo1.address, fakeTo2.address], + ["0x01", "0x02"], + [messageRelay.address], + [oracleAdapter.address], ) + const [message1, message2] = Message.fromReceipt(await tx.wait(1)) + await expect(yaru.executeMessages([message1, message2], [message1.id], [oracleAdapter.address])) .to.be.revertedWithCustomError(yaru, "UnequalArrayLengths") .withArgs(yaru.address) }) + it("reverts if reported hash does not match calculated hash", async function () { - const { yaru, wallet, oracleAdapter, message_1, message_2 } = await setup() - await expect( - yaru.executeMessages( - [message_1, message_2], - [ID_ZERO, ID_TWO], - [wallet.address, wallet.address], - [oracleAdapter.address], - ), - ).to.be.revertedWithCustomError(yaru, "HashMismatch") - await expect( - yaru.executeMessages([message_1], [ID_TWO], [wallet.address], [oracleAdapter.address]), - ).to.be.revertedWithCustomError(yaru, "HashMismatch") + const tx = await yaho["dispatchMessagesToAdapters(uint256[],address[],bytes[],address[],address[])"]( + [Chains.Gnosis, Chains.Mainnet], + [fakeTo1.address, fakeTo2.address], + ["0x01", "0x02"], + [messageRelay.address], + [oracleAdapter.address], + ) + const [message1, message2] = Message.fromReceipt(await tx.wait(1)) + await oracleAdapter.setHashes( + Chains.Hardhat, + [message1.id, message2.id], + [await yaho.hashes(message1.id), await yaho.hashes(message1.id)], + ) + message1.data = "0xff" + await expect(yaru.executeMessages([message1], [message1.id], [oracleAdapter.address])) + .to.be.revertedWithCustomError(yaru, "MessageFailure") + .withArgs(message1.id, ethers.utils.keccak256(Buffer.from("HashMismatch"))) + message2.data = "0xff" + await expect(yaru.executeMessages([message2], [message2.id], [oracleAdapter.address])) + .to.be.revertedWithCustomError(yaru, "MessageFailure") + .withArgs(message2.id, ethers.utils.keccak256(Buffer.from("HashMismatch"))) }) + it("reverts if call fails", async function () { - const { yaru, wallet, oracleAdapter, failMessage } = await setup() - await expect( - yaru.executeMessages([failMessage], [ID_TWO], [wallet.address], [oracleAdapter.address]), - ).to.be.revertedWithCustomError(yaru, "CallFailed") + const tx = await yaho["dispatchMessagesToAdapters(uint256[],address[],bytes[],address[],address[])"]( + [Chains.Gnosis], + [pingPong.address], + ["0x00"], + [messageRelay.address], + [oracleAdapter.address], + ) + const [failMessage] = Message.fromReceipt(await tx.wait(1)) + await oracleAdapter.setHashes(Chains.Hardhat, [failMessage.id], [await yaho.hashes(failMessage.id)]) + await expect(yaru.executeMessages([failMessage], [failMessage.id], [oracleAdapter.address])) + .to.be.revertedWithCustomError(yaru, "MessageFailure") + .withArgs(failMessage.id, ethers.utils.keccak256(Buffer.from("CallFailed"))) }) - it("executes a message", async function () { - const { yaru, wallet, oracleAdapter, message_1, message_2 } = await setup() - expect(await yaru.executeMessages([message_1], [ID_ZERO], [wallet.address], [oracleAdapter.address])) - }) - it("executes multiple messages", async function () { - const { yaru, wallet, oracleAdapter, message_1, message_2 } = await setup() - expect( - await yaru.executeMessages( - [message_1, message_2], - [ID_ZERO, ID_ONE], - [wallet.address, wallet.address], - [oracleAdapter.address], - ), + it("executes a message", async function () { + const tx = await yaho["dispatchMessagesToAdapters(uint256[],address[],bytes[],address[],address[])"]( + [Chains.Gnosis], + [pingPong.address], + ["0x01"], + [messageRelay.address], + [oracleAdapter.address], ) + const [message1] = Message.fromReceipt(await tx.wait(1)) + await oracleAdapter.setHashes(Chains.Hardhat, [message1.id], [await yaho.hashes(message1.id)]) + await expect(yaru.executeMessages([message1], [message1.id], [oracleAdapter.address])) + .to.emit(yaru, "MessageIdExecuted") + .withArgs(message1.fromChainId, message1.id) + .and.to.emit(pingPong, "Pong") + .withArgs(1) }) - it("reverts if transaction was already executed", async function () { - const { yaru, wallet, oracleAdapter, message_1 } = await setup() - await expect(yaru.executeMessages([message_1], [ID_ZERO], [wallet.address], [oracleAdapter.address])) - await expect( - yaru.executeMessages([message_1], [ID_ZERO], [wallet.address], [oracleAdapter.address]), - ).to.be.revertedWithCustomError(yaru, "AlreadyExecuted") + it("executes multiple messages", async function () { + const tx = await yaho["dispatchMessagesToAdapters(uint256[],address[],bytes[],address[],address[])"]( + [Chains.Gnosis, Chains.Gnosis], + [pingPong.address, pingPong.address], + ["0x01", "0x01"], + [messageRelay.address], + [oracleAdapter.address], + ) + const [message1, message2] = Message.fromReceipt(await tx.wait(1)) + await oracleAdapter.setHashes( + Chains.Hardhat, + [message1.id, message2.id], + [await yaho.hashes(message1.id), await yaho.hashes(message2.id)], + ) + await expect(yaru.executeMessages([message1, message2], [message1.id, message2.id], [oracleAdapter.address])) + .to.emit(yaru, "MessageIdExecuted") + .withArgs(message1.fromChainId, message1.id) + .and.to.emit(yaru, "MessageIdExecuted") + .withArgs(message2.fromChainId, message2.id) + .and.to.emit(pingPong, "Pong") + .withArgs(1) + .and.to.emit(pingPong, "Pong") + .withArgs(2) }) - it("emits MessageIDExecuted", async function () { - const { yaru, wallet, oracleAdapter, message_1 } = await setup() - await expect(yaru.executeMessages([message_1], [ID_ZERO], [wallet.address], [oracleAdapter.address])) - .to.emit(yaru, "MessageIdExecuted") - .withArgs(DOMAIN_ID, "0x0000000000000000000000000000000000000000000000000000000000000000") + it("reverts if message was already executed", async function () { + const tx = await yaho["dispatchMessagesToAdapters(uint256[],address[],bytes[],address[],address[])"]( + [Chains.Gnosis], + [pingPong.address], + ["0x01"], + [messageRelay.address], + [oracleAdapter.address], + ) + const [message1] = Message.fromReceipt(await tx.wait(1)) + await oracleAdapter.setHashes(Chains.Hardhat, [message1.id], [await yaho.hashes(message1.id)]) + await yaru.executeMessages([message1], [message1.id], [oracleAdapter.address]) + await expect(yaru.executeMessages([message1], [message1.id], [oracleAdapter.address])) + .to.be.revertedWithCustomError(yaru, "MessageIdAlreadyExecuted") + .withArgs(message1.id) }) - it("returns returnDatas[] from executedMessages", async function () { - const { yaru, wallet, oracleAdapter, message_1 } = await setup() - const response = await yaru.callStatic.executeMessages( - [message_1], - [ID_ZERO], - [wallet.address], + it("executes a message from HeaderReporter", async function () { + const blockNumber = await ethers.provider.getBlockNumber() + const block = await ethers.provider.getBlock(blockNumber - 1) + const tx = await headerReporter.reportHeaders( + [blockNumber - 1], + [Chains.Gnosis], + [messageRelay.address], [oracleAdapter.address], + yaho.address, ) - const output = await ethers.utils.defaultAbiCoder.decode(["string"], response[0]) + const [message1] = Message.fromReceipt(await tx.wait(1)) + await oracleAdapter.setHashes(Chains.Hardhat, [message1.id], [await yaho.hashes(message1.id)]) - expect(output[0]).to.equal("pong") + await expect(yaru.executeMessages([message1], [message1.id], [oracleAdapter.address])) + .to.emit(yaru, "MessageIdExecuted") + .withArgs(message1.fromChainId, message1.id) + .and.to.emit(headerVault, "NewBlock") + .withArgs(message1.fromChainId, blockNumber - 1, block.hash) + expect(await headerVault.getBlockHeader(Chains.Hardhat, blockNumber - 1)).to.be.eq(block.hash) }) }) }) diff --git a/packages/evm/test/constants.ts b/packages/evm/test/constants.ts new file mode 100644 index 00000000..cf668c27 --- /dev/null +++ b/packages/evm/test/constants.ts @@ -0,0 +1,7 @@ +export const Chains = { + Gnosis: 100, + Hardhat: 31337, + Mainnet: 1, +} + +export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" diff --git a/packages/evm/test/utils/Message.ts b/packages/evm/test/utils/Message.ts index a76137b0..b0b1520c 100644 --- a/packages/evm/test/utils/Message.ts +++ b/packages/evm/test/utils/Message.ts @@ -1,4 +1,4 @@ -import { ContractReceipt } from "ethers" +import { ContractReceipt, ethers } from "ethers" import { Chains } from "../constants" @@ -29,10 +29,16 @@ class Message { } static fromReceipt(_receipt: ContractReceipt) { - const events = _receipt.events.filter(({ event }) => event === "MessageDispatched") - const messages = events.map((_event) => _event.decode(_event?.data, _event?.topics)) + const abi = [ + "event MessageDispatched(bytes32 indexed messageId,address indexed from,uint256 indexed toChainId,address to,bytes data)", + ] + const iface = new ethers.utils.Interface(abi) + const logs = _receipt.logs.filter( + ({ topics }) => topics[0] === "0xe2f8f20ddbedfce5eb59a8b930077e7f4906a01300b9318db5f90d1c96c7b6d4", + ) + const messages = logs.map(({ topics, data }) => iface.parseLog({ topics, data })) return messages.map( - ({ data, from, messageId, to, toChainId }) => new Message({ data, from, messageId, to, toChainId }), + ({ args: { data, from, messageId, to, toChainId } }) => new Message({ data, from, messageId, to, toChainId }), ) }