diff --git a/spartan/aztec-network/eth-devnet/docker-compose.yml b/spartan/aztec-network/eth-devnet/docker-compose.yml index 33ac9552c7fa..31bbe4b5017e 100644 --- a/spartan/aztec-network/eth-devnet/docker-compose.yml +++ b/spartan/aztec-network/eth-devnet/docker-compose.yml @@ -17,6 +17,7 @@ services: - "${PWD}/out/jwt-secret.hex:/genesis/jwt-secret.hex" environment: - HTTP_PORT=8545 + - WS_PORT=8546 - MAX_TX_INPUT_SIZE_BYTES=1310720 eth_beacon: diff --git a/spartan/aztec-network/eth-devnet/run-locally.sh b/spartan/aztec-network/eth-devnet/run-locally.sh index 5a53730a0089..83eb495fcb07 100755 --- a/spartan/aztec-network/eth-devnet/run-locally.sh +++ b/spartan/aztec-network/eth-devnet/run-locally.sh @@ -3,4 +3,4 @@ REPO_ROOT=$(git rev-parse --show-toplevel) ${REPO_ROOT}/spartan/aztec-network/eth-devnet/create.sh -(cd ${REPO_ROOT}/spartan/aztec-network/eth-devnet && docker compose build && docker compose up) +(cd ${REPO_ROOT}/spartan/aztec-network/eth-devnet && docker compose down -v && docker compose build && docker compose up) 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 d1e8492e98ee..007a08669c8c 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -316,7 +316,7 @@ async function setupFromFresh( // Start anvil. We go via a wrapper script to ensure if the parent dies, anvil dies. logger.verbose('Starting anvil...'); - const res = await startAnvil(opts.ethereumSlotDuration); + const res = await startAnvil({ l1BlockTime: opts.ethereumSlotDuration }); const anvil = res.anvil; aztecNodeConfig.l1RpcUrl = res.rpcUrl; diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 63aac6b3de52..c40c15fedf1d 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -386,7 +386,7 @@ export async function setup( ); } - const res = await startAnvil(opts.ethereumSlotDuration); + const res = await startAnvil({ l1BlockTime: opts.ethereumSlotDuration }); anvil = res.anvil; config.l1RpcUrl = res.rpcUrl; } diff --git a/yarn-project/ethereum/src/contracts/forwarder.test.ts b/yarn-project/ethereum/src/contracts/forwarder.test.ts index 16ff9a74e290..04048dfa6272 100644 --- a/yarn-project/ethereum/src/contracts/forwarder.test.ts +++ b/yarn-project/ethereum/src/contracts/forwarder.test.ts @@ -19,9 +19,10 @@ import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts'; import { foundry } from 'viem/chains'; import { DefaultL1ContractsConfig } from '../config.js'; -import { type L1Clients, createL1Clients, deployL1Contract, deployL1Contracts } from '../deploy_l1_contracts.js'; +import { createL1Clients, deployL1Contract, deployL1Contracts } from '../deploy_l1_contracts.js'; import { L1TxUtils } from '../l1_tx_utils.js'; import { startAnvil } from '../test/start_anvil.js'; +import { type L1Clients } from '../types.js'; import { FormattedViemError } from '../utils.js'; import { ForwarderContract } from './forwarder.js'; diff --git a/yarn-project/ethereum/src/contracts/forwarder.ts b/yarn-project/ethereum/src/contracts/forwarder.ts index 6008e8a645cd..4a8b46977c4a 100644 --- a/yarn-project/ethereum/src/contracts/forwarder.ts +++ b/yarn-project/ethereum/src/contracts/forwarder.ts @@ -15,8 +15,9 @@ import { getContract, } from 'viem'; -import { type L1Clients, deployL1Contract } from '../deploy_l1_contracts.js'; +import { deployL1Contract } from '../deploy_l1_contracts.js'; import { type L1BlobInputs, type L1GasConfig, type L1TxRequest, type L1TxUtils } from '../l1_tx_utils.js'; +import { type L1Clients } from '../types.js'; import { RollupContract } from './rollup.js'; export class ForwarderContract { diff --git a/yarn-project/ethereum/src/contracts/governance_proposer.ts b/yarn-project/ethereum/src/contracts/governance_proposer.ts index 31d39cd669c7..00572125f775 100644 --- a/yarn-project/ethereum/src/contracts/governance_proposer.ts +++ b/yarn-project/ethereum/src/contracts/governance_proposer.ts @@ -13,8 +13,8 @@ import { getContract, } from 'viem'; -import type { L1Clients } from '../deploy_l1_contracts.js'; import type { GasPrice, L1TxRequest, L1TxUtils } from '../l1_tx_utils.js'; +import { type L1Clients } from '../types.js'; import { type IEmpireBase, encodeVote } from './empire_base.js'; export class GovernanceProposerContract implements IEmpireBase { diff --git a/yarn-project/ethereum/src/contracts/slashing_proposer.ts b/yarn-project/ethereum/src/contracts/slashing_proposer.ts index 6e0a5493aa59..b0322e56225e 100644 --- a/yarn-project/ethereum/src/contracts/slashing_proposer.ts +++ b/yarn-project/ethereum/src/contracts/slashing_proposer.ts @@ -10,8 +10,8 @@ import { getContract, } from 'viem'; -import type { L1Clients } from '../deploy_l1_contracts.js'; import type { L1TxRequest } from '../l1_tx_utils.js'; +import type { L1Clients } from '../types.js'; import { type IEmpireBase, encodeVote } from './empire_base.js'; export class SlashingProposerContract implements IEmpireBase { diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.test.ts b/yarn-project/ethereum/src/deploy_l1_contracts.test.ts index f6bb0947bd6b..e70d8135d8d1 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.test.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.test.ts @@ -5,18 +5,15 @@ 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 { createEthereumChain } from './chain.js'; 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; @@ -27,6 +24,13 @@ describe('deploy_l1_contracts', () => { let initialValidators: EthAddress[]; let l2FeeJuiceAddress: AztecAddress; + // Use these environment variables to run against a live node. Eg to test against spartan's eth-devnet: + // BLOCK_TIME=1 spartan/aztec-network/eth-devnet/run-locally.sh + // LOG_LEVEL=verbose L1_RPC_URL=http://localhost:8545 L1_CHAIN_ID=1337 yarn test deploy_l1_contracts + const chainId = process.env.L1_CHAIN_ID ? parseInt(process.env.L1_CHAIN_ID, 10) : 31337; + let rpcUrl = process.env.L1_RPC_URL; + let stop: () => Promise = () => Promise.resolve(); + beforeAll(async () => { logger = createLogger('ethereum:test:deploy_l1_contracts'); privateKey = privateKeyToAccount('0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'); @@ -37,15 +41,23 @@ describe('deploy_l1_contracts', () => { initialValidators = times(3, EthAddress.random); l2FeeJuiceAddress = await AztecAddress.random(); - ({ anvil, rpcUrl } = await startAnvil()); + if (!rpcUrl) { + ({ stop, rpcUrl } = await startAnvil()); + } }); afterAll(async () => { - await anvil.stop().catch(err => createLogger('cleanup').error(err)); + if (stop) { + try { + await stop(); + } catch (err) { + createLogger('ethereum:cleanup').error(`Error during cleanup`, err); + } + } }); const deploy = (args: Partial = {}) => - deployL1Contracts(rpcUrl, privateKey, foundry, logger, { + deployL1Contracts(rpcUrl!, privateKey, createEthereumChain(rpcUrl!, chainId).chainInfo, logger, { ...DefaultL1ContractsConfig, salt: undefined, vkTreeRoot, @@ -53,6 +65,7 @@ describe('deploy_l1_contracts', () => { genesisArchiveRoot, genesisBlockHash, l2FeeJuiceAddress, + l1TxConfig: { checkIntervalMs: 100 }, ...args, }); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index d7aa32017452..90193bd045de 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -40,15 +40,10 @@ import type { Abi, Narrow } from 'abitype'; import { type Account, type Chain, - type Client, type Hex, type HttpTransport, - type PublicActions, type PublicClient, - type PublicRpcSchema, - type WalletActions, type WalletClient, - type WalletRpcSchema, concatHex, createPublicClient, createWalletClient, @@ -68,6 +63,7 @@ import { isAnvilTestChain } from './chain.js'; import { type L1ContractsConfig } from './config.js'; import { type L1ContractAddresses } from './l1_contract_addresses.js'; import { L1TxUtils, type L1TxUtilsConfig, defaultL1TxUtilsConfig } from './l1_tx_utils.js'; +import type { L1Clients } from './types.js'; export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C'; @@ -206,19 +202,10 @@ export interface DeployL1ContractsArgs extends L1ContractsConfig { salt: number | undefined; /** The initial validators for the rollup contract. */ initialValidators?: EthAddress[]; + /** Configuration for the L1 tx utils module. */ + l1TxConfig?: Partial; } -export type L1Clients = { - publicClient: PublicClient; - walletClient: Client< - HttpTransport, - Chain, - PrivateKeyAccount, - [...WalletRpcSchema, ...PublicRpcSchema], - PublicActions & WalletActions - >; -}; - /** * Creates a wallet and a public viem client for interacting with L1. * @param rpcUrl - RPC URL to connect to L1. @@ -353,7 +340,7 @@ export const deployL1Contracts = async ( await govDeployer.waitForDeployments(); logger.verbose(`All governance contracts deployed`); - const deployer = new L1Deployer(walletClient, publicClient, args.salt, logger); + const deployer = new L1Deployer(walletClient, publicClient, args.salt, logger, args.l1TxConfig ?? {}); const feeJuicePortalAddress = await deployer.deploy(l1Artifacts.feeJuicePortal, [ registryAddress.toString(), @@ -665,11 +652,11 @@ export async function deployL1Contract( } const replacements: Record = {}; - + const libraryTxs: Hex[] = []; for (const libraryName in libraries?.libraryCode) { const lib = libraries.libraryCode[libraryName]; - const { address } = await deployL1Contract( + const { address, txHash } = await deployL1Contract( walletClient, publicClient, lib.contractAbi, @@ -681,6 +668,10 @@ export async function deployL1Contract( l1TxUtils, ); + if (txHash) { + libraryTxs.push(txHash); + } + for (const linkRef in libraries.linkReferences) { for (const contractName in libraries.linkReferences[linkRef]) { // If the library name matches the one we just deployed, we replace it. @@ -706,6 +697,13 @@ export async function deployL1Contract( const replacement = replacements[toReplace].toString().slice(2); bytecode = bytecode.replace(new RegExp(escapeRegExp(toReplace), 'g'), replacement) as Hex; } + + // Reth fails gas estimation if the deployed contract attempts to call a library that is not yet deployed, + // so we wait for all library deployments to be mined before deploying the contract. + if (libraryTxs.length > 0) { + logger?.verbose(`Awaiting for linked libraries to be deployed`); + await Promise.all(libraryTxs.map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash }))); + } } if (maybeSalt) { diff --git a/yarn-project/ethereum/src/l1_tx_utils.test.ts b/yarn-project/ethereum/src/l1_tx_utils.test.ts index 7eefcb0ca4f2..2bdb0c334899 100644 --- a/yarn-project/ethereum/src/l1_tx_utils.test.ts +++ b/yarn-project/ethereum/src/l1_tx_utils.test.ts @@ -45,7 +45,7 @@ describe('GasUtils', () => { const logger = createLogger('ethereum:test:l1_gas_test'); beforeAll(async () => { - const { anvil: anvilInstance, rpcUrl } = await startAnvil(1); + const { anvil: anvilInstance, rpcUrl } = await startAnvil({ l1BlockTime: 1 }); anvil = anvilInstance; cheatCodes = new EthCheatCodes(rpcUrl); const hdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: 0 }); diff --git a/yarn-project/ethereum/src/l1_tx_utils.ts b/yarn-project/ethereum/src/l1_tx_utils.ts index 73f6fa7ddb2f..eca62b459398 100644 --- a/yarn-project/ethereum/src/l1_tx_utils.ts +++ b/yarn-project/ethereum/src/l1_tx_utils.ts @@ -30,7 +30,7 @@ import { hexToBytes, } from 'viem'; -import { type L1Clients } from './deploy_l1_contracts.js'; +import { type L1Clients } from './types.js'; import { formatViemError } from './utils.js'; // 1_000_000_000 Gwei = 1 ETH diff --git a/yarn-project/ethereum/src/test/start_anvil.ts b/yarn-project/ethereum/src/test/start_anvil.ts index 9c81b2e2832a..ff8e6d975558 100644 --- a/yarn-project/ethereum/src/test/start_anvil.ts +++ b/yarn-project/ethereum/src/test/start_anvil.ts @@ -7,7 +7,11 @@ import { dirname, resolve } from 'path'; /** * Ensures there's a running Anvil instance and returns the RPC URL. */ -export async function startAnvil(l1BlockTime?: number): Promise<{ anvil: Anvil; rpcUrl: string }> { +export async function startAnvil( + opts: { + l1BlockTime?: number; + } = {}, +): Promise<{ anvil: Anvil; rpcUrl: string; stop: () => Promise }> { const anvilBinary = resolve(dirname(fileURLToPath(import.meta.url)), '../../', 'scripts/anvil_kill_wrapper.sh'); let port: number | undefined; @@ -19,7 +23,7 @@ export async function startAnvil(l1BlockTime?: number): Promise<{ anvil: Anvil; const anvil = createAnvil({ anvilBinary, port: 0, - blockTime: l1BlockTime, + blockTime: opts.l1BlockTime, stopTimeout: 1000, }); @@ -44,5 +48,5 @@ export async function startAnvil(l1BlockTime?: number): Promise<{ anvil: Anvil; // Monkeypatch the anvil instance to include the actually assigned port Object.defineProperty(anvil, 'port', { value: port, writable: false }); - return { anvil, rpcUrl: `http://127.0.0.1:${port}` }; + return { anvil, stop: () => anvil.stop(), rpcUrl: `http://127.0.0.1:${port}` }; } diff --git a/yarn-project/ethereum/src/test/tx_delayer.test.ts b/yarn-project/ethereum/src/test/tx_delayer.test.ts index af2d2fa58919..119d3736cb9b 100644 --- a/yarn-project/ethereum/src/test/tx_delayer.test.ts +++ b/yarn-project/ethereum/src/test/tx_delayer.test.ts @@ -21,7 +21,7 @@ describe('tx_delayer', () => { const ETHEREUM_SLOT_DURATION = 2; beforeAll(async () => { - ({ anvil, rpcUrl } = await startAnvil(ETHEREUM_SLOT_DURATION)); + ({ anvil, rpcUrl } = await startAnvil({ l1BlockTime: ETHEREUM_SLOT_DURATION })); logger = createLogger('ethereum:test:tx_delayer'); }); diff --git a/yarn-project/ethereum/src/types.ts b/yarn-project/ethereum/src/types.ts index 1638685841c8..e957000e138b 100644 --- a/yarn-project/ethereum/src/types.ts +++ b/yarn-project/ethereum/src/types.ts @@ -24,3 +24,9 @@ export type ViemClient = Client< /** Type for a viem public client */ export type ViemPublicClient = PublicClient; + +/** Both L1 clients */ +export type L1Clients = { + publicClient: ViemPublicClient; + walletClient: ViemClient; +};