// 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 = address(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);
    }
}
[{"inputs":[{"internalType":"address","name":"attestor","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ATTESTOR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"core","type":"address"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"deploy","outputs":[],"stateMutability":"nonpayable","type":"function"}]