diff --git a/package.json b/package.json index 475926f8..a882d554 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "postinstall": "patch-package", "deploy-factory": "hardhat run scripts/deployment.ts", "deploy-eth-rollup": "hardhat run scripts/createEthRollup.ts", - "deploy-erc20-rollup": "hardhat run scripts/createERC20Rollup.ts" + "deploy-erc20-rollup": "hardhat run scripts/createERC20Rollup.ts", + "create-rollup-testnode": "hardhat run scripts/local-deployment/deployCreatorAndCreateRollup.ts" }, "dependencies": { "@offchainlabs/upgrade-executor": "1.1.0-beta.0", diff --git a/scripts/config.ts.example b/scripts/config.ts.example index cf5d8704..78d020e0 100644 --- a/scripts/config.ts.example +++ b/scripts/config.ts.example @@ -29,5 +29,6 @@ export const config = { '0x1234123412341234123412341234123412341234', '0x1234512345123451234512345123451234512345', ], - batchPoster: '0x1234123412341234123412341234123412341234', + batchPosters: ['0x1234123412341234123412341234123412341234'], + batchPosterManager: '0x1234123412341234123412341234123412341234' } diff --git a/scripts/createERC20Rollup.ts b/scripts/createERC20Rollup.ts index 604fa17e..49947b27 100644 --- a/scripts/createERC20Rollup.ts +++ b/scripts/createERC20Rollup.ts @@ -29,8 +29,18 @@ async function main() { ) } + const rollupCreatorAddress = process.env.ROLLUP_CREATOR_ADDRESS + if (!rollupCreatorAddress) { + throw new Error('ROLLUP_CREATOR_ADDRESS not set') + } + console.log('Creating new rollup with', customFeeTokenAddress, 'as fee token') - await createRollup(customFeeTokenAddress) + await createRollup( + deployer, + false, + rollupCreatorAddress, + customFeeTokenAddress + ) } main() diff --git a/scripts/createEthRollup.ts b/scripts/createEthRollup.ts index eb11e83d..4248ac21 100644 --- a/scripts/createEthRollup.ts +++ b/scripts/createEthRollup.ts @@ -1,8 +1,17 @@ +import { ethers } from 'hardhat' import '@nomiclabs/hardhat-ethers' import { createRollup } from './rollupCreation' async function main() { - await createRollup() + const feeToken = undefined + const rollupCreatorAddress = process.env.ROLLUP_CREATOR_ADDRESS + if (!rollupCreatorAddress) { + throw new Error('ROLLUP_CREATOR_ADDRESS not set') + } + + const [signer] = await ethers.getSigners() + + await createRollup(signer, false, rollupCreatorAddress, feeToken) } main() diff --git a/scripts/deployment.ts b/scripts/deployment.ts index fdb51877..8da3acc2 100644 --- a/scripts/deployment.ts +++ b/scripts/deployment.ts @@ -1,13 +1,18 @@ import { ethers } from 'hardhat' import '@nomiclabs/hardhat-ethers' import { deployAllContracts } from './deploymentUtils' +import { maxDataSize } from './config' async function main() { const [signer] = await ethers.getSigners() try { // Deploying all contracts - const contracts = await deployAllContracts(signer) + const contracts = await deployAllContracts( + signer, + ethers.BigNumber.from(maxDataSize), + true + ) // Call setTemplates with the deployed contract addresses console.log('Waiting for the Template to be set on the Rollup Creator') diff --git a/scripts/deploymentUtils.ts b/scripts/deploymentUtils.ts index 2df51be6..4c521270 100644 --- a/scripts/deploymentUtils.ts +++ b/scripts/deploymentUtils.ts @@ -1,12 +1,11 @@ import { ethers } from 'hardhat' -import { ContractFactory, Contract, Overrides } from 'ethers' +import { ContractFactory, Contract, Overrides, BigNumber } from 'ethers' import '@nomiclabs/hardhat-ethers' import { run } from 'hardhat' import { abi as UpgradeExecutorABI, bytecode as UpgradeExecutorBytecode, } from '@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json' -import { maxDataSize } from './config' import { Toolkit4844 } from '../test/contract/toolkit4844' import { ArbSys__factory } from '../build/types' import { ARB_SYS_ADDRESS } from '@arbitrum/sdk/dist/lib/dataEntities/constants' @@ -87,80 +86,138 @@ export async function deployUpgradeExecutor(signer: any): Promise { // Function to handle all deployments of core contracts using deployContract function export async function deployAllContracts( - signer: any + signer: any, + maxDataSize: BigNumber, + verify: boolean = true ): Promise> { const isOnArb = await _isRunningOnArbitrum(signer) - const ethBridge = await deployContract('Bridge', signer, []) + const ethBridge = await deployContract('Bridge', signer, [], verify) const reader4844 = isOnArb ? ethers.constants.AddressZero : (await Toolkit4844.deployReader4844(signer)).address - const ethSequencerInbox = await deployContract('SequencerInbox', signer, [ - maxDataSize, - reader4844, - false, - ]) + const ethSequencerInbox = await deployContract( + 'SequencerInbox', + signer, + [maxDataSize, reader4844, false], + verify + ) - const ethInbox = await deployContract('Inbox', signer, [maxDataSize]) + const ethInbox = await deployContract('Inbox', signer, [maxDataSize], verify) const ethRollupEventInbox = await deployContract( 'RollupEventInbox', signer, - [] + [], + verify ) - const ethOutbox = await deployContract('Outbox', signer, []) + const ethOutbox = await deployContract('Outbox', signer, [], verify) - const erc20Bridge = await deployContract('ERC20Bridge', signer, []) - const erc20SequencerInbox = await deployContract('SequencerInbox', signer, [ - maxDataSize, - reader4844, - true, - ]) - const erc20Inbox = await deployContract('ERC20Inbox', signer, [maxDataSize]) + const erc20Bridge = await deployContract('ERC20Bridge', signer, [], verify) + const erc20SequencerInbox = await deployContract( + 'SequencerInbox', + signer, + [maxDataSize, reader4844, true], + verify + ) + const erc20Inbox = await deployContract( + 'ERC20Inbox', + signer, + [maxDataSize], + verify + ) const erc20RollupEventInbox = await deployContract( 'ERC20RollupEventInbox', signer, - [] + [], + verify ) - const erc20Outbox = await deployContract('ERC20Outbox', signer, []) + const erc20Outbox = await deployContract('ERC20Outbox', signer, [], verify) - const bridgeCreator = await deployContract('BridgeCreator', signer, [ + const bridgeCreator = await deployContract( + 'BridgeCreator', + signer, [ - ethBridge.address, - ethSequencerInbox.address, - ethInbox.address, - ethRollupEventInbox.address, - ethOutbox.address, + [ + ethBridge.address, + ethSequencerInbox.address, + ethInbox.address, + ethRollupEventInbox.address, + ethOutbox.address, + ], + [ + erc20Bridge.address, + erc20SequencerInbox.address, + erc20Inbox.address, + erc20RollupEventInbox.address, + erc20Outbox.address, + ], ], + verify + ) + const prover0 = await deployContract('OneStepProver0', signer, [], verify) + const proverMem = await deployContract( + 'OneStepProverMemory', + signer, + [], + verify + ) + const proverMath = await deployContract( + 'OneStepProverMath', + signer, + [], + verify + ) + const proverHostIo = await deployContract( + 'OneStepProverHostIo', + signer, + [], + verify + ) + const osp: Contract = await deployContract( + 'OneStepProofEntry', + signer, [ - erc20Bridge.address, - erc20SequencerInbox.address, - erc20Inbox.address, - erc20RollupEventInbox.address, - erc20Outbox.address, + prover0.address, + proverMem.address, + proverMath.address, + proverHostIo.address, ], - ]) - const prover0 = await deployContract('OneStepProver0', signer) - const proverMem = await deployContract('OneStepProverMemory', signer) - const proverMath = await deployContract('OneStepProverMath', signer) - const proverHostIo = await deployContract('OneStepProverHostIo', signer) - const osp: Contract = await deployContract('OneStepProofEntry', signer, [ - prover0.address, - proverMem.address, - proverMath.address, - proverHostIo.address, - ]) - const challengeManager = await deployContract('ChallengeManager', signer) - const rollupAdmin = await deployContract('RollupAdminLogic', signer) - const rollupUser = await deployContract('RollupUserLogic', signer) + verify + ) + const challengeManager = await deployContract( + 'ChallengeManager', + signer, + [], + verify + ) + const rollupAdmin = await deployContract( + 'RollupAdminLogic', + signer, + [], + verify + ) + const rollupUser = await deployContract('RollupUserLogic', signer, [], verify) const upgradeExecutor = await deployUpgradeExecutor(signer) - const validatorUtils = await deployContract('ValidatorUtils', signer) + const validatorUtils = await deployContract( + 'ValidatorUtils', + signer, + [], + verify + ) const validatorWalletCreator = await deployContract( 'ValidatorWalletCreator', - signer + signer, + [], + verify + ) + const rollupCreator = await deployContract( + 'RollupCreator', + signer, + [], + verify ) - const rollupCreator = await deployContract('RollupCreator', signer) - const deployHelper = await deployContract('DeployHelper', signer) + const deployHelper = await deployContract('DeployHelper', signer, [], verify) return { bridgeCreator, prover0, @@ -180,7 +237,7 @@ export async function deployAllContracts( } // Check if we're deploying to an Arbitrum chain -async function _isRunningOnArbitrum(signer: any): Promise { +export async function _isRunningOnArbitrum(signer: any): Promise { const arbSys = ArbSys__factory.connect(ARB_SYS_ADDRESS, signer) try { await arbSys.arbOSVersion() diff --git a/scripts/local-deployment/deployCreatorAndCreateRollup.ts b/scripts/local-deployment/deployCreatorAndCreateRollup.ts new file mode 100644 index 00000000..0b7f7367 --- /dev/null +++ b/scripts/local-deployment/deployCreatorAndCreateRollup.ts @@ -0,0 +1,117 @@ +import { ethers } from 'hardhat' +import '@nomiclabs/hardhat-ethers' +import { deployAllContracts } from '../deploymentUtils' +import { createRollup } from '../rollupCreation' +import { promises as fs } from 'fs' +import { BigNumber } from 'ethers' + +async function main() { + /// read env vars needed for deployment + let childChainName = process.env.CHILD_CHAIN_NAME as string + if (!childChainName) { + throw new Error('CHILD_CHAIN_NAME not set') + } + + let deployerPrivKey = process.env.DEPLOYER_PRIVKEY as string + if (!deployerPrivKey) { + throw new Error('DEPLOYER_PRIVKEY not set') + } + + let parentChainRpc = process.env.PARENT_CHAIN_RPC as string + if (!parentChainRpc) { + throw new Error('PARENT_CHAIN_RPC not set') + } + + if (!process.env.PARENT_CHAIN_ID) { + throw new Error('PARENT_CHAIN_ID not set') + } + + const deployerWallet = new ethers.Wallet( + deployerPrivKey, + new ethers.providers.JsonRpcProvider(parentChainRpc) + ) + + const maxDataSize = + process.env.MAX_DATA_SIZE !== undefined + ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) + : ethers.BigNumber.from(117964) + + /// get fee token address, if undefined use address(0) to have ETH as fee token + let feeToken = process.env.FEE_TOKEN_ADDRESS as string + if (!feeToken) { + feeToken = ethers.constants.AddressZero + } + console.log('Fee token address:', feeToken) + + /// deploy templates and rollup creator + console.log('Deploy RollupCreator') + const contracts = await deployAllContracts(deployerWallet, maxDataSize, false) + + console.log('Set templates on the Rollup Creator') + await ( + await contracts.rollupCreator.setTemplates( + contracts.bridgeCreator.address, + contracts.osp.address, + contracts.challengeManager.address, + contracts.rollupAdmin.address, + contracts.rollupUser.address, + contracts.upgradeExecutor.address, + contracts.validatorUtils.address, + contracts.validatorWalletCreator.address, + contracts.deployHelper.address, + { gasLimit: BigNumber.from('300000') } + ) + ).wait() + + /// Create rollup + const chainId = (await deployerWallet.provider.getNetwork()).chainId + console.log( + 'Create rollup on top of chain', + chainId, + 'using RollupCreator', + contracts.rollupCreator.address + ) + const result = await createRollup( + deployerWallet, + true, + contracts.rollupCreator.address, + feeToken + ) + + if (!result) { + throw new Error('Rollup creation failed') + } + + const { rollupCreationResult, chainInfo } = result + + /// store deployment address + // chain deployment info + const chainDeploymentInfo = + process.env.CHAIN_DEPLOYMENT_INFO !== undefined + ? process.env.CHAIN_DEPLOYMENT_INFO + : 'deploy.json' + await fs.writeFile( + chainDeploymentInfo, + JSON.stringify(rollupCreationResult, null, 2), + 'utf8' + ) + + // child chain info + chainInfo['chain-name'] = childChainName + const childChainInfo = + process.env.CHILD_CHAIN_INFO !== undefined + ? process.env.CHILD_CHAIN_INFO + : 'l2_chain_info.json' + await fs.writeFile( + childChainInfo, + JSON.stringify([chainInfo], null, 2), + 'utf8' + ) +} + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/rollupCreation.ts b/scripts/rollupCreation.ts index 82e40cb5..3e1d300c 100644 --- a/scripts/rollupCreation.ts +++ b/scripts/rollupCreation.ts @@ -3,9 +3,11 @@ import '@nomiclabs/hardhat-ethers' import { run } from 'hardhat' import { abi as rollupCreatorAbi } from '../build/contracts/src/rollup/RollupCreator.sol/RollupCreator.json' import { config, maxDataSize } from './config' -import { BigNumber } from 'ethers' +import { BigNumber, Signer } from 'ethers' import { IERC20__factory } from '../build/types' import { sleep } from './testSetup' +import { promises as fs } from 'fs' +import { _isRunningOnArbitrum } from './deploymentUtils' // 1 gwei const MAX_FER_PER_GAS = BigNumber.from('1000000000') @@ -15,6 +17,7 @@ interface RollupCreatedEvent { address: string args?: { rollupAddress: string + nativeToken: string inboxAddress: string outbox: string rollupEventInbox: string @@ -22,47 +25,61 @@ interface RollupCreatedEvent { adminProxy: string sequencerInbox: string bridge: string + upgradeExecutor: string validatorUtils: string validatorWalletCreator: string } } -export async function createRollup(feeToken?: string) { - const rollupCreatorAddress = process.env.ROLLUP_CREATOR_ADDRESS +interface RollupCreationResult { + bridge: string + inbox: string + 'sequencer-inbox': string + 'deployed-at': number + rollup: string + 'native-token': string + 'upgrade-executor': string + 'validator-utils': string + 'validator-wallet-creator': string +} - if (!rollupCreatorAddress) { - console.error( - 'Please provide ROLLUP_CREATOR_ADDRESS as an environment variable.' - ) - process.exit(1) - } +interface ChainInfo { + 'chain-name': string + 'parent-chain-id': number + 'parent-chain-is-arbitrum': boolean + 'chain-config': any + rollup: RollupCreationResult + 'sequencer-url': string + 'secondary-forwarding-target': string + 'feed-url': string + 'secondary-feed-url': string + 'das-index-url': string + 'has-genesis-state': boolean +} +export async function createRollup( + signer: Signer, + isDevDeployment: boolean, + rollupCreatorAddress: string, + feeToken: string +): Promise<{ + rollupCreationResult: RollupCreationResult + chainInfo: ChainInfo +} | null> { if (!rollupCreatorAbi) { throw new Error( 'You need to first run script to deploy and compile the contracts first' ) } - const [signer] = await ethers.getSigners() - const rollupCreator = new ethers.Contract( rollupCreatorAddress, rollupCreatorAbi, signer ) - if (!feeToken) { - feeToken = ethers.constants.AddressZero - } - try { - let vals: boolean[] = [] - for (let i = 0; i < config.validators.length; i++) { - vals.push(true) - } - //// funds for deploying L2 factories - // 0.13 ETH is enough to deploy L2 factories via retryables. Excess is refunded let feeCost = ethers.utils.parseEther('0.13') if (feeToken != ethers.constants.AddressZero) { @@ -78,15 +95,19 @@ export async function createRollup(feeToken?: string) { // Call the createRollup function console.log('Calling createRollup to generate a new rollup ...') - const deployParams = { - config: config.rollupConfig, - batchPoster: config.batchPoster, - validators: config.validators, - maxDataSize: maxDataSize, - nativeToken: feeToken, - deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FER_PER_GAS, - } + const deployParams = isDevDeployment + ? await _getDevRollupConfig(feeToken) + : { + config: config.rollupConfig, + validators: config.validators, + maxDataSize: ethers.BigNumber.from(maxDataSize), + nativeToken: feeToken, + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FER_PER_GAS, + batchPosters: config.batchPosters, + batchPosterManager: config.batchPosterManager, + } + const createRollupTx = await rollupCreator.createRollup(deployParams, { value: feeCost, }) @@ -101,6 +122,7 @@ export async function createRollup(feeToken?: string) { // Checking for RollupCreated event for new rollup address if (rollupCreatedEvent) { const rollupAddress = rollupCreatedEvent.args?.rollupAddress + const nativeToken = rollupCreatedEvent.args?.nativeToken const inboxAddress = rollupCreatedEvent.args?.inboxAddress const outbox = rollupCreatedEvent.args?.outbox const rollupEventInbox = rollupCreatedEvent.args?.rollupEventInbox @@ -108,32 +130,37 @@ export async function createRollup(feeToken?: string) { const adminProxy = rollupCreatedEvent.args?.adminProxy const sequencerInbox = rollupCreatedEvent.args?.sequencerInbox const bridge = rollupCreatedEvent.args?.bridge + const upgradeExecutor = rollupCreatedEvent.args?.upgradeExecutor const validatorUtils = rollupCreatedEvent.args?.validatorUtils const validatorWalletCreator = rollupCreatedEvent.args?.validatorWalletCreator console.log("Congratulations! 🎉🎉🎉 All DONE! Here's your addresses:") console.log('RollupProxy Contract created at address:', rollupAddress) - console.log('Wait a minute before starting the contract verification') - await sleep(1 * 60 * 1000) - console.log( - `Attempting to verify Rollup contract at address ${rollupAddress}...` - ) - try { - await run('verify:verify', { - contract: 'src/rollup/RollupProxy.sol:RollupProxy', - address: rollupAddress, - constructorArguments: [], - }) - } catch (error: any) { - if (error.message.includes('Already Verified')) { - console.log(`Contract RollupProxy is already verified.`) - } else { - console.error( - `Verification for RollupProxy failed with the following error: ${error.message}` - ) + + if (!isDevDeployment) { + console.log('Wait a minute before starting the contract verification') + await sleep(1 * 60 * 1000) + console.log( + `Attempting to verify Rollup contract at address ${rollupAddress}...` + ) + try { + await run('verify:verify', { + contract: 'src/rollup/RollupProxy.sol:RollupProxy', + address: rollupAddress, + constructorArguments: [], + }) + } catch (error: any) { + if (error.message.includes('Already Verified')) { + console.log(`Contract RollupProxy is already verified.`) + } else { + console.error( + `Verification for RollupProxy failed with the following error: ${error.message}` + ) + } } } + console.log('Inbox (proxy) Contract created at address:', inboxAddress) console.log('Outbox (proxy) Contract created at address:', outbox) console.log( @@ -155,6 +182,34 @@ export async function createRollup(feeToken?: string) { const blockNumber = createRollupReceipt.blockNumber console.log('All deployed at block number:', blockNumber) + + const rollupCreationResult: RollupCreationResult = { + bridge: bridge, + inbox: inboxAddress, + 'sequencer-inbox': sequencerInbox, + 'deployed-at': blockNumber, + rollup: rollupAddress, + 'native-token': nativeToken, + 'upgrade-executor': upgradeExecutor, + 'validator-utils': validatorUtils, + 'validator-wallet-creator': validatorWalletCreator, + } + + const chainInfo: ChainInfo = { + 'chain-name': 'dev-chain', + 'parent-chain-id': +process.env.PARENT_CHAIN_ID!, + 'parent-chain-is-arbitrum': await _isRunningOnArbitrum(signer), + 'sequencer-url': '', + 'secondary-forwarding-target': '', + 'feed-url': '', + 'secondary-feed-url': '', + 'das-index-url': '', + 'has-genesis-state': false, + 'chain-config': JSON.parse(deployParams.config.chainConfig), + rollup: rollupCreationResult, + } + + return { rollupCreationResult, chainInfo } } else { console.error('RollupCreated event not found') } @@ -164,4 +219,107 @@ export async function createRollup(feeToken?: string) { error instanceof Error ? error.message : error ) } + + return null +} + +async function _getDevRollupConfig(feeToken: string) { + // set up owner address + const ownerAddress = + process.env.OWNER_ADDRESS !== undefined ? process.env.OWNER_ADDRESS : '' + + // set up max data size + const _maxDataSize = + process.env.MAX_DATA_SIZE !== undefined + ? ethers.BigNumber.from(process.env.MAX_DATA_SIZE) + : ethers.BigNumber.from(117964) + + // set up validators + const authorizeValidators: number = + parseInt(process.env.AUTHORIZE_VALIDATORS as string, 0) || 0 + const validators: string[] = [] + for (let i = 1; i <= authorizeValidators; i++) { + validators.push(ethers.Wallet.createRandom().address) + } + + // get chain config + const childChainConfigPath = + process.env.CHILD_CHAIN_CONFIG_PATH !== undefined + ? process.env.CHILD_CHAIN_CONFIG_PATH + : 'l2_chain_config.json' + + const chainConfig = await fs.readFile(childChainConfigPath, { + encoding: 'utf8', + }) + + // get wasmModuleRoot + const wasmModuleRoot = + process.env.WASM_MODULE_ROOT !== undefined + ? process.env.WASM_MODULE_ROOT + : '' + + // set up batch posters + const sequencerAddress = + process.env.SEQUENCER_ADDRESS !== undefined + ? process.env.SEQUENCER_ADDRESS + : '' + const batchPostersString = + process.env.BATCH_POSTERS !== undefined ? process.env.BATCH_POSTERS : '' + let batchPosters: string[] = [] + if (batchPostersString.length == 0) { + batchPosters.push(sequencerAddress) + } else { + const batchPostesArr = batchPostersString.split(',') + for (let i = 0; i < batchPostesArr.length; i++) { + if (ethers.utils.isAddress(batchPostesArr[i])) { + batchPosters.push(batchPostesArr[i]) + } else { + throw new Error('Invalid address in batch posters array') + } + } + } + + // set up batch poster manager + const batchPosterManagerEnv = + process.env.BATCH_POSTER_MANAGER !== undefined + ? process.env.BATCH_POSTER_MANAGER + : '' + let batchPosterManager = '' + if (ethers.utils.isAddress(batchPosterManagerEnv)) { + batchPosterManager = batchPosterManagerEnv + } else { + if (batchPosterManagerEnv.length == 0) { + batchPosterManager = ownerAddress + } else { + throw new Error('Invalid address for batch poster manager') + } + } + + return { + config: { + confirmPeriodBlocks: ethers.BigNumber.from('20'), + extraChallengeTimeBlocks: ethers.BigNumber.from('200'), + stakeToken: ethers.constants.AddressZero, + baseStake: ethers.utils.parseEther('1'), + wasmModuleRoot: wasmModuleRoot, + owner: ownerAddress, + loserStakeEscrow: ethers.constants.AddressZero, + chainId: JSON.parse(chainConfig)['chainId'], + chainConfig: chainConfig, + genesisBlockNum: 0, + sequencerInboxMaxTimeVariation: { + delayBlocks: ethers.BigNumber.from('5760'), + futureBlocks: ethers.BigNumber.from('12'), + delaySeconds: ethers.BigNumber.from('86400'), + futureSeconds: ethers.BigNumber.from('3600'), + }, + }, + validators: validators, + maxDataSize: _maxDataSize, + nativeToken: feeToken, + deployFactoriesToL2: true, + maxFeePerGasForRetryables: MAX_FER_PER_GAS, + batchPosters: batchPosters, + batchPosterManager: batchPosterManager, + } }