From 1d3bec30cf4509d17369618aaeb0c0acbff7e9ce Mon Sep 17 00:00:00 2001 From: JDawg287 Date: Wed, 11 Sep 2024 20:40:03 -0400 Subject: [PATCH] feat: create genesis script for CDKs Signed-off-by: JDawg287 --- foundry.toml | 4 +- script/CreateGenesis.s.sol | 321 ++++++++++++++++++++++++++++++++ script/inputs/genesisInput.json | 12 ++ tools/zkevm-commonjs-wrapper.js | 55 +++++- 4 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 script/CreateGenesis.s.sol create mode 100644 script/inputs/genesisInput.json diff --git a/foundry.toml b/foundry.toml index da9268351..2cb899f8a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,7 +7,9 @@ optimizer_runs = 999999 #via_ir = true verbosity = 2 ffi = true -fs_permissions = [{ access = "read", path = "./script/"}] +fs_permissions = [{ access = "read-write", path = "./script/"}] +evm_version = "shanghai" +metadata_hash = "none" remappings = [ "@ensdomains/=node_modules/@ensdomains/", diff --git a/script/CreateGenesis.s.sol b/script/CreateGenesis.s.sol new file mode 100644 index 000000000..0e5cd1545 --- /dev/null +++ b/script/CreateGenesis.s.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {PolygonRollupManagerNotUpgraded} from "contracts/newDeployments/PolygonRollupManagerNotUpgraded.sol"; +import {PolygonZkEVMBridgeV2} from "contracts-ignored-originals/PolygonZkEVMBridgeV2.sol"; +import {PolygonZkEVMGlobalExitRootV2} from "contracts/PolygonZkEVMGlobalExitRootV2.sol"; +import {PolygonZkEVMDeployer} from "contracts/deployment/PolygonZkEVMDeployer.sol"; +import {PolygonZkEVMTimelock} from "contracts/PolygonZkEVMTimelock.sol"; + +contract CreateGenesis is Script { + using stdJson for string; + + struct ContractInfo { + string name; + address addr; + } + + struct Slot { + bytes32 slot; + bytes32 value; + } + + string constant OUTPUT_DIR = "script/"; + string constant OUTPUT_FILENAME = "genesis.json"; + bytes32 constant ADMIN_SLOT = + 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + bytes32 constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + bytes32 constant EMPTY_SLOT_VALUE = + 0x0000000000000000000000000000000000000000000000000000000000000000; + uint256 constant BALANCE_BRIDGE = 0xffffffffffffffffffffffffffffffff; + uint256 constant BALANCE_DEPLOYER = 0x152d02c7e14af6800000; + bytes16 private constant HEX_SYMBOLS = "0123456789abcdef"; + + address deployerAddr; + address timelockAdminAddress; + bool isTest; + + string[] internal contractNames = [ + "PolygonZkEVMDeployer", + "ProxyAdmin", + "PolygonZkEVMBridgeImplementation", + "PolygonZkEVMBridgeProxy", + "PolygonZkEVMGlobalExitRootL2Implementation", + "PolygonZkEVMGlobalExitRootL2Proxy", + "PolygonZkEVMTimelock" + ]; + + bytes32[] internal timelockRoles = [ + keccak256("TIMELOCK_ADMIN_ROLE"), + keccak256("PROPOSER_ROLE"), + keccak256("EXECUTOR_ROLE"), + keccak256("CANCELLER_ROLE") + ]; + + ContractInfo[] internal contracts; + Slot[] internal contractSlots; + + function run() public { + require(vm.isDir(OUTPUT_DIR), "Output directory does not exist"); + string memory outPath = string.concat(OUTPUT_DIR, OUTPUT_FILENAME); + + loadConfig(); + + string memory gensis = generateGenesisJson(); + bytes32 stateRoot = _calculateStateRoot(_wrapJson(gensis)); + string memory finalGenesis = _insertStateRoot(gensis, stateRoot); + + vm.writeFile(outPath, finalGenesis); + console.log("Genesis file created at path: %s\n", outPath); + } + + function loadConfig() public { + string memory inputPath = "script/inputs/genesisInput.json"; + console.log("Reading config from path: %s \n", inputPath); + + string memory input = vm.readFile(inputPath); + for (uint256 i = 0; i < contractNames.length; i++) { + ContractInfo memory currentContract = ContractInfo({ + name: contractNames[i], + addr: input.readAddress(string.concat(".", contractNames[i])) + }); + contracts.push(currentContract); + console.log( + "%s's address: %s", + currentContract.name, + currentContract.addr + ); + } + deployerAddr = input.readAddress(".Deployer"); + console.log("Deployer's address: %s", deployerAddr); + + timelockAdminAddress = input.readAddress(".timelockAdminAddress"); + console.log("Timelock Admin's address: %s", timelockAdminAddress); + + isTest = input.readBool(".isTest"); + console.log("Is test: %s", isTest); + console.log("Config loaded successfully!\n"); + } + + function generateGenesisJson() public returns (string memory) { + string memory finalOutput = '"genesis": ['; + for (uint256 i = 0; i < contracts.length; i++) { + uint256 balance = 0; + if ( + keccak256(abi.encodePacked(contracts[i].name)) == + keccak256(abi.encodePacked("PolygonZkEVMBridgeProxy")) + ) { + balance = BALANCE_BRIDGE; + } + string memory contractOutput = _generateContractGenesisInfo( + contracts[i].name, + balance, + contracts[i].addr + ); + finalOutput = string.concat(finalOutput, contractOutput); + if (i != contracts.length - 1) { + finalOutput = string.concat(finalOutput, ","); + } + } + finalOutput = string.concat(finalOutput, ","); + string memory deployerOutput = _getDeployerInfo(); + finalOutput = string.concat(finalOutput, deployerOutput); + finalOutput = string.concat(finalOutput, "]"); + return finalOutput; + } + + function _getContractSlots(address contractAddr) internal { + // try to get 200 slots + uint256 n = 200; + for (uint256 i = 0; i < n; i++) { + bytes32 currentSlot = bytes32(uint256(i)); + bytes32 currentSlotValue = vm.load(contractAddr, currentSlot); + if (currentSlotValue != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: currentSlot, value: currentSlotValue}) + ); + } + } + } + + function _generateContractGenesisInfo( + string memory contractName, + uint256 balance, + address contractAddr + ) internal returns (string memory) { + string memory contractObj = contractName; + + bytes memory runtimeCode = contractAddr.code; + require(runtimeCode.length > 0, "Contract runtime code is empty"); + vm.serializeBytes(contractObj, "bytecode", runtimeCode); + + vm.serializeString(contractObj, "contractName", contractName); + vm.serializeString(contractObj, "balance", _toHexString(balance)); + vm.serializeString( + contractObj, + "nonce", + _toHexString(vm.getNonce(contractAddr)) + ); + vm.serializeAddress(contractObj, "address", contractAddr); + + _getContractSlots(contractAddr); + + // Special handling for PolygonZkEVMTimelock + if ( + keccak256(abi.encodePacked(contractName)) != + keccak256(abi.encodePacked("PolygonZkEVMTimelock")) + ) { + bytes32 adminSlotValue = vm.load(contractAddr, ADMIN_SLOT); + if (adminSlotValue != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: ADMIN_SLOT, value: adminSlotValue}) + ); + } + bytes32 implementationSlotValue = vm.load( + contractAddr, + IMPLEMENTATION_SLOT + ); + if (implementationSlotValue != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({ + slot: IMPLEMENTATION_SLOT, + value: implementationSlotValue + }) + ); + } + } else { + for (uint256 i = 0; i < timelockRoles.length; i++) { + uint256 rolesMappingStoragePositionStruct = 0; + bytes32 storagePosition = keccak256( + abi.encodePacked( + timelockRoles[i], + rolesMappingStoragePositionStruct + ) + ); + + address[] memory addressArray = new address[](2); + addressArray[0] = timelockAdminAddress; + addressArray[1] = contractAddr; + for (uint256 j = 0; j < addressArray.length; j++) { + bytes32 storagePositionRole = keccak256( + abi.encodePacked( + uint256(uint160(addressArray[j])), + storagePosition + ) + ); + bytes32 valueRole = vm.load( + contractAddr, + storagePositionRole + ); + if (valueRole != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: storagePositionRole, value: valueRole}) + ); + } + } + bytes32 roleAdminSlot = bytes32(uint256(storagePosition) + 1); // shift by 1 to get the next slot + bytes32 valueRoleAdminSlot = vm.load( + contractAddr, + roleAdminSlot + ); + if (valueRoleAdminSlot != EMPTY_SLOT_VALUE) { + contractSlots.push( + Slot({slot: roleAdminSlot, value: valueRoleAdminSlot}) + ); + } + } + } + + string memory slotsObj = string.concat("storage slots:", contractName); + string memory slotsOutput; + for (uint256 i = 0; i < contractSlots.length; i++) { + slotsOutput = vm.serializeBytes32( + slotsObj, + vm.toString(contractSlots[i].slot), + contractSlots[i].value + ); + } + // reset contractSlots for next contract + delete contractSlots; + return vm.serializeString(contractObj, "storage", slotsOutput); + } + + function _getDeployerInfo() internal returns (string memory) { + uint256 balance = 0; + if (isTest) { + balance = BALANCE_DEPLOYER; + } + string memory deployerObj = "deployer info"; + vm.serializeString(deployerObj, "accountName", "deployer"); + vm.serializeString( + deployerObj, + "nonce", + _toHexString(vm.getNonce(deployerAddr)) + ); + vm.serializeString(deployerObj, "balance", _toHexString(balance)); + return vm.serializeAddress(deployerObj, "address", deployerAddr); + } + + function _calculateStateRoot( + string memory genesis + ) public returns (bytes32) { + string[] memory operation = new string[](4); + operation[0] = "node"; + operation[1] = "tools/zkevm-commonjs-wrapper"; + operation[2] = "calculateRoot"; + operation[3] = genesis; + + bytes memory result = vm.ffi(operation); + return abi.decode(result, (bytes32)); + } + + function _toHexString(uint256 value) internal pure returns (string memory) { + // Determine the length of the hex string + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp >>= 4; + } + + // Handle case when value is zero + if (digits == 0) return "0x00"; + + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits--; + buffer[digits] = HEX_SYMBOLS[value & 15]; + value >>= 4; + } + + // Add '0x' prefix + return string(abi.encodePacked("0x", buffer)); + } + + function _wrapJson( + string memory json + ) internal pure returns (string memory) { + return string.concat("{", json, "}"); + } + + function _insertStateRoot( + string memory json, + bytes32 stateRoot + ) internal pure returns (string memory) { + return + string.concat( + "{", + '"root":"', + vm.toString(stateRoot), + '",', + json, + "}" + ); + } +} diff --git a/script/inputs/genesisInput.json b/script/inputs/genesisInput.json new file mode 100644 index 000000000..884fb0bc9 --- /dev/null +++ b/script/inputs/genesisInput.json @@ -0,0 +1,12 @@ +{ + "PolygonZkEVMDeployer": "0x68276c6b151e998ed7997035601c0b56a9629743", + "ProxyAdmin": "0x32E260DFae00383Ef927F2e56372F38F441DE3E4", + "PolygonZkEVMBridgeImplementation": "0x851a6a502CF89a03beEAE8B352457b69eaB0216e", + "PolygonZkEVMBridgeProxy": "0x50d3F98bbaCC7d1AcB9266e679B4005524Be0746", + "PolygonZkEVMGlobalExitRootL2Implementation": "0xE73A9D8EAc49a8B20B920122e9230fC81FB1d4b2", + "PolygonZkEVMGlobalExitRootL2Proxy": "0x6738ae51fd2124651c7eb4446ea9141E45A32F00", + "PolygonZkEVMTimelock": "0xe0b4438C77e8E71C142703B088A57d369AeEA136", + "Deployer": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "timelockAdminAddress": "0x9Ab254be10F6Df45E5c58Ba2cE6CB3c9a1aff954", + "isTest": true +} diff --git a/tools/zkevm-commonjs-wrapper.js b/tools/zkevm-commonjs-wrapper.js index 67ce1c69c..1de6992a4 100644 --- a/tools/zkevm-commonjs-wrapper.js +++ b/tools/zkevm-commonjs-wrapper.js @@ -1,5 +1,8 @@ +/* eslint-disable no-undef */ /* eslint-disable no-console */ -const { MTBridge, mtBridgeUtils } = require('@0xpolygonhermez/zkevm-commonjs'); +const { + MemDB, ZkEVMDB, getPoseidon, smtUtils, MTBridge, mtBridgeUtils, +} = require('@0xpolygonhermez/zkevm-commonjs'); const { verifyMerkleProof } = mtBridgeUtils; @@ -43,6 +46,54 @@ function makeTreeAndVerifyProof(height, encodedLeaves, index, root) { return verifyMerkleProof(leaves[index], proof, index, root); } +async function calculateRoot(genesisJson) { + const parsedGenesis = JSON.parse(genesisJson); + + const genesis = []; + // eslint-disable-next-line no-restricted-syntax + for (entry of parsedGenesis.genesis) { + if (entry.contractName !== null) { + genesis.push({ + contractName: entry.contractName, + balance: BigInt(entry.balance), + nonce: BigInt(entry.nonce), + address: entry.address, + bytecode: entry.bytecode, + storage: entry.storage, + }); + } else if (entry.accountName !== null) { + genesis.push({ + accountName: entry.accountName, + balance: BigInt(entry.balance), + nonce: BigInt(entry.nonce), + address: entry.address, + }); + } + } + + const poseidon = await getPoseidon(); + const { F } = poseidon; + const db = new MemDB(F); + const genesisRoot = [F.zero, F.zero, F.zero, F.zero]; + const accHashInput = [F.zero, F.zero, F.zero, F.zero]; + const defaultChainId = 1000; + + const zkEVMDB = await ZkEVMDB.newZkEVM( + db, + poseidon, + genesisRoot, + accHashInput, + genesis, + null, + null, + defaultChainId, + ); + + const root = smtUtils.h4toString(zkEVMDB.stateRoot); + console.log(root); + return root; +} + function main(args) { const [command, ...rest] = args; switch (command) { @@ -52,6 +103,8 @@ function main(args) { return makeTreeAndGetProofByIndex(...rest); case 'makeTreeAndVerifyProof': return makeTreeAndVerifyProof(...rest); + case 'calculateRoot': + return calculateRoot(...rest); default: throw new Error('Usage: zkevm-commonjs-wrapper.js '); }