From 8820bd5f3004fedd6c286e2dbf5f8b24fc767fd2 Mon Sep 17 00:00:00 2001 From: David Banks <47112877+dbanks12@users.noreply.github.com> Date: Thu, 19 Dec 2024 06:36:48 -0500 Subject: [PATCH] fix: handle calls to non-existent contracts in AVM witgen (#10862) Exceptionally halt & consume all gas on a call to a non-existent contract. Should be able to prove. Hacked this to work for top-level/enqueued-calls by adding a dummy row (`op_add`) and then raising an exceptional halt. Resolves https://github.com/AztecProtocol/aztec-packages/issues/10373 Resolves https://github.com/AztecProtocol/aztec-packages/issues/10044 Follow-up work: - Add tests for bytecode deserialization failures (sim & witgen) --- .../vm/avm/trace/bytecode_trace.cpp | 67 +++++++------- .../src/barretenberg/vm/avm/trace/errors.hpp | 3 +- .../barretenberg/vm/avm/trace/execution.cpp | 65 +++++++++---- .../barretenberg/vm/avm/trace/gas_trace.cpp | 6 ++ .../src/barretenberg/vm/avm/trace/helper.cpp | 2 + .../src/barretenberg/vm/avm/trace/trace.cpp | 91 +++++++++---------- .../contracts/avm_test_contract/src/main.nr | 2 + .../bb-prover/src/avm_proving.test.ts | 40 +++++++- .../simulator/src/public/fixtures/index.ts | 43 +++++---- 9 files changed, 204 insertions(+), 115 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/bytecode_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/bytecode_trace.cpp index 5956a653481..e61f1109a09 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/bytecode_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/bytecode_trace.cpp @@ -111,38 +111,43 @@ void AvmBytecodeTraceBuilder::build_bytecode_hash_columns() { // This is the main loop that will generate the bytecode trace for (auto& contract_bytecode : all_contracts_bytecode) { - FF running_hash = FF::zero(); - auto field_encoded_bytecode = encode_bytecode(contract_bytecode.bytecode); - // This size is already based on the number of fields - for (size_t i = 0; i < field_encoded_bytecode.size(); ++i) { - bytecode_hash_trace.push_back(BytecodeHashTraceEntry{ - .field_encoded_bytecode = field_encoded_bytecode[i], - .running_hash = running_hash, - .bytecode_field_length_remaining = static_cast(field_encoded_bytecode.size() - i), - }); - // We pair-wise hash the i-th bytecode field with the running hash (which is the output of previous i-1 - // round). I.e. - // initially running_hash = 0, - // the first round is running_hash = hash(bytecode[0], running_hash), - // the second round is running_hash = hash(bytecode[1],running_hash), and so on. - running_hash = poseidon2::hash({ field_encoded_bytecode[i], running_hash }); + if (contract_bytecode.bytecode.size() == 0) { + vinfo("Excluding non-existent contract from bytecode hash columns..."); + } else { + FF running_hash = FF::zero(); + auto field_encoded_bytecode = encode_bytecode(contract_bytecode.bytecode); + // This size is already based on the number of fields + for (size_t i = 0; i < field_encoded_bytecode.size(); ++i) { + bytecode_hash_trace.push_back(BytecodeHashTraceEntry{ + .field_encoded_bytecode = field_encoded_bytecode[i], + .running_hash = running_hash, + .bytecode_field_length_remaining = static_cast(field_encoded_bytecode.size() - i), + }); + // We pair-wise hash the i-th bytecode field with the running hash (which is the output of previous i-1 + // round). I.e. + // initially running_hash = 0, + // the first round is running_hash = hash(bytecode[0], running_hash), + // the second round is running_hash = hash(bytecode[1],running_hash), and so on. + running_hash = poseidon2::hash({ field_encoded_bytecode[i], running_hash }); + } + // Now running_hash actually contains the bytecode hash + BytecodeHashTraceEntry last_entry; + last_entry.bytecode_field_length_remaining = 0; + last_entry.running_hash = running_hash; + // Assert that the computed bytecode hash is the same as what we received as the hint + ASSERT(running_hash == contract_bytecode.contract_class_id_preimage.public_bytecode_commitment); + + last_entry.class_id = + compute_contract_class_id(contract_bytecode.contract_class_id_preimage.artifact_hash, + contract_bytecode.contract_class_id_preimage.private_fn_root, + running_hash); + // Assert that the computed class id is the same as what we received as the hint + ASSERT(last_entry.class_id == contract_bytecode.contract_instance.contract_class_id); + + last_entry.contract_address = compute_address_from_instance(contract_bytecode.contract_instance); + // Assert that the computed contract address is the same as what we received as the hint + ASSERT(last_entry.contract_address == contract_bytecode.contract_instance.address); } - // Now running_hash actually contains the bytecode hash - BytecodeHashTraceEntry last_entry; - last_entry.bytecode_field_length_remaining = 0; - last_entry.running_hash = running_hash; - // Assert that the computed bytecode hash is the same as what we received as the hint - ASSERT(running_hash == contract_bytecode.contract_class_id_preimage.public_bytecode_commitment); - - last_entry.class_id = compute_contract_class_id(contract_bytecode.contract_class_id_preimage.artifact_hash, - contract_bytecode.contract_class_id_preimage.private_fn_root, - running_hash); - // Assert that the computed class id is the same as what we received as the hint - ASSERT(last_entry.class_id == contract_bytecode.contract_instance.contract_class_id); - - last_entry.contract_address = compute_address_from_instance(contract_bytecode.contract_instance); - // Assert that the computed contract address is the same as what we received as the hint - ASSERT(last_entry.contract_address == contract_bytecode.contract_instance.address); } } diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp index 081ee2fbc5e..eb17641710b 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp @@ -21,7 +21,8 @@ enum class AvmError : uint32_t { DUPLICATE_NULLIFIER, SIDE_EFFECT_LIMIT_REACHED, OUT_OF_GAS, - STATIC_CALL_ALTERATION + STATIC_CALL_ALTERATION, + NO_BYTECODE_FOUND, }; } // namespace bb::avm_trace diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp index a2478a292ef..e5bbbeb2251 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp @@ -19,6 +19,7 @@ #include "barretenberg/vm/aztec_constants.hpp" #include "barretenberg/vm/constants.hpp" #include "barretenberg/vm/stats.hpp" +#include "errors.hpp" #include #include @@ -444,10 +445,25 @@ AvmError Execution::execute_enqueued_call(AvmTraceBuilder& trace_builder, .da_gas_left = da_gas_allocated_to_enqueued_call, .internal_return_ptr_stack = {}, }; - trace_builder.allocate_gas_for_call(l2_gas_allocated_to_enqueued_call, da_gas_allocated_to_enqueued_call); // Find the bytecode based on contract address of the public call request - std::vector bytecode = - trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, check_bytecode_membership); + std::vector bytecode; + try { + bytecode = + trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, check_bytecode_membership); + } catch ([[maybe_unused]] const std::runtime_error& e) { + info("AVM enqueued call exceptionally halted. Error: No bytecode found for enqueued call"); + // FIXME: properly handle case when bytecode is not found! + // For now, we add a dummy row in main trace to mutate later. + // Dummy row in main trace to mutate afterwards. + // This error was encountered before any opcodes were executed, but + // we need at least one row in the execution trace to then mutate and say "it halted and consumed all gas!" + trace_builder.op_add(0, 0, 0, 0, OpCode::ADD_8); + trace_builder.handle_exceptional_halt(); + return AvmError::NO_BYTECODE_FOUND; + ; + } + + trace_builder.allocate_gas_for_call(l2_gas_allocated_to_enqueued_call, da_gas_allocated_to_enqueued_call); // Copied version of pc maintained in trace builder. The value of pc is evolving based // on opcode logic and therefore is not maintained here. However, the next opcode in the execution @@ -456,12 +472,16 @@ AvmError Execution::execute_enqueued_call(AvmTraceBuilder& trace_builder, std::stack debug_counter_stack; uint32_t counter = 0; trace_builder.set_call_ptr(context_id); - while ((pc = trace_builder.get_pc()) < bytecode.size()) { + while (is_ok(error) && (pc = trace_builder.get_pc()) < bytecode.size()) { auto [inst, parse_error] = Deserialization::parse(bytecode, pc); - // FIXME: properly handle case when an instruction fails parsing - // especially first instruction in bytecode if (!is_ok(error)) { + info("AVM failed to deserialize bytecode at pc: ", pc); + // FIXME: properly handle case when an instruction fails parsing! + // For now, we add a dummy row in main trace to mutate later. + // This error was encountered before any opcodes were executed, but + // we need at least one row in the execution trace to then mutate and say "it halted and consumed all gas!" + trace_builder.op_add(0, 0, 0, 0, OpCode::ADD_8); error = parse_error; break; } @@ -855,12 +875,17 @@ AvmError Execution::execute_enqueued_call(AvmTraceBuilder& trace_builder, std::get(inst.operands.at(3)), std::get(inst.operands.at(4)), std::get(inst.operands.at(5))); - // TODO: what if an error is encountered on return or call which have already modified stack? - // We hack it in here the logic to change contract address that we are processing - bytecode = trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, - /*check_membership=*/false); - debug_counter_stack.push(counter); - counter = 0; + // If opcode errored, nested call won't happen. Don't retrieve bytecode, etc. + if (is_ok(error)) { + try { + bytecode = trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, + /*check_membership=*/true); + } catch ([[maybe_unused]] const std::runtime_error& e) { + error = AvmError::NO_BYTECODE_FOUND; + } + debug_counter_stack.push(counter); + counter = 0; + } break; } case OpCode::STATICCALL: { @@ -870,11 +895,17 @@ AvmError Execution::execute_enqueued_call(AvmTraceBuilder& trace_builder, std::get(inst.operands.at(3)), std::get(inst.operands.at(4)), std::get(inst.operands.at(5))); - // We hack it in here the logic to change contract address that we are processing - bytecode = trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, - /*check_membership=*/false); - debug_counter_stack.push(counter); - counter = 0; + // If opcode errored, nested call won't happen. Don't retrieve bytecode, etc. + if (is_ok(error)) { + try { + bytecode = trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, + /*check_membership=*/true); + } catch ([[maybe_unused]] const std::runtime_error& e) { + error = AvmError::NO_BYTECODE_FOUND; + } + debug_counter_stack.push(counter); + counter = 0; + } break; } case OpCode::RETURN: { diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/gas_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/gas_trace.cpp index 3bc5b3a6754..c6af50ad5d8 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/gas_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/gas_trace.cpp @@ -154,6 +154,9 @@ void AvmGasTraceBuilder::constrain_gas_for_halt(bool exceptional_halt, halting_entry.is_halt_or_first_row_in_nested_call = true; gas_opcode_lookup_counter[halting_entry.opcode]--; + + // clear this flag (in case the CALL opcode itself led to an exception) + next_row_is_first_in_nested_call = false; } void AvmGasTraceBuilder::constrain_gas_for_top_level_exceptional_halt(uint32_t l2_gas_allocated, @@ -172,6 +175,9 @@ void AvmGasTraceBuilder::constrain_gas_for_top_level_exceptional_halt(uint32_t l halting_entry.is_halt_or_first_row_in_nested_call = true; gas_opcode_lookup_counter[halting_entry.opcode]--; + + // clear this flag (in case the CALL opcode itself led to an exception) + next_row_is_first_in_nested_call = false; } void AvmGasTraceBuilder::finalize(std::vector>& main_trace) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.cpp index 1028bad79fc..7f525bd136e 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.cpp @@ -135,6 +135,8 @@ std::string to_name(AvmError error) return "SIDE EFFECT LIMIT REACHED"; case AvmError::OUT_OF_GAS: return "OUT OF GAS"; + case AvmError::NO_BYTECODE_FOUND: + return "NO BYTECODE FOUND"; default: throw std::runtime_error("Invalid error type"); break; diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index 8002787ebcd..f632962d980 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -199,10 +199,8 @@ std::vector AvmTraceBuilder::get_bytecode(const FF contract_address, bo } else { // This was a non-membership proof! // Enforce that the tree access membership checked a low-leaf that skips the contract address nullifier. - // Show that the contract address nullifier meets the non membership conditions (sandwich or max) - ASSERT(contract_address_nullifier < nullifier_read_hint.low_leaf_preimage.nullifier && - (nullifier_read_hint.low_leaf_preimage.next_nullifier == FF::zero() || - contract_address_nullifier > nullifier_read_hint.low_leaf_preimage.next_nullifier)); + AvmMerkleTreeTraceBuilder::assert_nullifier_non_membership_check(nullifier_read_hint.low_leaf_preimage, + contract_address_nullifier); } } @@ -210,7 +208,6 @@ std::vector AvmTraceBuilder::get_bytecode(const FF contract_address, bo vinfo("Found bytecode for contract address: ", contract_address); return bytecode_hint.bytecode; } - // TODO(dbanks12): handle non-existent bytecode vinfo("Bytecode not found for contract address: ", contract_address); throw std::runtime_error("Bytecode not found"); } @@ -3774,48 +3771,50 @@ AvmError AvmTraceBuilder::constrain_external_call(OpCode opcode, pc += Deserialization::get_pc_increment(opcode); - // Save the current gas left in the context before pushing it to stack - // It will be used on RETURN/REVERT/halt to remember how much gas the caller had left. - current_ext_call_ctx.l2_gas_left = gas_trace_builder.get_l2_gas_left(); - current_ext_call_ctx.da_gas_left = gas_trace_builder.get_da_gas_left(); - - // We push the current ext call ctx onto the stack and initialize a new one - current_ext_call_ctx.last_pc = pc; - current_ext_call_ctx.success_offset = resolved_success_offset; - current_ext_call_ctx.tree_snapshot = merkle_tree_trace_builder.get_tree_snapshots(); - current_ext_call_ctx.public_data_unique_writes = merkle_tree_trace_builder.get_public_data_unique_writes(); - external_call_ctx_stack.emplace(current_ext_call_ctx); - - // Ext Ctx setup - std::vector calldata; - read_slice_from_memory(resolved_args_offset, args_size, calldata); - - set_call_ptr(static_cast(clk)); - - // Don't try allocating more than the gas that is actually left - const auto l2_gas_allocated_to_nested_call = - std::min(static_cast(read_gas_l2.val), gas_trace_builder.get_l2_gas_left()); - const auto da_gas_allocated_to_nested_call = - std::min(static_cast(read_gas_da.val), gas_trace_builder.get_da_gas_left()); - current_ext_call_ctx = ExtCallCtx{ - .context_id = static_cast(clk), - .parent_id = current_ext_call_ctx.context_id, - .is_static_call = opcode == OpCode::STATICCALL, - .contract_address = read_addr.val, - .calldata = calldata, - .nested_returndata = {}, - .last_pc = 0, - .success_offset = 0, - .start_l2_gas_left = l2_gas_allocated_to_nested_call, - .start_da_gas_left = da_gas_allocated_to_nested_call, - .l2_gas_left = l2_gas_allocated_to_nested_call, - .da_gas_left = da_gas_allocated_to_nested_call, - .internal_return_ptr_stack = {}, - .tree_snapshot = {}, - }; + if (is_ok(error)) { + // Save the current gas left in the context before pushing it to stack + // It will be used on RETURN/REVERT/halt to remember how much gas the caller had left. + current_ext_call_ctx.l2_gas_left = gas_trace_builder.get_l2_gas_left(); + current_ext_call_ctx.da_gas_left = gas_trace_builder.get_da_gas_left(); + + // We push the current ext call ctx onto the stack and initialize a new one + current_ext_call_ctx.last_pc = pc; + current_ext_call_ctx.success_offset = resolved_success_offset, + current_ext_call_ctx.tree_snapshot = merkle_tree_trace_builder.get_tree_snapshots(); + current_ext_call_ctx.public_data_unique_writes = merkle_tree_trace_builder.get_public_data_unique_writes(); + external_call_ctx_stack.emplace(current_ext_call_ctx); + + // Ext Ctx setup + std::vector calldata; + read_slice_from_memory(resolved_args_offset, args_size, calldata); + + set_call_ptr(static_cast(clk)); + + // Don't try allocating more than the gas that is actually left + const auto l2_gas_allocated_to_nested_call = + std::min(static_cast(read_gas_l2.val), gas_trace_builder.get_l2_gas_left()); + const auto da_gas_allocated_to_nested_call = + std::min(static_cast(read_gas_da.val), gas_trace_builder.get_da_gas_left()); + current_ext_call_ctx = ExtCallCtx{ + .context_id = static_cast(clk), + .parent_id = current_ext_call_ctx.context_id, + .is_static_call = opcode == OpCode::STATICCALL, + .contract_address = read_addr.val, + .calldata = calldata, + .nested_returndata = {}, + .last_pc = 0, + .success_offset = 0, + .start_l2_gas_left = l2_gas_allocated_to_nested_call, + .start_da_gas_left = da_gas_allocated_to_nested_call, + .l2_gas_left = l2_gas_allocated_to_nested_call, + .da_gas_left = da_gas_allocated_to_nested_call, + .internal_return_ptr_stack = {}, + .tree_snapshot = {}, + }; - allocate_gas_for_call(l2_gas_allocated_to_nested_call, da_gas_allocated_to_nested_call); - set_pc(0); + allocate_gas_for_call(l2_gas_allocated_to_nested_call, da_gas_allocated_to_nested_call); + set_pc(0); + } return error; } 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 149cfae9aec..c18b0bfaa9f 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 @@ -663,6 +663,8 @@ contract AvmTest { dep::aztec::oracle::debug_log::debug_log("pedersen_hash_with_index"); let _ = pedersen_hash_with_index(args_field); dep::aztec::oracle::debug_log::debug_log("test_get_contract_instance"); + // address should match yarn-project/simulator/src/public/fixtures/index.ts's + // MockedAvmTestContractDataSource.otherContractInstance test_get_contract_instance(AztecAddress::from_field(0x4444)); dep::aztec::oracle::debug_log::debug_log("get_address"); let _ = get_address(); diff --git a/yarn-project/bb-prover/src/avm_proving.test.ts b/yarn-project/bb-prover/src/avm_proving.test.ts index f0c5582c996..ab3aad9dd39 100644 --- a/yarn-project/bb-prover/src/avm_proving.test.ts +++ b/yarn-project/bb-prover/src/avm_proving.test.ts @@ -110,10 +110,46 @@ describe('AVM WitGen, proof generation and verification', () => { }, TIMEOUT, ); + 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', /*calldata=*/ [], /*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', /*calldata=*/ [], /*expectRevert=*/ false); + }, + TIMEOUT, + ); + it( + 'Should prove and verify a top-level exceptional halt due to a non-existent contract', + async () => { + await proveAndVerifyAvmTestContract( + 'add_args_return', + /*calldata=*/ [new Fr(1), new Fr(2)], + /*expectRevert=*/ true, + /*skipContractDeployments=*/ true, + ); + }, + TIMEOUT, + ); }); -async function proveAndVerifyAvmTestContract(functionName: string, calldata: Fr[] = [], expectRevert = false) { - const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(functionName, calldata, expectRevert); +async function proveAndVerifyAvmTestContract( + functionName: string, + calldata: Fr[] = [], + expectRevert = false, + skipContractDeployments = false, +) { + const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs( + functionName, + calldata, + expectRevert, + skipContractDeployments, + ); const internalLogger = createLogger('bb-prover:avm-proving-test'); const logger = (msg: string, _data?: any) => internalLogger.verbose(msg); diff --git a/yarn-project/simulator/src/public/fixtures/index.ts b/yarn-project/simulator/src/public/fixtures/index.ts index 36c126c0ef3..af8b3a5b3cd 100644 --- a/yarn-project/simulator/src/public/fixtures/index.ts +++ b/yarn-project/simulator/src/public/fixtures/index.ts @@ -41,6 +41,7 @@ export async function simulateAvmTestContractGenerateCircuitInputs( functionName: string, calldata: Fr[] = [], expectRevert: boolean = false, + skipContractDeployments: boolean = false, assertionErrString?: string, ): Promise { const sender = AztecAddress.random(); @@ -53,21 +54,24 @@ export async function simulateAvmTestContractGenerateCircuitInputs( const telemetry = new NoopTelemetryClient(); const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork(); - const contractDataSource = new MockedAvmTestContractDataSource(); + const contractDataSource = new MockedAvmTestContractDataSource(skipContractDeployments); const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource); const contractInstance = contractDataSource.contractInstance; - const contractAddressNullifier = siloNullifier( - AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS), - contractInstance.address.toField(), - ); - await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0); - // other contract address used by the bulk test's GETCONTRACTINSTANCE test - const otherContractAddressNullifier = siloNullifier( - AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS), - contractDataSource.otherContractInstance.address.toField(), - ); - await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0); + + if (!skipContractDeployments) { + const contractAddressNullifier = siloNullifier( + AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS), + contractInstance.address.toField(), + ); + await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0); + // other contract address used by the bulk test's GETCONTRACTINSTANCE test + const otherContractAddressNullifier = siloNullifier( + AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS), + contractDataSource.otherContractInstance.address.toField(), + ); + await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0); + } const simulator = new PublicTxSimulator( merkleTrees, @@ -155,7 +159,7 @@ export class MockedAvmTestContractDataSource implements ContractDataSource { private bytecodeCommitment: Fr; public otherContractInstance: ContractInstanceWithAddress; - constructor() { + constructor(private noContractsDeployed: boolean = false) { this.bytecode = getAvmTestContractBytecode(this.fnName); this.fnSelector = getAvmTestContractFunctionSelector(this.fnName); this.publicFn = { bytecode: this.bytecode, selector: this.fnSelector }; @@ -199,12 +203,15 @@ export class MockedAvmTestContractDataSource implements ContractDataSource { return Promise.resolve(); } - getContract(address: AztecAddress): Promise { - if (address.equals(this.contractInstance.address)) { - return Promise.resolve(this.contractInstance); - } else { - return Promise.resolve(this.otherContractInstance); + getContract(address: AztecAddress): Promise { + if (!this.noContractsDeployed) { + if (address.equals(this.contractInstance.address)) { + return Promise.resolve(this.contractInstance); + } else if (address.equals(this.otherContractInstance.address)) { + return Promise.resolve(this.otherContractInstance); + } } + return Promise.resolve(undefined); } getContractClassIds(): Promise {