In Solidity, there are two opcodes for creating contracts: CREATE
and CREATE2
. Also, the deployed contract address can be precomputed via:
keccak256(deployerAddress, deployerNonce)
if you're usingCREATE
opcodekeccak256(0xFF, deployerAddress, salt, bytecode)
if you're usingCREATE2
opcode
CREATE
Default opcode used when deploying smart contracts. If you're deploying a contract using new YourContract()
without salt
, then you're using CREATE
.
The following code written in TypeScript and ethers.js, shows how to deploy a contract using CREATE
under the hood:
import { ethers } from 'ethers';
const deployer = await ethers.getNamedSigner("deployer")
const nonce = await deployer.getTransactionCount()
const computedAddress = ethers.utils.getContractAddress({
from: deployer.address,
nonce: nonce,
})
console.log(`computed address: ${computedAddress}`)
const ktbArbitrageurFactory = await ethers.getContractFactory("KtbArbitrageur", deployer)
const ktbArbitrageur = await ktbArbitrageurFactory.deploy(oinchAggregationRouterV5)
console.log(`deployed address: ${ktbArbitrageur.address}`)
Though it's pretty inefficient, but you can specify the deployed address (to some extend) by keeping increasing nonce
until it meets some conditions you set:
async increaseNonceToDeployUpgradeable(condition: string, targetAddr: string) {
const { ethers } = this._hre
const deployer = await ethers.getNamedSigner("deployer")
// We use deployer's address and nonce to compute a contract's address which deployed with that nonce,
// to find the nonce that matches the condition
let nonce = await deployer.getTransactionCount()
console.log(`Next nonce: ${nonce}`)
let computedAddress = "0x0"
let count = 0
while (
count < 2 ||
(condition == "GREATER_THAN"
? computedAddress.toLowerCase() <= targetAddr.toLowerCase()
: computedAddress.toLowerCase() >= targetAddr.toLowerCase())
) {
// Increase the nonce until we find a contract address that matches the condition
computedAddress = ethers.utils.getContractAddress({
from: deployer.address,
nonce: nonce,
})
console.log(`Computed address: ${nonce}, ${computedAddress}`)
nonce += 1
count += 1
}
// When deploying a upgradable contract,
// it will deploy the implementation contract first, then deploy the proxy
// so we need to increase the nonce to "the expected nonce - 1"
let nextNonce = await deployer.getTransactionCount()
for (let i = 0; i < count - 1 - 1; i++) {
nextNonce += 1
console.log(`Increasing nonce to ${nextNonce}`)
const tx = await deployer.sendTransaction({
to: deployer.address,
value: ethers.utils.parseEther("0"),
})
await tx.wait()
}
console.log(`Finalized nonce`)
}
ref:
https://docs.ethers.org/v5/api/utils/address/#utils-getContractAddress
CREATE2
The Solidity code below demonstrates how to deploy a contract using CREATE2
which is introduced in EIP-1014 to provide more flexible and predictable address generation:
bytes32 salt = bytes32("perp");
address oinchAggregationRouterV5 = 0x1111111254EEB25477B68fb85Ed929f73A960582;
address computedAddress = address(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff), // avoid conflict with CREATE
address(this), // deployer
salt,
keccak256(
abi.encodePacked(
type(KtbArbitrageur).creationCode, // bytecode
abi.encode(oinchAggregationRouterV5) // constructor parameter
)
)
)
)
)
);
KtbArbitrageur ktbArbitrageur = new KtbArbitrageur{ salt: salt }(oinchAggregationRouterV5);
console.logAddress(address(ktbArbitrageur));
console.logAddress(computedAddress);
You can change salt
to an arbitrary value to produce different contract addresses.
ref:
https://docs.soliditylang.org/en/v0.7.6/control-structures.html#salted-contract-creations-create2