diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index d6e63d75e3ae..1c496078ba07 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -23,7 +23,7 @@ "test:debug": "LOG_LEVEL=${LOG_LEVEL:-verbose} NODE_NO_WARNINGS=1 node --inspect --experimental-vm-modules ../node_modules/.bin/jest --testTimeout=300000 --forceExit", "test:integration": "concurrently -k -s first -c reset,dim -n test,anvil \"yarn test:integration:run\" \"anvil\"", "test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --no-cache --runInBand --config jest.integration.config.json", - "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest src/fixtures", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests src/fixtures", "test:compose": "./scripts/test.sh compose", "formatting": "run -T prettier --check ./src && run -T eslint ./src" }, diff --git a/yarn-project/end-to-end/package.local.json b/yarn-project/end-to-end/package.local.json index 1175a867b28e..2193ade2cf91 100644 --- a/yarn-project/end-to-end/package.local.json +++ b/yarn-project/end-to-end/package.local.json @@ -1,7 +1,7 @@ { "scripts": { "build": "yarn clean && tsc -b && webpack", - "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest src/fixtures" + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests src/fixtures" }, "jest": { "reporters": [ diff --git a/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.test.ts b/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.test.ts deleted file mode 100644 index fc0ac7d818ee..000000000000 --- a/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { type Logger, createLogger } from '@aztec/foundation/log'; - -import { type Anvil } from '@viem/anvil'; -import { type PrivateKeyAccount } from 'viem'; -import { privateKeyToAccount } from 'viem/accounts'; - -import { setupL1Contracts, startAnvil } from './utils.js'; - -describe('deploy_l1_contracts', () => { - let anvil: Anvil; - let rpcUrl: string; - let privateKey: PrivateKeyAccount; - let logger: Logger; - - beforeAll(async () => { - logger = createLogger('e2e:setup_l1_contracts'); - privateKey = privateKeyToAccount('0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'); - - ({ anvil, rpcUrl } = await startAnvil()); - }); - - afterAll(async () => { - await anvil.stop(); - }); - - const deploy = (salt: number | undefined) => setupL1Contracts(rpcUrl, privateKey, logger, { salt }); - - it('deploys without salt', async () => { - await deploy(undefined); - }); - - it('deploys with salt on different addresses', async () => { - const first = await deploy(42); - const second = await deploy(43); - - expect(first.l1ContractAddresses).not.toEqual(second.l1ContractAddresses); - }); - - it('deploys twice with salt on same addresses', async () => { - const first = await deploy(44); - const second = await deploy(44); - - expect(first.l1ContractAddresses).toEqual(second.l1ContractAddresses); - }); -}); diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index f3a34aab5233..ff9745e9b0a8 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -353,7 +353,7 @@ async function setupFromFresh( client: deployL1ContractsValues.publicClient, }); - const blockReward = await rewardDistributor.read.BLOCK_REWARD([]); + const blockReward = await rewardDistributor.read.BLOCK_REWARD(); const mintAmount = 10_000n * (blockReward as bigint); const feeJuice = getContract({ diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 6389f1bb1c33..5e495d4bad10 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -426,7 +426,7 @@ export async function setup( client: deployL1ContractsValues.publicClient, }); - const blockReward = await rewardDistributor.read.BLOCK_REWARD([]); + const blockReward = await rewardDistributor.read.BLOCK_REWARD(); const mintAmount = 10_000n * (blockReward as bigint); const feeJuice = getContract({ diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.test.ts b/yarn-project/ethereum/src/deploy_l1_contracts.test.ts new file mode 100644 index 000000000000..78eb31564809 --- /dev/null +++ b/yarn-project/ethereum/src/deploy_l1_contracts.test.ts @@ -0,0 +1,99 @@ +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { times } from '@aztec/foundation/collection'; +import { EthAddress } from '@aztec/foundation/eth-address'; +import { Fr } from '@aztec/foundation/fields'; +import { type Logger, createLogger } from '@aztec/foundation/log'; +import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi'; + +import { type Anvil } from '@viem/anvil'; +import { getContract } from 'viem'; +import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts'; +import { foundry } from 'viem/chains'; + +import { DefaultL1ContractsConfig } from './config.js'; +import { type DeployL1ContractsArgs, deployL1Contracts } from './deploy_l1_contracts.js'; +import { startAnvil } from './test/start_anvil.js'; + +describe('deploy_l1_contracts', () => { + let anvil: Anvil; + let rpcUrl: string; + let privateKey: PrivateKeyAccount; + let logger: Logger; + + let vkTreeRoot: Fr; + let protocolContractTreeRoot: Fr; + let initialValidators: EthAddress[]; + let l2FeeJuiceAddress: AztecAddress; + + beforeAll(async () => { + logger = createLogger('ethereum:test:deploy_l1_contracts'); + privateKey = privateKeyToAccount('0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'); + vkTreeRoot = Fr.random(); + protocolContractTreeRoot = Fr.random(); + initialValidators = times(3, EthAddress.random); + l2FeeJuiceAddress = AztecAddress.random(); + + ({ anvil, rpcUrl } = await startAnvil()); + }); + + afterAll(async () => { + await anvil.stop(); + }); + + const deploy = (args: Partial = {}) => + deployL1Contracts(rpcUrl, privateKey, foundry, logger, { + ...DefaultL1ContractsConfig, + salt: undefined, + vkTreeRoot, + protocolContractTreeRoot, + l2FeeJuiceAddress, + ...args, + }); + + const getRollup = (deployed: Awaited>) => + getContract({ + address: deployed.l1ContractAddresses.rollupAddress.toString(), + abi: RollupAbi, + client: deployed.publicClient, + }); + + it('deploys without salt', async () => { + await deploy(); + }); + + it('deploys initializing validators', async () => { + const deployed = await deploy({ initialValidators }); + const rollup = getRollup(deployed); + for (const validator of initialValidators) { + const { status } = await rollup.read.getInfo([validator.toString()]); + expect(status).toBeGreaterThan(0); + } + }); + + it('deploys with salt on different addresses', async () => { + const first = await deploy({ salt: 42 }); + const second = await deploy({ salt: 43 }); + + expect(first.l1ContractAddresses).not.toEqual(second.l1ContractAddresses); + }); + + it('deploys twice with salt on same addresses', async () => { + const first = await deploy({ salt: 44 }); + const second = await deploy({ salt: 44 }); + + expect(first.l1ContractAddresses).toEqual(second.l1ContractAddresses); + }); + + it('deploys twice with salt on same addresses initializing validators', async () => { + const first = await deploy({ salt: 44, initialValidators }); + const second = await deploy({ salt: 44, initialValidators }); + + expect(first.l1ContractAddresses).toEqual(second.l1ContractAddresses); + + const rollup = getRollup(first); + for (const validator of initialValidators) { + const { status } = await rollup.read.getInfo([validator.toString()]); + expect(status).toBeGreaterThan(0); + } + }); +}); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 35ff655a03ef..09e49baf2627 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -109,121 +109,67 @@ export interface ContractArtifacts { libraries?: Libraries; } -/** - * All L1 Contract Artifacts for deployment - */ -export interface L1ContractArtifactsForDeployment { - /** - * Inbox contract artifacts - */ - inbox: ContractArtifacts; - /** - * Outbox contract artifacts - */ - outbox: ContractArtifacts; - /** - * Registry contract artifacts - */ - registry: ContractArtifacts; - /** - * Rollup contract artifacts - */ - rollup: ContractArtifacts; - /** - * The token to stake. - */ - stakingAsset: ContractArtifacts; - /** - * The token to pay for gas. This will be bridged to L2 via the feeJuicePortal below - */ - feeAsset: ContractArtifacts; - /** - * Fee juice portal contract artifacts. Optional for now as gas is not strictly enforced - */ - feeJuicePortal: ContractArtifacts; - /** - * CoinIssuer contract artifacts. - */ - coinIssuer: ContractArtifacts; - /** - * RewardDistributor contract artifacts. - */ - rewardDistributor: ContractArtifacts; - /** - * GovernanceProposer contract artifacts. - */ - governanceProposer: ContractArtifacts; - /** - * Governance contract artifacts. - */ - governance: ContractArtifacts; - /** - * SlashFactory contract artifacts. - */ - slashFactory: ContractArtifacts; -} - -export const l1Artifacts: L1ContractArtifactsForDeployment = { +export const l1Artifacts = { registry: { contractAbi: RegistryAbi, - contractBytecode: RegistryBytecode, + contractBytecode: RegistryBytecode as Hex, }, inbox: { contractAbi: InboxAbi, - contractBytecode: InboxBytecode, + contractBytecode: InboxBytecode as Hex, }, outbox: { contractAbi: OutboxAbi, - contractBytecode: OutboxBytecode, + contractBytecode: OutboxBytecode as Hex, }, rollup: { contractAbi: RollupAbi, - contractBytecode: RollupBytecode, + contractBytecode: RollupBytecode as Hex, libraries: { linkReferences: RollupLinkReferences, libraryCode: { LeonidasLib: { contractAbi: LeonidasLibAbi, - contractBytecode: LeonidasLibBytecode, + contractBytecode: LeonidasLibBytecode as Hex, }, ExtRollupLib: { contractAbi: ExtRollupLibAbi, - contractBytecode: ExtRollupLibBytecode, + contractBytecode: ExtRollupLibBytecode as Hex, }, }, }, }, stakingAsset: { contractAbi: TestERC20Abi, - contractBytecode: TestERC20Bytecode, + contractBytecode: TestERC20Bytecode as Hex, }, feeAsset: { contractAbi: TestERC20Abi, - contractBytecode: TestERC20Bytecode, + contractBytecode: TestERC20Bytecode as Hex, }, feeJuicePortal: { contractAbi: FeeJuicePortalAbi, - contractBytecode: FeeJuicePortalBytecode, + contractBytecode: FeeJuicePortalBytecode as Hex, }, rewardDistributor: { contractAbi: RewardDistributorAbi, - contractBytecode: RewardDistributorBytecode, + contractBytecode: RewardDistributorBytecode as Hex, }, coinIssuer: { contractAbi: CoinIssuerAbi, - contractBytecode: CoinIssuerBytecode, + contractBytecode: CoinIssuerBytecode as Hex, }, governanceProposer: { contractAbi: GovernanceProposerAbi, - contractBytecode: GovernanceProposerBytecode, + contractBytecode: GovernanceProposerBytecode as Hex, }, governance: { contractAbi: GovernanceAbi, - contractBytecode: GovernanceBytecode, + contractBytecode: GovernanceBytecode as Hex, }, slashFactory: { contractAbi: SlashFactoryAbi, - contractBytecode: SlashFactoryBytecode, + contractBytecode: SlashFactoryBytecode as Hex, }, }; @@ -443,25 +389,42 @@ export const deployL1Contracts = async ( } if (args.initialValidators && args.initialValidators.length > 0) { - // Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch. - const stakeNeeded = args.minimumStake * BigInt(args.initialValidators.length); - await Promise.all( - [ - await stakingAsset.write.mint([walletClient.account.address, stakeNeeded], {} as any), - await stakingAsset.write.approve([rollupAddress.toString(), stakeNeeded], {} as any), - ].map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })), + // Check if some of the initial validators are already registered, so we support idempotent deployments + const validatorsInfo = await Promise.all( + args.initialValidators.map(async address => ({ address, ...(await rollup.read.getInfo([address.toString()])) })), ); + const existingValidators = validatorsInfo.filter(v => v.status !== 0); + if (existingValidators.length > 0) { + logger.warn( + `Validators ${existingValidators.map(v => v.address).join(', ')} already exist. Skipping from initialization.`, + ); + } + + const newValidatorsAddresses = validatorsInfo.filter(v => v.status === 0).map(v => v.address.toString()); - const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([ - args.initialValidators.map(v => ({ - attester: v.toString(), - proposer: v.toString(), - withdrawer: v.toString(), - amount: args.minimumStake, - })), - ]); - txHashes.push(initiateValidatorSetTxHash); - logger.info(`Initialized validator set (${args.initialValidators.join(', ')}) in tx ${initiateValidatorSetTxHash}`); + if (newValidatorsAddresses.length > 0) { + // Mint tokens, approve them, use cheat code to initialise validator set without setting up the epoch. + const stakeNeeded = args.minimumStake * BigInt(newValidatorsAddresses.length); + await Promise.all( + [ + await stakingAsset.write.mint([walletClient.account.address, stakeNeeded], {} as any), + await stakingAsset.write.approve([rollupAddress.toString(), stakeNeeded], {} as any), + ].map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })), + ); + + const initiateValidatorSetTxHash = await rollup.write.cheat__InitialiseValidatorSet([ + newValidatorsAddresses.map(v => ({ + attester: v, + proposer: v, + withdrawer: v, + amount: args.minimumStake, + })), + ]); + txHashes.push(initiateValidatorSetTxHash); + logger.info( + `Initialized validator set (${newValidatorsAddresses.join(', ')}) in tx ${initiateValidatorSetTxHash}`, + ); + } } // @note This value MUST match what is in `constants.nr`. It is currently specified here instead of just importing @@ -476,8 +439,8 @@ export const deployL1Contracts = async ( await publicClient.waitForTransactionReceipt({ hash: mintTxHash }); logger.verbose(`Funding fee juice portal contract with fee juice in ${mintTxHash}`); - if (!(await feeJuicePortal.read.initialized([]))) { - const initPortalTxHash = await feeJuicePortal.write.initialize([]); + if (!(await feeJuicePortal.read.initialized())) { + const initPortalTxHash = await feeJuicePortal.write.initialize(); txHashes.push(initPortalTxHash); logger.verbose(`Fee juice portal initializing in tx ${initPortalTxHash}`); } else { @@ -493,13 +456,13 @@ export const deployL1Contracts = async ( // The edge case being that the genesis block is already occupying slot 0, so we cannot have another block. try { // Need to get the time - const currentSlot = (await rollup.read.getCurrentSlot([])) as bigint; + const currentSlot = (await rollup.read.getCurrentSlot()) as bigint; if (BigInt(currentSlot) === 0n) { - const ts = Number(await rollup.read.getTimestampForSlot([1])); + const ts = Number(await rollup.read.getTimestampForSlot([1n])); await rpcCall('evm_setNextBlockTimestamp', [ts]); await rpcCall('hardhat_mine', [1]); - const currentSlot = (await rollup.read.getCurrentSlot([])) as bigint; + const currentSlot = (await rollup.read.getCurrentSlot()) as bigint; if (BigInt(currentSlot) !== 1n) { throw new Error(`Error jumping time: current slot is ${currentSlot}`); @@ -518,10 +481,10 @@ export const deployL1Contracts = async ( } // Inbox and Outbox are immutable and are deployed from Rollup's constructor so we just fetch them from the contract. - const inboxAddress = EthAddress.fromString((await rollup.read.INBOX([])) as any); + const inboxAddress = EthAddress.fromString((await rollup.read.INBOX()) as any); logger.verbose(`Inbox available at ${inboxAddress}`); - const outboxAddress = EthAddress.fromString((await rollup.read.OUTBOX([])) as any); + const outboxAddress = EthAddress.fromString((await rollup.read.OUTBOX()) as any); logger.verbose(`Outbox available at ${outboxAddress}`); // We need to call a function on the registry to set the various contract addresses. @@ -541,7 +504,7 @@ export const deployL1Contracts = async ( } // If the owner is not the Governance contract, transfer ownership to the Governance contract - if ((await registryContract.read.owner([])) !== getAddress(governanceAddress.toString())) { + if ((await registryContract.read.owner()) !== getAddress(governanceAddress.toString())) { const transferOwnershipTxHash = await registryContract.write.transferOwnership( [getAddress(governanceAddress.toString())], {