diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index 465d9732ac1..4552e66deb5 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -683,6 +683,27 @@ void print_avm_stats() #endif } +/** + * @brief Performs "check circuit" on the AVM circuit for the given public inputs and hints. + * + * @param public_inputs_path Path to the file containing the serialised avm public inputs + * @param hints_path Path to the file containing the serialised avm circuit hints + */ +void avm_check_circuit(const std::filesystem::path& public_inputs_path, const std::filesystem::path& hints_path) +{ + + const auto avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path)); + const auto avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path)); + avm_hints.print_sizes(); + + vinfo("initializing crs with size: ", avm_trace::Execution::SRS_SIZE); + init_bn254_crs(avm_trace::Execution::SRS_SIZE); + + avm_trace::Execution::check_circuit(avm_public_inputs, avm_hints); + + print_avm_stats(); +} + /** * @brief Writes an avm proof and corresponding (incomplete) verification key to files. * @@ -700,18 +721,7 @@ void avm_prove(const std::filesystem::path& public_inputs_path, const auto avm_public_inputs = AvmPublicInputs::from(read_file(public_inputs_path)); const auto avm_hints = bb::avm_trace::ExecutionHints::from(read_file(hints_path)); - - // Using [0] is fine now for the top-level call, but we might need to index by address in future - vinfo("bytecode size: ", avm_hints.all_contract_bytecode[0].bytecode.size()); - vinfo("hints.storage_read_hints size: ", avm_hints.storage_read_hints.size()); - vinfo("hints.storage_write_hints size: ", avm_hints.storage_write_hints.size()); - vinfo("hints.nullifier_read_hints size: ", avm_hints.nullifier_read_hints.size()); - vinfo("hints.nullifier_write_hints size: ", avm_hints.nullifier_write_hints.size()); - vinfo("hints.note_hash_read_hints size: ", avm_hints.note_hash_read_hints.size()); - vinfo("hints.note_hash_write_hints size: ", avm_hints.note_hash_write_hints.size()); - vinfo("hints.l1_to_l2_message_read_hints size: ", avm_hints.l1_to_l2_message_read_hints.size()); - vinfo("hints.contract_instance_hints size: ", avm_hints.contract_instance_hints.size()); - vinfo("hints.contract_bytecode_hints size: ", avm_hints.all_contract_bytecode.size()); + avm_hints.print_sizes(); vinfo("initializing crs with size: ", avm_trace::Execution::SRS_SIZE); init_bn254_crs(avm_trace::Execution::SRS_SIZE); @@ -1448,6 +1458,13 @@ int main(int argc, char* argv[]) std::filesystem::path public_inputs_path = get_option(args, "--avm-public-inputs", "./target/avm_public_inputs.bin"); return avm2_verify(proof_path, public_inputs_path, vk_path) ? 0 : 1; + } else if (command == "avm_check_circuit") { + std::filesystem::path avm_public_inputs_path = + get_option(args, "--avm-public-inputs", "./target/avm_public_inputs.bin"); + std::filesystem::path avm_hints_path = get_option(args, "--avm-hints", "./target/avm_hints.bin"); + extern std::filesystem::path avm_dump_trace_path; + avm_dump_trace_path = get_option(args, "--avm-dump-trace", ""); + avm_check_circuit(avm_public_inputs_path, avm_hints_path); } else if (command == "avm_prove") { std::filesystem::path avm_public_inputs_path = get_option(args, "--avm-public-inputs", "./target/avm_public_inputs.bin"); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp index a06cbfd8fb4..cbd6888107a 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp @@ -243,6 +243,40 @@ std::vector Execution::getDefaultPublicInputs() return public_inputs_vec; } +/** + * @brief Run the bytecode, generate the corresponding execution trace and check the circuit for + * execution of the supplied bytecode. + * + * @throws runtime_error exception when the bytecode is invalid. + */ +void Execution::check_circuit(AvmPublicInputs const& public_inputs, ExecutionHints const& execution_hints) +{ + std::vector returndata; + std::vector calldata; + for (const auto& enqueued_call_hints : execution_hints.enqueued_call_hints) { + calldata.insert(calldata.end(), enqueued_call_hints.calldata.begin(), enqueued_call_hints.calldata.end()); + } + std::vector trace = AVM_TRACK_TIME_V( + "prove/gen_trace", gen_trace(public_inputs, returndata, execution_hints, /*apply_e2e_assertions=*/true)); + if (!avm_dump_trace_path.empty()) { + info("Dumping trace as CSV to: " + avm_dump_trace_path.string()); + dump_trace_as_csv(trace, avm_dump_trace_path); + } + auto circuit_builder = bb::avm::AvmCircuitBuilder(); + circuit_builder.set_trace(std::move(trace)); + vinfo("Circuit subgroup size: 2^", + // this calculates the integer log2 + std::bit_width(circuit_builder.get_circuit_subgroup_size()) - 1); + + if (circuit_builder.get_circuit_subgroup_size() > SRS_SIZE) { + throw_or_abort("Circuit subgroup size (" + std::to_string(circuit_builder.get_circuit_subgroup_size()) + + ") exceeds SRS_SIZE (" + std::to_string(SRS_SIZE) + ")"); + } + + vinfo("------- CHECKING CIRCUIT -------"); + AVM_TRACK_TIME("prove/check_circuit", circuit_builder.check_circuit()); +} + /** * @brief Run the bytecode, generate the corresponding execution trace and prove the correctness * of the execution of the supplied bytecode. @@ -352,8 +386,8 @@ std::vector Execution::gen_trace(AvmPublicInputs const& public_inputs, uint32_t start_side_effect_counter = 0; // Temporary until we get proper nested call handling std::vector calldata; - for (const auto& enqueued_call_hints : execution_hints.enqueued_call_hints) { - calldata.insert(calldata.end(), enqueued_call_hints.calldata.begin(), enqueued_call_hints.calldata.end()); + for (const auto& enqueued_call_hint : execution_hints.enqueued_call_hints) { + calldata.insert(calldata.end(), enqueued_call_hint.calldata.begin(), enqueued_call_hint.calldata.end()); } AvmTraceBuilder trace_builder = Execution::trace_builder_constructor(public_inputs, execution_hints, start_side_effect_counter); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.hpp index c570a5ffe53..288535364fd 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.hpp @@ -53,6 +53,8 @@ class Execution { static std::tuple prove( AvmPublicInputs const& public_inputs = AvmPublicInputs(), ExecutionHints const& execution_hints = {}); + static void check_circuit(AvmPublicInputs const& public_inputs = AvmPublicInputs(), + ExecutionHints const& execution_hints = {}); static bool verify(bb::avm::AvmFlavor::VerificationKey vk, HonkProof const& proof); private: diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp index dc554ebd2b5..568af787cf0 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp @@ -257,6 +257,24 @@ struct ExecutionHints { return *this; } + void print_sizes() const + { + vinfo("hints.enqueued_call_hints size: ", enqueued_call_hints.size()); + vinfo("hints.contract_instance_hints size: ", contract_instance_hints.size()); + vinfo("hints.contract_bytecode_hints size: ", all_contract_bytecode.size()); + if (all_contract_bytecode.size() > 0) { + // Using [0] is fine now for the top-level call, but we might need to index by address in future + vinfo("0th bytecode size: ", all_contract_bytecode[0].bytecode.size()); + } + vinfo("hints.storage_read_hints size: ", storage_read_hints.size()); + vinfo("hints.storage_write_hints size: ", storage_write_hints.size()); + vinfo("hints.nullifier_read_hints size: ", nullifier_read_hints.size()); + vinfo("hints.nullifier_write_hints size: ", nullifier_write_hints.size()); + vinfo("hints.note_hash_read_hints size: ", note_hash_read_hints.size()); + vinfo("hints.note_hash_write_hints size: ", note_hash_write_hints.size()); + vinfo("hints.l1_to_l2_message_read_hints size: ", l1_to_l2_message_read_hints.size()); + } + static void push_vec_into_map(std::unordered_map& into_map, const std::vector>& from_pair_vec) { 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 fbf8816d8b2..39a79d0e865 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 @@ -71,6 +71,11 @@ contract AvmTest { storage.single.read() } + #[public] + fn read_assert_storage_single(a: Field) { + assert(a == storage.single.read(), "Storage value does not match input"); + } + // should still be able to use ` -> pub *` for return type even though macro forces `pub` #[public] fn set_read_storage_single(a: Field) -> pub Field { diff --git a/yarn-project/bb-prover/src/avm_proving.test.ts b/yarn-project/bb-prover/src/avm_proving.test.ts index 255da9d331c..5119af27f2a 100644 --- a/yarn-project/bb-prover/src/avm_proving.test.ts +++ b/yarn-project/bb-prover/src/avm_proving.test.ts @@ -34,7 +34,8 @@ describe('AVM WitGen, proof generation and verification', () => { it( 'Should prove and verify bulk_testing v1', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ false, // full proving & verifying 'bulk_testing', /*args=*/ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => new Fr(x)), ); @@ -44,7 +45,8 @@ describe('AVM WitGen, proof generation and verification', () => { it( 'Should prove and verify test that performs too many storage writes and reverts', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'n_storage_writes', /*args=*/ [new Fr(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + 1)], /*expectRevert=*/ true, @@ -53,9 +55,10 @@ describe('AVM WitGen, proof generation and verification', () => { TIMEOUT, ); it( - 'Should prove and verify test that creates too many note hashes and reverts', + 'Should run check circuit for test that creates too many note hashes and reverts', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'n_new_note_hashes', /*args=*/ [new Fr(MAX_NOTE_HASHES_PER_TX + 1)], /*expectRevert=*/ true, @@ -66,7 +69,8 @@ describe('AVM WitGen, proof generation and verification', () => { it( 'Should prove and verify test that creates too many nullifiers and reverts', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'n_new_nullifiers', /*args=*/ [new Fr(MAX_NULLIFIERS_PER_TX + 1)], /*expectRevert=*/ true, @@ -77,7 +81,8 @@ describe('AVM WitGen, proof generation and verification', () => { it( 'Should prove and verify test that creates too many l2tol1 messages and reverts', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'n_new_l2_to_l1_msgs', /*args=*/ [new Fr(MAX_L2_TO_L1_MSGS_PER_TX + 1)], /*expectRevert=*/ true, @@ -88,7 +93,8 @@ describe('AVM WitGen, proof generation and verification', () => { it( 'Should prove and verify test that creates too many unencrypted logs and reverts', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'n_new_unencrypted_logs', /*args=*/ [new Fr(MAX_UNENCRYPTED_LOGS_PER_TX + 1)], /*expectRevert=*/ true, @@ -108,7 +114,8 @@ describe('AVM WitGen, proof generation and verification', () => { args.push(args[0]); // include another contract address that reuses a class ID to ensure that we can call it even after the limit is reached args.push(contractDataSource.instanceSameClassAsFirstContract.address.toField()); - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'nested_call_to_add_n_times_different_addresses', args, /*expectRevert=*/ false, @@ -129,7 +136,8 @@ describe('AVM WitGen, proof generation and verification', () => { ); // push an empty one (just padding to match function calldata size of MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS+2) args.push(new Fr(0)); - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'nested_call_to_add_n_times_different_addresses', args, /*expectRevert=*/ true, @@ -142,21 +150,32 @@ describe('AVM WitGen, proof generation and verification', () => { it( 'Should prove and verify a top-level exceptional halt', async () => { - await proveAndVerifyAvmTestContract('divide_by_zero', /*args=*/ [], /*expectRevert=*/ true); + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick + 'divide_by_zero', + /*args=*/ [], + /*expectRevert=*/ true, + ); }, TIMEOUT, ); it( 'Should prove and verify a nested exceptional halt that propagates to top-level', async () => { - await proveAndVerifyAvmTestContract('external_call_to_divide_by_zero', /*args=*/ [], /*expectRevert=*/ true); + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick + 'external_call_to_divide_by_zero', + /*args=*/ [], + /*expectRevert=*/ true, + ); }, TIMEOUT, ); it( 'Should prove and verify a nested exceptional halt that is recovered from in caller', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'external_call_to_divide_by_zero_recovers', /*args=*/ [], /*expectRevert=*/ false, @@ -167,21 +186,32 @@ describe('AVM WitGen, proof generation and verification', () => { it( 'Should prove and verify an exceptional halt due to a nested call to non-existent contract that is propagated to top-level', async () => { - await proveAndVerifyAvmTestContract('nested_call_to_nothing', /*args=*/ [], /*expectRevert=*/ true); + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick + 'nested_call_to_nothing', + /*args=*/ [], + /*expectRevert=*/ true, + ); }, TIMEOUT, ); it( 'Should prove and verify an exceptional halt due to a nested call to non-existent contract that is recovered from in caller', async () => { - await proveAndVerifyAvmTestContract('nested_call_to_nothing_recovers', /*args=*/ [], /*expectRevert=*/ false); + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick + 'nested_call_to_nothing_recovers', + /*args=*/ [], + /*expectRevert=*/ false, + ); }, TIMEOUT, ); it( 'Should prove and verify a top-level exceptional halt due to a non-existent contract', async () => { - await proveAndVerifyAvmTestContract( + await proveAndVerifyAvmTestContractSimple( + /*checkCircuitOnly=*/ true, // quick 'add_args_return', /*args=*/ [new Fr(1), new Fr(2)], /*expectRevert=*/ true, @@ -190,18 +220,96 @@ describe('AVM WitGen, proof generation and verification', () => { }, TIMEOUT, ); + it.skip( + 'Should prove and verify multiple app logic enqueued calls (set storage in first call, read it in next)', + async () => { + await proveAndVerifyAvmTestContract( + /*checkCircuitOnly=*/ true, + /*setupFunctionNames=*/ [], + /*setupArgs=*/ [], + /*appFunctionNames=*/ ['set_storage_single', 'read_assert_storage_single'], + /*appArgs=*/ [[new Fr(5)], [new Fr(5)]], + ); + }, + TIMEOUT, + ); + it.skip( + 'Should prove and verify multiple app logic enqueued calls (like `enqueue_public_from_private`)', + async () => { + await proveAndVerifyAvmTestContract( + /*checkCircuitOnly=*/ true, + /*setupFunctionNames=*/ [], + /*setupArgs=*/ [], + /*appFunctionNames=*/ ['set_opcode_u8', 'set_read_storage_single'], + /*appArgs=*/ [[], [new Fr(5)]], + ); + }, + TIMEOUT, + ); + it.skip( + 'Should prove and verify enqueued calls in every phase, with enqueued calls that depend on each other', + async () => { + await proveAndVerifyAvmTestContract( + /*checkCircuitOnly=*/ true, + /*setupFunctionNames=*/ ['read_assert_storage_single', 'set_storage_single'], + /*setupArgs=*/ [[new Fr(0)], [new Fr(5)]], + /*appFunctionNames=*/ ['read_assert_storage_single', 'set_storage_single'], + /*appArgs=*/ [[new Fr(5)], [new Fr(10)]], + /*teardownFunctionName=*/ 'read_assert_storage_single', + /*teardownArgs=*/ [new Fr(10)], + ); + }, + TIMEOUT, + ); }); -async function proveAndVerifyAvmTestContract( +/** + * Simulate, prove and verify just a single App Logic enqueued call. + */ +async function proveAndVerifyAvmTestContractSimple( + checkCircuitOnly: boolean, functionName: string, args: Fr[] = [], expectRevert = false, skipContractDeployments = false, contractDataSource = new MockedAvmTestContractDataSource(skipContractDeployments), +) { + await proveAndVerifyAvmTestContract( + checkCircuitOnly, + /*setupFunctionNames=*/ [], + /*setupArgs=*/ [], + /*appFunctionNames=*/ [functionName], + /*appArgs=*/ [args], + /*teardownFunctionName=*/ undefined, + /*teardownArgs=*/ [], + expectRevert, + skipContractDeployments, + contractDataSource, + ); +} + +/** + * Simulate, prove and verify setup calls, app logic calls and optionally a teardown call in one TX. + */ +async function proveAndVerifyAvmTestContract( + checkCircuitOnly: boolean, + setupFunctionNames: string[], + setupArgs: Fr[][], + appFunctionNames: string[], + appArgs: Fr[][] = [], + teardownFunctionName?: string, + teardownArgs: Fr[] = [], + expectRevert = false, + skipContractDeployments = false, + contractDataSource = new MockedAvmTestContractDataSource(skipContractDeployments), ) { const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs( - functionName, - args, + setupFunctionNames, + setupArgs, + appFunctionNames, + appArgs, + teardownFunctionName, + teardownArgs, expectRevert, contractDataSource, ); @@ -213,28 +321,39 @@ async function proveAndVerifyAvmTestContract( const bbWorkingDirectory = await fs.mkdtemp(path.join(tmpdir(), 'bb-')); // Then we prove. - const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger); + const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger, checkCircuitOnly); if (proofRes.status === BB_RESULT.FAILURE) { logger.error(`Proof generation failed: ${proofRes.reason}`); } expect(proofRes.status).toEqual(BB_RESULT.SUCCESS); - // Then we test VK extraction and serialization. - const succeededRes = proofRes as BBSuccess; - const vkData = await extractAvmVkData(succeededRes.vkPath!); - VerificationKeyData.fromBuffer(vkData.toBuffer()); + // There is no proof to verify if we only check circuit. + if (!checkCircuitOnly) { + // Then we test VK extraction and serialization. + const succeededRes = proofRes as BBSuccess; + const vkData = await extractAvmVkData(succeededRes.vkPath!); + VerificationKeyData.fromBuffer(vkData.toBuffer()); - // Then we verify. - const rawVkPath = path.join(succeededRes.vkPath!, 'vk'); - const verificationRes = await verifyAvmProof(bbPath, succeededRes.proofPath!, rawVkPath, logger); - expect(verificationRes.status).toBe(BB_RESULT.SUCCESS); + // Then we verify. + const rawVkPath = path.join(succeededRes.vkPath!, 'vk'); + const verificationRes = await verifyAvmProof(bbPath, succeededRes.proofPath!, rawVkPath, logger); + expect(verificationRes.status).toBe(BB_RESULT.SUCCESS); + } } describe('AVM WitGen, proof generation and verification', () => { it('Should prove and verify bulk_testing v2', async () => { const functionName = 'bulk_testing'; const calldata = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => new Fr(x)); - const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(functionName, calldata, false); + const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs( + /*setupFunctionNames=*/ [], + /*setupArgs=*/ [], + /*appFunctionNames=*/ [functionName], + /*appArgs=*/ [calldata], + /*teardownFunctionName=*/ undefined, + /*teardownArgs=*/ [], + /*expectRevert=*/ false, + ); const logger = createLogger('bb-prover:avm-proving-test'); diff --git a/yarn-project/bb-prover/src/bb/execute.ts b/yarn-project/bb-prover/src/bb/execute.ts index 42a054264e8..7089f888e23 100644 --- a/yarn-project/bb-prover/src/bb/execute.ts +++ b/yarn-project/bb-prover/src/bb/execute.ts @@ -573,7 +573,7 @@ export async function generateAvmProofV2( } /** - * Used for generating AVM proofs. + * Used for generating AVM proofs (or doing check-circuit). * It is assumed that the working directory is a temporary and/or random directory used solely for generating this proof. * @param pathToBB - The full path to the bb binary * @param workingDirectory - A working directory for use by bb @@ -586,6 +586,7 @@ export async function generateAvmProof( workingDirectory: string, input: AvmCircuitInputs, logger: Logger, + checkCircuitOnly: boolean = false, ): Promise { // Check that the working directory exists try { @@ -633,12 +634,14 @@ export async function generateAvmProof( '-o', outputPath, logger.level === 'debug' || logger.level === 'trace' ? '-d' : logger.level === 'verbose' ? '-v' : '', + checkCircuitOnly ? '--check-circuit-only' : '', ]; const timer = new Timer(); + const cmd = checkCircuitOnly ? 'check_circuit' : 'prove'; const logFunction = (message: string) => { - logger.verbose(`AvmCircuit (prove) BB out - ${message}`); + logger.verbose(`AvmCircuit (${cmd}) BB out - ${message}`); }; - const result = await executeBB(pathToBB, 'avm_prove', args, logFunction); + const result = await executeBB(pathToBB, `avm_${cmd}`, args, logFunction); const duration = timer.ms(); if (result.status == BB_RESULT.SUCCESS) { diff --git a/yarn-project/ivc-integration/src/avm_integration.test.ts b/yarn-project/ivc-integration/src/avm_integration.test.ts index 20f913b6a95..067ea935bca 100644 --- a/yarn-project/ivc-integration/src/avm_integration.test.ts +++ b/yarn-project/ivc-integration/src/avm_integration.test.ts @@ -121,7 +121,15 @@ describe('AVM Integration', () => { }); async function proveAvmTestContract(functionName: string, calldata: Fr[] = []): Promise { - const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(functionName, calldata); + const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs( + /*setupFunctionNames=*/ [], + /*setupArgs=*/ [], + /*appFunctionNames=*/ [functionName], + /*appArgs=*/ [calldata], + /*teardownFunctionName=*/ undefined, + /*teardownArgs=*/ [], + /*expectRevert=*/ false, + ); const internalLogger = createLogger('ivc-integration:test:avm-proving'); diff --git a/yarn-project/package.json b/yarn-project/package.json index 6028af4c8f1..b2678f6457a 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -10,7 +10,7 @@ "formatting:fix": "FORCE_COLOR=true yarn workspaces foreach -A -p -v run formatting:fix", "lint": "yarn eslint --cache --ignore-pattern l1-artifacts .", "format": "yarn prettier --cache -w .", - "test": "RAYON_NUM_THREADS=4 FORCE_COLOR=true yarn workspaces foreach -A --exclude @aztec/aztec3-packages --exclude @aztec/bb-prover --exclude @aztec/prover-client -p -v run test --config=../jest.root.config.js", + "test": "RAYON_NUM_THREADS=4 FORCE_COLOR=true yarn workspaces foreach -A --exclude @aztec/aztec3-packages --exclude @aztec/prover-client -p -v run test --config=../jest.root.config.js", "build": "FORCE_COLOR=true yarn workspaces foreach -A --parallel --topological-dev --verbose --exclude @aztec/aztec3-packages --exclude @aztec/docs run build", "build:fast": "cd foundation && yarn build && cd ../l1-artifacts && yarn build && cd ../circuits.js && yarn build && cd .. && yarn generate && tsc -b", "build:dev": "./watch.sh", diff --git a/yarn-project/simulator/src/avm/avm_simulator.ts b/yarn-project/simulator/src/avm/avm_simulator.ts index a69aa089377..3549706283e 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.ts @@ -137,6 +137,7 @@ export class AvmSimulator { this.bytecode = bytecode; const { machineState } = this.context; + const callStartGas = machineState.gasLeft; // Save gas before executing instruction (for profiling) try { // Execute instruction pointed to by the current program counter // continuing until the machine state signifies a halt @@ -180,7 +181,11 @@ export class AvmSimulator { const revertReason = reverted ? await revertReasonFromExplicitRevert(output, this.context) : undefined; const results = new AvmContractCallResult(reverted, output, machineState.gasLeft, revertReason); this.log.debug(`Context execution results: ${results.toString()}`); - this.log.debug(`Executed ${instrCounter} instructions`); + const totalGasUsed: Gas = { + l2Gas: callStartGas.l2Gas - machineState.l2GasLeft, + daGas: callStartGas.daGas - machineState.daGasLeft, + }; + this.log.debug(`Executed ${instrCounter} instructions and consumed ${totalGasUsed.l2Gas} L2 Gas`); this.tallyPrintFunction(); // Return results for processing by calling context diff --git a/yarn-project/simulator/src/public/fixtures/index.ts b/yarn-project/simulator/src/public/fixtures/index.ts index e7d4ce26b6f..e47f37df6de 100644 --- a/yarn-project/simulator/src/public/fixtures/index.ts +++ b/yarn-project/simulator/src/public/fixtures/index.ts @@ -46,8 +46,12 @@ import { PublicTxSimulator } from '../public_tx_simulator.js'; const TIMESTAMP = new Fr(99833); export async function simulateAvmTestContractGenerateCircuitInputs( - functionName: string, - args: Fr[] = [], + setupFunctionNames: string[], + setupArgs: Fr[][] = [], + appFunctionNames: string[], + appArgs: Fr[][] = [], + teardownFunctionName?: string, + teardownArgs: Fr[] = [], expectRevert: boolean = false, contractDataSource = new MockedAvmTestContractDataSource(), assertionErrString?: string, @@ -68,17 +72,37 @@ export async function simulateAvmTestContractGenerateCircuitInputs( ); const sender = AztecAddress.random(); - const functionSelector = getAvmTestContractFunctionSelector(functionName); - args = [functionSelector.toField(), ...args]; const callContext = new CallContext( sender, contractDataSource.firstContractInstance.address, contractDataSource.fnSelector, /*isStaticCall=*/ false, ); - const executionRequest = new PublicExecutionRequest(callContext, args); + const setupExecutionRequests: PublicExecutionRequest[] = []; + // we reverse order because the simulator expects it to be like a "stack" of calls to pop from + for (let i = setupFunctionNames.length - 1; i >= 0; i--) { + const functionSelector = getAvmTestContractFunctionSelector(setupFunctionNames[i]); + const fnArgs = [functionSelector.toField(), ...setupArgs[i]]; + const executionRequest = new PublicExecutionRequest(callContext, fnArgs); + setupExecutionRequests.push(executionRequest); + } + const appExecutionRequests: PublicExecutionRequest[] = []; + // we reverse order because the simulator expects it to be like a "stack" of calls to pop from + for (let i = appFunctionNames.length - 1; i >= 0; i--) { + const functionSelector = getAvmTestContractFunctionSelector(appFunctionNames[i]); + const fnArgs = [functionSelector.toField(), ...appArgs[i]]; + const executionRequest = new PublicExecutionRequest(callContext, fnArgs); + appExecutionRequests.push(executionRequest); + } + + let teardownExecutionRequest: PublicExecutionRequest | undefined = undefined; + if (teardownFunctionName) { + const functionSelector = getAvmTestContractFunctionSelector(teardownFunctionName); + const fnArgs = [functionSelector.toField(), ...teardownArgs]; + teardownExecutionRequest = new PublicExecutionRequest(callContext, fnArgs); + } - const tx: Tx = createTxForPublicCall(executionRequest); + const tx: Tx = createTxForPublicCalls(setupExecutionRequests, appExecutionRequests, teardownExecutionRequest); const avmResult = await simulator.simulate(tx); @@ -138,27 +162,38 @@ export async function simulateAvmTestContractCall( } /** - * Craft a carrier transaction for a public call for simulation by PublicTxSimulator. + * Craft a carrier transaction for some public calls for simulation by PublicTxSimulator. */ -export function createTxForPublicCall( - executionRequest: PublicExecutionRequest, +export function createTxForPublicCalls( + setupExecutionRequests: PublicExecutionRequest[], + appExecutionRequests: PublicExecutionRequest[], + teardownExecutionRequest?: PublicExecutionRequest, gasUsedByPrivate: Gas = Gas.empty(), - isTeardown: boolean = false, ): Tx { - const callRequest = executionRequest.toCallRequest(); + assert( + setupExecutionRequests.length > 0 || appExecutionRequests.length > 0 || teardownExecutionRequest !== undefined, + "Can't create public tx with no enqueued calls", + ); + const setupCallRequests = setupExecutionRequests.map(er => er.toCallRequest()); + const appCallRequests = appExecutionRequests.map(er => er.toCallRequest()); // use max limits const gasLimits = new Gas(DEFAULT_GAS_LIMIT, MAX_L2_GAS_PER_TX_PUBLIC_PORTION); const forPublic = PartialPrivateTailPublicInputsForPublic.empty(); // TODO(#9269): Remove this fake nullifier method as we move away from 1st nullifier as hash. forPublic.nonRevertibleAccumulatedData.nullifiers[0] = Fr.random(); // fake tx nullifier - if (isTeardown) { - forPublic.publicTeardownCallRequest = callRequest; - } else { - forPublic.revertibleAccumulatedData.publicCallRequests[0] = callRequest; + + for (let i = 0; i < setupExecutionRequests.length; i++) { + forPublic.nonRevertibleAccumulatedData.publicCallRequests[i] = setupCallRequests[i]; + } + for (let i = 0; i < appCallRequests.length; i++) { + forPublic.revertibleAccumulatedData.publicCallRequests[i] = appCallRequests[i]; + } + if (teardownExecutionRequest) { + forPublic.publicTeardownCallRequest = teardownExecutionRequest.toCallRequest(); } - const teardownGasLimits = isTeardown ? gasLimits : Gas.empty(); + const teardownGasLimits = teardownExecutionRequest ? gasLimits : Gas.empty(); const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty(), GasFees.empty()); const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings); const constantData = new TxConstantData(BlockHeader.empty(), txContext, Fr.zero(), Fr.zero()); @@ -170,9 +205,13 @@ export function createTxForPublicCall( AztecAddress.zero(), forPublic, ); - const tx = isTeardown ? Tx.newWithTxData(txData, executionRequest) : Tx.newWithTxData(txData); - if (!isTeardown) { - tx.enqueuedPublicFunctionCalls[0] = executionRequest; + const tx = Tx.newWithTxData(txData, teardownExecutionRequest); + + for (let i = 0; i < setupExecutionRequests.length; i++) { + tx.enqueuedPublicFunctionCalls.push(setupExecutionRequests[i]); + } + for (let i = 0; i < appExecutionRequests.length; i++) { + tx.enqueuedPublicFunctionCalls.push(appExecutionRequests[i]); } return tx; diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index e973d28f654..50394b00ede 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -294,7 +294,7 @@ export class PublicTxSimulator { const gasUsed = allocatedGas.sub(result.gasLeft); // by enqueued call context.consumeGas(phase, gasUsed); this.log.debug( - `Simulated enqueued public call consumed ${gasUsed.l2Gas} L2 gas ending with ${result.gasLeft.l2Gas} L2 gas left.`, + `Simulated enqueued public call (${fnName}) consumed ${gasUsed.l2Gas} L2 gas ending with ${result.gasLeft.l2Gas} L2 gas left.`, ); stateManager.traceEnqueuedCall(callRequest, executionRequest.args, result.reverted); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index aa25f0b241b..de0e7be647d 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -75,7 +75,7 @@ import { toACVMWitness, witnessMapToFields, } from '@aztec/simulator/client'; -import { createTxForPublicCall } from '@aztec/simulator/public/fixtures'; +import { createTxForPublicCalls } from '@aztec/simulator/public/fixtures'; import { ExecutionError, type PackedValuesCache, @@ -852,7 +852,12 @@ export class TXE implements TypedOracle { // When setting up a teardown call, we tell it that // private execution used Gas(1, 1) so it can compute a tx fee. const gasUsedByPrivate = isTeardown ? new Gas(1, 1) : Gas.empty(); - const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown); + const tx = createTxForPublicCalls( + /*setupExecutionRequests=*/ [], + /*appExecutionRequests=*/ isTeardown ? [] : [executionRequest], + /*teardownExecutionRequests=*/ isTeardown ? executionRequest : undefined, + gasUsedByPrivate, + ); const result = await simulator.simulate(tx);