diff --git a/contracts/contracts/Create2Factory.sol b/contracts/contracts/Create2Factory.sol new file mode 100644 index 0000000000..9267eb6e38 --- /dev/null +++ b/contracts/contracts/Create2Factory.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.7.0; + +contract Create2Factory { + /** + * @dev Deploys a contract using `CREATE2`. The address where the contract + * will be deployed can be known in advance via {computeAddress}. Note that + * a contract cannot be deployed twice using the same salt. + */ + function deploy(bytes32 salt, bytes memory bytecode) public returns (address) { + address addr; + // solhint-disable-next-line no-inline-assembly + assembly { + addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) + } + require(addr != address(0), "Create2: Failed on deploy"); + return addr; + } + + /** + * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the `bytecode` + * or `salt` will result in a new destination address. + */ + function computeAddress(bytes32 salt, bytes memory bytecode) external view returns (address) { + return computeAddress(salt, bytecode, address(this)); + } + + /** + * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at + * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}. + */ + function computeAddress( + bytes32 salt, + bytes memory bytecodeHash, + address deployer + ) public pure returns (address) { + bytes32 bytecodeHashHash = keccak256(bytecodeHash); + bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHashHash)); + return address(bytes20(_data << 96)); + } +} diff --git a/contracts/scripts/deploy.ts b/contracts/scripts/deploy.ts index 60ef9db101..f1cb315102 100644 --- a/contracts/scripts/deploy.ts +++ b/contracts/scripts/deploy.ts @@ -58,6 +58,10 @@ async function main() { await deployer.deployRegenesisMultisig({ gasPrice, nonce: args.nonce }); } + if (args.contract === 'Create2Factory' || args.contract == null) { + await deployer.deployCreate2Factory({ gasPrice, nonce: args.nonce }); + } + if (args.contract === 'AdditionalZkSync' || args.contract == null) { await deployer.deployAdditionalZkSync({ gasPrice, nonce: args.nonce }); } diff --git a/contracts/src.ts/deploy.ts b/contracts/src.ts/deploy.ts index b8825351e7..bf3a2ae999 100644 --- a/contracts/src.ts/deploy.ts +++ b/contracts/src.ts/deploy.ts @@ -15,7 +15,9 @@ import { ForcedExit, ForcedExitFactory, TokenGovernanceFactory, - TokenGovernance + TokenGovernance, + Create2Factory, + Create2FactoryFactory } from '../typechain'; export interface Contracts { @@ -29,6 +31,7 @@ export interface Contracts { nftFactory; additionalZkSync; tokenGovernance; + create2Factory; } export interface DeployedAddresses { @@ -45,6 +48,7 @@ export interface DeployedAddresses { NFTFactory: string; AdditionalZkSync: string; TokenGovernance: string; + Create2Factory: string; } export interface DeployerConfig { @@ -72,7 +76,8 @@ export function readProductionContracts(): Contracts { forcedExit: readContractCode('ForcedExit'), regenesisMultisig: readContractCode('RegenesisMultisig'), additionalZkSync: readContractCode('AdditionalZkSync'), - tokenGovernance: readContractCode('TokenGovernance') + tokenGovernance: readContractCode('TokenGovernance'), + create2Factory: readContractCode('Create2Factory') }; } @@ -90,7 +95,8 @@ export function deployedAddressesFromEnv(): DeployedAddresses { ForcedExit: process.env.CONTRACTS_FORCED_EXIT_ADDR, RegenesisMultisig: process.env.MISC_REGENESIS_MULTISIG_ADDRESS, AdditionalZkSync: process.env.CONTRACTS_ADDITIONAL_ZKSYNC_ADDR, - TokenGovernance: process.env.CONTRACTS_LISTING_GOVERNANCE + TokenGovernance: process.env.CONTRACTS_LISTING_GOVERNANCE, + Create2Factory: process.env.CONTRACTS_CREATE2_FACTORY_ADDR }; } @@ -111,80 +117,124 @@ export class Deployer { this.governorAddress = config.governorAddress != null ? config.governorAddress : this.deployWallet.address; } + public async deployCreate2Factory(ethTxOptions?: ethers.providers.TransactionRequest) { + if (this.verbose) { + console.log('Deploying create2 factory'); + } + + if (this.addresses.Create2Factory != '') { + if (this.verbose) { + console.log(`CONTRACTS_CREATE2_FACTORY_ADDR=${this.addresses.Create2Factory}`); + console.log('Create2 factory already deployed'); + } + return; + } + + const create2Factory = await deployContract(this.deployWallet, this.contracts.create2Factory, [], { + gasLimit: 1500000, + ...ethTxOptions + }); + const rec = await create2Factory.deployTransaction.wait(); + const gasUsed = rec.gasUsed; + let gasPrice = create2Factory.deployTransaction.gasPrice; + if (gasPrice == null) { + gasPrice = await this.deployWallet.provider.getGasPrice(); + } + + if (this.verbose) { + console.log(`CONTRACTS_CREATE2_FACTORY_ADDR=${create2Factory.address}`); + console.log( + `Create2 factory deployed, gasUsed: ${gasUsed.toString()}, eth spent: ${formatEther( + gasUsed.mul(gasPrice) + )}` + ); + } + this.addresses.Create2Factory = create2Factory.address; + } + + private async deployViaCreate2(bytecode, ethTxOptions: ethers.providers.TransactionRequest) { + const create2Factory = this.create2FactoryContract(this.deployWallet); + const tx = await create2Factory.deploy(ethers.constants.HashZero, bytecode, ethTxOptions); + const address = await create2Factory['computeAddress(bytes32,bytes)'](ethers.constants.HashZero, bytecode); + + return { tx, address }; + } + public async deployGovernanceTarget(ethTxOptions?: ethers.providers.TransactionRequest) { if (this.verbose) { console.log('Deploying governance target'); } - - const govContract = await deployContract(this.deployWallet, this.contracts.governance, [], { + const { tx, address } = await this.deployViaCreate2(this.contracts.governance.bytecode, { gasLimit: 1500000, ...ethTxOptions }); - const govRec = await govContract.deployTransaction.wait(); + const govRec = await tx.wait(); const govGasUsed = govRec.gasUsed; - let gasPrice = govContract.deployTransaction.gasPrice; + let gasPrice = tx.gasPrice; if (gasPrice == null) { gasPrice = await this.deployWallet.provider.getGasPrice(); } if (this.verbose) { - console.log(`CONTRACTS_GOVERNANCE_TARGET_ADDR=${govContract.address}`); + console.log(`CONTRACTS_GOVERNANCE_TARGET_ADDR=${address}`); console.log( `Governance target deployed, gasUsed: ${govGasUsed.toString()}, eth spent: ${formatEther( govGasUsed.mul(gasPrice) )}` ); } - this.addresses.GovernanceTarget = govContract.address; + this.addresses.GovernanceTarget = address; } public async deployVerifierTarget(ethTxOptions?: ethers.providers.TransactionRequest) { if (this.verbose) { console.log('Deploying verifier target'); } - const verifierContract = await deployContract(this.deployWallet, this.contracts.verifier, [], { + const { tx, address } = await this.deployViaCreate2(this.contracts.verifier.bytecode, { gasLimit: 8000000, ...ethTxOptions }); - const verRec = await verifierContract.deployTransaction.wait(); + + const verRec = await tx.wait(); const verGasUsed = verRec.gasUsed; - let gasPrice = verifierContract.deployTransaction.gasPrice; + let gasPrice = tx.gasPrice; if (gasPrice == null) { gasPrice = await this.deployWallet.provider.getGasPrice(); } if (this.verbose) { - console.log(`CONTRACTS_VERIFIER_TARGET_ADDR=${verifierContract.address}`); + console.log(`CONTRACTS_VERIFIER_TARGET_ADDR=${address}`); console.log( `Verifier target deployed, gasUsed: ${verGasUsed.toString()}, eth spent: ${formatEther( verGasUsed.mul(gasPrice) )}` ); } - this.addresses.VerifierTarget = verifierContract.address; + this.addresses.VerifierTarget = address; } public async deployZkSyncTarget(ethTxOptions?: ethers.providers.TransactionRequest) { if (this.verbose) { console.log('Deploying zkSync target'); } - const zksContract = await deployContract(this.deployWallet, this.contracts.zkSync, [], { + const { tx, address } = await this.deployViaCreate2(this.contracts.zkSync.bytecode, { gasLimit: 6000000, ...ethTxOptions }); - const zksRec = await zksContract.deployTransaction.wait(); + + const zksRec = await tx.wait(); const zksGasUsed = zksRec.gasUsed; - let gasPrice = zksContract.deployTransaction.gasPrice; + let gasPrice = tx.gasPrice; if (gasPrice == null) { gasPrice = await this.deployWallet.provider.getGasPrice(); } if (this.verbose) { - console.log(`CONTRACTS_CONTRACT_TARGET_ADDR=${zksContract.address}`); + console.log(`CONTRACTS_CONTRACT_TARGET_ADDR=${address}`); console.log( `zkSync target deployed, gasUsed: ${zksGasUsed.toString()}, eth spent: ${formatEther( zksGasUsed.mul(gasPrice) )}` ); } - this.addresses.ZkSyncTarget = zksContract.address; + this.addresses.ZkSyncTarget = address; } public async deployProxiesAndGatekeeper(ethTxOptions?: ethers.providers.TransactionRequest) { @@ -353,26 +403,26 @@ export class Deployer { if (this.verbose) { console.log('Deploying Additional Zksync contract'); } - - const additionalZkSyncContract = await deployContract(this.deployWallet, this.contracts.additionalZkSync, [], { + const { tx, address } = await this.deployViaCreate2(this.contracts.additionalZkSync.bytecode, { gasLimit: 6000000, ...ethTxOptions }); - const zksRec = await additionalZkSyncContract.deployTransaction.wait(); + + const zksRec = await tx.wait(); const zksGasUsed = zksRec.gasUsed; - let gasPrice = additionalZkSyncContract.deployTransaction.gasPrice; + let gasPrice = tx.gasPrice; if (gasPrice == null) { gasPrice = await this.deployWallet.provider.getGasPrice(); } if (this.verbose) { - console.log(`CONTRACTS_ADDITIONAL_ZKSYNC_ADDR=${additionalZkSyncContract.address}`); + console.log(`CONTRACTS_ADDITIONAL_ZKSYNC_ADDR=${address}`); console.log( `Additiinal zkSync contract deployed, gasUsed: ${zksGasUsed.toString()}, eth spent: ${formatEther( zksGasUsed.mul(gasPrice) )}` ); } - this.addresses.AdditionalZkSync = additionalZkSyncContract.address; + this.addresses.AdditionalZkSync = address; } public async deployRegenesisMultisig(ethTxOptions?: ethers.providers.TransactionRequest) { @@ -457,6 +507,7 @@ export class Deployer { } public async deployAll(ethTxOptions?: ethers.providers.TransactionRequest) { + await this.deployCreate2Factory(ethTxOptions); await this.deployAdditionalZkSync(ethTxOptions); await this.deployZkSyncTarget(ethTxOptions); await this.deployGovernanceTarget(ethTxOptions); @@ -466,6 +517,10 @@ export class Deployer { await this.deployNFTFactory(ethTxOptions); } + public create2FactoryContract(signerOrProvider: Signer | providers.Provider): Create2Factory { + return Create2FactoryFactory.connect(this.addresses.Create2Factory, signerOrProvider); + } + public governanceContract(signerOrProvider: Signer | providers.Provider): Governance { return GovernanceFactory.connect(this.addresses.Governance, signerOrProvider); } diff --git a/core/lib/config/src/configs/contracts.rs b/core/lib/config/src/configs/contracts.rs index e7a0124303..32bf948dd9 100644 --- a/core/lib/config/src/configs/contracts.rs +++ b/core/lib/config/src/configs/contracts.rs @@ -18,6 +18,7 @@ pub struct ContractsConfig { pub verifier_addr: Address, pub deploy_factory_addr: Address, pub forced_exit_addr: Address, + pub create2_factory_addr: Address, pub genesis_tx_hash: H256, pub init_contract_version: u32, pub upgrade_eth_blocks: Vec, @@ -46,6 +47,7 @@ mod tests { verifier_addr: addr("DAbb67b676F5b01FcC8997Cc8439846D0d8078ca"), deploy_factory_addr: addr("FC073319977e314F251EAE6ae6bE76B0B3BAeeCF"), forced_exit_addr: addr("9c7AeE886D6FcFc14e37784f143a6dAccEf50Db7"), + create2_factory_addr: addr("5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9"), genesis_tx_hash: hash( "b99ebfea46cbe05a21cd80fe5597d97b204befc52a16303f579c607dc1ac2e2e", ), @@ -61,6 +63,7 @@ CONTRACTS_UPGRADE_GATEKEEPER_ADDR="0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" CONTRACTS_GOVERNANCE_TARGET_ADDR="0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" CONTRACTS_VERIFIER_TARGET_ADDR="0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" CONTRACTS_ADDITIONAL_ZKSYNC_ADDR="0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" +CONTRACTS_CREATE2_FACTORY_ADDR="0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" CONTRACTS_CONTRACT_TARGET_ADDR="0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" CONTRACTS_CONTRACT_ADDR="0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55" CONTRACTS_GOVERNANCE_ADDR="0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" diff --git a/etc/env/base/contracts.toml b/etc/env/base/contracts.toml index 16e0f74880..e2307874d3 100644 --- a/etc/env/base/contracts.toml +++ b/etc/env/base/contracts.toml @@ -16,6 +16,7 @@ GENESIS_ROOT="0x2d5ab622df708ab44944bb02377be85b6f27812e9ae520734873b7a193898ba4 LISTING_GOVERNANCE="0xaFe6A91979021206ad79F58562Eef4204720E2A3" NFT_FACTORY_ADDR="" ADDITIONAL_ZKSYNC_ADDR="0x7fbaD9d9C9a1204F45FA38CcbF732B0930F8B582" +CREATE2_FACTORY_ADDR="" # The initial version of the deployed zkSync contract. # Data restore uses this variable for tracking contract updates and # setting correct available block chunk sizes.