// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Messenger} from "../src/messenger/Messenger.sol"; import {Script} from "forge-std/Script.sol"; import {IWormhole} from "../src/interfaces/IWormhole.sol"; // MessengerDeployer deploys & configures the Messenger contract // contracts at deterministic addresses. // The MessengerDeployer logic ensures that // the Messenger contract at the deterministic addresses // can only be configured via a signed attestation from the ATTESTOR key. // Anyone can pay gas to `deploy`, as long as they provide a valid attestation. // The attestation ensures that the contracts are configured by a trusted ATTESTOR // with the valid Wormhole core address for each chain. // - Calling `IWormhole(core).chainId()` checks that there is a valid contract at `core` address // which implements a `chainId` function, which should prevent accidental mis-configuration. // - Binding the attestation to `block.chainid` ensures attestations from other chains cannot be abused. // As such, potential for mis-configuration of Messenger should be limited to // scenarios where the ATTESTOR key produces intentionally incorrect attestations. // TECHNICAL BREAKDOWN // Foundry uses this deployer for create2 invocations: // https://github.com/Arachnid/deterministic-deployment-proxy // in create2, // new_address = hash(0xFF, sender, salt, bytecode) // thus, MessengerDeployer address is constant: // sender: Arachnid DeterministicDeployer address - constant // salt: keccak256("wormhole-messenger") - constant // bytecode: constant // Messenger address is constant: // sender: MessengerDeployer address - constant // salt: keccak256(packed("wormhole-messenger")) - constant // bytecode: constant // because Messenger sender = MessengerDeployer contract, // the logic in this contract `verifyAttestation` // ensures that deployment at the deterministic address // MUST supply a valid attestation // Mainnet attestor address constant PROD_ATTESTOR = 0x115715AB213a744680C43c131AC557fC88928e32; // Testnet attestor address constant TEST_ATTESTOR = 0xc5F78C55EBa96aE4B61126dBC219080611C6bD7c; // Salt used for create2 deploy of MessengerDeployer & Messenger bytes32 constant salt = keccak256("wormhole-messenger"); /// @notice Format an attestation to be signed by ATTESTOR key. /// @dev ATTESTOR must produce an ECDSA signature over the following: /// keccak256(abi.encodePacked("wormhole-core-address", block.chainid, whChainId, core)) /// @return attestation the raw bytes attestation (to inspect) /// @return hash the hashed attestation (to sign with ATTESTOR key) function formatAttestation(address core) view returns (bytes memory attestation, bytes32 hash) { uint16 whChainId = IWormhole(core).chainId(); attestation = abi.encodePacked("wormhole-core-address", block.chainid, whChainId, core); hash = keccak256(attestation); } contract MessengerDeployer { // ATTESTOR key address public immutable ATTESTOR; constructor(address attestor) { ATTESTOR = attestor; } /// @notice Verify the provided attestation to the `core` address, /// Then deploy Messenger contract configured with `core` /// @dev `v`, `r`, `s` are an ECDSA signature over the hash /// produced by `formatAttestation` /// @param core The 20-byte EVM core address. /// @param v The v component of the ECDSA signature. /// @param r The r component of the ECDSA signature. /// @param s The s component of the ECDSA signature. /// @custom:reverts As `verifyAttestation`. function deploy(address core, uint8 v, bytes32 r, bytes32 s) external { verifyAttestation(core, v, r, s); // deploy & initialize Messenger Messenger messenger = new Messenger{salt: salt}(); messenger.initialize(core); } /// @notice Verifies a simple attestation from the hardcoded ATTESTOR key. /// @custom:params as `deploy` /// @custom:reverts If the signature is invalid. /// @custom:reverts If the attestation is not signed by the attestor. function verifyAttestation(address core, uint8 v, bytes32 r, bytes32 s) internal view { bytes memory attestation; bytes32 hash; (attestation, hash) = formatAttestation(core); address signer = ecrecover(hash, v, r, s); require(signer != address(0), "Signature not valid"); require(signer == ATTESTOR, "Must be signed by attestor"); } } contract DeployMessenger is Script { // DryRun - Deploy the system // // dry run: forge script DeployMessenger --sig "dryRun(bool,address,uint8,bytes32,bytes32)" --rpc-url $RPC $IS_TESTNET $CORE_ADDRESS $V $R $S function dryRun(bool testnet, address core, uint8 v, bytes32 r, bytes32 s) public { address attestor = testnet ? TEST_ATTESTOR : PROD_ATTESTOR; // tx 1: deploy MessengerDeployer MessengerDeployer deployer = new MessengerDeployer{salt: salt}(attestor); // tx 2: deploy & configure Messenger deployer.deploy(core, v, r, s); } // DryRun - Alternate invocation with custom attestor supplied // // dry run: forge script DeployMessenger --sig "run(address,address,uint8,bytes32,bytes32)" --rpc-url $RPC $ATTESTOR $CORE_ADDRESS $V $R $S function dryRun(address attestor, address core, uint8 v, bytes32 r, bytes32 s) public { // tx 1: deploy MessengerDeployer MessengerDeployer deployer = new MessengerDeployer{salt: salt}(attestor); // tx 2: deploy & configure Messenger deployer.deploy(core, v, r, s); } // Deploy the system // // deploy: forge script DeployMessenger --sig "run(bool,address,uint8,bytes32,bytes32)" --rpc-url $RPC --etherscan-api-key $ETHERSCAN_API_KEY --private-key $RAW_PRIVATE_KEY --broadcast --verify $IS_TESTNET $CORE_ADDRESS $V $R $S function run(bool testnet, address core, uint8 v, bytes32 r, bytes32 s) public { vm.startBroadcast(); dryRun(testnet, core, v, r, s); vm.stopBroadcast(); } // Alternate invocation with custom attestor supplied // // deploy: forge script DeployMessenger --sig "run(address,address,uint8,bytes32,bytes32)" --rpc-url $RPC --etherscan-api-key $ETHERSCAN_API_KEY --private-key $RAW_PRIVATE_KEY --broadcast --verify $ATTESTOR $CORE_ADDRESS $V $R $S function run(address attestor, address core, uint8 v, bytes32 r, bytes32 s) public { vm.startBroadcast(); dryRun(attestor, core, v, r, s); vm.stopBroadcast(); } // forge script DeployMessenger --sig "attestation(address)" --rpc-url $RPC $CORE_ADDRESS function attestation(address core) public view returns (bytes memory _attestation, bytes32 hash) { return formatAttestation(core); } }