synapsecns/sanguine

View on GitHub
packages/solidity-devops/src/deploy/Deployer.sol

Summary

Maintainability
Test Coverage
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.12;
pragma experimental ABIEncoderV2;

import {Logger} from "../base/Logger.sol";
import {StringUtils} from "../libs/StringUtils.sol";
import {ChainAwareReader} from "../reader/ChainAwareReader.sol";

abstract contract Deployer is ChainAwareReader, Logger {
    string internal constant CREATE2_FACTORY_NAME = "Create2Factory";
    /// @dev Deployed on lots of chains, could be deployed on more chains using EOA
    address private constant DEFAULT_CREATE2_FACTORY = 0xa6190aBC82427800935E0598892f7488a7F73A04;

    address private envCreate2Factory;

    /// @dev Salt to be used for the next create2 deployment, will be erased after the deployment
    bytes32 private nextDeploymentSalt;
    /// @dev Salt to be used for the next create2 deployment, will be kept after the deployment
    bytes32 private permanentDeploymentSalt;

    function loadEnvCreate2Factory() internal {
        // Try loading the factory address from env. Use the default factory as a fallback
        envCreate2Factory = vm.envOr("CREATE2_FACTORY", DEFAULT_CREATE2_FACTORY);
    }

    // ═══════════════════════════════════════════ CUSTOMIZABLE GETTERS ════════════════════════════════════════════════

    /// @notice Returns the address of the create2 factory to be used for the create2 deployments.
    /// @dev There are a few ways to set the factory address. Priority in descending order:
    /// 1. Deployment artifact for the factory saved in deployments/chainName
    /// 2. CREATE2_FACTORY env variable
    /// 3. DEFAULT_CREATE2_FACTORY
    function getCreate2Factory() internal view virtual returns (address) {
        // First, check if we have a deployment artifact for the factory saved
        address factory = getDeploymentAddress({contractName: CREATE2_FACTORY_NAME, revertIfNotFound: false});
        // If it is not saved we fallback to the env variable (which falls back to the default factory)
        return factory != address(0) ? factory : envCreate2Factory;
    }

    function getDeploymentSalt() internal view virtual returns (bytes32) {
        // Use single-use salt by default, fallback to permanent salt
        return nextDeploymentSalt != 0 ? nextDeploymentSalt : permanentDeploymentSalt;
    }

    function getInitCode(
        string memory contractName,
        bytes memory constructorArgs
    )
        internal
        view
        virtual
        returns (bytes memory)
    {
        bytes memory bytecode = getContractBytecode({contractName: contractName, revertIfNotFound: true});
        // Init code is the contract bytecode with constructor args appended
        return abi.encodePacked(bytecode, constructorArgs);
    }

    // ═════════════════════════════════════════ DEPLOY CALLBACKS: CREATE ══════════════════════════════════════════════

    /// @notice Creates a contract with the given name and constructor args.
    /// Bytecode generated by forge is used for the deployment.
    /// Note: could be used as callback for `deployAndSave` and `deployAndSaveAs`
    /// for contracts with a different compiler version.
    function cbDeploy(string memory contractName, bytes memory constructorArgs) internal returns (address deployedAt) {
        bytes memory initCode = getInitCode(contractName, constructorArgs);
        return deployCode(initCode);
    }

    /// @notice Creates a contract with the given init code.
    /// @dev Does not save the deployment JSON, and therefore the direct usage should be avoided.
    /// Use as a part of a deploy callback for `deployAndSave` and `deployAndSaveAs` instead.
    function deployCode(bytes memory initCode) internal returns (address deployedAt) {
        // Use assembly to deploy the contract
        // solhint-disable-next-line no-inline-assembly
        assembly {
            // Add 0x20 to skip the length field of initCode
            deployedAt := create(0, add(initCode, 0x20), mload(initCode))
        }
        require(deployedAt != address(0), "Deployer: failed to deploy using CREATE");
    }

    // ═════════════════════════════════════════ DEPLOY CALLBACKS: CREATE2 ═════════════════════════════════════════════

    /// @notice Creates a contract with the given name and constructor args.
    /// Bytecode generated by forge is used for the deployment.
    /// Deployment is done in a deterministic way, using create2 and nextDeploymentSalt()
    /// Note: could be used as callback for `deployAndSave` and `deployAndSaveAs`
    function cbDeployCreate2(
        string memory contractName,
        bytes memory constructorArgs
    )
        internal
        returns (address deployedAt)
    {
        bytes memory initCode = getInitCode(contractName, constructorArgs);
        bytes32 salt = getDeploymentSalt();
        // Print init code hash for potential vanity address mining
        printLogWithIndent(StringUtils.concat("Init code hash: ", vm.toString(keccak256(initCode))));
        printLogWithIndent(StringUtils.concat("Using salt: ", vm.toString(salt)));
        deployedAt = factoryDeployCreate2(getCreate2Factory(), initCode, getDeploymentSalt());
        // Erase single-use salt
        delete nextDeploymentSalt;
    }

    /// @notice Creates a contract with the given init code.
    /// Deployment is done in a deterministic way, using create2 and the given salt.
    /// @dev Does not save the deployment JSON, and therefore the direct usage should be avoided.
    /// Use deployCreate2 as the deploy callback for `deployAndSave` and `deployAndSaveAs` instead.
    function factoryDeployCreate2(
        address factory,
        bytes memory initCode,
        bytes32 salt
    )
        internal
        returns (address deployedAt)
    {
        // Check that the factory exists
        assertContractCodeExists({contractAddr: factory, errMsg: "Deployer: factory contract not found"});
        address predicted = predictAddress(factory, initCode, salt);
        // Check that the contract is not already deployed
        assertContractCodeEmpty({contractAddr: predicted, errMsg: "Deployer: contract already deployed"});
        // Construct payload for factory deployment: salt + init code
        bytes memory payload = abi.encodePacked(salt, initCode);
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returnData) = factory.call(payload);
        require(success, StringUtils.concat("Deployer: failed to deploy using factory at ", vm.toString(factory)));
        // returnData is 20 bytes of address rather than ABI-encoded address
        require(
            returnData.length == 20,
            StringUtils.concat(
                "Deployer: invalid return data from factory at ", vm.toString(factory), ": ", vm.toString(returnData)
            )
        );
        returnData = abi.encodePacked(bytes12(0), returnData);
        deployedAt = abi.decode(returnData, (address));
        require(
            deployedAt == predicted,
            StringUtils.concat(
                "Deployer: invalid deployment address returned by factory, expected: ",
                vm.toString(predicted),
                ", actual: ",
                vm.toString(deployedAt)
            )
        );
    }

    // ═══════════════════════════════════════════ CREATE2 DEPLOY UTILS ════════════════════════════════════════════════

    /// @notice Set a deployment salt that will be used for a single next create2 deployment.
    function setNextDeploymentSalt(bytes32 salt) internal {
        nextDeploymentSalt = salt;
    }

    /// @notice Set a deployment salt that will be used for all create2 deployments.
    function setPermanentDeploymentSalt(bytes32 salt) internal {
        permanentDeploymentSalt = salt;
    }

    /// @notice Predicts the address of a contract that would be deployed with the given init code and salt,
    /// using the default create2 factory.
    function predictAddress(bytes memory initCode, bytes32 salt) internal view returns (address deployedAt) {
        return predictAddress(getCreate2Factory(), initCode, salt);
    }

    /// @notice Predicts the address of a contract that would be deployed with the given init code and salt,
    /// using the given create2 factory.
    function predictAddress(
        address factory,
        bytes memory initCode,
        bytes32 salt
    )
        internal
        pure
        returns (address deployedAt)
    {
        return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), factory, salt, keccak256(initCode))))));
    }
}