synapsecns/sanguine

View on GitHub
packages/contracts-core/script/DeployMessaging003Base.s.sol

Summary

Maintainability
Test Coverage
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// ═════════════════════════════ CONTRACT IMPORTS ══════════════════════════════
import {AgentSecured, MessagingBase} from "../contracts/base/AgentSecured.sol";
import {BondingManager} from "../contracts/manager/BondingManager.sol";
import {Destination} from "../contracts/Destination.sol";
import {GasOracle} from "../contracts/GasOracle.sol";
import {Origin} from "../contracts/Origin.sol";
import {Summit} from "../contracts/Summit.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {DeployerUtils} from "./utils/DeployerUtils.sol";
// ═════════════════════════════ EXTERNAL IMPORTS ══════════════════════════════
import {console, stdJson} from "forge-std/Script.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

// solhint-disable no-console
// solhint-disable ordering
abstract contract DeployMessaging003BaseScript is DeployerUtils {
    using stdJson for string;
    using Strings for uint256;

    string public constant DESTINATION = "Destination";
    string public constant GAS_ORACLE = "GasOracle";
    string public constant ORIGIN = "Origin";
    string public constant SUMMIT = "Summit";

    /// @notice BondingManager or LightManager address
    address public agentManager;
    /// @notice Inbox or LightInbox address
    address public statementInbox;

    // Common deployments for all chains
    address public destination;
    address public gasOracle;
    address public origin;

    // Only present on SynChain
    address public summit;

    uint32 public localDomain;
    uint256 public synapseDomain;
    string public globalConfig;

    constructor() {
        setupPK("MESSAGING_DEPLOYER_PRIVATE_KEY");
        localDomain = uint32(block.chainid);
        deploymentSalt = keccak256("Messaging003");
        setupDevnetIfEnabled();
    }

    /// @dev Function to exclude script from coverage report
    function testScript() external {}

    /// @notice Main function with the deploy logic.
    /// @dev To deploy contracts on $chainName
    /// Make sure "./script/configs/Messaging003.dc.json" exists, then call
    /// forge script script/DeployMessaging003<?>.s.sol -f chainName --ffi --broadcast
    /// Note: Verification will not natively work, need to verify externally
    function run() external {
        _deploy(true);
    }

    /// @notice Function to simulate the deployment procedure.
    /// @dev To simulate deployment on $chainName
    /// forge script script/DeployMessaging003<?>.s.sol -f chainName --sig "runDry()"
    function runDry() external {
        _deploy(false);
    }

    function isSynapseChain() public returns (bool) {
        return globalConfig.readUint(".chainidSummit") == localDomain;
    }

    function agentManagerName() public returns (string memory) {
        return isSynapseChain() ? "BondingManager" : "LightManager";
    }

    function statementInboxName() public returns (string memory) {
        return isSynapseChain() ? "Inbox" : "LightInbox";
    }

    /// @dev Deploys Messaging contracts, transfer ownership and sanity check the new deployments.
    /// Will save the deployments, if script is being broadcasted.
    function _deploy(bool _isBroadcasted) internal {
        globalConfig = loadGlobalDeployConfig("Messaging003");
        synapseDomain = globalConfig.readUint(".chainidSummit");
        startBroadcast(_isBroadcasted);
        // assert this is the first thing deployed
        getFactory();
        // Predict deployments
        agentManager = predictFactoryDeployment(agentManagerName());
        statementInbox = predictFactoryDeployment(statementInboxName());
        destination = predictFactoryDeployment(DESTINATION);
        gasOracle = predictFactoryDeployment(GAS_ORACLE);
        origin = predictFactoryDeployment(ORIGIN);
        if (isSynapseChain()) {
            summit = predictFactoryDeployment(SUMMIT);
        }
        // Deploy and initialize contracts
        _deployAndCheckAddress(agentManagerName(), _deployAgentManager, _initializeAgentManager, agentManager);
        _deployAndCheckAddress(statementInboxName(), _deployStatementInbox, _initializeStatementInbox, statementInbox);
        _deployAndCheckAddress(DESTINATION, _deployDestination, _initializeDestination, destination);
        _deployAndCheckAddress(GAS_ORACLE, _deployGasOracle, _initializeGasOracle, gasOracle);
        _deployAndCheckAddress(ORIGIN, _deployOrigin, _initializeOrigin, origin);
        if (isSynapseChain()) {
            _deployAndCheckAddress(SUMMIT, _deploySummit, _initializeSummit, summit);
        }
        // Add agents to BondingManager
        _addAgents();
        // Transfer ownership of contracts
        _transferOwnership();
        stopBroadcast();
        // Do some sanity checks
        _checkDeployments();
        _checkAgents();
    }

    function _deployAndCheckAddress(
        string memory contractName,
        function() internal returns (address, bytes memory) deployFunc,
        function(address) internal initFunc,
        address predictedDeployment
    ) internal {
        (address deployment,) = deployContract(contractName, deployFunc, initFunc);
        require(deployment == predictedDeployment, string.concat(contractName, ": wrong address"));
    }

    /// @dev Deploys BondingManager or LightManager
    /// Note: requires Origin, Destination, StatementInbox (and Summit for BondingManager) addresses to be set
    function _deployAgentManager() internal virtual returns (address deployment, bytes memory constructorArgs);

    /// @dev Initializes BondingManager or LightManager
    function _initializeAgentManager(address deployment) internal virtual;

    /// @dev Deploys Inbox or LightInbox
    /// Note: requires AgentManager, Origin, Destination (and Summit for Inbox) addresses to be set
    function _deployStatementInbox() internal virtual returns (address deployment, bytes memory constructorArgs);

    /// @dev Initializes Inbox or LightInbox
    function _initializeStatementInbox(address deployment) internal virtual;

    /// @dev Adds agents to BondingManager (no-op for LightManager)
    function _addAgents() internal virtual;

    // ═══════════════════════════════════════ DEPLOY AND INITIALIZE ROUTINE ═══════════════════════════════════════════

    /// @dev Deploys Destination.
    /// Note: requires AgentManager and StatementInbox addresses to have been set.
    /// Note: requires AgentManager to have been deployed.
    function _deployDestination() internal returns (address deployment, bytes memory constructorArgs) {
        // new Destination(domain, agentManager, statementInbox)
        require(agentManager != address(0), "Agent Manager not set");
        require(statementInbox != address(0), "Statement Inbox not set");
        require(agentManager.code.length > 0, "Agent Manager not deployed");
        constructorArgs = abi.encode(synapseDomain, agentManager, statementInbox);
        deployment = factoryDeploy(DESTINATION, type(Destination).creationCode, constructorArgs);
    }

    /// @dev Initializes Destination.
    function _initializeDestination(address deployment) internal {
        if (Destination(deployment).owner() == address(0)) {
            console.log("   %s: initializing", DESTINATION);
            Destination(deployment).initialize(_getInitialAgentRoot());
        } else {
            console.log("   %s: already initialized", DESTINATION);
        }
    }

    /// @dev Deploys GasOracle.
    /// Note: requires Destination address to have been set.
    function _deployGasOracle() internal returns (address deployment, bytes memory constructorArgs) {
        // new GasOracle(domain, destination)
        require(destination != address(0), "Destination not set");
        constructorArgs = abi.encode(synapseDomain, destination);
        deployment = factoryDeploy(GAS_ORACLE, type(GasOracle).creationCode, constructorArgs);
    }

    /// @dev Initializes GasOracle.
    function _initializeGasOracle(address deployment) internal {
        if (GasOracle(deployment).owner() == address(0)) {
            console.log("   %s: initializing", GAS_ORACLE);
            GasOracle(deployment).initialize();
        } else {
            console.log("   %s: already initialized", GAS_ORACLE);
        }
    }

    /// @dev Deploys Origin.
    /// Note: requires AgentManager, StatementInbox and GasOracle addresses to have been set.
    function _deployOrigin() internal returns (address deployment, bytes memory constructorArgs) {
        // new Origin(domain, agentManager, statementInbox, gasOracle)
        require(agentManager != address(0), "Agent Manager not set");
        require(statementInbox != address(0), "Statement Inbox not set");
        require(gasOracle != address(0), "Gas Oracle not set");
        constructorArgs = abi.encode(synapseDomain, agentManager, statementInbox, gasOracle);
        deployment = factoryDeploy(ORIGIN, type(Origin).creationCode, constructorArgs);
    }

    /// @dev Initializes Origin.
    function _initializeOrigin(address deployment) internal {
        if (Origin(deployment).owner() == address(0)) {
            console.log("   %s: initializing", ORIGIN);
            Origin(deployment).initialize();
        } else {
            console.log("   %s: already initialized", ORIGIN);
        }
    }

    /// @dev Deploys Summit.
    /// Note: requires AgentManager and StatementInbox addresses to have been set.
    function _deploySummit() internal returns (address deployment, bytes memory constructorArgs) {
        // new Summit(domain, agentManager, statementInbox)
        require(agentManager != address(0), "Agent Manager not set");
        require(statementInbox != address(0), "Statement Inbox not set");
        constructorArgs = abi.encode(synapseDomain, agentManager, statementInbox);
        deployment = factoryDeploy(SUMMIT, type(Summit).creationCode, constructorArgs);
    }

    /// @dev Initializes Summit.
    function _initializeSummit(address deployment) internal {
        if (Summit(deployment).owner() == address(0)) {
            console.log("   %s: initializing", SUMMIT);
            Summit(deployment).initialize();
        } else {
            console.log("   %s: already initialized", SUMMIT);
        }
    }

    // ═════════════════════════════════════════════════ OWNERSHIP ═════════════════════════════════════════════════════

    /// @dev Transfers ownership of contracts to the owner defined in the global config.
    function _transferOwnership() internal {
        address owner = globalConfig.readAddress(".owner");
        console.log("Transferring ownership to %s", owner);
        _transferOwnership(agentManagerName(), agentManager, owner);
        _transferOwnership(statementInboxName(), statementInbox, owner);
        _transferOwnership(DESTINATION, destination, owner);
        _transferOwnership(GAS_ORACLE, gasOracle, owner);
        _transferOwnership(ORIGIN, origin, owner);
        _transferOwnership(SUMMIT, summit, owner);
    }

    /// @dev Transfers ownership of a given contract to a new owner.
    function _transferOwnership(string memory name, address deployment, address newOwner) internal {
        if (deployment == address(0)) {
            console.log("   %s: skipped (not deployed)", name);
            return;
        }
        if (Ownable(deployment).owner() == newOwner) {
            console.log("   %s: skipped (already owned)", name);
            return;
        }
        console.log("   %s: transferring ownership to %s", name, newOwner);
        Ownable(deployment).transferOwnership(newOwner);
    }

    // ══════════════════════════════════════════ POST DEPLOYMENT CHECKS ═══════════════════════════════════════════════

    /// @dev Checks that all agents have been added correctly to BondingManager
    /// or that they could be added to LightManager.
    function _checkAgents() internal virtual;

    /// @dev Checks that all contracts have been deployed and initialized correctly
    function _checkDeployments() internal {
        console.log("Checking deployments");
        // Check AgentSecured contracts
        console.log("   Checking AgentSecured contracts");
        _checkAgentSecured(DESTINATION, destination);
        _checkAgentSecured(ORIGIN, origin);
        if (isSynapseChain()) _checkAgentSecured(SUMMIT, summit);
        // Check MessagingBase contracts
        console.log("   Checking MessagingBase contracts");
        _checkMessagingBase(agentManagerName(), agentManager);
        _checkMessagingBase(statementInboxName(), statementInbox);
        _checkMessagingBase(GAS_ORACLE, gasOracle);
        // Check MessagingBase having .agentManager() getter
        console.log("   Checking contracts having .agentManager() getter");
        _checkGetterAgentManager(statementInboxName(), statementInbox);
        // Check MessagingBase having .inbox() getter
        console.log("   Checking contracts having .inbox() getter");
        _checkGetterInbox(agentManagerName(), agentManager);
        // Check contracts having .destination() getter
        console.log("   Checking contracts having .destination() getter");
        _checkGetterDestination(agentManagerName(), agentManager);
        _checkGetterDestination(statementInboxName(), statementInbox);
        _checkGetterDestination(GAS_ORACLE, gasOracle);
        // Check contracts having .gasOracle() getter
        console.log("   Checking contracts having .gasOracle() getter");
        _checkGetterGasOracle(ORIGIN, origin);
        // Check contracts having .origin() getter
        console.log("   Checking contracts having .origin() getter");
        _checkGetterOrigin(agentManagerName(), agentManager);
        _checkGetterOrigin(statementInboxName(), statementInbox);
        // Check contracts having .summit() getter
        if (isSynapseChain()) {
            console.log("   Checking contracts having .summit() getter");
            _checkGetterSummit(agentManagerName(), agentManager);
            _checkGetterSummit(statementInboxName(), statementInbox);
        }
    }

    /// @dev Checks AgentSecured getters:
    /// - .localDomain()
    /// - .owner()
    /// - .agentManager()
    /// - .inbox()
    function _checkAgentSecured(string memory name, address deployment) internal {
        _checkGetterAgentManager(name, deployment);
        _checkGetterInbox(name, deployment);
        _checkMessagingBase(name, deployment);
    }

    /// @dev Checks MessagingBase getters:
    /// - .localDomain()
    /// - .owner()
    function _checkMessagingBase(string memory name, address deployment) internal {
        _logCheckedGetter(name, "localDomain", deployment, localDomain);
        require(MessagingBase(deployment).localDomain() == localDomain, string.concat("Wrong localDomain: ", name));
        address owner = globalConfig.readAddress(".owner");
        _logCheckedGetter(name, "owner", deployment, owner);
        require(Ownable(deployment).owner() == owner, string.concat("Wrong owner: ", name));
    }

    /// @dev Checks .agentManager() getter
    function _checkGetterAgentManager(string memory name, address deployment) internal view {
        _logCheckedGetter(name, "agentManager", deployment, agentManager);
        require(AgentSecured(deployment).agentManager() == agentManager, string.concat("Wrong agentManager: ", name));
    }

    /// @dev Checks .inbox() getter
    function _checkGetterInbox(string memory name, address deployment) internal view {
        _logCheckedGetter(name, "inbox", deployment, statementInbox);
        require(AgentSecured(deployment).inbox() == statementInbox, string.concat("Wrong statementInbox: ", name));
    }

    /// @dev Checks .destination() getter
    function _checkGetterDestination(string memory name, address deployment) internal view {
        _logCheckedGetter(name, "destination", deployment, destination);
        require(GasOracle(deployment).destination() == destination, string.concat("Wrong destination: ", name));
    }

    /// @dev Checks .gasOracle() getter
    function _checkGetterGasOracle(string memory name, address deployment) internal view {
        _logCheckedGetter(name, "gasOracle", deployment, gasOracle);
        require(Origin(deployment).gasOracle() == gasOracle, string.concat("Wrong gasOracle: ", name));
    }

    /// @dev Checks .origin() getter
    function _checkGetterOrigin(string memory name, address deployment) internal view {
        _logCheckedGetter(name, "origin", deployment, origin);
        require(BondingManager(deployment).origin() == origin, string.concat("Wrong origin: ", name));
    }

    /// @dev Checks .summit() getter
    function _checkGetterSummit(string memory name, address deployment) internal view {
        _logCheckedGetter(name, "summit", deployment, summit);
        require(BondingManager(deployment).summit() == summit, string.concat("Wrong summit: ", name));
    }

    /// @dev Shortcut for logging
    function _logCheckedGetter(string memory name, string memory getter, address deployment, address expectedValue)
        internal
        view
    {
        console.log("       [%s][%s]: checking .%s()", name, deployment, getter);
        console.log("           expecting value: %s", expectedValue);
    }

    /// @dev Shortcut for logging
    function _logCheckedGetter(string memory name, string memory getter, address deployment, uint32 expectedValue)
        internal
        view
    {
        console.log("       [%s][%s]: checking .%s()", name, deployment, getter);
        console.log("           expecting value: %s", expectedValue);
    }

    // ══════════════════════════════════════════════════ HELPERS ══════════════════════════════════════════════════════

    /// @dev Reads initial agent root
    function _getInitialAgentRoot() internal returns (bytes32) {
        // No saved agent root exists when deploying to SynChain
        if (isSynapseChain()) return 0;
        string memory agentRootConfig = loadGlobalDeployConfig("Messaging003AgentRoot");
        return agentRootConfig.readBytes32(".initialAgentRoot");
    }
}