-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0d2b325
commit 027ffca
Showing
8 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |