diff --git a/packages/contracts/index.ts b/packages/contracts/index.ts index c6f9852b0cde1..6f39cd49b29ea 100644 --- a/packages/contracts/index.ts +++ b/packages/contracts/index.ts @@ -1,12 +1 @@ -import * as path from 'path' -import { ethers } from 'ethers' -import { Interface } from 'ethers/lib/utils' - -export const getContractDefinition = (name: string): any => { - return require(path.join(__dirname, 'artifacts', `${name}.json`)) -} - -export const getContractInterface = (name: string): Interface => { - const definition = getContractDefinition(name) - return new ethers.utils.Interface(definition.abi) -} +export * from './src' diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 7f80cbd3d5435..3efbb6517c97a 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -23,7 +23,7 @@ "scripts": { "all": "yarn clean && yarn build && yarn test && yarn fix && yarn lint", "test": "yarn run test:contracts", - "test:contracts": "buidler test --show-stack-traces", + "test:contracts": "buidler test \"test/deployment/deployment.spec.ts\" --show-stack-traces", "coverage": "yarn run coverage:contracts", "coverage:contracts": "buidler coverage --network coverage --show-stack-traces --testfiles \"test/contracts/**/*.spec.ts\"", "build": "yarn run build:contracts && yarn run build:typescript", diff --git a/packages/contracts/src/contract-imports.ts b/packages/contracts/src/contract-imports.ts new file mode 100644 index 0000000000000..584fae493a5f1 --- /dev/null +++ b/packages/contracts/src/contract-imports.ts @@ -0,0 +1,21 @@ +import * as path from 'path' +import { ethers, ContractFactory, Signer } from 'ethers' +import { Interface } from 'ethers/lib/utils' + +export const getContractDefinition = (name: string): any => { + return require(path.join(__dirname, '../artifacts', `${name}.json`)) +} + +export const getContractInterface = (name: string): Interface => { + const definition = getContractDefinition(name) + return new ethers.utils.Interface(definition.abi) +} + +export const getContractFactory = ( + name: string, + signer?: Signer +): ContractFactory => { + const definition = getContractDefinition(name) + const contractInterface = getContractInterface(name) + return new ContractFactory(contractInterface, definition.bytecode, signer) +} diff --git a/packages/contracts/src/deployment/contract-deploy.ts b/packages/contracts/src/deployment/contract-deploy.ts new file mode 100644 index 0000000000000..8147ba2100519 --- /dev/null +++ b/packages/contracts/src/deployment/contract-deploy.ts @@ -0,0 +1,86 @@ +/* External Imports */ +import { Contract } from 'ethers' + +/* Internal Imports */ +import { getContractFactory } from '../contract-imports' +import { mergeDefaultConfig } from './default-config' +import { + ContractDeployOptions, + RollupDeployConfig, + factoryToContractName, + AddressResolverMapping, +} from './types' + +/** + * Deploys a single contract. + * @param config Contract deployment configuration. + * @return Deployed contract. + */ +const deployContract = async ( + config: ContractDeployOptions +): Promise => { + config.factory = config.factory.connect(config.signer) + const deployedContract = await config.factory.deploy(...config.params) + return deployedContract +} + +/** + * Deploys a contract and registers it with the address resolver. + * @param addressResolver Address resolver to register to. + * @param signer Wallet to deploy the contract from. + * @param name Name of the contract within the resolver. + * @param deployConfig Contract deployment configuration. + * @returns Ethers Contract instance. + */ +export const deployAndRegister = async ( + addressResolver: Contract, + name: string, + deployConfig: ContractDeployOptions +): Promise => { + const deployedContract = await deployContract(deployConfig) + await addressResolver.setAddress(name, deployedContract.address) + return deployedContract +} + +/** + * Deploys all contracts according to a config. + * @param config Contract deployment config. + * @return AddressResolver and all other contracts. + */ +export const deployAllContracts = async ( + config: RollupDeployConfig +): Promise => { + if (!config.addressResolverConfig) { + config.addressResolverConfig = { + factory: getContractFactory('AddressResolver'), + params: [], + signer: config.signer, + } + } + + const addressResolver = await deployContract(config.addressResolverConfig) + + const deployConfig = await mergeDefaultConfig( + config.contractDeployConfig, + addressResolver, + config.signer, + config.rollupOptions + ) + + const contracts: any = {} + for (const name of Object.keys(deployConfig)) { + if (!config.dependencies || config.dependencies.includes(name as any)) { + const contractName = factoryToContractName[name] + contracts[contractName] = await deployAndRegister( + addressResolver, + name, + deployConfig[name] + ) + } + } + + return { + addressResolver, + contracts, + } +} diff --git a/packages/contracts/src/deployment/default-config.ts b/packages/contracts/src/deployment/default-config.ts new file mode 100644 index 0000000000000..097ba4dabfdb9 --- /dev/null +++ b/packages/contracts/src/deployment/default-config.ts @@ -0,0 +1,117 @@ +/* External Imports */ +import { Contract, Signer } from 'ethers' + +/* Internal Imports */ +import { getContractFactory } from '../contract-imports' +import { ContractDeployConfig, RollupOptions } from './types' + +/** + * Generates the default deployment configuration. Runs as an async function + * because we need to get the contract factories async via buidler. + * @param addressResolver Address resolver contract to connect to. + * @returns Default address resolver deployment configuration. + */ +export const getDefaultContractDeployConfig = async ( + addressResolver: Contract, + wallet: Signer, + options: RollupOptions +): Promise => { + return { + L1ToL2TransactionQueue: { + factory: getContractFactory('L1ToL2TransactionQueue'), + params: [ + addressResolver.address, + await options.l1ToL2TransactionPasser.getAddress(), + ], + signer: wallet, + }, + SafetyTransactionQueue: { + factory: getContractFactory('SafetyTransactionQueue'), + params: [addressResolver.address], + signer: wallet, + }, + CanonicalTransactionChain: { + factory: getContractFactory('CanonicalTransactionChain'), + params: [ + addressResolver.address, + await options.sequencer.getAddress(), + await options.l1ToL2TransactionPasser.getAddress(), + options.forceInclusionPeriod, + ], + signer: wallet, + }, + StateCommitmentChain: { + factory: getContractFactory('StateCommitmentChain'), + params: [addressResolver.address], + signer: wallet, + }, + StateManager: { + factory: getContractFactory('FullStateManager'), + params: [], + signer: wallet, + }, + ExecutionManager: { + factory: getContractFactory('ExecutionManager'), + params: [ + addressResolver.address, + await options.owner.getAddress(), + options.gasLimit, + ], + signer: wallet, + }, + SafetyChecker: { + factory: getContractFactory('StubSafetyChecker'), + params: [], + signer: wallet, + }, + FraudVerifier: { + factory: getContractFactory('FraudVerifier'), + params: [addressResolver.address, true], + signer: wallet, + }, + ContractAddressGenerator: { + factory: getContractFactory('ContractAddressGenerator'), + params: [], + signer: wallet, + }, + EthMerkleTrie: { + factory: getContractFactory('EthMerkleTrie'), + params: [], + signer: wallet, + }, + RLPEncode: { + factory: getContractFactory('RLPEncode'), + params: [], + signer: wallet, + }, + RollupMerkleUtils: { + factory: getContractFactory('RollupMerkleUtils'), + params: [], + signer: wallet, + }, + } +} + +/** + * Merges the given config with the default config. + * @param config Config to merge with default. + * @param addressResolver AddressResolver contract reference. + * @param signer Signer to use to deploy contracts. + * @param options Rollup chain options. + */ +export const mergeDefaultConfig = async ( + config: Partial, + addressResolver: Contract, + signer?: Signer, + options?: RollupOptions +): Promise => { + const defaultConfig = await getDefaultContractDeployConfig( + addressResolver, + signer, + options + ) + return { + ...defaultConfig, + ...config, + } +} diff --git a/packages/contracts/src/deployment/index.ts b/packages/contracts/src/deployment/index.ts new file mode 100644 index 0000000000000..086de396b196b --- /dev/null +++ b/packages/contracts/src/deployment/index.ts @@ -0,0 +1 @@ +export * from './contract-deploy' diff --git a/packages/contracts/src/deployment/types.ts b/packages/contracts/src/deployment/types.ts new file mode 100644 index 0000000000000..671cf7d8e14ff --- /dev/null +++ b/packages/contracts/src/deployment/types.ts @@ -0,0 +1,88 @@ +/* External Imports */ +import { Contract, ContractFactory, Signer } from 'ethers' + +export interface ContractDeployOptions { + factory: ContractFactory + params: any[] + signer: Signer +} + +export interface RollupOptions { + gasLimit: number + forceInclusionPeriod: number + owner: Signer + sequencer: Signer + l1ToL2TransactionPasser: Signer +} + +export type ContractFactoryName = + | 'L1ToL2TransactionQueue' + | 'SafetyTransactionQueue' + | 'CanonicalTransactionChain' + | 'StateCommitmentChain' + | 'StateManager' + | 'ExecutionManager' + | 'SafetyChecker' + | 'FraudVerifier' + | 'ContractAddressGenerator' + | 'EthMerkleTrie' + | 'RLPEncode' + | 'RollupMerkleUtils' + +export interface ContractDeployConfig { + L1ToL2TransactionQueue: ContractDeployOptions + SafetyTransactionQueue: ContractDeployOptions + CanonicalTransactionChain: ContractDeployOptions + StateCommitmentChain: ContractDeployOptions + StateManager: ContractDeployOptions + ExecutionManager: ContractDeployOptions + SafetyChecker: ContractDeployOptions + FraudVerifier: ContractDeployOptions + ContractAddressGenerator: ContractDeployOptions + EthMerkleTrie: ContractDeployOptions + RLPEncode: ContractDeployOptions + RollupMerkleUtils: ContractDeployOptions +} + +interface ContractMapping { + l1ToL2TransactionQueue: Contract + safetyTransactionQueue: Contract + canonicalTransactionChain: Contract + stateCommitmentChain: Contract + stateManager: Contract + executionManager: Contract + safetyChecker: Contract + fraudVerifier: Contract + contractAddressGenerator: Contract + ethMerkleTrie: Contract + rlpEncode: Contract + rollupMerkleUtils: Contract +} + +export interface AddressResolverMapping { + addressResolver: Contract + contracts: ContractMapping +} + +export const factoryToContractName = { + L1ToL2TransactionQueue: 'l1ToL2TransactionQueue', + SafetyTransactionQueue: 'safetyTransactionQueue', + CanonicalTransactionChain: 'canonicalTransactionChain', + StateCommitmentChain: 'stateCommitmentChain', + StateManager: 'stateManager', + ExecutionManager: 'executionManager', + SafetyChecker: 'safetyChecker', + FraudVerifier: 'fraudVerifier', + ContractAddressGenerator: 'contractAddressGenerator', + EthMerkleTrie: 'ethMerkleTrie', + RLPEncode: 'rlpEncode', + RollupMerkleUtils: 'rollupMerkleUtils', +} + +export interface RollupDeployConfig { + signer: Signer + rollupOptions: RollupOptions + addressResolverConfig?: ContractDeployOptions + contractDeployConfig?: Partial + dependencies?: ContractFactoryName[] +} diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts new file mode 100644 index 0000000000000..93e896037614b --- /dev/null +++ b/packages/contracts/src/index.ts @@ -0,0 +1,2 @@ +export * from './deployment' +export * from './contract-imports' diff --git a/packages/contracts/test/deployment/deployment.spec.ts b/packages/contracts/test/deployment/deployment.spec.ts new file mode 100644 index 0000000000000..6d1a97f0d9dc6 --- /dev/null +++ b/packages/contracts/test/deployment/deployment.spec.ts @@ -0,0 +1,45 @@ +import { expect } from '../setup' + +/* External Imports */ +import { ethers } from '@nomiclabs/buidler' + +/* Internal Imports */ +import { deployAllContracts } from '../../src' +import { + RollupDeployConfig, + factoryToContractName, +} from '../../src/deployment/types' +import { Signer } from 'ethers' +import { GAS_LIMIT, DEFAULT_FORCE_INCLUSION_PERIOD } from '../test-helpers' + +describe('Contract Deployment', () => { + let wallet: Signer + let sequencer: Signer + let l1ToL2TransactionPasser: Signer + before(async () => { + ;[wallet, sequencer, l1ToL2TransactionPasser] = await ethers.getSigners() + }) + + describe('deployAllContracts', () => { + it('should deploy contracts in a default configuration', async () => { + const config: RollupDeployConfig = { + signer: wallet, + rollupOptions: { + gasLimit: GAS_LIMIT, + forceInclusionPeriod: DEFAULT_FORCE_INCLUSION_PERIOD, + owner: wallet, + sequencer, + l1ToL2TransactionPasser, + }, + } + + const resolver = await deployAllContracts(config) + + expect( + Object.values(factoryToContractName).every((contractName) => { + return contractName in resolver.contracts + }) + ).to.be.true + }) + }) +})