Deterministic Address
Contract addresses are generally derived from the deployers address and the nonce. But there an opcode that lets you derive contract address without these constraints.
In case where a contract is deployed from an EOA (externally owned address), the contract address is derived from the EOA address and the nonce.
For eg. if you deploy a contract from a new EOA address (say, 0x109F6aC361Aa9d1fEba9a42C01960272C4C67FaD), it has a starting nonce of 0. Then the address of the contract will come out to be 0x83A5A76FfdCE0Eb86808074F507a59ADEE15097b.
You can try it yourself here, https://zkblock.app/contract-address-gen
But things are a bit different when it comes to deploying a contract from another contract
There are primarily two ways of deriving contract addresses when a new contract is being created from a contract.
1. CREATE
The first thing to note is that the starting nonce for the new contract is 1 instead of 0 as in the case of externally owned addresses. So when we deploy a new contract from another contract using a new keyword, the address of the new contract will be derived by using the contract address doing the deployment and its nonce.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
uint public x;
constructor(uint a) {
x = a;
}
}
contract Create2 {
event Deployed(address d);
function createD(uint arg) public {
address d = address(new D(arg));
emit Deployed(d);
}
}
The above can be verified using ethers,
const expectedAddress = ethers.utils.getContractAddress({
from: create2.address,
nonce: 1,
});
Also, note that a nonce of a contract is only incremented when that contract creates another contract.
CREATE2
However, there’s another way to determine the deployment address of the new contract by using create2. The contracts deployed using create2 takes in an input of the deploying contract’s address, the salt, and bytecode, such that if the salt and the bytecode are known beforehand, then one can derive the contract address.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
uint public x;
constructor(uint a) {
x = a;
}
}
contract Create2 {
event Deployed(address d);
function createD(uint arg) public {
address d = address(new D(arg));
emit Deployed(d);
}
function createDSalted(bytes32 salt, uint arg) public view returns (address){
// This complicated expression just tells you how the address
// can be pre-computed. It is just there for illustration.
// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(type(D).creationCode,abi.encode(arg)))
)))));
return predictedAddress;
// You can also do this instead
// D d = new D{salt: salt}(arg);
// require(address(d) == predictedAddress);
}
}
You can verify the above using ethers,
const expectedAddress = ethers.utils.getCreate2Address(
create2.address,
salt,
ethers.utils.keccak256(initCode)
);
You can run tests here.
Just keep in mind that you cannot have two contracts with the same address, so if you deploy a contract with the same salt and bytecode from the same contract it will fail, but in case you have self-destructed the first deployed contract you can deploy again on the same address.
Follow me on Medium so that you don't miss out on stories like this.
I also summarize my stories on X.