Skip to content

Commit d45c52d

Browse files
authored
Migrate contract deployer repo into contracts repo (#209)
Migrate contracts deployer repo into contracts repo Pass msg.value when calling deploy to contract init code fragment for OwnableCreateDeploy Resolve some solhint issues related to contract spacing / layout
1 parent d3db1b1 commit d45c52d

File tree

13 files changed

+787
-34
lines changed

13 files changed

+787
-34
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@
2222
[submodule "lib/openzeppelin-contracts-5.0.2"]
2323
path = lib/openzeppelin-contracts-5.0.2
2424
url = https://github.com/OpenZeppelin/openzeppelin-contracts
25+
[submodule "lib/axelar-gmp-sdk-solidity"]
26+
path = lib/axelar-gmp-sdk-solidity
27+
url = https://github.com/axelarnetwork/axelar-gmp-sdk-solidity

contracts/deployer/README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Contract Deployers
2+
3+
At present, use of the Contract Deployers described below is limited to Immutable.
4+
5+
This directory provides two types of contract deployers: CREATE2 and CREATE3. Both deployer types facilitate contract deployment to predictable addresses, independent of the deployer account’s nonce. The deployers offer a more reliable alternative to using a Nonce Reserver Key (a key that is only used for deploying contracts, and has specific nonces reserved for deploying specific contracts), particularly across different chains. These factories can also be utilized for contracts that don't necessarily need predictable addresses. The advantage of this method, compared to using a deployer key in conjunction with a deployment factory contract, is that it can enable better standardisation and simplification of deployment processes and enables the rotation of the deployer key without impacting the consistency of the perceived deployer address for contracts.
6+
7+
Deployments via these factories can only be performed by the owner of the factory.
8+
9+
**CAUTION**: When deploying a contract using one of these factories, it's crucial to note that `msg.sender` in the contract's constructor will refer to the contract factory's address and not to the deployer EOA. Therefore, any logic in a constructor that refers to `msg.sender` or assigns special privileges to this address would need to be changed.
10+
An example of this type of logic is the [`Ownable` contract in OpenZeppelin's v4.x](https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable) library, which assigns default ownership of an inheriting contract to its deployer. When deploying a contract that inherits from `Ownable` using one of these factories, the contract's owner would thus be the factory. This would result in a loss of control over the contract. Hence, when deploying such a contract, it is necessary to ensure that a transfer of ownership from the factory to the desired owner is performed as part of the contract's construction. Specifically, a call to `transferOwnership(newOwner)` could be made to transfer ownership from the factory to the desired owner in the contract's constructor.
11+
12+
13+
# Status
14+
15+
Contract audits and threat models:
16+
17+
| Description | Date |Version Audited | Link to Report |
18+
|---------------------------|------------------|-----------------|----------------|
19+
| Internal audit | Work in Progress | | |
20+
21+
22+
# Architecture
23+
24+
## Create2 Deployer
25+
26+
**Contract:** [`OwnableCreate2Deployer`](./create2/OwnableCreate2Deployer.sol)
27+
28+
The [`OwnableCreate2Deployer`](./create2/OwnableCreate2Deployer.sol) uses the `CREATE2` opcode to deploy contracts to predictable addresses. The address of the deployed contract is determined by the following inputs:
29+
- **Contract Bytecode**: A contract's creation code. In Solidity this can be obtained using `type(contractName).creationCode`. For contracts with constructor arguments, the bytecode has to be encoded along with the relevant constructor arguments e.g. `abi.encodePacked(type(contractName).creationCode, abi.encode(args...))`.
30+
- **Salt**: The salt is a 32 byte value that is used to differentiate between different deployments.
31+
- **Deployer Address**: The address of the authorised deployer.
32+
- **Factory Address**: The address of the factory contract.
33+
34+
The contract offers two functions for deployment:
35+
- `deploy(bytes memory bytecode, bytes32 salt)`: Deploys a contract to the address determined by the bytecode, salt and sender address. The bytecode should be encoded with the constructor arguments, if any.
36+
- `deployAndInit(bytes memory bytecode, bytes32 salt, bytes memory initCode)`: Deploys a contract to the address determined by the bytecode, salt and sender address, and executes the provided initialisation function after deployment. Contracts that are deployed on different chains, with the same salt and sender, will produce different addresses if the constructor parameters are different. `deployAndInit` offers one way to get around this issue, wherein contracts can define a separate `init` function that can be called after deployment, in place of having a constructor.
37+
38+
The address that a contract will be deployed to, can be determined by calling the view function below:
39+
- `deployedAddress(bytes memory bytecode, bytes32 salt, address deployer)`: Returns the address that a contract will be deployed to, given the bytecode, salt and authorised deployer address.
40+
41+
**Note:** Alternatively, you can use the `CREATE3` deployer, described below.
42+
43+
## Create3 Deployer
44+
**Contract:** [`OwnableCreate3Deployer`](./create3/OwnableCreate3Deployer.sol)
45+
46+
A limitation of the [`OwnableCreate2Deployer`](./create2/OwnableCreate2Deployer.sol) deployer is that the deployment addresses for contracts are influenced by specific constructor parameters used. This can pose a problem when identical contract addresses are needed across chains, but each chain requires different constructor arguments. The [`OwnableCreate3Deployer`](./create3/OwnableCreate3Deployer.sol) deployer addresses this issue by not requiring the contract bytecode as an input to determine the deployment address. The address of the deployed contract is determined solely by the following inputs:
47+
- **Salt**: A 32-byte value used to differentiate between various deployments.
48+
- **Deployer Address**: The address of the authorized deployer.
49+
- **Factory Address**: The address of the factory contract.
50+
51+
Similar to the `OwnableCreate2Deployer`, the contract offers two functions for deployment:
52+
- `deploy(bytes memory bytecode, bytes32 salt)`: Deploys a contract to the address determined by the salt and sender address. The bytecode should be encoded with the constructor arguments, if any.
53+
- `deployAndInit(bytes memory bytecode, bytes32 salt, bytes memory initCode)`: Deploys a contract to the address determined by the salt and sender address, and executes the provided initialisation function after deployment.
54+
55+
The address that a contract will be deployed to, can be determined by calling:
56+
- `deployedAddress(bytes memory, bytes32 salt, address deployer)`: Returns the address that a contract will be deployed to, given the salt and authorised deployer address. The first parameter is the bytecode of the contract, and can be left empty in the case of the `OwnableCreate3Deployer`, which is not influenced by the contract bytecode.
57+
58+
59+
60+
# Deployed Addresses
61+
The addresses of the deployed factories are listed below. The addresses of the factories are the same on both the Ethereum and Immutable chain, for each environment.
62+
63+
## Create2 Deployer
64+
There are two instances of the Create2 deployer deployed on Mainnet and Testnet.
65+
While the contract behind both deployments are the same, their deployment configurations are different. The recommended deployer, as detailed below, ensures consistent addresses are generated for a given salt and bytecode across both chains (L1 and L2) and environments (Testnet and Mainnet). This means that a contract deployed with a given salt, will have the same address across L1 and L2 and across Testnet and Mainnet environments.
66+
The second Create2 deployer is now deprecated. While it ensures consistent addresses across chains (L1 and L2), it does not maintain this consistency across environments (Testnet vs Mainnet). Thus, a deployment with identical bytecode and salt will result in different addresses on Mainnet compared to Testnet.
67+
68+
### Recommended
69+
70+
| | Testnet and Mainnet |
71+
|---------|----------------------------------------------|
72+
| Address | `0xeC3AAc81D3CE025E14620105d5e424c9a72B67B8` |
73+
| Owner | `0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333` |
74+
75+
76+
### Deprecated
77+
78+
| | Testnet | Mainnet |
79+
|---------|----------------------------------------------|----------------------------------------------|
80+
| Address | `0xFd30E66f968F93F4c0C5AeA33601096A3fB2c48c` | `0x90DA206238384D33d7A35DCd7119c0CE76D37921` |
81+
| Owner | `0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333` | `0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333` |
82+
83+
## Create3 Deployer
84+
85+
| | Testnet and Mainnet |
86+
|---------|----------------------------------------------|
87+
| Address | `0x37a59A845Bb6eD2034098af8738fbFFB9D589610` |
88+
| Owner | `0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333` |
89+
90+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright Immutable Pty Ltd 2018 - 2024
2+
// SPDX-License-Identifier: MIT
3+
pragma solidity 0.8.19;
4+
5+
/**
6+
* @title OwnableCreateDeploy Contract
7+
* @notice This contract deploys new contracts using the `CREATE` opcode and is used as part of
8+
* the `CREATE3` deployment method.
9+
* @dev The contract can only be called by the owner of the contract, which is set to the deployer of the contract.
10+
* @dev This is a copy of the `CreateDeploy` contract in Axelar's SDK, with a modification that adds basic access control to the deployment function.
11+
* see: https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/5f15a1036215f8b9c8eeb6438d352172b430dd38/contracts/deploy/CreateDeploy.sol
12+
*/
13+
contract OwnableCreateDeploy {
14+
// Address that is authorised to call the deploy function.
15+
address private immutable owner;
16+
17+
constructor() {
18+
owner = msg.sender;
19+
}
20+
/**
21+
* @dev Deploys a new contract with the specified bytecode using the `CREATE` opcode.
22+
* @param bytecode The bytecode of the contract to be deployed
23+
*/
24+
// slither-disable-next-line locked-ether
25+
function deploy(bytes memory bytecode) external payable {
26+
// solhint-disable-next-line custom-errors
27+
require(msg.sender == owner, "CreateDeploy: caller is not the owner");
28+
assembly {
29+
if iszero(create(callvalue(), add(bytecode, 32), mload(bytecode))) { revert(0, 0) }
30+
}
31+
}
32+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright Immutable Pty Ltd 2018 - 2024
2+
// SPDX-License-Identifier: Apache 2.0
3+
pragma solidity 0.8.19;
4+
5+
import "@openzeppelin/contracts/access/Ownable.sol";
6+
import {Deployer} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Deployer.sol";
7+
import {Create2} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create2.sol";
8+
9+
/**
10+
* @title OwnableCreate2Deployer
11+
* @notice Deploys and initializes contracts using the `CREATE2` opcode. The contract exposes two functions, {deploy} and {deployAndInit}.
12+
* {deploy} deploys a contract using the `CREATE2` opcode, and {deployAndInit} additionally initializes the contract using provided data.
13+
* The latter offers a way of ensuring that the constructor arguments do not affect the deployment address.
14+
*
15+
* @dev This contract extends the {Deployer} contract from the Axelar SDK, by adding basic access control to the deployment functions.
16+
* The contract has an owner, which is the only entity that can deploy new contracts.
17+
*
18+
* @dev The contract deploys a contract with the same bytecode, salt, and sender to the same address.
19+
* The address where the contract will be deployed can be found using {deployedAddress}.
20+
*/
21+
contract OwnableCreate2Deployer is Ownable, Create2, Deployer {
22+
constructor(address owner) Ownable() {
23+
transferOwnership(owner);
24+
}
25+
26+
/**
27+
* @dev Deploys a contract using the `CREATE2` opcode.
28+
* This function is called by {deploy} and {deployAndInit} external functions in the {Deployer} contract.
29+
* This function can only be called by the owner of this contract, hence the external {deploy} and {deployAndInit} functions can only be called by the owner.
30+
* The address where the contract will be deployed can be found using the {deployedAddress} function.
31+
* @param bytecode The bytecode of the contract to be deployed
32+
* @param deploySalt A salt which is a hash of the salt provided by the sender and the sender's address.
33+
* @return The address of the deployed contract
34+
*/
35+
function _deploy(bytes memory bytecode, bytes32 deploySalt) internal override onlyOwner returns (address) {
36+
return _create2(bytecode, deploySalt);
37+
}
38+
39+
/**
40+
* @dev Returns the address where a contract will be stored if deployed via {deploy} or {deployAndInit}.
41+
* This function is called by the {deployedAddress} external functions in the {Deployer} contract.
42+
* @param bytecode The bytecode of the contract to be deployed
43+
* @param deploySalt A salt which is a hash of the sender's address and the `salt` provided by the sender, when calling the {deployedAddress} function.
44+
* @return The predicted deployment address of the contract
45+
*/
46+
function _deployedAddress(bytes memory bytecode, bytes32 deploySalt) internal view override returns (address) {
47+
return _create2Address(bytecode, deploySalt);
48+
}
49+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright Immutable Pty Ltd 2018 - 2024
2+
// SPDX-License-Identifier: MIT
3+
pragma solidity 0.8.19;
4+
5+
import {IDeploy} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IDeploy.sol";
6+
import {ContractAddress} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/ContractAddress.sol";
7+
8+
import {OwnableCreate3Address} from "./OwnableCreate3Address.sol";
9+
import {OwnableCreateDeploy} from "../create/OwnableCreateDeploy.sol";
10+
11+
/**
12+
* @title OwnableCreate3 contract
13+
* @notice This contract can be used to deploy a contract with a deterministic address that depends only on
14+
* the deployer address and deployment salt, not the contract bytecode and constructor parameters.
15+
* @dev This contract is a copy of the `Create3` contract in Axelar's SDK, with a minor modification to use `OwnableCreateDeploy` instead of `CreateDeploy`.
16+
* See: https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/1d3dd9a42abd37a315c18ec51163ddc5e5a08c21/contracts/deploy/Create3.sol
17+
*/
18+
contract OwnableCreate3 is OwnableCreate3Address, IDeploy {
19+
using ContractAddress for address;
20+
21+
/**
22+
* @notice Deploys a new contract using the `CREATE3` method.
23+
* @dev This function first deploys the CreateDeploy contract using
24+
* the `CREATE2` opcode and then utilizes the CreateDeploy to deploy the
25+
* new contract with the `CREATE` opcode.
26+
* @param bytecode The bytecode of the contract to be deployed
27+
* @param deploySalt A salt to influence the contract address
28+
* @return deployed The address of the deployed contract
29+
*/
30+
function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) {
31+
deployed = _create3Address(deploySalt);
32+
33+
if (bytecode.length == 0) revert EmptyBytecode();
34+
if (deployed.isContract()) revert AlreadyDeployed();
35+
36+
// Deploy using create2
37+
OwnableCreateDeploy create = new OwnableCreateDeploy{salt: deploySalt}();
38+
39+
if (address(create) == address(0)) revert DeployFailed();
40+
41+
// Deploy using create
42+
create.deploy(bytecode);
43+
}
44+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright Immutable Pty Ltd 2018 - 2024
2+
// SPDX-License-Identifier: MIT
3+
pragma solidity 0.8.19;
4+
5+
import {OwnableCreateDeploy} from "../create/OwnableCreateDeploy.sol";
6+
7+
/**
8+
* @title OwnableCreate3Address contract
9+
* @notice This contract can be used to predict the deterministic deployment address of a contract deployed with the `CREATE3` technique.
10+
* @dev This contract is a copy of the `Create3Address` contract in Axelar's SDK, with a minor modification to use `OwnableCreateDeploy` instead of `CreateDeploy`.
11+
* See: https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/1d3dd9a42abd37a315c18ec51163ddc5e5a08c21/contracts/deploy/Create3Address.sol
12+
*/
13+
contract OwnableCreate3Address {
14+
/// @dev bytecode hash of the CreateDeploy helper contract
15+
bytes32 internal immutable createDeployBytecodeHash;
16+
17+
constructor() {
18+
createDeployBytecodeHash = keccak256(type(OwnableCreateDeploy).creationCode);
19+
}
20+
21+
/**
22+
* @notice Compute the deployed address that will result from the `CREATE3` method.
23+
* @param deploySalt A salt to influence the contract address
24+
* @return deployed The deterministic contract address if it was deployed
25+
*/
26+
function _create3Address(bytes32 deploySalt) internal view returns (address deployed) {
27+
address deployer = address(
28+
uint160(uint256(keccak256(abi.encodePacked(hex"ff", address(this), deploySalt, createDeployBytecodeHash))))
29+
);
30+
31+
deployed = address(uint160(uint256(keccak256(abi.encodePacked(hex"d694", deployer, hex"01")))));
32+
}
33+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright Immutable Pty Ltd 2018 - 2023
2+
// SPDX-License-Identifier: Apache 2.0
3+
pragma solidity 0.8.19;
4+
5+
import "@openzeppelin/contracts/access/Ownable.sol";
6+
import {Deployer} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Deployer.sol";
7+
8+
import {OwnableCreate3} from "./OwnableCreate3.sol";
9+
10+
/**
11+
* @title OwnableCreate3Deployer
12+
* @notice The contract deploys contracts to deterministic addresses using the `CREATE3` method.
13+
* This address of deployed contracts depends only on the deployer address, and a provided salt.
14+
* Unlike the `CREATE2` deployment approach implemented in {OwnableCreate2Deployer}, the address of
15+
* contracts does not depend on the bytecode of the contract or constructor parameters.
16+
*
17+
* @dev This contract extends the {Deployer} contract from the Axelar SDK, by adding basic access control to the deployment functions.
18+
* The contract has an owner, which is the only entity that can deploy new contracts.
19+
*
20+
* @dev The contract deploys a contract with the same salt and sender to the same address.
21+
* The address where a contract will be deployed can be found using {deployedAddress}.
22+
*
23+
* @dev The contract deploys an single use intermediary contract using the `CREATE2` opcode, and then uses the intermediary contract to deploy the new contract using the `CREATE` opcode.
24+
* The intermediary contract is an instance of the {OwnableCreateDeploy} contract and can only be called by this contract.
25+
*/
26+
contract OwnableCreate3Deployer is Ownable, OwnableCreate3, Deployer {
27+
constructor(address owner) Ownable() {
28+
transferOwnership(owner);
29+
}
30+
31+
/**
32+
* @dev Deploys a contract using the `CREATE3` method.
33+
* This function is called by {deploy} and {deployAndInit} external functions in the {Deployer} contract.
34+
* This function can only be called by the owner of this contract, hence the external {deploy} and {deployAndInit} functions can only be called by the owner.
35+
* The address where the contract will be deployed can be found using the {deployedAddress} function.
36+
* @param bytecode The bytecode of the contract to be deployed
37+
* @param deploySalt A salt which is a hash of the salt provided by the sender and the sender's address.
38+
* @return The address of the deployed contract
39+
*/
40+
function _deploy(bytes memory bytecode, bytes32 deploySalt) internal override onlyOwner returns (address) {
41+
return _create3(bytecode, deploySalt);
42+
}
43+
44+
/**
45+
* @dev Returns the address where a contract will be stored if deployed via {deploy} or {deployAndInit}.
46+
* This function is called by the {deployedAddress} external functions in the {Deployer} contract.
47+
* @param deploySalt A salt which is a hash of the sender's address and the `salt` provided by the sender, when calling the {deployedAddress} function.
48+
* @return The predicted deployment address of the contract
49+
*/
50+
function _deployedAddress(bytes memory, bytes32 deploySalt) internal view override returns (address) {
51+
return _create3Address(deploySalt);
52+
}
53+
}

lib/axelar-gmp-sdk-solidity

Submodule axelar-gmp-sdk-solidity added at 3f6ae1a

0 commit comments

Comments
 (0)