diff --git a/barretenberg/ts/src/barretenberg_wasm/fetch_code/browser/index.ts b/barretenberg/ts/src/barretenberg_wasm/fetch_code/browser/index.ts index 57b459b7ee81..f37c34405740 100644 --- a/barretenberg/ts/src/barretenberg_wasm/fetch_code/browser/index.ts +++ b/barretenberg/ts/src/barretenberg_wasm/fetch_code/browser/index.ts @@ -16,6 +16,7 @@ export async function fetchCode(multithreaded: boolean, wasmPath?: string) { : (await import(/* webpackIgnore: true */ './barretenberg.js')).default; } const res = await fetch(url); + // Default bb wasm is compressed, but user could point it to a non-compressed version const maybeCompressedData = await res.arrayBuffer(); const buffer = new Uint8Array(maybeCompressedData); const isGzip = diff --git a/barretenberg/ts/src/barretenberg_wasm/fetch_code/node/index.ts b/barretenberg/ts/src/barretenberg_wasm/fetch_code/node/index.ts index bc4a0a6c8e6b..ddbc1e61df53 100644 --- a/barretenberg/ts/src/barretenberg_wasm/fetch_code/node/index.ts +++ b/barretenberg/ts/src/barretenberg_wasm/fetch_code/node/index.ts @@ -16,7 +16,19 @@ function getCurrentDir() { // eslint-disable-next-line @typescript-eslint/no-unused-vars export async function fetchCode(multithreaded: boolean, wasmPath?: string) { const path = wasmPath ?? getCurrentDir() + '/../../barretenberg-threads.wasm.gz'; - const compressedData = await readFile(path); - const decompressedData = pako.ungzip(new Uint8Array(compressedData)); - return decompressedData.buffer; + // Default bb wasm is compressed, but user could point it to a non-compressed version + const maybeCompressedData = await readFile(path); + const buffer = new Uint8Array(maybeCompressedData); + const isGzip = + // Check magic number + buffer[0] === 0x1f && + buffer[1] === 0x8b && + // Check compression method: + buffer[2] === 0x08; + if (isGzip) { + const decompressedData = pako.ungzip(buffer); + return decompressedData.buffer; + } else { + return buffer; + } } diff --git a/playground/src/aztecEnv.ts b/playground/src/aztecEnv.ts index 715220205ee5..7896e55f89f1 100644 --- a/playground/src/aztecEnv.ts +++ b/playground/src/aztecEnv.ts @@ -16,9 +16,11 @@ import { NetworkDB, WalletDB } from './utils/storage'; import { type ContractFunctionInteractionTx } from './utils/txs'; const logLevel = ['silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'trace'] as const; +type LogLevel = (typeof logLevel)[number]; type Log = { - type: (typeof logLevel)[number]; + id: string; + type: LogLevel; timestamp: number; prefix: string; message: string; @@ -30,10 +32,20 @@ export class WebLogger { private static instance: WebLogger; private logs: Log[] = []; - private constructor(private setLogs: (logs: Log[]) => void) {} + private static updateIntervalMs = 1000; + private static readonly MAX_LOGS_TO_KEEP = 200; + + private constructor() {} static create(setLogs: (logs: Log[]) => void) { - WebLogger.instance = new WebLogger(setLogs); + if (!WebLogger.instance) { + WebLogger.instance = new WebLogger(); + setInterval(() => { + const instance = WebLogger.getInstance(); + const newLogs = instance.logs.slice(0, WebLogger.MAX_LOGS_TO_KEEP).sort((a, b) => b.timestamp - a.timestamp); + setLogs(newLogs); + }, WebLogger.updateIntervalMs); + } } static getInstance() { @@ -44,12 +56,11 @@ export class WebLogger { return new Proxy(createLogger(prefix), { get: (target, prop) => { if (logLevel.includes(prop as (typeof logLevel)[number])) { - return function () { - // eslint-disable-next-line prefer-rest-params - const args = [prop, prefix, ...arguments] as Parameters; + return function (this: Logger, ...data: Parameters) { + const loggingFn = prop as LogLevel; + const args = [loggingFn, prefix, ...data] as Parameters; WebLogger.getInstance().handleLog(...args); - // eslint-disable-next-line prefer-rest-params - target[prop].apply(this, arguments); + target[loggingFn].call(this, ...[data[0], data[1]]); }; } else { return target[prop]; @@ -65,8 +76,12 @@ export class WebLogger { // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any, ) { - this.logs.unshift({ type, prefix, message, data, timestamp: Date.now() }); - this.setLogs([...this.logs]); + this.logs.unshift({ id: this.randomId(), type, prefix, message, data, timestamp: Date.now() }); + } + + private randomId(): string { + const uint32 = window.crypto.getRandomValues(new Uint32Array(1))[0]; + return uint32.toString(16); } } diff --git a/playground/src/components/logPanel/logPanel.tsx b/playground/src/components/logPanel/logPanel.tsx index d0567d6c114c..07e26250e310 100644 --- a/playground/src/components/logPanel/logPanel.tsx +++ b/playground/src/components/logPanel/logPanel.tsx @@ -117,7 +117,7 @@ export function LogPanel() { {logs.map((log, index) => ( -
+
{log.prefix}: 
diff --git a/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts b/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts index 04727dcce21e..baba8b954c39 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/amm.test.ts @@ -1,14 +1,15 @@ -import { AccountWallet, Fr, PrivateFeePaymentMethod, type SimulateMethodOptions } from '@aztec/aztec.js'; +import { AccountWallet, Fr, type SimulateMethodOptions } from '@aztec/aztec.js'; import { FEE_FUNDING_FOR_TESTER_ACCOUNT } from '@aztec/constants'; import type { AMMContract } from '@aztec/noir-contracts.js/AMM'; import type { FPCContract } from '@aztec/noir-contracts.js/FPC'; +import type { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { jest } from '@jest/globals'; import { mintNotes } from '../../fixtures/token_utils.js'; import { capturePrivateExecutionStepsIfEnvSet } from '../../shared/capture_private_execution_steps.js'; -import { type AccountType, ClientFlowsBenchmark } from './client_flows_benchmark.js'; +import { type AccountType, type BenchmarkingFeePaymentMethod, ClientFlowsBenchmark } from './client_flows_benchmark.js'; jest.setTimeout(900_000); @@ -16,7 +17,7 @@ const AMOUNT_PER_NOTE = 1_000_000; const MINIMUM_NOTES_FOR_RECURSION_LEVEL = [0, 2, 10]; -describe('Client flows benchmarking', () => { +describe('AMM benchmark', () => { const t = new ClientFlowsBenchmark('amm'); // The admin that aids in the setup of the test let adminWallet: AccountWallet; @@ -30,6 +31,10 @@ describe('Client flows benchmarking', () => { let amm: AMMContract; // Liquidity contract for the AMM let liquidityToken: TokenContract; + // Sponsored FPC contract + let sponsoredFPC: SponsoredFPCContract; + // Benchmarking configuration + const config = t.config.amm; beforeAll(async () => { await t.applyBaseSnapshots(); @@ -37,15 +42,17 @@ describe('Client flows benchmarking', () => { await t.applyFPCSetupSnapshot(); await t.applyDeployCandyBarTokenSnapshot(); await t.applyDeployAmmSnapshot(); - ({ adminWallet, bananaFPC, bananaCoin, candyBarCoin, amm, liquidityToken } = await t.setup()); + await t.applyDeploySponsoredFPCSnapshot(); + ({ adminWallet, bananaFPC, bananaCoin, candyBarCoin, amm, liquidityToken, sponsoredFPC } = await t.setup()); }); afterAll(async () => { await t.teardown(); }); - ammBenchmark('ecdsar1'); - ammBenchmark('schnorr'); + for (const accountType of config.accounts) { + ammBenchmark(accountType); + } function ammBenchmark(accountType: AccountType) { return describe(`AMM benchmark for ${accountType}`, () => { @@ -68,68 +75,91 @@ describe('Client flows benchmarking', () => { // Register the AMM and liquidity token on the user's Wallet so we can simulate and prove await benchysWallet.registerContract(amm); await benchysWallet.registerContract(liquidityToken); + // Register the sponsored FPC on the user's PXE so we can simulate and prove + await benchysWallet.registerContract(sponsoredFPC); }); - describe(`Add liquidity with ${notesToCreate} notes in both tokens using a ${accountType} account`, () => { - beforeEach(async () => { - // Mint some CandyBarCoins for the user, separated in different notes - await mintNotes( - adminWallet, - benchysWallet.getAddress(), - candyBarCoin, - Array(notesToCreate).fill(BigInt(AMOUNT_PER_NOTE)), - ); - // Mint some BananaCoins for the user, separated in different notes - await mintNotes( - adminWallet, - benchysWallet.getAddress(), - bananaCoin, - Array(notesToCreate).fill(BigInt(AMOUNT_PER_NOTE)), - ); - }); - - // Ensure we create a change note, by sending an amount that is not a multiple of the note amount - const amountToSend = MINIMUM_NOTES_FOR_RECURSION_LEVEL[1] * AMOUNT_PER_NOTE + 1; - - it(`${accountType} contract adds liquidity to the AMM sending ${amountToSend} tokens using 1 recursions in both`, async () => { - const paymentMethod = new PrivateFeePaymentMethod(bananaFPC.address, benchysWallet); - const options: SimulateMethodOptions = { fee: { paymentMethod } }; - - const nonceForAuthwits = Fr.random(); - const token0Authwit = await benchysWallet.createAuthWit({ - caller: amm.address, - action: bananaCoin.methods.transfer_to_public( + function addLiquidityTest(benchmarkingPaymentMethod: BenchmarkingFeePaymentMethod) { + return describe(`Add liquidity with ${notesToCreate} notes in both tokens using a ${accountType} account`, () => { + beforeEach(async () => { + // Mint some CandyBarCoins for the user, separated in different notes + await mintNotes( + adminWallet, benchysWallet.getAddress(), - amm.address, - amountToSend, - nonceForAuthwits, - ), - }); - const token1Authwit = await benchysWallet.createAuthWit({ - caller: amm.address, - action: candyBarCoin.methods.transfer_to_public( + candyBarCoin, + Array(notesToCreate).fill(BigInt(AMOUNT_PER_NOTE)), + ); + // Mint some BananaCoins for the user, separated in different notes + await mintNotes( + adminWallet, benchysWallet.getAddress(), - amm.address, - amountToSend, - nonceForAuthwits, - ), + bananaCoin, + Array(notesToCreate).fill(BigInt(AMOUNT_PER_NOTE)), + ); }); - const addLiquidityInteraction = amm - .withWallet(benchysWallet) - .methods.add_liquidity(amountToSend, amountToSend, amountToSend, amountToSend, nonceForAuthwits) - .with({ authWitnesses: [token0Authwit, token1Authwit] }); - - await capturePrivateExecutionStepsIfEnvSet( - `${accountType}+amm_add_liquidity_1_recursions+pay_private_fpc`, - addLiquidityInteraction, - options, - ); - - const tx = await addLiquidityInteraction.send().wait(); - expect(tx.transactionFee!).toBeGreaterThan(0n); + // Ensure we create a change note, by sending an amount that is not a multiple of the note amount + const amountToSend = MINIMUM_NOTES_FOR_RECURSION_LEVEL[1] * AMOUNT_PER_NOTE + 1; + + it(`${accountType} contract adds liquidity to the AMM sending ${amountToSend} tokens using 1 recursions in both and pays using ${benchmarkingPaymentMethod}`, async () => { + const paymentMethod = t.paymentMethods[benchmarkingPaymentMethod]; + const options: SimulateMethodOptions = { + fee: { paymentMethod: await paymentMethod.forWallet(benchysWallet) }, + }; + + const nonceForAuthwits = Fr.random(); + const token0Authwit = await benchysWallet.createAuthWit({ + caller: amm.address, + action: bananaCoin.methods.transfer_to_public( + benchysWallet.getAddress(), + amm.address, + amountToSend, + nonceForAuthwits, + ), + }); + const token1Authwit = await benchysWallet.createAuthWit({ + caller: amm.address, + action: candyBarCoin.methods.transfer_to_public( + benchysWallet.getAddress(), + amm.address, + amountToSend, + nonceForAuthwits, + ), + }); + + const addLiquidityInteraction = amm + .withWallet(benchysWallet) + .methods.add_liquidity(amountToSend, amountToSend, amountToSend, amountToSend, nonceForAuthwits) + .with({ authWitnesses: [token0Authwit, token1Authwit] }); + + await capturePrivateExecutionStepsIfEnvSet( + `${accountType}+amm_add_liquidity_1_recursions+${benchmarkingPaymentMethod}`, + addLiquidityInteraction, + options, + 1 + // Account entrypoint + 1 + // Kernel init + paymentMethod.circuits + // Payment method circuits + 2 + // AMM add_liquidity + kernel inner + 2 + // Token transfer_to_public + kernel inner (token0) + 2 + // Account verify_private_authwit + kernel inner + 2 + // Token prepare_private_balance_increase + kernel inner (token0 refund) + 2 + // Token transfer_to_public + kernel inner (token1) + 2 + // Account verify_private_authwit + kernel inner + 2 + // Token prepare_private_balance_increase + kernel inner (token1 refund) + 2 + // Token prepare_private_balance_increase + kernel inner (liquidity token mint) + 1 + // Kernel reset + 1, // Kernel tail + ); + + const tx = await addLiquidityInteraction.send().wait(); + expect(tx.transactionFee!).toBeGreaterThan(0n); + }); }); - }); + } + + for (const paymentMethod of config.feePaymentMethods) { + addLiquidityTest(paymentMethod); + } }); } }); diff --git a/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts b/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts index cb562da47cc1..851ba75091f9 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/bridging.test.ts @@ -1,17 +1,18 @@ -import { AccountWallet, PrivateFeePaymentMethod, type SimulateMethodOptions } from '@aztec/aztec.js'; +import { AccountWallet, type SimulateMethodOptions } from '@aztec/aztec.js'; import { FEE_FUNDING_FOR_TESTER_ACCOUNT } from '@aztec/constants'; import type { FPCContract } from '@aztec/noir-contracts.js/FPC'; +import type { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { jest } from '@jest/globals'; import { capturePrivateExecutionStepsIfEnvSet } from '../../shared/capture_private_execution_steps.js'; import type { CrossChainTestHarness } from '../../shared/cross_chain_test_harness.js'; -import { type AccountType, ClientFlowsBenchmark } from './client_flows_benchmark.js'; +import { type AccountType, type BenchmarkingFeePaymentMethod, ClientFlowsBenchmark } from './client_flows_benchmark.js'; jest.setTimeout(300_000); -describe('Client flows benchmarking', () => { +describe('Bridging benchmark', () => { const t = new ClientFlowsBenchmark('bridging'); // The admin that aids in the setup of the test let adminWallet: AccountWallet; @@ -19,20 +20,26 @@ describe('Client flows benchmarking', () => { let bananaFPC: FPCContract; // BananaCoin Token contract, which we want to use to pay for the bridging let bananaCoin: TokenContract; + // Sponsored FPC contract + let sponsoredFPC: SponsoredFPCContract; + // Benchmarking configuration + const config = t.config.bridging; beforeAll(async () => { await t.applyBaseSnapshots(); await t.applyDeployBananaTokenSnapshot(); await t.applyFPCSetupSnapshot(); - ({ bananaFPC, bananaCoin, adminWallet } = await t.setup()); + await t.applyDeploySponsoredFPCSnapshot(); + ({ bananaFPC, bananaCoin, adminWallet, sponsoredFPC } = await t.setup()); }); afterAll(async () => { await t.teardown(); }); - bridgingBenchmark('ecdsar1'); - bridgingBenchmark('schnorr'); + for (const accountType of config.accounts) { + bridgingBenchmark(accountType); + } function bridgingBenchmark(accountType: AccountType) { return describe(`Bridging benchmark for ${accountType}`, () => { @@ -53,57 +60,64 @@ describe('Client flows benchmarking', () => { // Register both FPC and BananCoin on the user's PXE so we can simulate and prove await benchysWallet.registerContract(bananaFPC); await benchysWallet.registerContract(bananaCoin); + // Register the sponsored FPC on the user's PXE so we can simulate and prove + await benchysWallet.registerContract(sponsoredFPC); }); - it(`${accountType} contract bridges tokens from L1 claiming privately, pays using private FPC`, async () => { - // Generate a claim secret using pedersen - const l1TokenBalance = 1000000n; - const bridgeAmount = 100n; - - // 1. Mint tokens on L1 - await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); - - // 2. Deposit tokens to the TokenPortal - const claim = await crossChainTestHarness.sendTokensToPortalPrivate(bridgeAmount); - await crossChainTestHarness.makeMessageConsumable(claim.messageHash); - - // 3. Consume L1 -> L2 message and mint private tokens on L2, paying via FPC - const paymentMethod = new PrivateFeePaymentMethod(bananaFPC.address, benchysWallet); - const options: SimulateMethodOptions = { fee: { paymentMethod } }; - - const { recipient, claimAmount, claimSecret: secretForL2MessageConsumption, messageLeafIndex } = claim; - const claimInteraction = crossChainTestHarness.l2Bridge.methods.claim_private( - recipient, - claimAmount, - secretForL2MessageConsumption, - messageLeafIndex, - ); - - await capturePrivateExecutionStepsIfEnvSet( - `${accountType}+token_bridge_claim_private+pay_private_fpc`, - claimInteraction, - options, - 1 + // Account entrypoint - 1 + // Kernel init - 2 + // FPC entrypoint + kernel inner - 2 + // BananaCoin transfer_to_public + kernel inner - 2 + // Account verify_private_authwit + kernel inner - 2 + // BananaCoin prepare_private_balance_increase + kernel inner - 2 + // TokenBridge claim_private + kernel inner - 2 + // BridgedAsset mint_to_private + kernel inner - 1 + // Kernel reset - 1, // Kernel tail - ); - - // Ensure we paid a fee - const tx = await claimInteraction.send(options).wait(); - expect(tx.transactionFee!).toBeGreaterThan(0n); - - // 4. Check the balance - - const balance = await crossChainTestHarness.getL2PrivateBalanceOf(benchysWallet.getAddress()); - expect(balance).toBe(bridgeAmount); - }); + function privateClaimTest(benchmarkingPaymentMethod: BenchmarkingFeePaymentMethod) { + return it(`${accountType} contract bridges tokens from L1 claiming privately, pays using ${benchmarkingPaymentMethod}`, async () => { + // Generate a claim secret using pedersen + const l1TokenBalance = 1000000n; + const bridgeAmount = 100n; + + // 1. Mint tokens on L1 + await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); + + // 2. Deposit tokens to the TokenPortal + const claim = await crossChainTestHarness.sendTokensToPortalPrivate(bridgeAmount); + await crossChainTestHarness.makeMessageConsumable(claim.messageHash); + + // 3. Consume L1 -> L2 message and mint private tokens on L2 + const paymentMethod = t.paymentMethods[benchmarkingPaymentMethod]; + const options: SimulateMethodOptions = { + fee: { paymentMethod: await paymentMethod.forWallet(benchysWallet) }, + }; + + const { recipient, claimAmount, claimSecret: secretForL2MessageConsumption, messageLeafIndex } = claim; + const claimInteraction = crossChainTestHarness.l2Bridge.methods.claim_private( + recipient, + claimAmount, + secretForL2MessageConsumption, + messageLeafIndex, + ); + + await capturePrivateExecutionStepsIfEnvSet( + `${accountType}+token_bridge_claim_private+${benchmarkingPaymentMethod}`, + claimInteraction, + options, + 1 + // Account entrypoint + 1 + // Kernel init + paymentMethod.circuits + // Payment method circuits + 2 + // TokenBridge claim_private + kernel inner + 2 + // BridgedAsset mint_to_private + kernel inner + 1 + // Kernel reset + 1, // Kernel tail + ); + + // Ensure we paid a fee + const tx = await claimInteraction.send(options).wait(); + expect(tx.transactionFee!).toBeGreaterThan(0n); + + // 4. Check the balance + + const balance = await crossChainTestHarness.getL2PrivateBalanceOf(benchysWallet.getAddress()); + expect(balance).toBe(bridgeAmount); + }); + } + + for (const paymentMethod of config.feePaymentMethods) { + privateClaimTest(paymentMethod); + } }); } }); diff --git a/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts b/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts index fa73c202fb86..fe7b41c2a56e 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/client_flows_benchmark.ts @@ -5,8 +5,12 @@ import { AztecAddress, type AztecNode, FeeJuicePaymentMethodWithClaim, + type FeePaymentMethod, type Logger, type PXE, + PrivateFeePaymentMethod, + SponsoredFeePaymentMethod, + type Wallet, createLogger, } from '@aztec/aztec.js'; import { CheatCodes } from '@aztec/aztec.js/testing'; @@ -21,6 +25,7 @@ import { TestERC20Bytecode } from '@aztec/l1-artifacts/TestERC20Bytecode'; import { AMMContract } from '@aztec/noir-contracts.js/AMM'; import { FPCContract } from '@aztec/noir-contracts.js/FPC'; import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice'; +import { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC'; import { TokenContract as BananaCoin, TokenContract } from '@aztec/noir-contracts.js/Token'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice'; @@ -35,16 +40,19 @@ import { deployAccounts, } from '../../fixtures/snapshot_manager.js'; import { mintTokensToPrivate } from '../../fixtures/token_utils.js'; -import { type SetupOptions, setupCanonicalFeeJuice } from '../../fixtures/utils.js'; +import { type SetupOptions, setupCanonicalFeeJuice, setupSponsoredFPC } from '../../fixtures/utils.js'; import { CrossChainTestHarness } from '../../shared/cross_chain_test_harness.js'; import { FeeJuicePortalTestingHarnessFactory, type GasBridgingTestHarness, } from '../../shared/gas_portal_test_harness.js'; +import { type ClientFlowsConfig, FULL_FLOWS_CONFIG, KEY_FLOWS_CONFIG } from './config.js'; -const { E2E_DATA_PATH: dataPath } = process.env; +const { E2E_DATA_PATH: dataPath, BENCHMARK_CONFIG } = process.env; export type AccountType = 'ecdsar1' | 'schnorr'; +export type FeePaymentMethodGetter = (wallet: Wallet) => Promise; +export type BenchmarkingFeePaymentMethod = 'bridged_fee_juice' | 'private_fpc' | 'sponsored_fpc'; export class ClientFlowsBenchmark { private snapshotManager: ISnapshotManager; @@ -76,10 +84,37 @@ export class ClientFlowsBenchmark { public amm!: AMMContract; // Liquidity token for AMM public liquidityToken!: TokenContract; + // Sponsored FPC contract + public sponsoredFPC!: SponsoredFPCContract; // PXE used by the benchmarking user. It can be set up with client-side proving enabled public userPXE!: PXE; + public paymentMethods: Record = + { + // eslint-disable-next-line camelcase + bridged_fee_juice: { + forWallet: this.getBridgedFeeJuicePaymentMethodForWallet.bind(this), + circuits: 2, // FeeJuice claim + kernel inner + }, + // eslint-disable-next-line camelcase + private_fpc: { + forWallet: this.getPrivateFPCPaymentMethodForWallet.bind(this), + circuits: + 2 + // FPC entrypoint + kernel inner + 2 + // BananaCoin transfer_to_public + kernel inner + 2 + // Account verify_private_authwit + kernel inner + 2, // BananaCoin prepare_private_balance_increase + kernel inner + }, + // eslint-disable-next-line camelcase + sponsored_fpc: { + forWallet: this.getSponsoredFPCPaymentMethodForWallet.bind(this), + circuits: 2, // Sponsored FPC sponsor_unconditionally + kernel inner + }, + }; + + public config: ClientFlowsConfig; + constructor(testName?: string, setupOptions: Partial = {}) { this.logger = createLogger(`bench:client_flows${testName ? `:${testName}` : ''}`); this.snapshotManager = createSnapshotManager( @@ -88,6 +123,7 @@ export class ClientFlowsBenchmark { { startProverNode: true, ...setupOptions }, { ...setupOptions }, ); + this.config = BENCHMARK_CONFIG === 'key_flows' ? KEY_FLOWS_CONFIG : FULL_FLOWS_CONFIG; } async setup() { @@ -259,6 +295,20 @@ export class ClientFlowsBenchmark { ); } + async applyDeploySponsoredFPCSnapshot() { + await this.snapshotManager.snapshot( + 'deploy_sponsored_fpc', + async () => { + const sponsoredFPC = await setupSponsoredFPC(this.pxe); + this.logger.info(`SponsoredFPC deployed at ${sponsoredFPC.address}`); + return { sponsoredFPCAddress: sponsoredFPC.address }; + }, + async ({ sponsoredFPCAddress }) => { + this.sponsoredFPC = await SponsoredFPCContract.at(sponsoredFPCAddress, this.adminWallet); + }, + ); + } + public async createCrossChainTestHarness(owner: AccountWallet) { const { publicClient, walletClient } = createL1Clients(this.context.aztecNodeConfig.l1RpcUrls, MNEMONIC); @@ -328,4 +378,20 @@ export class ClientFlowsBenchmark { }, ); } + + public async getBridgedFeeJuicePaymentMethodForWallet(wallet: Wallet) { + const claim = await this.feeJuiceBridgeTestHarness.prepareTokensOnL1( + FEE_FUNDING_FOR_TESTER_ACCOUNT, + wallet.getAddress(), + ); + return new FeeJuicePaymentMethodWithClaim(wallet, claim); + } + + public getPrivateFPCPaymentMethodForWallet(wallet: Wallet) { + return Promise.resolve(new PrivateFeePaymentMethod(this.bananaFPC.address, wallet)); + } + + public getSponsoredFPCPaymentMethodForWallet(_wallet: Wallet) { + return Promise.resolve(new SponsoredFeePaymentMethod(this.sponsoredFPC.address)); + } } diff --git a/yarn-project/end-to-end/src/bench/client_flows/config.ts b/yarn-project/end-to-end/src/bench/client_flows/config.ts new file mode 100644 index 000000000000..eaebb286eeaa --- /dev/null +++ b/yarn-project/end-to-end/src/bench/client_flows/config.ts @@ -0,0 +1,53 @@ +import type { AccountType, BenchmarkingFeePaymentMethod } from './client_flows_benchmark.js'; + +export type ClientFlowConfig = { + accounts: AccountType[]; + feePaymentMethods: BenchmarkingFeePaymentMethod[]; + recursions?: number[]; +}; + +type ClientFlows = 'deployments' | 'transfers' | 'bridging' | 'amm'; + +export type ClientFlowsConfig = { + [key in ClientFlows]: ClientFlowConfig; +}; + +export const KEY_FLOWS_CONFIG: ClientFlowsConfig = { + deployments: { + accounts: ['ecdsar1', 'schnorr'], + feePaymentMethods: ['sponsored_fpc'], + }, + amm: { + accounts: ['ecdsar1'], + feePaymentMethods: ['sponsored_fpc'], + }, + bridging: { + accounts: ['ecdsar1'], + feePaymentMethods: ['sponsored_fpc'], + }, + transfers: { + accounts: ['ecdsar1'], + feePaymentMethods: ['sponsored_fpc'], + recursions: [1], + }, +}; + +export const FULL_FLOWS_CONFIG: ClientFlowsConfig = { + deployments: { + accounts: ['ecdsar1', 'schnorr'], + feePaymentMethods: ['bridged_fee_juice', 'sponsored_fpc'], + }, + amm: { + accounts: ['ecdsar1', 'schnorr'], + feePaymentMethods: ['sponsored_fpc', 'private_fpc'], + }, + bridging: { + accounts: ['ecdsar1', 'schnorr'], + feePaymentMethods: ['sponsored_fpc', 'private_fpc'], + }, + transfers: { + accounts: ['ecdsar1', 'schnorr'], + feePaymentMethods: ['sponsored_fpc', 'private_fpc'], + recursions: [0, 1, 2], + }, +}; diff --git a/yarn-project/end-to-end/src/bench/client_flows/data_extractor.ts b/yarn-project/end-to-end/src/bench/client_flows/data_extractor.ts index 2d0a47f47a06..1ee1a1815d84 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/data_extractor.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/data_extractor.ts @@ -163,7 +163,6 @@ async function main() { bytecode: acirStack[i], witness: witnessStack[i], })); - let minimumTrace: StructuredTrace | undefined; let stats: { duration: number; eventName: string; proofSize: number } | undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any let error: any | undefined; @@ -179,10 +178,11 @@ async function main() { await writeFile(join(ivcFolder, flow, 'logs.json'), JSON.stringify(currentLogs, null, 2)); if (!error) { - minimumTrace = getMinimumTrace(currentLogs, proverType); stats = currentLogs[0].data as { duration: number; eventName: string; proofSize: number }; } + const minimumTrace = getMinimumTrace(currentLogs, proverType); + const steps = executionSteps.reduce((acc, step, i) => { const previousAccGateCount = i === 0 ? 0 : acc[i - 1].accGateCount!; return [ diff --git a/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts b/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts index 7eedf7a5a812..ee200b0e8d10 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/deployments.test.ts @@ -1,114 +1,95 @@ import { EcdsaRAccountContractArtifact } from '@aztec/accounts/ecdsa'; -import { - AccountWallet, - type DeployOptions, - FeeJuicePaymentMethodWithClaim, - Fr, - registerContractClass, -} from '@aztec/aztec.js'; -import { FEE_FUNDING_FOR_TESTER_ACCOUNT } from '@aztec/constants'; +import { AccountWallet, type DeployOptions, Fr, registerContractClass } from '@aztec/aztec.js'; +import type { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC'; import { jest } from '@jest/globals'; import { capturePrivateExecutionStepsIfEnvSet } from '../../shared/capture_private_execution_steps.js'; -import { ClientFlowsBenchmark } from './client_flows_benchmark.js'; +import { type AccountType, type BenchmarkingFeePaymentMethod, ClientFlowsBenchmark } from './client_flows_benchmark.js'; jest.setTimeout(300_000); -describe('Client flows benchmarking', () => { +describe('Deployment benchmark', () => { const t = new ClientFlowsBenchmark('deployments'); // The admin that aids in the setup of the test let adminWallet: AccountWallet; + // Sponsored FPC contract + let sponsoredFPC: SponsoredFPCContract; + // Benchmarking configuration + const config = t.config.deployments; beforeAll(async () => { await t.applyBaseSnapshots(); - ({ adminWallet } = await t.setup()); + await t.applyDeploySponsoredFPCSnapshot(); + ({ adminWallet, sponsoredFPC } = await t.setup()); + // Ensure the ECDSAK1 contract is already registered, to avoid benchmarking an extra call to the ContractClassRegisterer + // The typical interaction would be for a user to deploy an account contract that is already registered in the + // network. + const registerContractClassInteraction = await registerContractClass(adminWallet, EcdsaRAccountContractArtifact); + await registerContractClassInteraction.send().wait(); }); afterAll(async () => { await t.teardown(); }); - describe('Deployments', () => { - it('Deploy ECDSA R1 account contract, pay using bridged fee juice', async () => { - // Ensure the contract is already registered, to avoid benchmarking an extra call to the ContractClassRegisterer - // The typical interaction would be for a user to deploy an account contract that is already registered in the - // network. - const registerContractClassInteraction = await registerContractClass(adminWallet, EcdsaRAccountContractArtifact); - await registerContractClassInteraction.send().wait(); - - const benchysAccountManager = await t.createBenchmarkingAccountManager('ecdsar1'); - const benchysWallet = await benchysAccountManager.getWallet(); - const benchysAddress = benchysWallet.getAddress(); - - const claim = await t.feeJuiceBridgeTestHarness.prepareTokensOnL1(FEE_FUNDING_FOR_TESTER_ACCOUNT, benchysAddress); - const paymentMethod = new FeeJuicePaymentMethodWithClaim(benchysWallet, claim); - - const deploymentInteraction = await benchysAccountManager.getDeployMethod(); - const wrappedPaymentMethod = await benchysAccountManager.getSelfPaymentMethod(paymentMethod); - const fee = { paymentMethod: wrappedPaymentMethod }; - // Publicly deploy the contract, but skip the class registration as that is the - // "typical" use case - const options: DeployOptions = { - fee, - universalDeploy: true, - skipClassRegistration: true, - skipPublicDeployment: false, - skipInitialization: false, - contractAddressSalt: new Fr(benchysAccountManager.salt), - }; - - await capturePrivateExecutionStepsIfEnvSet( - 'deploy_r1+claim_fee_juice+pay_fee_juice', - deploymentInteraction, - options, - ); - - // Ensure we paid a fee - const tx = await deploymentInteraction.send(options).wait(); - expect(tx.transactionFee!).toBeGreaterThan(0n); - }); - - it('Deploy Schnorr Account contract, pay using bridged fee juice', async () => { - const benchysAccountManager = await t.createBenchmarkingAccountManager('schnorr'); - const benchysWallet = await benchysAccountManager.getWallet(); - const benchysAddress = benchysWallet.getAddress(); - - const claim = await t.feeJuiceBridgeTestHarness.prepareTokensOnL1(FEE_FUNDING_FOR_TESTER_ACCOUNT, benchysAddress); - const paymentMethod = new FeeJuicePaymentMethodWithClaim(benchysWallet, claim); - - const deploymentInteraction = await benchysAccountManager.getDeployMethod(); - const wrappedPaymentMethod = await benchysAccountManager.getSelfPaymentMethod(paymentMethod); - const fee = { paymentMethod: wrappedPaymentMethod }; - // Publicly deploy the contract, but skip the class registration as that is the - // "typical" use case - const options: DeployOptions = { - fee, - universalDeploy: true, - skipClassRegistration: true, - skipPublicDeployment: false, - skipInitialization: false, - contractAddressSalt: new Fr(benchysAccountManager.salt), - }; - - await capturePrivateExecutionStepsIfEnvSet( - 'deploy_schnorr+claim_fee_juice+pay_fee_juice', - deploymentInteraction, - options, - 1 + // Multicall entrypoint - 1 + // Kernel init - 2 + // ContractInstanceDeployer deploy + kernel inner - 2 + // ContractClassRegisterer assert_class_id_is_registered + kernel inner - 2 + // Account constructor + kernel inner - 2 + // Account entrypoint (wrapped fee payload) + kernel inner - 2 + // FeeJuice claim + kernel inner - 1 + // Kernel reset - 1, // Kernel tail - ); - - // Ensure we paid a fee - const tx = await deploymentInteraction.send(options).wait(); - expect(tx.transactionFee!).toBeGreaterThan(0n); + for (const accountType of config.accounts) { + deploymentBenchmark(accountType); + } + + function deploymentBenchmark(accountType: AccountType) { + return describe(`Deployment benchmark for ${accountType}`, () => { + function deploymentTest(benchmarkingPaymentMethod: BenchmarkingFeePaymentMethod) { + return it(`Deploys a ${accountType} account contract, pays using ${benchmarkingPaymentMethod}`, async () => { + const benchysAccountManager = await t.createBenchmarkingAccountManager(accountType); + const benchysWallet = await benchysAccountManager.getWallet(); + + if (benchmarkingPaymentMethod === 'sponsored_fpc') { + await benchysWallet.registerContract(sponsoredFPC); + } + + const deploymentInteraction = await benchysAccountManager.getDeployMethod(); + + const paymentMethod = t.paymentMethods[benchmarkingPaymentMethod]; + const wrappedPaymentMethod = await benchysAccountManager.getSelfPaymentMethod( + await paymentMethod.forWallet(benchysWallet), + ); + const fee = { paymentMethod: wrappedPaymentMethod }; + // Publicly deploy the contract, but skip the class registration as that is the + // "typical" use case + const options: DeployOptions = { + fee, + universalDeploy: true, + skipClassRegistration: true, + skipPublicDeployment: false, + skipInitialization: false, + contractAddressSalt: new Fr(benchysAccountManager.salt), + }; + + await capturePrivateExecutionStepsIfEnvSet( + `deploy_${accountType}+${benchmarkingPaymentMethod}`, + deploymentInteraction, + options, + 1 + // Multicall entrypoint + 1 + // Kernel init + 2 + // ContractInstanceDeployer deploy + kernel inner + 2 + // ContractClassRegisterer assert_class_id_is_registered + kernel inner + 2 + // Account constructor + kernel inner + 2 + // Account entrypoint (wrapped fee payload) + kernel inner + paymentMethod.circuits + // Payment method circuits + 1 + // Kernel reset + 1, // Kernel tail + ); + + // Ensure we paid a fee + const tx = await deploymentInteraction.send(options).wait(); + expect(tx.transactionFee!).toBeGreaterThan(0n); + }); + } + + for (const paymentMethod of config.feePaymentMethods) { + deploymentTest(paymentMethod); + } }); - }); + } }); diff --git a/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts b/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts index ab76e06a6a27..a49ba677bcbc 100644 --- a/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts +++ b/yarn-project/end-to-end/src/bench/client_flows/transfers.test.ts @@ -1,27 +1,22 @@ -import { - AccountWallet, - type AztecNode, - Fr, - PrivateFeePaymentMethod, - type SimulateMethodOptions, -} from '@aztec/aztec.js'; +import { AccountWallet, type AztecNode, Fr, type SimulateMethodOptions } from '@aztec/aztec.js'; import { FEE_FUNDING_FOR_TESTER_ACCOUNT } from '@aztec/constants'; import type { FPCContract } from '@aztec/noir-contracts.js/FPC'; +import type { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { jest } from '@jest/globals'; import { mintNotes } from '../../fixtures/token_utils.js'; import { capturePrivateExecutionStepsIfEnvSet } from '../../shared/capture_private_execution_steps.js'; -import { type AccountType, ClientFlowsBenchmark } from './client_flows_benchmark.js'; +import { type AccountType, type BenchmarkingFeePaymentMethod, ClientFlowsBenchmark } from './client_flows_benchmark.js'; -jest.setTimeout(900_000); +jest.setTimeout(1_600_000); const AMOUNT_PER_NOTE = 1_000_000; const MINIMUM_NOTES_FOR_RECURSION_LEVEL = [0, 2, 10]; -describe('Client flows benchmarking', () => { +describe('Transfer benchmark', () => { const t = new ClientFlowsBenchmark('transfers'); // The admin that aids in the setup of the test let adminWallet: AccountWallet; @@ -33,21 +28,28 @@ describe('Client flows benchmarking', () => { let candyBarCoin: TokenContract; // Aztec node to answer queries let node: AztecNode; + // Sponsored FPC contract + let sponsoredFPC: SponsoredFPCContract; + // Benchmarking configuration + const config = t.config.transfers; beforeAll(async () => { await t.applyBaseSnapshots(); await t.applyDeployBananaTokenSnapshot(); await t.applyFPCSetupSnapshot(); await t.applyDeployCandyBarTokenSnapshot(); - ({ adminWallet, bananaFPC, bananaCoin, candyBarCoin, aztecNode: node } = await t.setup()); + await t.applyDeploySponsoredFPCSnapshot(); + + ({ adminWallet, bananaFPC, bananaCoin, candyBarCoin, aztecNode: node, sponsoredFPC } = await t.setup()); }); afterAll(async () => { await t.teardown(); }); - transferBenchmark('ecdsar1'); - transferBenchmark('schnorr'); + for (const accountType of config.accounts) { + transferBenchmark(accountType); + } function transferBenchmark(accountType: AccountType) { return describe(`Transfer benchmark for ${accountType}`, () => { @@ -65,9 +67,15 @@ describe('Client flows benchmarking', () => { await benchysWallet.registerContract(bananaCoin); // Register the CandyBarCoin on the user's Wallet so we can simulate and prove await benchysWallet.registerContract(candyBarCoin); + // Register the sponsored FPC on the user's PXE so we can simulate and prove + await benchysWallet.registerContract(sponsoredFPC); }); - function recursionTest(recursions: number, notesToCreate: number) { + function recursionTest( + recursions: number, + notesToCreate: number, + benchmarkingPaymentMethod: BenchmarkingFeePaymentMethod, + ) { return describe(`Mint ${notesToCreate} notes and transfer using a ${accountType} account`, () => { // Total amount of coins minted across all notes let totalAmount: bigint; @@ -98,30 +106,29 @@ describe('Client flows benchmarking', () => { caller: adminWallet.getAddress(), action: interaction, }); - await interaction.send({ authWitnesses: [witness] }).wait(); + await interaction.send({ authWitnesses: [witness] }).wait({ timeout: 120 }); }); // Ensure we create a change note, by sending an amount that is not a multiple of the note amount const amountToSend = MINIMUM_NOTES_FOR_RECURSION_LEVEL[recursions] * AMOUNT_PER_NOTE + 1; - it(`${accountType} contract transfers ${amountToSend} tokens using ${recursions} recursions`, async () => { - const paymentMethod = new PrivateFeePaymentMethod(bananaFPC.address, benchysWallet); - const options: SimulateMethodOptions = { fee: { paymentMethod } }; + it(`${accountType} contract transfers ${amountToSend} tokens using ${recursions} recursions, pays using ${benchmarkingPaymentMethod}`, async () => { + const paymentMethod = t.paymentMethods[benchmarkingPaymentMethod]; + const options: SimulateMethodOptions = { + fee: { paymentMethod: await paymentMethod.forWallet(benchysWallet) }, + }; const asset = await TokenContract.at(candyBarCoin.address, benchysWallet); const transferInteraction = asset.methods.transfer(adminWallet.getAddress(), amountToSend); await capturePrivateExecutionStepsIfEnvSet( - `${accountType}+transfer_${recursions}_recursions+pay_private_fpc`, + `${accountType}+transfer_${recursions}_recursions+${benchmarkingPaymentMethod}`, transferInteraction, options, 1 + // Account entrypoint 1 + // Kernel init - 2 + // FPC entrypoint + kernel inner - 2 + // BananaCoin transfer_to_public + kernel inner - 2 + // Account verify_private_authwit + kernel inner - 2 + // BananaCoin prepare_private_balance_increase + kernel inner + paymentMethod.circuits + // Payment method circuits 2 + // CandyBarCoin transfer + kernel inner recursions * 2 + // (CandyBarCoin _recurse_subtract_balance + kernel inner) * recursions 1 + // Kernel reset @@ -138,17 +145,19 @@ describe('Client flows benchmarking', () => { /* * We should have created the following nullifiers: * - One per created note - * - One for the fee note * - One for the transaction + * - One for the fee note if we're using private fpc */ - expect(txEffects!.data.nullifiers.length).toBe(notesToCreate + 2); + expect(txEffects!.data.nullifiers.length).toBe( + notesToCreate + 1 + (benchmarkingPaymentMethod === 'private_fpc' ? 1 : 0), + ); /** We should have created 4 new notes, * - One for the recipient * - One for the sender (with the change) - * - One for the fee - * - One for the fee refund + * - One for the fee if we're using private fpc + * - One for the fee refund if we're using private fpc */ - expect(txEffects!.data.noteHashes.length).toBe(4); + expect(txEffects!.data.noteHashes.length).toBe(2 + (benchmarkingPaymentMethod === 'private_fpc' ? 2 : 0)); expectedChange = totalAmount - BigInt(amountToSend); @@ -158,9 +167,10 @@ describe('Client flows benchmarking', () => { }); } - for (let i = 0; i < MINIMUM_NOTES_FOR_RECURSION_LEVEL.length; i++) { - // Create the minimum amount of notes for the test, which is just above the threshold for recursion at each level - recursionTest(i, MINIMUM_NOTES_FOR_RECURSION_LEVEL[i] + 1); + for (const paymentMethod of config.feePaymentMethods) { + for (const recursions of config.recursions ?? []) { + recursionTest(recursions, MINIMUM_NOTES_FOR_RECURSION_LEVEL[recursions] + 1, paymentMethod); + } } }); }