synapsecns/sanguine

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

Summary

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

import {StringUtils} from "../libs/StringUtils.sol";
import {ChainAwareReader} from "../reader/ChainAwareReader.sol";
import {ChainAwareWriter} from "../writer/ChainAwareWriter.sol";

import {stdJson} from "forge-std/Script.sol";

abstract contract DeploymentSaver is ChainAwareReader, ChainAwareWriter {
    using stdJson for string;
    using StringUtils for *;

    function checkBeforeDeploying(string memory contractName) internal view returns (address deployedAt) {
        return checkBeforeDeploying(contractName, contractName);
    }

    function checkBeforeDeploying(
        string memory contractName,
        string memory contractAlias
    )
        internal
        view
        returns (address deployedAt)
    {
        printLog(
            contractName.equals(contractAlias)
                ? StringUtils.concat("Deploying: ", contractName)
                : StringUtils.concat("Deploying: ", contractName, " as ", contractAlias)
        );
        // Check if the contract is already deployed
        deployedAt = getDeploymentAddress({contractName: contractAlias, revertIfNotFound: false});
        if (deployedAt != address(0)) {
            printSkipWithIndent(StringUtils.concat("already deployed at ", vm.toString(deployedAt)));
        }
    }

    /// @notice Deploys a contract and saves the deployment JSON, if the contract hasn't been deployed yet.
    /// See `deployAndSaveAs` below for more details.
    function deployAndSave(
        string memory contractName,
        bytes memory constructorArgs,
        function(string memory, bytes memory) internal returns (address) deployCodeFunc
    )
        internal
        returns (address deployedAt)
    {
        // Use contractName as contractAlias by default
        return deployAndSaveAs(contractName, contractName, constructorArgs, deployCodeFunc);
    }

    /// @notice Deploys a contract and saves the deployment JSON, if the contract hasn't been deployed yet.
    /// Needs to be passed a generic function that deploys the contract, that follows this signature:
    /// deployCodeFunc(string memory contractName, bytes memory constructorArgs) internal returns (address deployedAt);
    /// - Example: cbDeploy() and cbDeployCreate2() could be used here, as they follow the signature. Or anything
    /// else that could deploy raw bytecode of any contract, most likely using assembly/factory approach.
    /// - Note: contract should be configured outside of `deployCodeFunc`.
    function deployAndSaveAs(
        string memory contractName,
        string memory contractAlias,
        bytes memory constructorArgs,
        function(string memory, bytes memory) internal returns (address) deployCodeFunc
    )
        internal
        returns (address deployedAt)
    {
        deployedAt = checkBeforeDeploying(contractName, contractAlias);
        if (deployedAt != address(0)) {
            return deployedAt;
        }
        // Trigger callback to deploy the contract
        deployedAt = deployCodeFunc(contractName, constructorArgs);
        printSuccessWithIndent(StringUtils.concat("Deployed at ", vm.toString(deployedAt)));
        // Save the deployment JSON
        saveDeployment(contractAlias, deployedAt, constructorArgs);
    }

    /// @notice Deploys a contract and saves the deployment JSON, if the contract hasn't been deployed yet.
    /// See `deployAndSaveAs` below for more details.
    function deployAndSave(
        string memory contractName,
        function() internal returns (address, bytes memory) deployContractFunc
    )
        internal
        returns (address deployedAt)
    {
        // Use contractName as contractAlias by default
        return deployAndSaveAs(contractName, contractName, deployContractFunc);
    }

    /// @notice Deploys a contract and saves the deployment JSON, if the contract hasn't been deployed yet.
    /// Needs to be passed a contract-specific function that deploys the contract, that follows this signature:
    /// deployContractFunc() internal returns (address deployedAt, bytes memory constructorArgs);
    /// - Example: use anything that deploys a specific contract, most likely using `new Contract(...);` approach.
    /// - Note: contract should be configured outside of `deployContractFunc`.
    function deployAndSaveAs(
        string memory contractName,
        string memory contractAlias,
        function() internal returns (address, bytes memory) deployContractFunc
    )
        internal
        returns (address deployedAt)
    {
        deployedAt = checkBeforeDeploying(contractName, contractAlias);
        if (deployedAt != address(0)) {
            return deployedAt;
        }
        // Trigger callback to deploy the specific contract
        bytes memory constructorArgs;
        (deployedAt, constructorArgs) = deployContractFunc();
        printSuccessWithIndent(StringUtils.concat("Deployed at ", vm.toString(deployedAt)));
        // Save the deployment JSON
        saveDeployment(contractAlias, deployedAt, constructorArgs);
    }

    // ═══════════════════════════════════════════════════ UTILS ═══════════════════════════════════════════════════════

    /// @notice Produces a JSON string that can be used to save a contract deployment.
    /// Note: contract ABI is not included in the output.
    function serializeDeploymentData(
        address deployedAt,
        bytes memory constructorArgs
    )
        internal
        returns (string memory data)
    {
        data = "deployment";
        data.serialize("address", deployedAt);
        if (constructorArgs.length == 0) {
            return data.serialize("constructorArgs", string("0x"));
        } else {
            return data.serialize("constructorArgs", constructorArgs);
        }
    }

    /// @notice Saves the deployment JSON for a contract on a given chain under the specified alias.
    /// Example: contractName = "LinkedPool", contractAlias = "LinkedPool.USDC"
    /// Note: writes the JSON file to the FRESH deployments directory. The written file needs to be moved
    /// to the correct location outside of the deployment script.
    /// Note: will not include the ABI in the output JSON.
    function saveDeployment(
        string memory contractAlias,
        address deployedAt,
        bytes memory constructorArgs
    )
        internal
        withIndent
    {
        writeDeploymentArtifact({
            contractAlias: contractAlias,
            artifact: serializeDeploymentData(deployedAt, constructorArgs)
        });
    }
}