diff --git a/.circleci/config.yml b/.circleci/config.yml index 01726da680b..e5cbccf0ea1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -71,9 +71,9 @@ setup_env: &setup_env command: ./build-system/scripts/setup_env "$CIRCLE_SHA1" "$CIRCLE_TAG" "$CIRCLE_JOB" "$CIRCLE_REPOSITORY_URL" "$CIRCLE_BRANCH" "$CIRCLE_PULL_REQUEST" defaults_e2e_test: &defaults_e2e_test - docker: - - image: aztecprotocol/alpine-build-image - resource_class: small + docker: + - image: aztecprotocol/alpine-build-image + resource_class: small jobs: # Dynamically filter our code, quickly figuring out which jobs we can skip. @@ -868,7 +868,6 @@ jobs: aztec_manifest_key: end-to-end <<: *defaults_e2e_test - e2e-outbox: docker: - image: aztecprotocol/alpine-build-image @@ -958,7 +957,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_fees.test.ts + command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_fees.test.ts ENABLE_GAS=1 aztec_manifest_key: end-to-end <<: *defaults_e2e_test @@ -968,7 +967,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_dapp_subscription.test.ts + command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_dapp_subscription.test.ts ENABLE_GAS=1 aztec_manifest_key: end-to-end <<: *defaults_e2e_test @@ -1221,7 +1220,7 @@ defaults: &defaults - slack/notify: event: fail branch_pattern: "master" - + bb_acir_tests: &bb_acir_tests requires: - barretenberg-x86_64-linux-clang-assert diff --git a/yarn-project/archiver/src/archiver/config.ts b/yarn-project/archiver/src/archiver/config.ts index daddf75df06..8a741ff31ac 100644 --- a/yarn-project/archiver/src/archiver/config.ts +++ b/yarn-project/archiver/src/archiver/config.ts @@ -62,6 +62,8 @@ export function getConfigEnvVars(): ArchiverConfig { INBOX_CONTRACT_ADDRESS, OUTBOX_CONTRACT_ADDRESS, REGISTRY_CONTRACT_ADDRESS, + GAS_TOKEN_CONTRACT_ADDRESS, + GAS_PORTAL_CONTRACT_ADDRESS, DATA_DIRECTORY, } = process.env; // Populate the relevant addresses for use by the archiver. @@ -73,6 +75,10 @@ export function getConfigEnvVars(): ArchiverConfig { registryAddress: REGISTRY_CONTRACT_ADDRESS ? EthAddress.fromString(REGISTRY_CONTRACT_ADDRESS) : EthAddress.ZERO, inboxAddress: INBOX_CONTRACT_ADDRESS ? EthAddress.fromString(INBOX_CONTRACT_ADDRESS) : EthAddress.ZERO, outboxAddress: OUTBOX_CONTRACT_ADDRESS ? EthAddress.fromString(OUTBOX_CONTRACT_ADDRESS) : EthAddress.ZERO, + gasTokenAddress: GAS_TOKEN_CONTRACT_ADDRESS ? EthAddress.fromString(GAS_TOKEN_CONTRACT_ADDRESS) : EthAddress.ZERO, + gasPortalAddress: GAS_PORTAL_CONTRACT_ADDRESS + ? EthAddress.fromString(GAS_PORTAL_CONTRACT_ADDRESS) + : EthAddress.ZERO, }; return { rpcUrl: ETHEREUM_HOST || 'http://127.0.0.1:8545/', diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index f3deb9c6666..90e822a8f8e 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -27,6 +27,8 @@ describe('Contract Class', () => { registryAddress: EthAddress.random(), inboxAddress: EthAddress.random(), outboxAddress: EthAddress.random(), + gasTokenAddress: EthAddress.random(), + gasPortalAddress: EthAddress.random(), }; const mockNodeInfo: NodeInfo = { nodeVersion: 'vx.x.x', diff --git a/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts index d8d88910036..04627578c64 100644 --- a/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts @@ -1,25 +1,33 @@ import { FunctionCall } from '@aztec/circuit-types'; -import { FunctionData } from '@aztec/circuits.js'; +import { AztecAddress, FunctionData } from '@aztec/circuits.js'; import { FunctionSelector } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; -import { GasTokenAddress } from '@aztec/protocol-contracts/gas-token'; +import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; +import { Wallet } from '../account/wallet.js'; import { FeePaymentMethod } from './fee_payment_method.js'; /** * Pay fee directly in the native gas token. */ export class NativeFeePaymentMethod implements FeePaymentMethod { - static #GAS_TOKEN = GasTokenAddress; + #gasTokenAddress: AztecAddress; - constructor() {} + private constructor(canonicalGasTokenAddress: AztecAddress) { + this.#gasTokenAddress = canonicalGasTokenAddress; + } + + static async create(wallet: Wallet): Promise { + const nodeInfo = await wallet.getNodeInfo(); + return new NativeFeePaymentMethod(getCanonicalGasTokenAddress(nodeInfo.l1ContractAddresses.gasPortalAddress)); + } /** * Gets the native gas asset used to pay the fee. * @returns The asset used to pay the fee. */ getAsset() { - return NativeFeePaymentMethod.#GAS_TOKEN; + return this.#gasTokenAddress; } /** @@ -27,7 +35,7 @@ export class NativeFeePaymentMethod implements FeePaymentMethod { * @returns The contract address responsible for holding the fee payment. */ getPaymentContract() { - return NativeFeePaymentMethod.#GAS_TOKEN; + return this.#gasTokenAddress; } /** @@ -46,12 +54,12 @@ export class NativeFeePaymentMethod implements FeePaymentMethod { getFunctionCalls(feeLimit: Fr): Promise { return Promise.resolve([ { - to: NativeFeePaymentMethod.#GAS_TOKEN, + to: this.#gasTokenAddress, functionData: new FunctionData(FunctionSelector.fromSignature('check_balance(Field)'), false), args: [feeLimit], }, { - to: NativeFeePaymentMethod.#GAS_TOKEN, + to: this.#gasTokenAddress, functionData: new FunctionData(FunctionSelector.fromSignature('pay_fee(Field)'), false), args: [feeLimit], }, diff --git a/yarn-project/aztec/package.json b/yarn-project/aztec/package.json index c002da53152..defde0e93a6 100644 --- a/yarn-project/aztec/package.json +++ b/yarn-project/aztec/package.json @@ -40,6 +40,7 @@ "@aztec/noir-compiler": "workspace:^", "@aztec/noir-contracts.js": "workspace:^", "@aztec/p2p": "workspace:^", + "@aztec/protocol-contracts": "workspace:^", "@aztec/pxe": "workspace:^", "abitype": "^0.8.11", "commander": "^11.1.0", diff --git a/yarn-project/aztec/src/bin/index.ts b/yarn-project/aztec/src/bin/index.ts index b463e48c895..afd1f0a6d58 100644 --- a/yarn-project/aztec/src/bin/index.ts +++ b/yarn-project/aztec/src/bin/index.ts @@ -20,7 +20,7 @@ const debugLogger = createDebugLogger('aztec:cli'); const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../package.json'); const cliVersion: string = JSON.parse(readFileSync(packageJsonPath).toString()).version; -const { TEST_ACCOUNTS = 'true', PORT = '8080' } = process.env; +const { TEST_ACCOUNTS = 'true', PORT = '8080', ENABLE_GAS = '' } = process.env; /** CLI & full node main entrypoint */ async function main() { @@ -32,7 +32,9 @@ async function main() { // If no CLI arguments were provided, run aztec full node for sandbox usage. userLog(`${splash}\n${github}\n\n`); userLog(`Setting up Aztec Sandbox v${cliVersion}, please stand by...`); - const { aztecNodeConfig, node, pxe, stop } = await createSandbox(); + const { aztecNodeConfig, node, pxe, stop } = await createSandbox({ + enableGas: ['true', '1'].includes(ENABLE_GAS), + }); installSignalHandlers(userLog, [stop]); // Deploy test accounts by default diff --git a/yarn-project/aztec/src/sandbox.ts b/yarn-project/aztec/src/sandbox.ts index 161100eefce..4549bc1adb1 100644 --- a/yarn-project/aztec/src/sandbox.ts +++ b/yarn-project/aztec/src/sandbox.ts @@ -1,8 +1,11 @@ #!/usr/bin/env -S node --no-warnings import { AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node'; +import { AztecAddress, SignerlessWallet, Wallet } from '@aztec/aztec.js'; +import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; import { AztecNode } from '@aztec/circuit-types'; import { DeployL1Contracts, + L1ContractAddresses, L1ContractArtifactsForDeployment, NULL_KEY, createEthereumChain, @@ -13,18 +16,23 @@ import { retryUntil } from '@aztec/foundation/retry'; import { AvailabilityOracleAbi, AvailabilityOracleBytecode, + GasPortalAbi, + GasPortalBytecode, InboxAbi, InboxBytecode, OutboxAbi, OutboxBytecode, + PortalERC20Abi, + PortalERC20Bytecode, RegistryAbi, RegistryBytecode, RollupAbi, RollupBytecode, } from '@aztec/l1-artifacts'; +import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token'; import { PXEServiceConfig, createPXEService, getPXEServiceConfig } from '@aztec/pxe'; -import { HDAccount, PrivateKeyAccount, createPublicClient, http as httpViemTransport } from 'viem'; +import { HDAccount, PrivateKeyAccount, createPublicClient, getContract, http as httpViemTransport } from 'viem'; import { mnemonicToAccount } from 'viem/accounts'; import { foundry } from 'viem/chains'; @@ -98,21 +106,77 @@ export async function deployContractsToL1( contractAbi: RollupAbi, contractBytecode: RollupBytecode, }, + gasToken: { + contractAbi: PortalERC20Abi, + contractBytecode: PortalERC20Bytecode, + }, + gasPortal: { + contractAbi: GasPortalAbi, + contractBytecode: GasPortalBytecode, + }, }; - aztecNodeConfig.l1Contracts = ( - await waitThenDeploy(aztecNodeConfig, () => - deployL1Contracts(aztecNodeConfig.rpcUrl, hdAccount, localAnvil, contractDeployLogger, l1Artifacts), - ) - ).l1ContractAddresses; + const l1Contracts = await waitThenDeploy(aztecNodeConfig, () => + deployL1Contracts(aztecNodeConfig.rpcUrl, hdAccount, localAnvil, contractDeployLogger, l1Artifacts), + ); + await initL1GasPortal(l1Contracts, getCanonicalGasToken(l1Contracts.l1ContractAddresses.gasPortalAddress).address); + + aztecNodeConfig.l1Contracts = l1Contracts.l1ContractAddresses; return aztecNodeConfig.l1Contracts; } +/** + * Initializes the portal between L1 and L2 used to pay for gas. + * @param l1Data - The deployed L1 data. + */ +async function initL1GasPortal( + { walletClient, l1ContractAddresses }: DeployL1Contracts, + l2GasTokenAddress: AztecAddress, +) { + const gasPortal = getContract({ + address: l1ContractAddresses.gasPortalAddress.toString(), + abi: GasPortalAbi, + client: walletClient, + }); + + await gasPortal.write.initialize( + [ + l1ContractAddresses.registryAddress.toString(), + l1ContractAddresses.gasTokenAddress.toString(), + l2GasTokenAddress.toString(), + ], + {} as any, + ); + + logger( + `Initialized Gas Portal at ${l1ContractAddresses.gasPortalAddress} to bridge between L1 ${l1ContractAddresses.gasTokenAddress} to L2 ${l2GasTokenAddress}`, + ); +} + +/** + * Deploys the contract to pay for gas on L2. + */ +async function deployCanonicalL2GasToken(deployer: Wallet, l1ContractAddresses: L1ContractAddresses) { + const gasPortalAddress = l1ContractAddresses.gasPortalAddress; + const canonicalGasToken = getCanonicalGasToken(gasPortalAddress); + + if (await deployer.isContractClassPubliclyRegistered(canonicalGasToken.contractClass.id)) { + return; + } + + await (await registerContractClass(deployer, canonicalGasToken.artifact)).send().wait(); + await deployInstance(deployer, canonicalGasToken.instance).send().wait(); + + logger(`Deployed Gas Token on L2 at ${canonicalGasToken.address}`); +} + /** Sandbox settings. */ export type SandboxConfig = AztecNodeConfig & { /** Mnemonic used to derive the L1 deployer private key.*/ l1Mnemonic: string; + /** Enable the contracts to track and pay for gas */ + enableGas: boolean; }; /** @@ -135,6 +199,11 @@ export async function createSandbox(config: Partial = {}) { const node = await createAztecNode(aztecNodeConfig); const pxe = await createAztecPXE(node); + if (config.enableGas) { + const deployer = new SignerlessWallet(pxe); + await deployCanonicalL2GasToken(deployer, aztecNodeConfig.l1Contracts); + } + const stop = async () => { await pxe.stop(); await node.stop(); diff --git a/yarn-project/aztec/tsconfig.json b/yarn-project/aztec/tsconfig.json index 46586f47cf4..2f1632454b0 100644 --- a/yarn-project/aztec/tsconfig.json +++ b/yarn-project/aztec/tsconfig.json @@ -45,6 +45,9 @@ { "path": "../p2p" }, + { + "path": "../protocol-contracts" + }, { "path": "../pxe" } diff --git a/yarn-project/cli/src/utils.ts b/yarn-project/cli/src/utils.ts index 281a8fd297b..756b3193c7b 100644 --- a/yarn-project/cli/src/utils.ts +++ b/yarn-project/cli/src/utils.ts @@ -4,7 +4,14 @@ import { type L1ContractArtifactsForDeployment } from '@aztec/aztec.js/ethereum' import { type PXE } from '@aztec/aztec.js/interfaces/pxe'; import { DebugLogger, LogFn } from '@aztec/foundation/log'; import { NoirPackageConfig } from '@aztec/foundation/noir'; -import { AvailabilityOracleAbi, AvailabilityOracleBytecode } from '@aztec/l1-artifacts'; +import { + AvailabilityOracleAbi, + AvailabilityOracleBytecode, + GasPortalAbi, + GasPortalBytecode, + PortalERC20Abi, + PortalERC20Bytecode, +} from '@aztec/l1-artifacts'; import TOML from '@iarna/toml'; import { CommanderError, InvalidArgumentError } from 'commander'; @@ -85,6 +92,14 @@ export async function deployAztecContracts( contractAbi: RollupAbi, contractBytecode: RollupBytecode, }, + gasToken: { + contractAbi: PortalERC20Abi, + contractBytecode: PortalERC20Bytecode, + }, + gasPortal: { + contractAbi: GasPortalAbi, + contractBytecode: GasPortalBytecode, + }, }; return await deployL1Contracts(chain.rpcUrl, account, chain.chainInfo, debugLogger, l1Artifacts); } diff --git a/yarn-project/end-to-end/Earthfile b/yarn-project/end-to-end/Earthfile index c7228768a72..aa4758b52ae 100644 --- a/yarn-project/end-to-end/Earthfile +++ b/yarn-project/end-to-end/Earthfile @@ -105,9 +105,11 @@ e2e-avm-simulator: DO +E2E_TEST --test=e2e_avm_simulator.test.ts e2e-fees: + ENV ENABLE_GAS=1 DO +E2E_TEST --test=e2e_fees.test.ts e2e-dapp-subscription: + ENV ENABLE_GAS=1 DO +E2E_TEST --test=e2e_dapp_subscription.test.ts pxe: diff --git a/yarn-project/end-to-end/scripts/docker-compose.yml b/yarn-project/end-to-end/scripts/docker-compose.yml index 114fd5f9ad8..86bc514ac81 100644 --- a/yarn-project/end-to-end/scripts/docker-compose.yml +++ b/yarn-project/end-to-end/scripts/docker-compose.yml @@ -28,6 +28,7 @@ services: PXE_BLOCK_POLLING_INTERVAL_MS: 50 ARCHIVER_VIEM_POLLING_INTERVAL_MS: 500 AVM_ENABLED: ${AVM_ENABLED:-} + ENABLE_GAS: ${ENABLE_GAS:-} ports: - '8080:8080' diff --git a/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts b/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts index 1b793259c1a..37ed31610a7 100644 --- a/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts +++ b/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts @@ -15,7 +15,7 @@ import { FPCContract, GasTokenContract, } from '@aztec/noir-contracts.js'; -import { GasTokenAddress } from '@aztec/protocol-contracts/gas-token'; +import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; import { jest } from '@jest/globals'; @@ -62,13 +62,16 @@ describe('e2e_dapp_subscription', () => { beforeAll(async () => { process.env.PXE_URL = ''; - e2eContext = await setup(3, { deployProtocolContracts: true }); + e2eContext = await setup(3); await publicDeployAccounts(e2eContext.wallet, e2eContext.accounts); - const { wallets, accounts, aztecNode } = e2eContext; + const { wallets, accounts, aztecNode, deployL1ContractsValues } = e2eContext; // this should be a SignerlessWallet but that can't call public functions directly - gasTokenContract = await GasTokenContract.at(GasTokenAddress, wallets[0]); + gasTokenContract = await GasTokenContract.at( + getCanonicalGasTokenAddress(deployL1ContractsValues.l1ContractAddresses.gasPortalAddress), + wallets[0], + ); aliceAddress = accounts.at(0)!.address; bobAddress = accounts.at(1)!.address; diff --git a/yarn-project/end-to-end/src/e2e_fees.test.ts b/yarn-project/end-to-end/src/e2e_fees.test.ts index 49309780080..0f455f840d4 100644 --- a/yarn-project/end-to-end/src/e2e_fees.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees.test.ts @@ -59,7 +59,6 @@ describe('e2e_fees', () => { let bananaPrivateBalances: BalancesFn; beforeAll(async () => { - process.env.PXE_URL = ''; e2eContext = await setup(3); const { accounts, logger, aztecNode, pxe, deployL1ContractsValues, wallets } = e2eContext; diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index ae2287cd47a..f926d6e9e75 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -30,16 +30,20 @@ import { deployInstance, registerContractClass } from '@aztec/aztec.js/deploymen import { AvailabilityOracleAbi, AvailabilityOracleBytecode, + GasPortalAbi, + GasPortalBytecode, InboxAbi, InboxBytecode, OutboxAbi, OutboxBytecode, + PortalERC20Abi, + PortalERC20Bytecode, RegistryAbi, RegistryBytecode, RollupAbi, RollupBytecode, } from '@aztec/l1-artifacts'; -import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token'; +import { getCanonicalGasToken, getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; import { PXEService, PXEServiceConfig, createPXEService, getPXEServiceConfig } from '@aztec/pxe'; import { SequencerClient } from '@aztec/sequencer-client'; @@ -53,6 +57,7 @@ import { PrivateKeyAccount, createPublicClient, createWalletClient, + getContract, http, } from 'viem'; import { mnemonicToAccount } from 'viem/accounts'; @@ -69,6 +74,7 @@ const { TEMP_DIR = '/tmp', ACVM_BINARY_PATH = '', ACVM_WORKING_DIRECTORY = '', + ENABLE_GAS = '', } = process.env; const getAztecUrl = () => { @@ -118,10 +124,39 @@ export const setupL1Contracts = async ( contractAbi: RollupAbi, contractBytecode: RollupBytecode, }, + gasToken: { + contractAbi: PortalERC20Abi, + contractBytecode: PortalERC20Bytecode, + }, + gasPortal: { + contractAbi: GasPortalAbi, + contractBytecode: GasPortalBytecode, + }, }; - return await deployL1Contracts(l1RpcUrl, account, foundry, logger, l1Artifacts); + + const l1Data = await deployL1Contracts(l1RpcUrl, account, foundry, logger, l1Artifacts); + await initGasBridge(l1Data); + + return l1Data; }; +async function initGasBridge({ walletClient, l1ContractAddresses }: DeployL1Contracts) { + const gasPortal = getContract({ + address: l1ContractAddresses.gasPortalAddress.toString(), + abi: GasPortalAbi, + client: walletClient, + }); + + await gasPortal.write.initialize( + [ + l1ContractAddresses.registryAddress.toString(), + l1ContractAddresses.gasTokenAddress.toString(), + getCanonicalGasTokenAddress(l1ContractAddresses.gasPortalAddress).toString(), + ], + {} as any, + ); +} + /** * Sets up Private eXecution Environment (PXE). * @param numberOfAccounts - The number of new accounts to be created once the PXE is initiated. @@ -183,7 +218,6 @@ async function setupWithRemoteEnvironment( config: AztecNodeConfig, logger: DebugLogger, numberOfAccounts: number, - deployProtocolContracts = false, ) { // we are setting up against a remote environment, l1 contracts are already deployed const aztecNodeUrl = getAztecUrl(); @@ -221,8 +255,10 @@ async function setupWithRemoteEnvironment( const cheatCodes = CheatCodes.create(config.rpcUrl, pxeClient!); const teardown = () => Promise.resolve(); - if (deployProtocolContracts) { - await deployPublicProtocolContracts(wallets[0]); + if (['1', 'true'].includes(ENABLE_GAS)) { + // this contract might already have been deployed + // the following function is idempotent + await deployCanonicalGasToken(wallets[0]); } return { @@ -246,9 +282,6 @@ type SetupOptions = { stateLoad?: string; /** Previously deployed contracts on L1 */ deployL1ContractsValues?: DeployL1Contracts; - - /** Deploy protocol contracts */ - deployProtocolContracts?: boolean; } & Partial; /** Context for an end-to-end test as returned by the `setup` function */ @@ -308,7 +341,7 @@ export async function setup( if (PXE_URL) { // we are setting up against a remote environment, l1 contracts are assumed to already be deployed - return await setupWithRemoteEnvironment(hdAccount, config, logger, numberOfAccounts, opts.deployProtocolContracts); + return await setupWithRemoteEnvironment(hdAccount, config, logger, numberOfAccounts); } const deployL1ContractsValues = @@ -330,10 +363,10 @@ export async function setup( const { pxe, accounts, wallets } = await setupPXEService(numberOfAccounts, aztecNode!, pxeOpts, logger); - if (opts.deployProtocolContracts) { + if (['1', 'true'].includes(ENABLE_GAS)) { // this should be a neutral wallet, but the SignerlessWallet only accepts a single function call // and this needs two: one to register the class and another to deploy the instance - await deployPublicProtocolContracts(wallets[0]); + await deployCanonicalGasToken(wallets[0]); } const cheatCodes = CheatCodes.create(config.rpcUrl, pxe!); @@ -493,9 +526,10 @@ export async function expectMapping( /** * Deploy the protocol contracts to a running instance. */ -export async function deployPublicProtocolContracts(deployer: Wallet) { +export async function deployCanonicalGasToken(deployer: Wallet) { // "deploy" the Gas token as it contains public functions - const canonicalGasToken = getCanonicalGasToken(); + const gasPortalAddress = (await deployer.getNodeInfo()).l1ContractAddresses.gasPortalAddress; + const canonicalGasToken = getCanonicalGasToken(gasPortalAddress); if (await deployer.isContractClassPubliclyRegistered(canonicalGasToken.contractClass.id)) { return; diff --git a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts index 4f873c3ab3b..8b4f0c1f35d 100644 --- a/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts @@ -1,16 +1,7 @@ -import { - AztecAddress, - DebugLogger, - EthAddress, - Fr, - PXE, - Wallet, - computeMessageSecretHash, - deployL1Contract, -} from '@aztec/aztec.js'; -import { GasPortalAbi, GasPortalBytecode, OutboxAbi, PortalERC20Abi, PortalERC20Bytecode } from '@aztec/l1-artifacts'; +import { AztecAddress, DebugLogger, EthAddress, Fr, PXE, Wallet, computeMessageSecretHash } from '@aztec/aztec.js'; +import { GasPortalAbi, OutboxAbi, PortalERC20Abi } from '@aztec/l1-artifacts'; import { GasTokenContract } from '@aztec/noir-contracts.js'; -import { getCanonicalGasToken } from '@aztec/protocol-contracts/gas-token'; +import { getCanonicalGasToken, getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; import { Account, Chain, HttpTransport, PublicClient, WalletClient, getContract } from 'viem'; @@ -19,79 +10,12 @@ export interface IGasBridgingTestHarness { l2Token: GasTokenContract; } -/** - * Deploy L1 token and portal, initialize portal, deploy a non native l2 token contract, its L2 bridge contract and attach is to the portal. - * @param wallet - the wallet instance - * @param walletClient - A viem WalletClient. - * @param publicClient - A viem PublicClient. - * @param rollupRegistryAddress - address of rollup registry to pass to initialize the token portal - * @param owner - owner of the L2 contract - * @param underlyingERC20Address - address of the underlying ERC20 contract to use (if none supplied, it deploys one) - * @returns l2 contract instance, bridge contract instance, token portal instance, token portal address and the underlying ERC20 instance - */ -export async function deployAndInitializeTokenAndBridgeContracts( - wallet: Wallet, - walletClient: WalletClient, - publicClient: PublicClient, - rollupRegistryAddress: EthAddress, - owner: AztecAddress, - underlyingERC20Address?: EthAddress, -): Promise<{ - gasL2: GasTokenContract; - /** - * The token portal contract address. - */ - gasPortalAddress: EthAddress; - /** - * The token portal contract instance - */ - gasPortal: any; - /** - * The underlying ERC20 contract instance. - */ - gasL1: any; -}> { - if (!underlyingERC20Address) { - underlyingERC20Address = await deployL1Contract(walletClient, publicClient, PortalERC20Abi, PortalERC20Bytecode); - } - const gasL1 = getContract({ - address: underlyingERC20Address.toString(), - abi: PortalERC20Abi, - client: walletClient, - }); - - // deploy the gas portal - const gasPortalAddress = await deployL1Contract(walletClient, publicClient, GasPortalAbi, GasPortalBytecode); - const gasPortal = getContract({ - address: gasPortalAddress.toString(), - abi: GasPortalAbi, - client: walletClient, - }); - - // deploy l2 token - const gasL2 = await GasTokenContract.deploy(wallet) - .send({ - portalContract: gasPortalAddress, - contractAddressSalt: getCanonicalGasToken().instance.salt, - }) - .deployed(); - - // initialize portal - await gasPortal.write.initialize( - [rollupRegistryAddress.toString(), underlyingERC20Address.toString(), gasL2.address.toString()], - {} as any, - ); - - return { gasL2, gasPortalAddress, gasPortal, gasL1 }; -} - export interface GasPortalTestingHarnessFactoryConfig { pxeService: PXE; publicClient: PublicClient; walletClient: WalletClient; wallet: Wallet; logger: DebugLogger; - underlyingERC20Address?: EthAddress; mockL1?: boolean; } export class GasPortalTestingHarnessFactory { @@ -102,36 +26,44 @@ export class GasPortalTestingHarnessFactory { const gasL2 = await GasTokenContract.deploy(wallet) .send({ - contractAddressSalt: getCanonicalGasToken().instance.salt, + contractAddressSalt: getCanonicalGasToken(EthAddress.ZERO).instance.salt, }) .deployed(); return Promise.resolve(new MockGasBridgingTestHarness(gasL2)); } private async createReal() { - const { pxeService, publicClient, walletClient, wallet, logger, underlyingERC20Address } = this.config; + const { pxeService, publicClient, walletClient, wallet, logger } = this.config; const ethAccount = EthAddress.fromString((await walletClient.getAddresses())[0]); - const owner = wallet.getCompleteAddress(); const l1ContractAddresses = (await pxeService.getNodeInfo()).l1ContractAddresses; + const gasTokenAddress = l1ContractAddresses.gasTokenAddress; + const gasPortalAddress = l1ContractAddresses.gasPortalAddress; + + if (gasTokenAddress.isZero() || gasPortalAddress.isZero()) { + throw new Error('Gas portal not deployed on L1'); + } + const outbox = getContract({ address: l1ContractAddresses.outboxAddress.toString(), abi: OutboxAbi, client: walletClient, }); - // Deploy and initialize all required contracts - logger('Deploying and initializing token, portal and its bridge...'); - const { gasPortalAddress, gasL1, gasL2, gasPortal } = await deployAndInitializeTokenAndBridgeContracts( - wallet, - walletClient, - publicClient, - l1ContractAddresses.registryAddress, - owner.address, - underlyingERC20Address, - ); - logger('Deployed and initialized token, portal and its bridge.'); + const gasL1 = getContract({ + address: gasTokenAddress.toString(), + abi: PortalERC20Abi, + client: walletClient, + }); + + const gasPortal = getContract({ + address: gasPortalAddress.toString(), + abi: GasPortalAbi, + client: walletClient, + }); + + const gasL2 = await GasTokenContract.at(getCanonicalGasTokenAddress(gasPortalAddress), wallet); return new GasBridgingTestHarness( pxeService, diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index c21ebace469..d48a8d25d72 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -76,6 +76,14 @@ export interface L1ContractArtifactsForDeployment { * Rollup contract artifacts */ rollup: ContractArtifacts; + /** + * The token to pay for gas. This will be bridged to L2 via the gasPortal below + */ + gasToken: ContractArtifacts; + /** + * Gas portal contract artifacts. Optional for now as gas is not strictly enforced + */ + gasPortal: ContractArtifacts; } /** @@ -162,12 +170,34 @@ export const deployL1Contracts = async ( { account }, ); + // this contract remains uninitialized because at this point we don't know the address of the gas token on L2 + const gasTokenAddress = await deployL1Contract( + walletClient, + publicClient, + contractsToDeploy.gasToken.contractAbi, + contractsToDeploy.gasToken.contractBytecode, + ); + + logger(`Deployed Gas Token at ${gasTokenAddress}`); + + // this contract remains uninitialized because at this point we don't know the address of the gas token on L2 + const gasPortalAddress = await deployL1Contract( + walletClient, + publicClient, + contractsToDeploy.gasPortal.contractAbi, + contractsToDeploy.gasPortal.contractBytecode, + ); + + logger(`Deployed Gas Portal at ${gasPortalAddress}`); + const l1Contracts: L1ContractAddresses = { availabilityOracleAddress, rollupAddress, registryAddress, inboxAddress, outboxAddress, + gasTokenAddress, + gasPortalAddress, }; return { diff --git a/yarn-project/ethereum/src/l1_contract_addresses.ts b/yarn-project/ethereum/src/l1_contract_addresses.ts index be1c3af72fc..ff4e4c6a373 100644 --- a/yarn-project/ethereum/src/l1_contract_addresses.ts +++ b/yarn-project/ethereum/src/l1_contract_addresses.ts @@ -6,6 +6,8 @@ export const l1ContractsNames = [ 'registryAddress', 'inboxAddress', 'outboxAddress', + 'gasTokenAddress', + 'gasPortalAddress', ] as const; /** diff --git a/yarn-project/protocol-contracts/src/gas-token/index.test.ts b/yarn-project/protocol-contracts/src/gas-token/index.test.ts index 68a23995858..3e17606291a 100644 --- a/yarn-project/protocol-contracts/src/gas-token/index.test.ts +++ b/yarn-project/protocol-contracts/src/gas-token/index.test.ts @@ -1,8 +1,9 @@ +import { EthAddress } from '@aztec/circuits.js'; import { setupCustomSnapshotSerializers } from '@aztec/foundation/testing'; import omit from 'lodash.omit'; -import { GasTokenAddress, getCanonicalGasToken } from './index.js'; +import { getCanonicalGasToken } from './index.js'; describe('GasToken', () => { setupCustomSnapshotSerializers(expect); @@ -10,7 +11,7 @@ describe('GasToken', () => { // if you're updating the snapshots here then you'll also have to update CANONICAL_GAS_TOKEN_ADDRESS in // - noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr // - noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr - const contract = getCanonicalGasToken(); + const contract = getCanonicalGasToken(EthAddress.ZERO); expect(omit(contract, ['artifact', 'contractClass'])).toMatchSnapshot(); // bytecode is very large @@ -19,6 +20,5 @@ describe('GasToken', () => { // this contract has public bytecode expect(contract.contractClass.publicFunctions.map(x => omit(x, 'bytecode'))).toMatchSnapshot(); expect(contract.contractClass.packedBytecode.length).toBeGreaterThan(0); - expect(contract.address.toString()).toEqual(GasTokenAddress.toString()); }); }); diff --git a/yarn-project/protocol-contracts/src/gas-token/index.ts b/yarn-project/protocol-contracts/src/gas-token/index.ts index c3daa5dd96e..2ecfe6c8ed1 100644 --- a/yarn-project/protocol-contracts/src/gas-token/index.ts +++ b/yarn-project/protocol-contracts/src/gas-token/index.ts @@ -1,11 +1,13 @@ -import { EthAddress, Point } from '@aztec/circuits.js'; +import { AztecAddress, EthAddress, Point } from '@aztec/circuits.js'; import { ProtocolContract, getCanonicalProtocolContract } from '../protocol_contract.js'; import { GasTokenArtifact } from './artifact.js'; /** Returns the canonical deployment of the gas token. */ -export function getCanonicalGasToken(): ProtocolContract { - return getCanonicalProtocolContract(GasTokenArtifact, 1, [], Point.ZERO, EthAddress.ZERO); +export function getCanonicalGasToken(l1Bridge: EthAddress): ProtocolContract { + return getCanonicalProtocolContract(GasTokenArtifact, 1, [], Point.ZERO, l1Bridge); } -export const GasTokenAddress = getCanonicalGasToken().address; +export function getCanonicalGasTokenAddress(l1Bridge: EthAddress): AztecAddress { + return getCanonicalGasToken(l1Bridge).address; +} diff --git a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts index 98c9c1809c9..c2cba52f151 100644 --- a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts @@ -43,7 +43,11 @@ export async function createPXEService( const db = new KVPxeDatabase(await initStoreForRollup(AztecLmdbStore.open(pxeDbPath), l1Contracts.rollupAddress)); const server = new PXEService(keyStore, aztecNode, db, config, logSuffix); - for (const contract of [getCanonicalClassRegisterer(), getCanonicalInstanceDeployer(), getCanonicalGasToken()]) { + for (const contract of [ + getCanonicalClassRegisterer(), + getCanonicalInstanceDeployer(), + getCanonicalGasToken(l1Contracts.gasPortalAddress), + ]) { await server.registerContract(contract); } diff --git a/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts b/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts index 57bef759a67..fcb41bd9014 100644 --- a/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts +++ b/yarn-project/pxe/src/pxe_service/test/pxe_service.test.ts @@ -31,6 +31,8 @@ function createPXEService(): Promise { registryAddress: EthAddress.random(), inboxAddress: EthAddress.random(), outboxAddress: EthAddress.random(), + gasTokenAddress: EthAddress.random(), + gasPortalAddress: EthAddress.random(), }; node.getL1ContractAddresses.mockResolvedValue(mockedContracts); diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index e0e6a9c701a..a1d60103099 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -45,6 +45,8 @@ export function getConfigEnvVars(): SequencerClientConfig { REGISTRY_CONTRACT_ADDRESS, INBOX_CONTRACT_ADDRESS, OUTBOX_CONTRACT_ADDRESS, + GAS_TOKEN_CONTRACT_ADDRESS, + GAS_PORTAL_CONTRACT_ADDRESS, COINBASE, FEE_RECIPIENT, ACVM_WORKING_DIRECTORY, @@ -63,6 +65,10 @@ export function getConfigEnvVars(): SequencerClientConfig { registryAddress: REGISTRY_CONTRACT_ADDRESS ? EthAddress.fromString(REGISTRY_CONTRACT_ADDRESS) : EthAddress.ZERO, inboxAddress: INBOX_CONTRACT_ADDRESS ? EthAddress.fromString(INBOX_CONTRACT_ADDRESS) : EthAddress.ZERO, outboxAddress: OUTBOX_CONTRACT_ADDRESS ? EthAddress.fromString(OUTBOX_CONTRACT_ADDRESS) : EthAddress.ZERO, + gasTokenAddress: GAS_TOKEN_CONTRACT_ADDRESS ? EthAddress.fromString(GAS_TOKEN_CONTRACT_ADDRESS) : EthAddress.ZERO, + gasPortalAddress: GAS_PORTAL_CONTRACT_ADDRESS + ? EthAddress.fromString(GAS_PORTAL_CONTRACT_ADDRESS) + : EthAddress.ZERO, }; return { diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index ae278e45d44..83d063e6450 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -233,6 +233,7 @@ __metadata: "@aztec/noir-compiler": "workspace:^" "@aztec/noir-contracts.js": "workspace:^" "@aztec/p2p": "workspace:^" + "@aztec/protocol-contracts": "workspace:^" "@aztec/pxe": "workspace:^" "@jest/globals": ^29.5.0 "@types/jest": ^29.5.0