diff --git a/deployment/testnet/prepareTestnet.ts b/deployment/testnet/prepareTestnet.ts index 5f208fb87..0a45e9d03 100644 --- a/deployment/testnet/prepareTestnet.ts +++ b/deployment/testnet/prepareTestnet.ts @@ -132,6 +132,21 @@ async function main() { } deployParameters.polTokenAddress = polTokenContract.target; + + /* + * Deployment gasToken address + * A erc20 is deployed in this testnet in case it's wanted to deploy a rollup that uses this token as the gas token + */ + const gasTokenName = "Gas Token"; + const gasTokenSymbol = "GAS"; + + const gasTokenFactory = await ethers.getContractFactory("ERC20", deployer); + const gasTokenContract = await gasTokenFactory.deploy(gasTokenName, gasTokenSymbol); + await gasTokenContract.waitForDeployment(); + deployParameters.gasTokenAddress = gasTokenContract.target; + console.log("#######################\n"); + console.log("gas token deployed to:", gasTokenContract.target); + fs.writeFileSync(pathDeployParameters, JSON.stringify(deployParameters, null, 1)); } diff --git a/deployment/v2/4_createRollup.ts b/deployment/v2/4_createRollup.ts index 7ae35ca3d..48937bfd1 100644 --- a/deployment/v2/4_createRollup.ts +++ b/deployment/v2/4_createRollup.ts @@ -19,6 +19,7 @@ import "../helpers/utils"; import updateVanillaGenesis from "./utils/updateVanillaGenesis"; const pathOutputJson = path.join(__dirname, "./create_rollup_output.json"); +const deployParameters = require("./deploy_parameters.json"); import { PolygonRollupManager, @@ -262,12 +263,19 @@ async function main() { ) as PolygonZkEVMBridgeV2; if ( createRollupParameters.gasTokenAddress && - createRollupParameters.gasTokenAddress != "" && - createRollupParameters.gasTokenAddress != ethers.ZeroAddress + createRollupParameters.gasTokenAddress !== "" && + createRollupParameters.gasTokenAddress !== ethers.ZeroAddress ) { + // If gas token address is "deployed" use the one from deploy parameters, erc20 deployed at prepare testnet script + if(createRollupParameters.gasTokenAddress == "deployed") { + createRollupParameters.gasTokenAddress = deployParameters.gasTokenAddress; + } // Get token metadata gasTokenMetadata = await polygonZkEVMBridgeContract.getTokenMetadata(createRollupParameters.gasTokenAddress); - + // If gas token metadata includes `0x124e4f545f56414c49445f454e434f44494e47 (NOT_VALID_ENCODING)` means there is no erc20 token deployed at the selected gas token network + if(gasTokenMetadata.includes("124e4f545f56414c49445f454e434f44494e47")) { + throw new Error(`Invalid gas token address, no ERC20 token deployed at the selected gas token network ${createRollupParameters.gasTokenAddress}`); + } const wrappedData = await polygonZkEVMBridgeContract.wrappedTokenToTokenInfo( createRollupParameters.gasTokenAddress ); @@ -382,6 +390,15 @@ async function main() { globalExitRootUpdater: sovereignParams.globalExitRootUpdater, }; genesis = await updateVanillaGenesis(genesis, chainID, initializeParams); + // Add weth address to deployment output if gas token address is provided and sovereignWETHAddress is not provided + if ( gasTokenAddress !== ethers.ZeroAddress && + ethers.isAddress(gasTokenAddress) && + (sovereignParams.sovereignWETHAddress === ethers.ZeroAddress || !ethers.isAddress(sovereignParams.sovereignWETHAddress))) { + const wethObject = genesis.genesis.find(function (obj) { + return obj.contractName == "WETH"; + }); + outputJson.WETHAddress = wethObject.address; + } } else { if (consensusContract === "PolygonPessimisticConsensus") { // Add the first batch of the created rollup diff --git a/deployment/v2/utils/updateVanillaGenesis.ts b/deployment/v2/utils/updateVanillaGenesis.ts index 780524352..986378e83 100644 --- a/deployment/v2/utils/updateVanillaGenesis.ts +++ b/deployment/v2/utils/updateVanillaGenesis.ts @@ -174,8 +174,13 @@ async function updateVanillaGenesis(genesis, chainID, initializeParams) { bridgeProxy.contractName = bridgeContractName + " proxy"; bridgeProxy.storage = await zkEVMDB2.dumpStorage(bridgeProxy.address); // If bridge initialized with a zero sovereign weth address and a non zero gas token, we should add created erc20 weth contract to the genesis - if (gasTokenAddress !== ethers.ZeroAddress && sovereignWETHAddress === ethers.ZeroAddress) { - const wethAddress = padTo20Bytes( + let wethAddress; + if ( + gasTokenAddress !== ethers.ZeroAddress && + ethers.isAddress(gasTokenAddress) && + (sovereignWETHAddress === ethers.ZeroAddress || !ethers.isAddress(sovereignWETHAddress)) + ) { + wethAddress = padTo20Bytes( bridgeProxy.storage["0x000000000000000000000000000000000000000000000000000000000000006f"] ); const wethGenesis = { @@ -199,38 +204,121 @@ async function updateVanillaGenesis(genesis, chainID, initializeParams) { return acc; }, {}); - // Sanity check bridge storage - if (rollupID !== 0) { - // RollupID value is stored at position 68 with globalExitRootManager address. Slice from byte 2 to 2-8 to get the rollupID - expect( - bridgeProxy.storage["0x0000000000000000000000000000000000000000000000000000000000000068"].slice( - 2 + 54, - 2 + 54 + 8 - ) - ).to.include(rollupID.toString(16)); - } - if (gasTokenAddress !== ethers.ZeroAddress) { + // CHECK BRIDGE PROXY STORAGE + // Storage value pointing bridge implementation + expect(bridgeProxy.storage["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"]).to.include( + oldBridge.address.toLowerCase().slice(2) + ); + + // Storage value of proxyAdmin + const proxyAdminObject = genesis.genesis.find(function (obj) { + return obj.contractName == "ProxyAdmin"; + }); + expect(bridgeProxy.storage["0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"]).to.include( + proxyAdminObject.address.toLowerCase().slice(2) + ); + + // Storage value of bridge manager + expect(bridgeProxy.storage["0x00000000000000000000000000000000000000000000000000000000000000a3"]).to.include( + bridgeManager.toLowerCase().slice(2) + ); + + // Storage value for the _initialized uint8 variable of Initializable.sol contract, incremented each time the contract is successfully initialized. It also stores the _initializing param set to true when an initialization function is being executed, and it reverts to false once the initialization completed. + expect(bridgeProxy.storage["0x0000000000000000000000000000000000000000000000000000000000000000"]).to.equal( + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + + // Storage value for the _status variable of ReentrancyGuardUpgradeable contract. Tracks the current "status" of the contract to enforce the non-reentrant behavior. Default value is 1 (_NOT_ENTERED) + expect(bridgeProxy.storage["0x0000000000000000000000000000000000000000000000000000000000000001"]).to.equal( + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + + // Storage value for global exit root manager (proxy) address + expect(bridgeProxy.storage["0x0000000000000000000000000000000000000000000000000000000000000068"]).to.include( + gerProxy.address.toLowerCase().slice(2) + ); + + // Storage value for rollup/network id + // RollupID value is stored at position 68 with globalExitRootManager address. Slice from byte 2 to 2-8 to get the rollupID + expect( + bridgeProxy.storage["0x0000000000000000000000000000000000000000000000000000000000000068"].slice( + 2 + 54, + 2 + 54 + 8 + ) + ).to.include(rollupID.toString(16)); + + // Storage value for gas token address + if (gasTokenAddress !== ethers.ZeroAddress && ethers.isAddress(gasTokenAddress)) { expect( ethers.toBigInt(bridgeProxy.storage["0x000000000000000000000000000000000000000000000000000000000000006d"]) ).to.equal( ethers.toBigInt(`${ethers.toBeHex(gasTokenNetwork)}${gasTokenAddress.replace(/^0x/, "")}`.toLowerCase()) ); + if (ethers.isAddress(sovereignWETHAddress) && sovereignWETHAddress !== ethers.ZeroAddress) { + // Storage value for sovereignWETH address (ony if network with native gas token) and sovereignWethAddress is set + expect( + bridgeProxy.storage["0x000000000000000000000000000000000000000000000000000000000000006f"] + ).to.include(sovereignWETHAddress.toLowerCase().slice(2)); + + // Storage address for sovereignWETHAddressIsNotMintable mapping + // To get the key we encode the key of the mapping with the position in the mapping + if (sovereignWETHAddressIsNotMintable) { + const mappingSlot = 162; // Slot of the mapping in the bridge contract + const key = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["address", "uint256"], [sovereignWETHAddress, mappingSlot])); + expect( + bridgeProxy.storage[key] + ).to.equal("0x0000000000000000000000000000000000000000000000000000000000000001"); + } + } else { + // Storage value for WETH address (ony if network with native gas token), deployed at bridge initialization + expect( + bridgeProxy.storage["0x000000000000000000000000000000000000000000000000000000000000006f"] + ).to.include(wethAddress.toLowerCase().slice(2)); + + // CHECK WETH STORAGE + const wethOject = genesis.genesis.find(function (obj) { + return obj.contractName == "WETH"; + }); + + // Storage for erc20 name 'Wrapped Ether' + expect(wethOject.storage["0x0000000000000000000000000000000000000000000000000000000000000003"]).to.equal( + "0x577261707065642045746865720000000000000000000000000000000000001a" + ); + + // Storage for erc20 code 'WETH' + expect(wethOject.storage["0x0000000000000000000000000000000000000000000000000000000000000004"]).to.equal( + "0x5745544800000000000000000000000000000000000000000000000000000008" + ); + } } - if (sovereignWETHAddress !== ethers.ZeroAddress) { - expect(bridgeProxy.storage["0x000000000000000000000000000000000000000000000000000000000000006f"]).to.include( - sovereignWETHAddress.toLowerCase().slice(2) - ); - } - if (sovereignWETHAddressIsNotMintable) { - expect(bridgeProxy.storage["0xc7edf51165adec508a5250d96d0588939529f9442a12e2ffa25d7692caac0ef0"]).to.equal( - "0x0000000000000000000000000000000000000000000000000000000000000001" - ); - } - expect(bridgeProxy.storage["0x0000000000000000000000000000000000000000000000000000000000000068"]).to.include( - gerProxy.address.toLowerCase().slice(2) + + // Storage values for gasTokenMetadata, its a bytes variable + let offset = 2 + 64; + expect(bridgeProxy.storage["0x9930d9ff0dee0ef5ca2f7710ea66b8f84dd0f5f5351ecffe72b952cd9db7142a"]).to.include( + gasTokenMetadata.slice(2, offset) ); - expect(bridgeProxy.storage["0x00000000000000000000000000000000000000000000000000000000000000a3"]).to.include( - bridgeManager.toLowerCase().slice(2) + expect(bridgeProxy.storage["0x9930d9ff0dee0ef5ca2f7710ea66b8f84dd0f5f5351ecffe72b952cd9db7142b"]).to.include( + gasTokenMetadata.slice(offset, offset + 64) + ); + offset += 64; + expect(bridgeProxy.storage["0x9930d9ff0dee0ef5ca2f7710ea66b8f84dd0f5f5351ecffe72b952cd9db7142c"]).to.include( + gasTokenMetadata.slice(offset, offset + 64) + ); + offset += 64; + expect(bridgeProxy.storage["0x9930d9ff0dee0ef5ca2f7710ea66b8f84dd0f5f5351ecffe72b952cd9db7142d"]).to.include( + gasTokenMetadata.slice(offset, offset + 64) + ); + offset += 64; + expect(bridgeProxy.storage["0x9930d9ff0dee0ef5ca2f7710ea66b8f84dd0f5f5351ecffe72b952cd9db7142e"]).to.include( + gasTokenMetadata.slice(offset, offset + 64) + ); + offset += 64; + expect(bridgeProxy.storage["0x9930d9ff0dee0ef5ca2f7710ea66b8f84dd0f5f5351ecffe72b952cd9db7142f"]).to.include( + gasTokenMetadata.slice(offset, offset + 64) + ); + offset += 64; + expect(bridgeProxy.storage["0x9930d9ff0dee0ef5ca2f7710ea66b8f84dd0f5f5351ecffe72b952cd9db71430"]).to.include( + gasTokenMetadata.slice(offset, offset + 64) ); // Check bridgeAddress is included in ger bytecode @@ -244,10 +332,22 @@ async function updateVanillaGenesis(genesis, chainID, initializeParams) { return acc; }, {}); - // Sanity check ger storage + // CHECK GER PROXY STORAGE + // Storage value of proxy implementation + expect(gerProxy.storage["0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"]).to.include( + oldGer.address.toLowerCase().slice(2) + ); + + // Storage value of proxyAdmin + expect(gerProxy.storage["0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"]).to.include( + proxyAdminObject.address.toLowerCase().slice(2) + ); + + // Storage value of global exit root updater expect(gerProxy.storage["0x0000000000000000000000000000000000000000000000000000000000000034"]).to.include( globalExitRootUpdater.toLowerCase().slice(2) ); + // Create a new zkEVM to generate a genesis an empty system address storage const zkEVMDB3 = await ZkEVMDB.newZkEVM( new MemDB(F),