diff --git a/packages/evm/contracts/adapters/Vea/VeaAdapter.sol b/packages/evm/contracts/adapters/Vea/VeaAdapter.sol new file mode 100644 index 00000000..e9b8ee2a --- /dev/null +++ b/packages/evm/contracts/adapters/Vea/VeaAdapter.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.20; + +import { BlockHashAdapter } from "../BlockHashAdapter.sol"; +import { IReceiverGateway } from "./interfaces/IReceiverGateway.sol"; + +contract VeaAdapter is IReceiverGateway, BlockHashAdapter { + string public constant PROVIDER = "vea"; + + address public immutable VEA_OUTBOX; + address public immutable REPORTER; + uint256 public immutable SOURCE_CHAIN_ID; + + error ArrayLengthMissmatch(); + error InvalidVeaOutbox(address veaOutbox, address expectedVeaOutboux); + error InvalidReporter(address reporter, address expectedReporter); + + constructor(address veaOutbox_, address reporter, uint256 sourceChainId) { + VEA_OUTBOX = veaOutbox_; + REPORTER = reporter; + SOURCE_CHAIN_ID = sourceChainId; + } + + modifier onlyFromAuthenticatedVeaSender(address sourceMsgSender) { + if (msg.sender != VEA_OUTBOX) revert InvalidVeaOutbox(msg.sender, VEA_OUTBOX); + if (sourceMsgSender != REPORTER) revert InvalidReporter(REPORTER, sourceMsgSender); + _; + } + + function senderGateway() external view override returns (address) { + return REPORTER; + } + + function veaOutbox() external view override returns (address) { + return VEA_OUTBOX; + } + + function storeHashes( + address sourceMsgSender, + uint256[] memory ids, + bytes32[] memory _hashes + ) external onlyFromAuthenticatedVeaSender(sourceMsgSender) { + if (ids.length != _hashes.length) revert ArrayLengthMissmatch(); + _storeHashes(uint256(SOURCE_CHAIN_ID), ids, _hashes); + } +} diff --git a/packages/evm/contracts/adapters/Vea/VeaReporter.sol b/packages/evm/contracts/adapters/Vea/VeaReporter.sol new file mode 100644 index 00000000..a7e71b59 --- /dev/null +++ b/packages/evm/contracts/adapters/Vea/VeaReporter.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.20; + +import { VeaAdapter } from "./VeaAdapter.sol"; +import { Reporter } from "../Reporter.sol"; +import { ISenderGateway } from "./interfaces/ISenderGateway.sol"; +import { IVeaInbox } from "./interfaces/IVeaInbox.sol"; + +contract VeaReporter is ISenderGateway, Reporter { + string public constant PROVIDER = "vea"; + + IVeaInbox public immutable VEA_INBOX; + address public immutable ADAPTER; + uint256 public immutable EXPECTED_TARGET_CHAIN_ID; + + error InvalidAdapter(address adapter, address expectedAdapter); + error InvalidTargetChainId(uint256 targetChainId, uint256 expectedTargetChainId); + + constructor( + address headerStorage, + address yaho, + IVeaInbox veaInbox_, + address adapter, + uint256 expectedTargetChainId + ) Reporter(headerStorage, yaho) { + VEA_INBOX = veaInbox_; + ADAPTER = adapter; + EXPECTED_TARGET_CHAIN_ID = expectedTargetChainId; + } + + function receiverGateway() external view override returns (address) { + return ADAPTER; + } + + function veaInbox() external view override returns (IVeaInbox) { + return VEA_INBOX; + } + + function _dispatch( + uint256 targetChainId, + address adapter, + uint256[] memory ids, + bytes32[] memory hashes + ) internal override returns (bytes32) { + if (targetChainId != EXPECTED_TARGET_CHAIN_ID) + revert InvalidTargetChainId(targetChainId, EXPECTED_TARGET_CHAIN_ID); + if (adapter != ADAPTER) revert InvalidAdapter(adapter, ADAPTER); + uint64 msgId = VEA_INBOX.sendMessage(ADAPTER, VeaAdapter.storeHashes.selector, abi.encode(ids, hashes)); + return bytes32(uint256(msgId)); + } +} diff --git a/packages/evm/contracts/adapters/Vea/interfaces/IReceiverGateway.sol b/packages/evm/contracts/adapters/Vea/interfaces/IReceiverGateway.sol new file mode 100644 index 00000000..a5df52f2 --- /dev/null +++ b/packages/evm/contracts/adapters/Vea/interfaces/IReceiverGateway.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.20; + +interface IReceiverGateway { + function veaOutbox() external view returns (address); + function senderGateway() external view returns (address); +} diff --git a/packages/evm/contracts/adapters/Vea/interfaces/ISenderGateway.sol b/packages/evm/contracts/adapters/Vea/interfaces/ISenderGateway.sol new file mode 100644 index 00000000..21562a7b --- /dev/null +++ b/packages/evm/contracts/adapters/Vea/interfaces/ISenderGateway.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.20; + +import { IVeaInbox } from "./IVeaInbox.sol"; + +interface ISenderGateway { + function veaInbox() external view returns (IVeaInbox); + function receiverGateway() external view returns (address); +} diff --git a/packages/evm/contracts/adapters/Vea/interfaces/IVeaInbox.sol b/packages/evm/contracts/adapters/Vea/interfaces/IVeaInbox.sol new file mode 100644 index 00000000..5fcf3853 --- /dev/null +++ b/packages/evm/contracts/adapters/Vea/interfaces/IVeaInbox.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.20; + +interface IVeaInbox { + /// @dev Sends an arbitrary message to receiving chain. + /// Note: Calls authenticated by receiving gateway checking the sender argument. + /// @param _to The cross-domain contract address which receives the calldata. + /// @param _fnSelection The function selector of the receiving contract. + /// @param _data The message calldata, abi.encode(...) + /// @return msgId The index of the message in the inbox, as a message Id, needed to relay the message. + function sendMessage(address _to, bytes4 _fnSelection, bytes memory _data) external returns (uint64 msgId); +} diff --git a/packages/evm/tasks/deploy/adapters/index.ts b/packages/evm/tasks/deploy/adapters/index.ts index 1dba46d3..e2ec8d67 100644 --- a/packages/evm/tasks/deploy/adapters/index.ts +++ b/packages/evm/tasks/deploy/adapters/index.ts @@ -15,5 +15,6 @@ import "./pnetwork" import "./router" import "./sygma" import "./telepathy" +import "./vea" import "./wormhole" import "./zetachain" diff --git a/packages/evm/tasks/deploy/adapters/vea.ts b/packages/evm/tasks/deploy/adapters/vea.ts new file mode 100644 index 00000000..ffa9a7f0 --- /dev/null +++ b/packages/evm/tasks/deploy/adapters/vea.ts @@ -0,0 +1,64 @@ +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" +import { task, types } from "hardhat/config" +import type { TaskArguments } from "hardhat/types" + +import { verify } from ".." +import type { VeaAdapter } from "../../../types/contracts/adapters/Vea/VeaAdapter" +import type { VeaReporter } from "../../../types/contracts/adapters/Vea/VeaReporter" +import type { VeaAdapter__factory } from "../../../types/factories/contracts/adapters/Vea/VeaAdapter__factory" +import { VeaReporter__factory } from "../../../types/factories/contracts/adapters/Vea/VeaReporter__factory" + +// Deploy on destination chain +task("deploy:Vea:Adapter") + .addParam("veaOutbox", "address of the Vea Outbox contract", undefined, types.string) + .addParam("reporter", "address of the hash reporter", undefined, types.string) + .addParam("sourceChainId", "sourceChainId of the source chain", undefined, types.int) + .addFlag("verify", "whether to verify the contract on Etherscan") + .setAction(async function (taskArguments: TaskArguments, hre) { + console.log("Deploying VeaAdapter...") + const signers: SignerWithAddress[] = await hre.ethers.getSigners() + const veaAdapterFactory: VeaAdapter__factory = ( + await hre.ethers.getContractFactory("VeaAdapter") + ) + + const constructorArguments = [ + taskArguments.veaOutbox, + taskArguments.reporter, + "0x" + taskArguments.sourceChainId.toString(16).padStart(64, "0"), + ] as const + const veaAdapter: VeaAdapter = ( + await veaAdapterFactory.connect(signers[0]).deploy(...constructorArguments) + ) + await veaAdapter.deployed() + console.log("VeaAdapter deployed to:", veaAdapter.address) + if (taskArguments.verify) await verify(hre, veaAdapter, constructorArguments) + }) + +// Deploy source chain +task("deploy:Vea:Reporter") + .addParam("headerStorage", "address of the header storage contract", undefined, types.string) + .addParam("yaho", "address of the Yaho contract", undefined, types.string) + .addParam("veaInbox", "address of the Vea inbox contract", undefined, types.string) + .addParam("adapter", "address of adapter contract", undefined, types.string) + .addParam("targetChainId", "target chain id", undefined, types.int) + .addFlag("verify", "whether to verify the contract on Etherscan") + .setAction(async function (taskArguments: TaskArguments, hre) { + console.log("Deploying VeaReporter...") + const signers: SignerWithAddress[] = await hre.ethers.getSigners() + const veaReporterFactory: VeaReporter__factory = ( + await hre.ethers.getContractFactory("VeaReporter") + ) + const constructorArguments = [ + taskArguments.headerStorage, + taskArguments.yaho, + taskArguments.veaInbox, + taskArguments.adapter, + taskArguments.targetChainId, + ] as const + const veaReporter: VeaReporter = ( + await veaReporterFactory.connect(signers[0]).deploy(...constructorArguments) + ) + await veaReporter.deployed() + console.log("VeaReporter deployed to:", veaReporter.address) + if (taskArguments.verify) await verify(hre, veaReporter, constructorArguments) + })