packages/solidity-devops/src/deploy/Deployer.sol
// 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))))));
}
}