Skip to content

Commit

Permalink
create2
Browse files Browse the repository at this point in the history
  • Loading branch information
hotaplayer committed Jun 9, 2023
1 parent 0d2b325 commit 027ffca
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 0 deletions.
11 changes: 11 additions & 0 deletions basic/77-create2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts

56 changes: 56 additions & 0 deletions basic/77-create2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# What is CREATE2

The Opcode CREATE2 is imported in EIP-1014,which allows the user to determinesticly deploy and address given the tripple: the deployer, the init code, and the salt, while with the traditional CREATE the address of the contract to deploy is decided by the hash of the pair: the deployer, the nonce of the deployer.


What's more ,with the help of CREATE2, we can even pre-calculate the address before the contract is deployed.

# Why we need CREATE2

CREATE2 allows user to determinesticly deploy the contract. This is useful when we want to controll the address of the contract, especially in multi-chain scenarios. For example, if we want to have the contrac to then same address on different blockchains, we cannot use CREATE because the same deployer may have different nonces on different chains.

# How the address is calculated?

The address is determined by the following rule:

```
addr = keccak256(0xff, deployer, salt, initCodeHash)
```

where:
- deployer: the address of the deployer. Also, it is a CA rather than EOA since the Ethereum transaction does not support it. Usually the deployer is a factory contract like the one defined in EIP-2470.
- salt: this is a byte32 value.
- initCodeHash: the keccak256 hash of the initCode which is the creationCode(compiled from source solidity) plus the constructor args.

# How to deploy

## Solidity

Basicly you can deploy it via **new**:

```
address actual = address(new Target{salt: salt}());
```

You can also deploy it via OZ:

```
import "@openzeppelin/contracts/utils/Create2.sol";
...
bytes32 salt = xxx;
bytes memory initCode = xxx;
address actual = Create2.deploy(0, salt,initCode);
require(actual != address(0), "deploy failed");
```
## Ethers

You can deploy it through EIP-2470.

# QA

## What if we deploy the contract to the address that has existing balance?

The deployment will be successful, and the contract will hold the previous balance.
22 changes: 22 additions & 0 deletions basic/77-create2/contracts/ContractFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/utils/Create2.sol";


contract ContractFactory {

event Deployed(address addr);

function deploy(bytes calldata initCode, bytes32 salt) external {
address deployed = Create2.deploy(0, salt,initCode);
require(deployed != address(0), "deploy failed");
emit Deployed(deployed);
}

function computeAddress(bytes calldata initCode, bytes32 salt) external view returns(address) {
bytes32 initCodeHash = keccak256(initCode);
address addr = Create2.computeAddress(salt, initCodeHash);
return addr;
}

}
9 changes: 9 additions & 0 deletions basic/77-create2/contracts/Target.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pragma solidity ^0.8.17;

contract Target {
uint256 private state;

constructor(uint256 _state) {
state = _state;
}
}
6 changes: 6 additions & 0 deletions basic/77-create2/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.18",
};
32 changes: 32 additions & 0 deletions basic/77-create2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "77-create2",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-verify": "^1.0.0",
"@openzeppelin/contracts": "^4.9.1",
"@typechain/ethers-v6": "^0.4.0",
"@types/mocha": ">=9.1.0",
"hardhat": "^2.15.0",
"ts-node": ">=8.0.0",
"typescript": ">=4.5.0"
},
"devDependencies": {
"@ethersproject/abi": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.1",
"@nomicfoundation/hardhat-network-helpers": "^1.0.8",
"@nomicfoundation/hardhat-toolbox": "^3.0.0",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@typechain/ethers-v5": "^11.0.0",
"@typechain/hardhat": "^8.0.0",
"chai": "^4.3.7",
"ethers": "^6.4.0",
"hardhat-gas-reporter": "^1.0.9",
"solidity-coverage": "^0.8.2",
"typechain": "^8.2.0"
}
}
33 changes: 33 additions & 0 deletions basic/77-create2/scripts/deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");

async function main() {
const currentTimestampInSeconds = Math.round(Date.now() / 1000);
const unlockTime = currentTimestampInSeconds + 60;

const lockedAmount = hre.ethers.parseEther("0.001");

const lock = await hre.ethers.deployContract("Lock", [unlockTime], {
value: lockedAmount,
});

await lock.waitForDeployment();

console.log(
`Lock with ${ethers.formatEther(
lockedAmount
)}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}`
);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
73 changes: 73 additions & 0 deletions basic/77-create2/test/create2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const {
time,
loadFixture,
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");
const { keccak256 } = require("@ethersproject/keccak256");
// import { toUtf8Bytes } from "@ethersproject/strings";

//NOTE: This demo is using ethers-v6.

describe("CREATE2", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployFixture() {
const ContractFactoryFactory = await ethers.getContractFactory("ContractFactory");
const contractFactory = await ContractFactoryFactory.deploy();
console.log("Contract Factory Deployed To: " + contractFactory.target);
return {contractFactory};
}

it("should predict and deploy the contract with Create2", async function(){
const [signer] = await ethers.getSigners();
const {contractFactory} = await loadFixture(deployFixture);

//Get InitCode. It is the bytecode + args(in this case the uint256 value "0xabcd")
const TargetFactory = await ethers.getContractFactory("Target", signer);
const {data: initCode} = await TargetFactory.getDeployTransaction(0xabcd);

//Prepare salt
const salt = ethers.zeroPadValue("0x1234", 32);
//Predict address
// const byteArray = ethers.getBytes(initCode);
const predictedAddress = await contractFactory.computeAddress(initCode, salt);

//Deploy the contract
const tx = await contractFactory.deploy(initCode, salt);
const receipt = await tx.wait();
const actualAddress = receipt.logs[0].args[0];

expect(predictedAddress).to.be.equal(actualAddress);

});

it("should deploy to an address with previous balance successfully", async function(){
const [signer] = await ethers.getSigners();
const {contractFactory} = await loadFixture(deployFixture);

//Get InitCode. It is the bytecode + args(in this case the uint256 value "0xabcd")
const TargetFactory = await ethers.getContractFactory("Target", signer);
const {data: initCode} = await TargetFactory.getDeployTransaction(0xabcd);

//Prepare salt
const salt = ethers.zeroPadValue("0x1234", 32);
//Predict address
const predictedAddress = await contractFactory.computeAddress(initCode, salt);

//Send money
const fundTx = {to: predictedAddress, value: ethers.parseEther('0.01')};
await (await signer.sendTransaction(fundTx)).wait();
const beforeBalance = await ethers.provider.getBalance(predictedAddress);
console.log(`${predictedAddress} has balance ${beforeBalance} before deployment`);
//Deploy the contract
const tx = await contractFactory.deploy(initCode, salt);
const receipt = await tx.wait();
const actualAddress = receipt.logs[0].args[0];
expect(predictedAddress).to.be.equal(actualAddress);
const afterBalance = await ethers.provider.getBalance(actualAddress);

expect(afterBalance).to.be.equal(beforeBalance);
});
});

0 comments on commit 027ffca

Please sign in to comment.