From aaf0d8c02b99eb1c037745d54c0859553492c088 Mon Sep 17 00:00:00 2001 From: David Banks <47112877+dbanks12@users.noreply.github.com> Date: Tue, 4 Feb 2025 07:51:43 -0500 Subject: [PATCH 1/2] chore: benchmark sha256 number of instructions executed in AVM (#11253) Also move hash stuff into another AVM test contract. This lets us get stats like below, although note that this isn't directly an output from the yarn tests: For a `sha256` of N bytes, how many AVM/Brillig instructions are executed and how much gas does it consume? ```N= 10 bytes: 3998 instructions, 69006 L2 Gas N= 20 bytes: 4513 instructions, 77778 L2 Gas N= 30 bytes: 5328 instructions, 91326 L2 Gas N= 40 bytes: 5843 instructions, 100098 L2 Gas N= 50 bytes: 6672 instructions, 113877 L2 Gas N= 60 bytes: 7490 instructions, 127626 L2 Gas N= 70 bytes: 8662 instructions, 147936 L2 Gas N= 80 bytes: 9207 instructions, 157368 L2 Gas N= 90 bytes: 10052 instructions, 171576 L2 Gas N= 100 bytes: 10597 instructions, 181008 L2 Gas N= 255 bytes: 23046 instructions, 392055 L2 Gas N= 256 bytes: 23022 instructions, 392121 L2 Gas N= 511 bytes: 43107 instructions, 732336 L2 Gas N= 512 bytes: 42978 instructions, 731004 L2 Gas N=2048 bytes: 162801 instructions, 2765820 L2 Gas``` --- noir-projects/noir-contracts/Nargo.toml | 1 + .../avm_gadgets_test_contract/Nargo.toml | 8 ++ .../avm_gadgets_test_contract/src/main.nr | 93 +++++++++++++++++++ .../contracts/avm_test_contract/src/main.nr | 13 ++- .../simulator/src/avm/avm_simulator.test.ts | 31 ++++++- .../simulator/src/avm/fixtures/index.ts | 39 ++++++++ 6 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/src/main.nr diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 18ba10820a7..263c45bba62 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -5,6 +5,7 @@ members = [ "contracts/auth_contract", "contracts/auth_registry_contract", "contracts/auth_wit_test_contract", + "contracts/avm_gadgets_test_contract", "contracts/avm_initializer_test_contract", "contracts/avm_test_contract", "contracts/fpc_contract", diff --git a/noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/Nargo.toml new file mode 100644 index 00000000000..ca88c967955 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "avm_gadgets_test_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/src/main.nr new file mode 100644 index 00000000000..0a353249339 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/avm_gadgets_test_contract/src/main.nr @@ -0,0 +1,93 @@ +use dep::aztec::macros::aztec; + +#[aztec] +contract AvmGadgetsTest { + use dep::aztec::macros::functions::public; + + #[public] + fn keccak_hash(data: [u8; 10]) -> [u8; 32] { + std::hash::keccak256(data, data.len() as u32) + } + + #[public] + fn keccak_f1600(data: [u64; 25]) -> [u64; 25] { + std::hash::keccak::keccakf1600(data) + } + + #[public] + fn poseidon2_hash(data: [Field; 10]) -> Field { + std::hash::poseidon2::Poseidon2::hash(data, data.len()) + } + + #[public] + fn sha256_hash_10(data: [u8; 10]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_20(data: [u8; 20]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_30(data: [u8; 30]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_40(data: [u8; 40]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_50(data: [u8; 50]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_60(data: [u8; 60]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_70(data: [u8; 70]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_80(data: [u8; 80]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_90(data: [u8; 90]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_100(data: [u8; 100]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_255(data: [u8; 255]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_256(data: [u8; 256]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_511(data: [u8; 511]) -> [u8; 32] { + std::hash::sha256(data) + } + #[public] + fn sha256_hash_512(data: [u8; 512]) -> [u8; 32] { + std::hash::sha256(data) + } + + #[public] + fn sha256_hash_2048(data: [u8; 2048]) -> [u8; 32] { + std::hash::sha256(data) + } + + #[public] + fn pedersen_hash(data: [Field; 10]) -> Field { + std::hash::pedersen_hash(data) + } + + #[public] + fn pedersen_hash_with_index(data: [Field; 10]) -> Field { + std::hash::pedersen_hash_with_separator(data, /*index=*/ 20) + } +} diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index e3d45c694ea..5fd24054467 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -297,6 +297,8 @@ pub contract AvmTest { } /************************************************************************ +<<<<<<< HEAD +======= * Hashing functions ************************************************************************/ #[public] @@ -330,6 +332,7 @@ pub contract AvmTest { } /************************************************************************ +>>>>>>> master * Contract instance ************************************************************************/ #[public] @@ -660,15 +663,15 @@ pub contract AvmTest { dep::aztec::oracle::debug_log::debug_log("read_storage_map"); let _ = read_storage_map(context.this_address()); dep::aztec::oracle::debug_log::debug_log("keccak_hash"); - let _ = keccak_hash(args_u8); + let _ = std::hash::keccak256(args_u8, args_u8.len() as u32); dep::aztec::oracle::debug_log::debug_log("sha256_hash"); - let _ = sha256_hash(args_u8); + let _ = std::hash::sha256(args_u8); dep::aztec::oracle::debug_log::debug_log("poseidon2_hash"); - let _ = poseidon2_hash(args_field); + let _ = std::hash::poseidon2::Poseidon2::hash(args_field, args_field.len()); dep::aztec::oracle::debug_log::debug_log("pedersen_hash"); - let _ = pedersen_hash(args_field); + let _ = std::hash::pedersen_hash(args_field); dep::aztec::oracle::debug_log::debug_log("pedersen_hash_with_index"); - let _ = pedersen_hash_with_index(args_field); + let _ = std::hash::pedersen_hash_with_separator(args_field, /*index=*/ 20); dep::aztec::oracle::debug_log::debug_log("test_get_contract_instance"); test_get_contract_instance_matches( get_instance_for_address, diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index ab3b1946c34..255420c45a2 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -36,6 +36,7 @@ import { AvmSimulator } from './avm_simulator.js'; import { AvmEphemeralForest } from './avm_tree.js'; import { isAvmBytecode, markBytecodeAsAvm } from './bytecode_utils.js'; import { + getAvmGadgetsTestContractBytecode, getAvmTestContractArtifact, getAvmTestContractBytecode, initContext, @@ -384,19 +385,43 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); }); + /* + * Can run these as follows to measure sha256 instruction execution counts: + * for i in 10 20 30 40 50 60 70 80 90 100 255 256 511 512 2048; do + * echo sha-ing $i...; + * LOG_LEVEL=debug yarn test src/avm/avm_simulator.test.ts -t "sha256_hash_$i " &> sha$i.log; + * done + * for i in 10 20 30 40 50 60 70 80 90 100 255 256 511 512 2048; do + * echo sha256 of $i bytes $(grep -Eo 'Executed .* instructions.* Gas' sha$i.log); + * done + */ describe.each([ - ['sha256_hash', /*input=*/ randomMemoryBytes(10), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_10', /*input=*/ randomMemoryBytes(10), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_20', /*input=*/ randomMemoryBytes(20), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_30', /*input=*/ randomMemoryBytes(30), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_40', /*input=*/ randomMemoryBytes(40), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_50', /*input=*/ randomMemoryBytes(50), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_60', /*input=*/ randomMemoryBytes(60), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_70', /*input=*/ randomMemoryBytes(70), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_80', /*input=*/ randomMemoryBytes(80), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_90', /*input=*/ randomMemoryBytes(90), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_100', /*input=*/ randomMemoryBytes(100), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_255', /*input=*/ randomMemoryBytes(255), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_256', /*input=*/ randomMemoryBytes(256), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_511', /*input=*/ randomMemoryBytes(511), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_512', /*input=*/ randomMemoryBytes(512), /*output=*/ sha256FromMemoryBytes], + ['sha256_hash_2048', /*input=*/ randomMemoryBytes(2048), /*output=*/ sha256FromMemoryBytes], ['keccak_hash', /*input=*/ randomMemoryBytes(10), /*output=*/ keccak256FromMemoryBytes], ['keccak_f1600', /*input=*/ randomMemoryUint64s(25), /*output=*/ keccakF1600FromMemoryUint64s], ['poseidon2_hash', /*input=*/ randomMemoryFields(10), /*output=*/ poseidon2FromMemoryFields], ['pedersen_hash', /*input=*/ randomMemoryFields(10), /*output=*/ pedersenFromMemoryFields], ['pedersen_hash_with_index', /*input=*/ randomMemoryFields(10), /*output=*/ indexedPedersenFromMemoryFields], ])('Hashes in noir contracts', (name: string, input: MemoryValue[], output: (msg: any[]) => Promise) => { - it(`Should execute contract function that performs ${name}`, async () => { + it(`Should execute contract function that performs ${name} on input of length ${input.length}`, async () => { const calldata = input.map(e => e.toFr()); const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode(name); + const bytecode = getAvmGadgetsTestContractBytecode(name); const results = await new AvmSimulator(context).executeBytecode(bytecode); expect(results.reverted).toBe(false); diff --git a/yarn-project/simulator/src/avm/fixtures/index.ts b/yarn-project/simulator/src/avm/fixtures/index.ts index 938bebf2a2c..8ef8937470e 100644 --- a/yarn-project/simulator/src/avm/fixtures/index.ts +++ b/yarn-project/simulator/src/avm/fixtures/index.ts @@ -4,6 +4,7 @@ import { type ContractArtifact, type FunctionArtifact, FunctionSelector } from ' import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; +import { AvmGadgetsTestContractArtifact } from '@aztec/noir-contracts.js/AvmGadgetsTest'; import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; import { strict as assert } from 'assert'; @@ -169,6 +170,13 @@ export function getAvmTestContractFunctionSelector(functionName: string): Promis return getFunctionSelector(functionName, AvmTestContractArtifact); } +export function getAvmGadgetsTestContractFunctionSelector(functionName: string): Promise { + const artifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName)!; + assert(!!artifact, `Function ${functionName} not found in AvmGadgetsTestContractArtifact`); + const params = artifact.parameters; + return FunctionSelector.fromNameAndParameters(artifact.name, params); +} + export function getAvmTestContractArtifact(functionName: string): FunctionArtifact { const artifact = getContractFunctionArtifact(functionName, AvmTestContractArtifact); assert( @@ -178,11 +186,25 @@ export function getAvmTestContractArtifact(functionName: string): FunctionArtifa return artifact; } +export function getAvmGadgetsTestContractArtifact(functionName: string): FunctionArtifact { + const artifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName)!; + assert( + !!artifact?.bytecode, + `No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`, + ); + return artifact; +} + export function getAvmTestContractBytecode(functionName: string): Buffer { const artifact = getAvmTestContractArtifact(functionName); return artifact.bytecode; } +export function getAvmGadgetsTestContractBytecode(functionName: string): Buffer { + const artifact = getAvmGadgetsTestContractArtifact(functionName); + return artifact.bytecode; +} + export function resolveAvmTestContractAssertionMessage( functionName: string, revertReason: AvmRevertReason, @@ -190,3 +212,20 @@ export function resolveAvmTestContractAssertionMessage( ): string | undefined { return resolveContractAssertionMessage(functionName, revertReason, output, AvmTestContractArtifact); } + +export function resolveAvmGadgetsTestContractAssertionMessage( + functionName: string, + revertReason: AvmRevertReason, + output: Fr[], +): string | undefined { + traverseCauseChain(revertReason, cause => { + revertReason = cause as AvmRevertReason; + }); + + const functionArtifact = AvmGadgetsTestContractArtifact.functions.find(f => f.name === functionName); + if (!functionArtifact || !revertReason.noirCallStack || !isNoirCallStackUnresolved(revertReason.noirCallStack)) { + return undefined; + } + + return resolveAssertionMessageFromRevertData(output, functionArtifact); +} From 177886764a23b9437fdc767726cc7c8533c27f08 Mon Sep 17 00:00:00 2001 From: ludamad Date: Tue, 4 Feb 2025 13:49:18 +0000 Subject: [PATCH 2/2] chore(bb-prover): avm test skip and split (#11717) --- .../avm_check_circuit1.test.ts | 114 +++++++++++++++++ .../avm_check_circuit2.test.ts | 118 ++++++++++++++++++ .../avm_check_circuit3.test.ts | 106 ++++++++++++++++ .../avm_contract_class_limits.test.ts | 84 +++++++++++++ .../avm_proving_and_verification.test.ts | 60 +++++++++ .../avm_public_fee_payment.test.ts | 73 +++++++++++ .../src/avm_proving_tests/avm_v2.test.ts | 64 ++++++++++ 7 files changed, 619 insertions(+) create mode 100644 yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts create mode 100644 yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts create mode 100644 yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts create mode 100644 yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts create mode 100644 yarn-project/bb-prover/src/avm_proving_tests/avm_proving_and_verification.test.ts create mode 100644 yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts create mode 100644 yarn-project/bb-prover/src/avm_proving_tests/avm_v2.test.ts diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts new file mode 100644 index 00000000000..6833b8786f6 --- /dev/null +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit1.test.ts @@ -0,0 +1,114 @@ +import { + type ContractClassPublic, + type ContractInstanceWithAddress, + FunctionSelector, + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_PUBLIC_LOGS_PER_TX, + PUBLIC_DISPATCH_SELECTOR, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { Fr } from '@aztec/foundation/fields'; +import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; +import { getAvmTestContractBytecode } from '@aztec/simulator/public/fixtures'; + +import { AvmProvingTester } from './avm_proving_tester.js'; + +const TIMEOUT = 300_000; +const DISPATCH_FN_NAME = 'public_dispatch'; +const DISPATCH_SELECTOR = new FunctionSelector(PUBLIC_DISPATCH_SELECTOR); + +describe('AVM WitGen & Circuit – check circuit', () => { + const avmTestContractClassSeed = 0; + const avmTestContractBytecode = getAvmTestContractBytecode(DISPATCH_FN_NAME); + let avmTestContractClass: ContractClassPublic; + let avmTestContractInstance: ContractInstanceWithAddress; + let tester: AvmProvingTester; + + beforeEach(async () => { + avmTestContractClass = await makeContractClassPublic( + /*seed=*/ avmTestContractClassSeed, + /*publicDispatchFunction=*/ { bytecode: avmTestContractBytecode, selector: DISPATCH_SELECTOR }, + ); + avmTestContractInstance = await makeContractInstanceFromClassId( + avmTestContractClass.id, + /*seed=*/ avmTestContractClassSeed, + ); + tester = await AvmProvingTester.create(/*checkCircuitOnly*/ true); + await tester.addContractClass(avmTestContractClass, AvmTestContractArtifact); + await tester.addContractInstance(avmTestContractInstance); + }); + + it( + 'perform too many storage writes and revert', + async () => { + await tester.simProveVerifyAppLogic( + { + address: avmTestContractInstance.address, + fnName: 'n_storage_writes', + args: [new Fr(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + 1)], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'create too many note hashes and revert', + async () => { + await tester.simProveVerifyAppLogic( + { + address: avmTestContractInstance.address, + fnName: 'n_new_note_hashes', + args: [new Fr(MAX_NOTE_HASHES_PER_TX + 1)], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'create too many nullifiers and revert', + async () => { + await tester.simProveVerifyAppLogic( + { + address: avmTestContractInstance.address, + fnName: 'n_new_nullifiers', + args: [new Fr(MAX_NULLIFIERS_PER_TX + 1)], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'create too many l2tol1 messages and revert', + async () => { + await tester.simProveVerifyAppLogic( + { + address: avmTestContractInstance.address, + fnName: 'n_new_l2_to_l1_msgs', + args: [new Fr(MAX_L2_TO_L1_MSGS_PER_TX + 1)], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'create too many public logs and revert', + async () => { + await tester.simProveVerifyAppLogic( + { + address: avmTestContractInstance.address, + fnName: 'n_new_public_logs', + args: [new Fr(MAX_PUBLIC_LOGS_PER_TX + 1)], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); +}); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts new file mode 100644 index 00000000000..7cdccc0adcb --- /dev/null +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit2.test.ts @@ -0,0 +1,118 @@ +import { + AztecAddress, + type ContractClassPublic, + type ContractInstanceWithAddress, + FunctionSelector, + PUBLIC_DISPATCH_SELECTOR, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { Fr } from '@aztec/foundation/fields'; +import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; +import { getAvmTestContractBytecode } from '@aztec/simulator/public/fixtures'; + +import { AvmProvingTester } from './avm_proving_tester.js'; + +const TIMEOUT = 300_000; +const DISPATCH_FN_NAME = 'public_dispatch'; +const DISPATCH_SELECTOR = new FunctionSelector(PUBLIC_DISPATCH_SELECTOR); + +describe('AVM WitGen & Circuit – check circuit', () => { + const sender = AztecAddress.fromNumber(42); + const avmTestContractClassSeed = 0; + const avmTestContractBytecode = getAvmTestContractBytecode(DISPATCH_FN_NAME); + let avmTestContractClass: ContractClassPublic; + let avmTestContractInstance: ContractInstanceWithAddress; + let tester: AvmProvingTester; + + beforeEach(async () => { + avmTestContractClass = await makeContractClassPublic( + /*seed=*/ avmTestContractClassSeed, + /*publicDispatchFunction=*/ { bytecode: avmTestContractBytecode, selector: DISPATCH_SELECTOR }, + ); + avmTestContractInstance = await makeContractInstanceFromClassId( + avmTestContractClass.id, + /*seed=*/ avmTestContractClassSeed, + ); + tester = await AvmProvingTester.create(/*checkCircuitOnly*/ true); + await tester.addContractClass(avmTestContractClass, AvmTestContractArtifact); + await tester.addContractInstance(avmTestContractInstance); + }); + + it( + 'an exceptional halt due to a nested call to non-existent contract is propagated to top-level', + async () => { + await tester.simProveVerifyAppLogic( + { address: avmTestContractInstance.address, fnName: 'nested_call_to_nothing', args: [] }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'an exceptional halt due to a nested call to non-existent contract is recovered from in caller', + async () => { + await tester.simProveVerifyAppLogic( + { address: avmTestContractInstance.address, fnName: 'nested_call_to_nothing_recovers', args: [] }, + /*expectRevert=*/ false, + ); + }, + TIMEOUT, + ); + it.skip('top-level exceptional halts due to a non-existent contract in app-logic and teardown', async () => { + // don't insert contracts into trees, and make sure retrieval fails + const tester = await AvmProvingTester.create(/*checkCircuitOnly=*/ true, /*skipContractDeployments=*/ true); + await tester.simProveVerify( + sender, + /*setupCalls=*/ [], + /*appCalls=*/ [ + { address: avmTestContractInstance.address, fnName: 'add_args_return', args: [new Fr(1), new Fr(2)] }, + ], + /*teardownCall=*/ { + address: avmTestContractInstance.address, + fnName: 'add_args_return', + args: [new Fr(1), new Fr(2)], + }, + /*expectRevert=*/ true, + ); + }); + it( + 'enqueued calls in every phase, with enqueued calls that depend on each other', + async () => { + await tester.simProveVerify( + sender, + /*setupCalls=*/ [ + { address: avmTestContractInstance.address, fnName: 'read_assert_storage_single', args: [new Fr(0)] }, + { address: avmTestContractInstance.address, fnName: 'set_storage_single', args: [new Fr(5)] }, + ], + /*appCalls=*/ [ + { address: avmTestContractInstance.address, fnName: 'read_assert_storage_single', args: [new Fr(5)] }, + { address: avmTestContractInstance.address, fnName: 'set_storage_single', args: [new Fr(10)] }, + ], + /*teardownCall=*/ { + address: avmTestContractInstance.address, + fnName: 'read_assert_storage_single', + args: [new Fr(10)], + }, + /*expectRevert=*/ false, + ); + }, + TIMEOUT, + ); + it( + 'Should prove and verify a TX that reverts in teardown', + async () => { + await tester.simProveVerify( + sender, + /*setupCalls=*/ [], + /*appCalls=*/ [], + /*teardownCall=*/ { + address: avmTestContractInstance.address, + fnName: 'read_assert_storage_single', + args: [new Fr(10)], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); +}); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts new file mode 100644 index 00000000000..034c8b2dce2 --- /dev/null +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_check_circuit3.test.ts @@ -0,0 +1,106 @@ +import { + AztecAddress, + type ContractClassPublic, + type ContractInstanceWithAddress, + FunctionSelector, + PUBLIC_DISPATCH_SELECTOR, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { Fr } from '@aztec/foundation/fields'; +import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; +import { getAvmTestContractBytecode } from '@aztec/simulator/public/fixtures'; + +import { AvmProvingTester } from './avm_proving_tester.js'; + +const TIMEOUT = 300_000; +const DISPATCH_FN_NAME = 'public_dispatch'; +const DISPATCH_SELECTOR = new FunctionSelector(PUBLIC_DISPATCH_SELECTOR); + +describe('AVM WitGen & Circuit – check circuit', () => { + const sender = AztecAddress.fromNumber(42); + const avmTestContractClassSeed = 0; + const avmTestContractBytecode = getAvmTestContractBytecode(DISPATCH_FN_NAME); + let avmTestContractClass: ContractClassPublic; + let avmTestContractInstance: ContractInstanceWithAddress; + let tester: AvmProvingTester; + + beforeEach(async () => { + avmTestContractClass = await makeContractClassPublic( + /*seed=*/ avmTestContractClassSeed, + /*publicDispatchFunction=*/ { bytecode: avmTestContractBytecode, selector: DISPATCH_SELECTOR }, + ); + avmTestContractInstance = await makeContractInstanceFromClassId( + avmTestContractClass.id, + /*seed=*/ avmTestContractClassSeed, + ); + tester = await AvmProvingTester.create(/*checkCircuitOnly*/ true); + await tester.addContractClass(avmTestContractClass, AvmTestContractArtifact); + await tester.addContractInstance(avmTestContractInstance); + }); + + it( + 'top-level exceptional halts in both app logic and teardown', + async () => { + await tester.simProveVerify( + sender, + /*setupCalls=*/ [], + /*appCalls=*/ [{ address: avmTestContractInstance.address, fnName: 'divide_by_zero', args: [] }], + /*teardownCall=*/ undefined, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'top-level exceptional halt in app logic, but teardown succeeds', + async () => { + await tester.simProveVerify( + sender, + /*setupCalls=*/ [], + /*appCalls=*/ [{ address: avmTestContractInstance.address, fnName: 'divide_by_zero', args: [] }], + /*teardownCall=*/ { + address: avmTestContractInstance.address, + fnName: 'add_args_return', + args: [new Fr(1), new Fr(2)], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'top-level exceptional halt in teardown, but app logic succeeds', + async () => { + await tester.simProveVerify( + sender, + /*setupCalls=*/ [], + /*appCalls=*/ [ + { address: avmTestContractInstance.address, fnName: 'add_args_return', args: [new Fr(1), new Fr(2)] }, + ], + /*teardownCall=*/ { address: avmTestContractInstance.address, fnName: 'divide_by_zero', args: [] }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'a nested exceptional halt propagate to top-level', + async () => { + await tester.simProveVerifyAppLogic( + { address: avmTestContractInstance.address, fnName: 'external_call_to_divide_by_zero', args: [] }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); + it( + 'a nested exceptional halt is recovered from in caller', + async () => { + await tester.simProveVerifyAppLogic( + { address: avmTestContractInstance.address, fnName: 'external_call_to_divide_by_zero_recovers', args: [] }, + /*expectRevert=*/ false, + ); + }, + TIMEOUT, + ); +}); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts new file mode 100644 index 00000000000..bbba6b94cb4 --- /dev/null +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_contract_class_limits.test.ts @@ -0,0 +1,84 @@ +import { + AztecAddress, + type ContractInstanceWithAddress, + MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS, +} from '@aztec/circuits.js'; +import { makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; + +import { AvmProvingTester } from './avm_proving_tester.js'; + +const TIMEOUT = 300_000; + +describe('AVM WitGen & Circuit – check circuit - contract class limits', () => { + const deployer = AztecAddress.fromNumber(42); + let instances: ContractInstanceWithAddress[]; + let tester: AvmProvingTester; + let avmTestContractAddress: AztecAddress; + + beforeEach(async () => { + tester = await AvmProvingTester.create(/*checkCircuitOnly=*/ true); + // create enough unique contract classes to hit the limit + instances = []; + for (let i = 0; i <= MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS; i++) { + const instance = await tester.registerAndDeployContract( + /*constructorArgs=*/ [], + deployer, + /*contractArtifact=*/ AvmTestContractArtifact, + /*seed=*/ i, + ); + instances.push(instance); + } + avmTestContractAddress = instances[0].address; + }); + it.skip( + 'call the max number of unique contract classes', + async () => { + // args is initialized to MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS contract addresses with unique class IDs + const instanceAddresses = instances + .map(instance => instance.address) + .slice(0, MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS); + + // include the first contract again again at the end to ensure that we can call it even after the limit is reached + instanceAddresses.push(instanceAddresses[0]); + + // include another contract address that reuses a class ID to ensure that we can call it even after the limit is reached + const instanceSameClassAsFirstContract = await makeContractInstanceFromClassId( + instances[0].contractClassId, + /*seed=*/ 1000, + ); + instanceAddresses.push(instanceSameClassAsFirstContract.address); + // add it to the contract data source so it is found + await tester.addContractInstance(instanceSameClassAsFirstContract); + + await tester.simProveVerifyAppLogic( + { + address: avmTestContractAddress, + fnName: 'nested_call_to_add_n_times_different_addresses', + args: [instanceAddresses], + }, + /*expectRevert=*/ false, + ); + }, + TIMEOUT, + ); + it.skip( + 'attempt too many calls to unique contract class ids', + async () => { + // args is initialized to MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS+1 contract addresses with unique class IDs + // should fail because we are trying to call MAX+1 unique class IDs + const instanceAddresses = instances.map(instance => instance.address); + // push an empty one (just padding to match function calldata size of MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS+2) + instanceAddresses.push(AztecAddress.zero()); + await tester.simProveVerifyAppLogic( + { + address: avmTestContractAddress, + fnName: 'nested_call_to_add_n_times_different_addresses', + args: [instanceAddresses], + }, + /*expectRevert=*/ true, + ); + }, + TIMEOUT, + ); +}); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_proving_and_verification.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_proving_and_verification.test.ts new file mode 100644 index 00000000000..56656363080 --- /dev/null +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_proving_and_verification.test.ts @@ -0,0 +1,60 @@ +import { + type ContractClassPublic, + type ContractInstanceWithAddress, + FunctionSelector, + PUBLIC_DISPATCH_SELECTOR, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { Fr } from '@aztec/foundation/fields'; +import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; +import { getAvmTestContractBytecode } from '@aztec/simulator/public/fixtures'; + +import { AvmProvingTester } from './avm_proving_tester.js'; + +const TIMEOUT = 300_000; +const DISPATCH_FN_NAME = 'public_dispatch'; +const DISPATCH_SELECTOR = new FunctionSelector(PUBLIC_DISPATCH_SELECTOR); + +describe('AVM WitGen & Circuit – proving and verification', () => { + const avmTestContractClassSeed = 0; + const avmTestContractBytecode = getAvmTestContractBytecode(DISPATCH_FN_NAME); + let avmTestContractClass: ContractClassPublic; + let avmTestContractInstance: ContractInstanceWithAddress; + let tester: AvmProvingTester; + + beforeEach(async () => { + avmTestContractClass = await makeContractClassPublic( + /*seed=*/ avmTestContractClassSeed, + /*publicDispatchFunction=*/ { bytecode: avmTestContractBytecode, selector: DISPATCH_SELECTOR }, + ); + avmTestContractInstance = await makeContractInstanceFromClassId( + avmTestContractClass.id, + /*seed=*/ avmTestContractClassSeed, + ); + tester = await AvmProvingTester.create(/*checkCircuitOnly*/ false); + await tester.addContractClass(avmTestContractClass, AvmTestContractArtifact); + await tester.addContractInstance(avmTestContractInstance); + }); + + it( + 'bulk_testing v1', + async () => { + // Get a deployed contract instance to pass to the contract + // for it to use as "expected" values when testing contract instance retrieval. + const expectContractInstance = avmTestContractInstance; + const argsField = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => new Fr(x)); + const argsU8 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => new Fr(x)); + const args = [ + argsField, + argsU8, + /*getInstanceForAddress=*/ expectContractInstance.address.toField(), + /*expectedDeployer=*/ expectContractInstance.deployer.toField(), + /*expectedClassId=*/ expectContractInstance.contractClassId.toField(), + /*expectedInitializationHash=*/ expectContractInstance.initializationHash.toField(), + ]; + + await tester.simProveVerifyAppLogic({ address: avmTestContractInstance.address, fnName: 'bulk_testing', args }); + }, + TIMEOUT, + ); +}); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts new file mode 100644 index 00000000000..90997ce9314 --- /dev/null +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_public_fee_payment.test.ts @@ -0,0 +1,73 @@ +import { + AztecAddress, + type ContractClassPublic, + type ContractInstanceWithAddress, + FunctionSelector, + PUBLIC_DISPATCH_SELECTOR, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { Fr } from '@aztec/foundation/fields'; +import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; +import { type ProtocolContract } from '@aztec/protocol-contracts'; +import { FeeJuiceArtifact, getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice'; +import { getAvmTestContractBytecode } from '@aztec/simulator/public/fixtures'; + +import { AvmProvingTester } from './avm_proving_tester.js'; + +const TIMEOUT = 300_000; +const DISPATCH_FN_NAME = 'public_dispatch'; +const DISPATCH_SELECTOR = new FunctionSelector(PUBLIC_DISPATCH_SELECTOR); + +describe('AVM WitGen & Circuit – public fee payment', () => { + const sender = AztecAddress.fromNumber(42); + const feePayer = sender; + + const initialFeeJuiceBalance = new Fr(20000); + let feeJuice: ProtocolContract; + let feeJuiceContractClassPublic: ContractClassPublic; + + const avmTestContractClassSeed = 0; + const avmTestContractBytecode = getAvmTestContractBytecode(DISPATCH_FN_NAME); + let avmTestContractClass: ContractClassPublic; + let avmTestContractInstance: ContractInstanceWithAddress; + let tester: AvmProvingTester; + + beforeEach(async () => { + feeJuice = await getCanonicalFeeJuice(); + feeJuiceContractClassPublic = { + ...feeJuice.contractClass, + privateFunctions: [], + unconstrainedFunctions: [], + }; + avmTestContractClass = await makeContractClassPublic( + /*seed=*/ avmTestContractClassSeed, + /*publicDispatchFunction=*/ { bytecode: avmTestContractBytecode, selector: DISPATCH_SELECTOR }, + ); + avmTestContractInstance = await makeContractInstanceFromClassId( + avmTestContractClass.id, + /*seed=*/ avmTestContractClassSeed, + ); + tester = await AvmProvingTester.create(/*checkCircuitOnly*/ true); + await tester.addContractClass(feeJuiceContractClassPublic, FeeJuiceArtifact); + await tester.addContractInstance(feeJuice.instance); + await tester.addContractClass(avmTestContractClass, AvmTestContractArtifact); + await tester.addContractInstance(avmTestContractInstance); + await tester.setFeePayerBalance(feePayer, initialFeeJuiceBalance); + }); + it( + 'fee payment', + async () => { + await tester.simProveVerify( + sender, + /*setupCalls=*/ [], + /*appCalls=*/ [ + { address: avmTestContractInstance.address, fnName: 'add_args_return', args: [new Fr(1), new Fr(2)] }, + ], + /*teardownCall=*/ undefined, + /*expectRevert=*/ false, + feePayer, + ); + }, + TIMEOUT, + ); +}); diff --git a/yarn-project/bb-prover/src/avm_proving_tests/avm_v2.test.ts b/yarn-project/bb-prover/src/avm_proving_tests/avm_v2.test.ts new file mode 100644 index 00000000000..1ba8f7658b1 --- /dev/null +++ b/yarn-project/bb-prover/src/avm_proving_tests/avm_v2.test.ts @@ -0,0 +1,64 @@ +import { + AztecAddress, + type ContractClassPublic, + type ContractInstanceWithAddress, + FunctionSelector, + PUBLIC_DISPATCH_SELECTOR, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { Fr } from '@aztec/foundation/fields'; +import { AvmTestContractArtifact } from '@aztec/noir-contracts.js/AvmTest'; +import { getAvmTestContractBytecode } from '@aztec/simulator/public/fixtures'; + +import { AvmProvingTesterV2 } from './avm_proving_tester.js'; + +const DISPATCH_FN_NAME = 'public_dispatch'; +const DISPATCH_SELECTOR = new FunctionSelector(PUBLIC_DISPATCH_SELECTOR); + +describe('AVM v2', () => { + const sender = AztecAddress.fromNumber(42); + const avmTestContractClassSeed = 0; + const avmTestContractBytecode = getAvmTestContractBytecode(DISPATCH_FN_NAME); + let avmTestContractClass: ContractClassPublic; + let avmTestContractInstance: ContractInstanceWithAddress; + + let tester: AvmProvingTesterV2; + + beforeEach(async () => { + avmTestContractClass = await makeContractClassPublic( + /*seed=*/ avmTestContractClassSeed, + /*publicDispatchFunction=*/ { bytecode: avmTestContractBytecode, selector: DISPATCH_SELECTOR }, + ); + avmTestContractInstance = await makeContractInstanceFromClassId( + avmTestContractClass.id, + /*seed=*/ avmTestContractClassSeed, + ); + tester = await AvmProvingTesterV2.create(); + await tester.addContractClass(avmTestContractClass, AvmTestContractArtifact); + await tester.addContractInstance(avmTestContractInstance); + }); + + it('bulk_testing v2', async () => { + // Get a deployed contract instance to pass to the contract + // for it to use as "expected" values when testing contract instance retrieval. + const expectContractInstance = avmTestContractInstance; + const argsField = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => new Fr(x)); + const argsU8 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => new Fr(x)); + const args = [ + argsField, + argsU8, + /*getInstanceForAddress=*/ expectContractInstance.address.toField(), + /*expectedDeployer=*/ expectContractInstance.deployer.toField(), + /*expectedClassId=*/ expectContractInstance.contractClassId.toField(), + /*expectedInitializationHash=*/ expectContractInstance.initializationHash.toField(), + ]; + + await tester.simProveVerifyV2( + sender, + /*setupCalls=*/ [], + /*appCalls=*/ [{ address: avmTestContractInstance.address, fnName: 'bulk_testing', args }], + /*teardownCall=*/ undefined, + /*expectRevert=*/ false, + ); + }, 180_000); +});