diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index f0888b22a46..c032ad64c55 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -423,9 +423,6 @@ fn handle_foreign_call( "avmOpcodeNullifierExists" => handle_nullifier_exists(avm_instrs, destinations, inputs), "avmOpcodeL1ToL2MsgExists" => handle_l1_to_l2_msg_exists(avm_instrs, destinations, inputs), "avmOpcodeSendL2ToL1Msg" => handle_send_l2_to_l1_msg(avm_instrs, destinations, inputs), - "avmOpcodeGetContractInstance" => { - handle_get_contract_instance(avm_instrs, destinations, inputs); - } "avmOpcodeCalldataCopy" => handle_calldata_copy(avm_instrs, destinations, inputs), "avmOpcodeReturn" => handle_return(avm_instrs, destinations, inputs), "avmOpcodeStorageRead" => handle_storage_read(avm_instrs, destinations, inputs), @@ -435,6 +432,11 @@ fn handle_foreign_call( _ if inputs.is_empty() && destinations.len() == 1 => { handle_getter_instruction(avm_instrs, function, destinations, inputs); } + // Get contract instance variations. + // TODO(dbanks12): pattern match instead + _ if inputs.len() == 1 && destinations.len() == 2 => { + handle_get_contract_instance(avm_instrs, function, destinations, inputs); + } // Anything else. _ => panic!("Transpiler doesn't know how to process ForeignCall function {}", function), } @@ -1288,22 +1290,42 @@ fn handle_storage_write( /// Emit a GETCONTRACTINSTANCE opcode fn handle_get_contract_instance( avm_instrs: &mut Vec, + function: &str, destinations: &Vec, inputs: &Vec, ) { + enum ContractInstanceMember { + DEPLOYER, + CLASS_ID, + INIT_HASH, + } + assert!(inputs.len() == 1); - assert!(destinations.len() == 1); + assert!(destinations.len() == 2); + + let member_idx = match function { + "avmOpcodeGetContractInstanceDeployer" => ContractInstanceMember::DEPLOYER, + "avmOpcodeGetContractInstanceClassId" => ContractInstanceMember::CLASS_ID, + "avmOpcodeGetContractInstanceInitializationHash" => ContractInstanceMember::INIT_HASH, + _ => panic!("Transpiler doesn't know how to process function {:?}", function), + }; let address_offset_maybe = inputs[0]; let address_offset = match address_offset_maybe { - ValueOrArray::MemoryAddress(slot_offset) => slot_offset, + ValueOrArray::MemoryAddress(offset) => offset, _ => panic!("GETCONTRACTINSTANCE address should be a single value"), }; let dest_offset_maybe = destinations[0]; let dest_offset = match dest_offset_maybe { - ValueOrArray::HeapArray(HeapArray { pointer, .. }) => pointer, - _ => panic!("GETCONTRACTINSTANCE destination should be an array"), + ValueOrArray::MemoryAddress(offset) => offset, + _ => panic!("GETCONTRACTINSTANCE dst destination should be a single value"), + }; + + let exists_offset_maybe = destinations[1]; + let exists_offset = match exists_offset_maybe { + ValueOrArray::MemoryAddress(offset) => offset, + _ => panic!("GETCONTRACTINSTANCE exists destination should be a single value"), }; avm_instrs.push(AvmInstruction { @@ -1311,12 +1333,15 @@ fn handle_get_contract_instance( indirect: Some( AddressingModeBuilder::default() .direct_operand(&address_offset) - .indirect_operand(&dest_offset) + .direct_operand(&dest_offset) + .direct_operand(&exists_offset) .build(), ), operands: vec![ - AvmOperand::U32 { value: address_offset.to_usize() as u32 }, - AvmOperand::U32 { value: dest_offset.to_usize() as u32 }, + AvmOperand::U8 { value: member_idx as u8 }, + AvmOperand::U16 { value: address_offset.to_usize() as u16 }, + AvmOperand::U16 { value: dest_offset.to_usize() as u16 }, + AvmOperand::U16 { value: exists_offset.to_usize() as u16 }, ], ..Default::default() }); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp index e4aa027a298..2654c84b36c 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp @@ -2274,43 +2274,14 @@ TEST_F(AvmExecutionTests, opCallOpcodes) validate_trace(std::move(trace), public_inputs, calldata, returndata); } -TEST_F(AvmExecutionTests, opGetContractInstanceOpcodes) +TEST_F(AvmExecutionTests, opGetContractInstanceOpcode) { - std::string bytecode_hex = to_hex(OpCode::SET_8) + // opcode SET - "00" // Indirect flag - + to_hex(AvmMemoryTag::U32) + - "00" // val - "00" // dst_offset - + to_hex(OpCode::SET_8) + // opcode SET - "00" // Indirect flag - + to_hex(AvmMemoryTag::U32) + - "01" // val - "01" + - to_hex(OpCode::CALLDATACOPY) + // opcode CALLDATACOPY for addr - "00" // Indirect flag - "0000" // cd_offset - "0001" // copy_size - "0001" // dst_offset, (i.e. where we store the addr) - + to_hex(OpCode::SET_8) + // opcode SET for the indirect dst offset - "00" // Indirect flag - + to_hex(AvmMemoryTag::U32) + - "03" // val i - "02" + // dst_offset 2 - to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode CALL - "02" // Indirect flag - "00000001" // address offset - "00000002" // dst offset - + to_hex(OpCode::RETURN) + // opcode RETURN - "00" // Indirect flag - "0003" // ret offset 3 - "0006"; // ret size 6 - - auto bytecode = hex_to_bytes(bytecode_hex); - auto instructions = Deserialization::parse(bytecode); - - FF address = 10; - std::vector calldata = { address }; - std::vector returndata = {}; + const uint8_t address_byte = 0x42; + const FF address(address_byte); + const FF deployer = 42; + const FF class_id = 66; + const FF init_hash = 99; + const FF exists = 1; // Generate Hint for call operation // Note: opcode does not write 'address' into memory @@ -2322,14 +2293,56 @@ TEST_F(AvmExecutionTests, opGetContractInstanceOpcodes) grumpkin::g1::affine_element::random_element(), grumpkin::g1::affine_element::random_element(), }; - auto execution_hints = - ExecutionHints().with_contract_instance_hints({ { address, { address, 1, 2, 3, 4, 5, public_keys_hints } } }); + const auto execution_hints = ExecutionHints().with_contract_instance_hints( + { { address, { address, exists, /*salt=*/2, deployer, class_id, init_hash, public_keys_hints } } }); + + std::string bytecode_hex = to_hex(OpCode::SET_8) + // opcode SET + "00" // Indirect flag + + to_hex(AvmMemoryTag::U8) + to_hex(address_byte) + // val + "01" // dst_offset 0 + + to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode GETCONTRACTINSTANCE + "00" // Indirect flag + + to_hex(static_cast(ContractInstanceMember::DEPLOYER)) + // member enum + "0001" // address offset + "0010" // dst offset + "0011" // exists offset + + to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode GETCONTRACTINSTANCE + "00" // Indirect flag + + to_hex(static_cast(ContractInstanceMember::CLASS_ID)) + // member enum + "0001" // address offset + "0012" // dst offset + "0013" // exists offset + + to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode GETCONTRACTINSTANCE + "00" // Indirect flag + + to_hex(static_cast(ContractInstanceMember::INIT_HASH)) + // member enum + "0001" // address offset + "0014" // dst offset + "0015" // exists offset + + to_hex(OpCode::RETURN) + // opcode RETURN + "00" // Indirect flag + "0010" // ret offset 1 + "0006"; // ret size 6 (dst & exists for all 3) + auto bytecode = hex_to_bytes(bytecode_hex); + auto instructions = Deserialization::parse(bytecode); + + ASSERT_THAT(instructions, SizeIs(5)); + + std::vector const calldata{}; + // alternating member value, exists bool + std::vector const expected_returndata = { + deployer, 1, class_id, 1, init_hash, 1, + }; + + std::vector returndata{}; auto trace = Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec, execution_hints); - EXPECT_EQ(returndata, std::vector({ 1, 2, 3, 4, 5, returned_point.x })); // The first one represents true validate_trace(std::move(trace), public_inputs, calldata, returndata); + + // Validate returndata + EXPECT_EQ(returndata, expected_returndata); } + // Negative test detecting an invalid opcode byte. TEST_F(AvmExecutionTests, invalidOpcode) { diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp index 81360626bfc..60c62390dd0 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp @@ -136,7 +136,8 @@ const std::unordered_map> OPCODE_WIRE_FORMAT = OperandType::UINT16, /*TODO: leafIndexOffset is not constrained*/ OperandType::UINT16, OperandType::UINT16 } }, - { OpCode::GETCONTRACTINSTANCE, { OperandType::INDIRECT8, OperandType::UINT32, OperandType::UINT32 } }, + { OpCode::GETCONTRACTINSTANCE, + { OperandType::INDIRECT8, OperandType::UINT8, OperandType::UINT16, OperandType::UINT16, OperandType::UINT16 } }, { OpCode::EMITUNENCRYPTEDLOG, { OperandType::INDIRECT8, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp index eedeac2f984..9039c63f0da 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp @@ -621,8 +621,10 @@ std::vector Execution::gen_trace(std::vector const& instructio break; case OpCode::GETCONTRACTINSTANCE: trace_builder.op_get_contract_instance(std::get(inst.operands.at(0)), - std::get(inst.operands.at(1)), - std::get(inst.operands.at(2))); + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3)), + std::get(inst.operands.at(4))); break; // Accrued Substate diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp index feab28e807a..fa30886a592 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp @@ -133,6 +133,14 @@ enum class EnvironmentVariable { MAX_ENV_VAR }; +enum class ContractInstanceMember { + DEPLOYER, + CLASS_ID, + INIT_HASH, + // sentinel + MAX_MEMBER, +}; + class Bytecode { public: static bool is_valid(uint8_t byte); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index f5af8ca0d78..e002b1afadc 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -2373,45 +2373,71 @@ void AvmTraceBuilder::op_l1_to_l2_msg_exists(uint8_t indirect, debug("l1_to_l2_msg_exists side-effect cnt: ", side_effect_counter); } -void AvmTraceBuilder::op_get_contract_instance(uint8_t indirect, uint32_t address_offset, uint32_t dst_offset) +void AvmTraceBuilder::op_get_contract_instance( + uint8_t indirect, uint8_t member_enum, uint16_t address_offset, uint16_t dst_offset, uint16_t exists_offset) { + ASSERT(member_enum < static_cast(ContractInstanceMember::MAX_MEMBER)); + ContractInstanceMember chosen_member = static_cast(member_enum); + auto clk = static_cast(main_trace.size()) + 1; - auto [resolved_address_offset, resolved_dst_offset] = - Addressing<2>::fromWire(indirect, call_ptr).resolve({ address_offset, dst_offset }, mem_trace_builder); + auto [resolved_address_offset, resolved_dst_offset, resolved_exists_offset] = + Addressing<3>::fromWire(indirect, call_ptr) + .resolve({ address_offset, dst_offset, exists_offset }, mem_trace_builder); auto read_address = constrained_read_from_memory( call_ptr, clk, resolved_address_offset, AvmMemoryTag::FF, AvmMemoryTag::FF, IntermRegister::IA); bool tag_match = read_address.tag_match; + // Read the contract instance + ContractInstanceHint instance = execution_hints.contract_instance_hints.at(read_address.val); + + const FF member_value = chosen_member == ContractInstanceMember::DEPLOYER ? instance.deployer_addr + : chosen_member == ContractInstanceMember::CLASS_ID + ? instance.contract_class_id + : instance.initialisation_hash; // chosen_member == ContractInstanceMember::INIT_HASH + + // TODO:(8603): once instructions can have multiple different tags for writes, write dst as FF and exists as U1 + // auto write_dst = constrained_write_to_memory(call_ptr, clk, resolved_dst_offset, member_value, AvmMemoryTag::FF, + // AvmMemoryTag::FF, IntermRegister::IC); auto write_exists = constrained_write_to_memory(call_ptr, clk, + // resolved_exists_offset, instance.instance_found_in_address, AvmMemoryTag::FF, AvmMemoryTag::FF, + // IntermRegister::ID); + // Constrain gas cost gas_trace_builder.constrain_gas(clk, OpCode::GETCONTRACTINSTANCE); main_trace.push_back(Row{ .main_clk = clk, + .main_call_ptr = call_ptr, .main_ia = read_address.val, + // TODO:(8603): uncomment this and below blocks once instructions can have multiple different tags for writes + //.main_ic = write_dst.val, + //.main_id = write_exists.val, .main_ind_addr_a = FF(read_address.indirect_address), + //.main_ind_addr_c = FF(write_dst.indirect_address), + //.main_ind_addr_d = FF(write_exists.indirect_address), .main_internal_return_ptr = FF(internal_return_ptr), .main_mem_addr_a = FF(read_address.direct_address), + //.main_mem_addr_c = FF(write_dst.direct_address), + //.main_mem_addr_d = FF(write_exists.direct_address), .main_pc = FF(pc++), .main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), .main_sel_mem_op_a = FF(1), + //.main_sel_mem_op_c = FF(1), + //.main_sel_mem_op_d = FF(1), .main_sel_op_get_contract_instance = FF(1), .main_sel_resolve_ind_addr_a = FF(static_cast(read_address.is_indirect)), + //.main_sel_resolve_ind_addr_c = FF(static_cast(write_dst.is_indirect)), + //.main_sel_resolve_ind_addr_d = FF(static_cast(write_exists.is_indirect)), .main_tag_err = FF(static_cast(!tag_match)), }); - // Read the contract instance - ContractInstanceHint contract_instance = execution_hints.contract_instance_hints.at(read_address.val); - std::vector public_key_fields = contract_instance.public_keys.to_fields(); - // NOTE: we don't write the first entry (the contract instance's address/key) to memory - std::vector contract_instance_vec = { contract_instance.instance_found_in_address, - contract_instance.salt, - contract_instance.deployer_addr, - contract_instance.contract_class_id, - contract_instance.initialisation_hash }; - contract_instance_vec.insert(contract_instance_vec.end(), public_key_fields.begin(), public_key_fields.end()); - write_slice_to_memory(resolved_dst_offset, AvmMemoryTag::FF, contract_instance_vec); + // TODO:(8603): once instructions can have multiple different tags for writes, remove this and do a constrained + // writes + write_to_memory(resolved_dst_offset, member_value, AvmMemoryTag::FF); + write_to_memory(resolved_exists_offset, instance.instance_found_in_address, AvmMemoryTag::U1); + + // TODO(dbanks12): compute contract address nullifier from instance preimage and perform membership check debug("contract_instance cnt: ", side_effect_counter); side_effect_counter++; diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp index dc8e743c3d1..828b4fafcc0 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp @@ -114,7 +114,8 @@ class AvmTraceBuilder { uint32_t log_offset, uint32_t leaf_index_offset, uint32_t dest_offset); - void op_get_contract_instance(uint8_t indirect, uint32_t address_offset, uint32_t dst_offset); + void op_get_contract_instance( + uint8_t indirect, uint8_t member_enum, uint16_t address_offset, uint16_t dst_offset, uint16_t exists_offset); // Accrued Substate void op_emit_unencrypted_log(uint8_t indirect, uint32_t log_offset, uint32_t log_size_offset); diff --git a/noir-projects/aztec-nr/aztec/src/context/public_context.nr b/noir-projects/aztec-nr/aztec/src/context/public_context.nr index ff476c4a672..06bf60893f4 100644 --- a/noir-projects/aztec-nr/aztec/src/context/public_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/public_context.nr @@ -219,10 +219,6 @@ unconstrained fn sender() -> AztecAddress { unconstrained fn portal() -> EthAddress { portal_opcode() } -// UNUSED: Remove. -// unconstrained fn function_selector() -> u32 { -// function_selector_opcode() -// } unconstrained fn transaction_fee() -> Field { transaction_fee_opcode() } @@ -326,10 +322,6 @@ unconstrained fn sender_opcode() -> AztecAddress {} #[oracle(avmOpcodePortal)] unconstrained fn portal_opcode() -> EthAddress {} -// UNUSED: Remove. -// #[oracle(avmOpcodeFunctionSelector)] -// unconstrained fn function_selector_opcode() -> u32 {} - #[oracle(avmOpcodeTransactionFee)] unconstrained fn transaction_fee_opcode() -> Field {} diff --git a/noir-projects/aztec-nr/aztec/src/initializer.nr b/noir-projects/aztec-nr/aztec/src/initializer.nr index 1be907c5ce9..a19aa3542a6 100644 --- a/noir-projects/aztec-nr/aztec/src/initializer.nr +++ b/noir-projects/aztec-nr/aztec/src/initializer.nr @@ -5,7 +5,7 @@ use dep::protocol_types::{ use crate::{ context::{PrivateContext, PublicContext}, oracle::get_contract_instance::get_contract_instance, - oracle::get_contract_instance::get_contract_instance_avm + oracle::get_contract_instance::{get_contract_instance_deployer_avm, get_contract_instance_initialization_hash_avm} }; pub fn mark_as_initialized_public(context: &mut PublicContext) { @@ -34,11 +34,12 @@ fn compute_unsiloed_contract_initialization_nullifier(address: AztecAddress) -> pub fn assert_initialization_matches_address_preimage_public(context: PublicContext) { let address = context.this_address(); - let instance = get_contract_instance_avm(address).unwrap(); + let deployer = get_contract_instance_deployer_avm(address).unwrap(); + let initialization_hash = get_contract_instance_initialization_hash_avm(address).unwrap(); let expected_init = compute_initialization_hash(context.selector(), context.get_args_hash()); - assert(instance.initialization_hash == expected_init, "Initialization hash does not match"); + assert(initialization_hash == expected_init, "Initialization hash does not match"); assert( - (instance.deployer.is_zero()) | (instance.deployer == context.msg_sender()), "Initializer address is not the contract deployer" + (deployer.is_zero()) | (deployer == context.msg_sender()), "Initializer address is not the contract deployer" ); } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr index 0912dd49d02..b6cac074bed 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_contract_instance.nr @@ -1,23 +1,15 @@ use dep::protocol_types::{ - address::AztecAddress, contract_instance::ContractInstance, constants::CONTRACT_INSTANCE_LENGTH, - utils::reader::Reader + address::AztecAddress, contract_class_id::ContractClassId, contract_instance::ContractInstance, + constants::CONTRACT_INSTANCE_LENGTH }; #[oracle(getContractInstance)] unconstrained fn get_contract_instance_oracle(_address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH] {} -// Returns a ContractInstance plus a boolean indicating whether the instance was found. -#[oracle(avmOpcodeGetContractInstance)] -unconstrained fn get_contract_instance_oracle_avm(_address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH + 1] {} - unconstrained fn get_contract_instance_internal(address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH] { get_contract_instance_oracle(address) } -pub unconstrained fn get_contract_instance_internal_avm(address: AztecAddress) -> [Field; CONTRACT_INSTANCE_LENGTH + 1] { - get_contract_instance_oracle_avm(address) -} - pub fn get_contract_instance(address: AztecAddress) -> ContractInstance { let instance = unsafe { ContractInstance::deserialize(get_contract_instance_internal(address)) @@ -29,12 +21,45 @@ pub fn get_contract_instance(address: AztecAddress) -> ContractInstance { instance } -pub fn get_contract_instance_avm(address: AztecAddress) -> Option { - let mut reader = Reader::new(get_contract_instance_internal_avm(address)); - let found = reader.read(); - if found == 0 { +// Returns a ContractInstance plus a boolean indicating whether the instance was found. +#[oracle(avmOpcodeGetContractInstanceDeployer)] +unconstrained fn get_contract_instance_deployer_oracle_avm(_address: AztecAddress) -> (Field, bool) {} +#[oracle(avmOpcodeGetContractInstanceClassId)] +unconstrained fn get_contract_instance_class_id_oracle_avm(_address: AztecAddress) -> (Field, bool) {} +#[oracle(avmOpcodeGetContractInstanceInitializationHash)] +unconstrained fn get_contract_instance_initialization_hash_oracle_avm(_address: AztecAddress) -> (Field, bool) {} + +pub unconstrained fn get_contract_instance_deployer_internal_avm(address: AztecAddress) -> (Field, bool) { + get_contract_instance_deployer_oracle_avm(address) +} +pub unconstrained fn get_contract_instance_class_id_internal_avm(address: AztecAddress) -> (Field, bool) { + get_contract_instance_class_id_oracle_avm(address) +} +pub unconstrained fn get_contract_instance_initialization_hash_internal_avm(address: AztecAddress) -> (Field, bool) { + get_contract_instance_initialization_hash_oracle_avm(address) +} + +pub fn get_contract_instance_deployer_avm(address: AztecAddress) -> Option { + let (member, exists) = get_contract_instance_deployer_internal_avm(address); + if exists { + Option::some(AztecAddress::from_field(member)) + } else { Option::none() + } +} +pub fn get_contract_instance_class_id_avm(address: AztecAddress) -> Option { + let (member, exists) = get_contract_instance_class_id_internal_avm(address); + if exists { + Option::some(ContractClassId::from_field(member)) } else { - Option::some(reader.read_struct(ContractInstance::deserialize)) + Option::none() + } +} +pub fn get_contract_instance_initialization_hash_avm(address: AztecAddress) -> Option { + let (member, exists) = get_contract_instance_initialization_hash_internal_avm(address); + if exists { + Option::some(member) + } else { + Option::none() } } 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 5077d304543..d6c5b476be5 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 @@ -32,9 +32,15 @@ contract AvmTest { use dep::aztec::protocol_types::constants::CONTRACT_INSTANCE_LENGTH; use dep::aztec::prelude::Map; use dep::aztec::state_vars::PublicMutable; - use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, point::Point, scalar::Scalar}; - use dep::aztec::oracle::get_contract_instance::{get_contract_instance_avm, get_contract_instance_internal_avm}; - use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, storage::map::derive_storage_slot_in_map}; + use dep::aztec::protocol_types::{ + abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, + contract_class_id::ContractClassId, contract_instance::ContractInstance, point::Point, + scalar::Scalar, storage::map::derive_storage_slot_in_map + }; + use dep::aztec::oracle::get_contract_instance::{ + get_contract_instance_deployer_avm, get_contract_instance_class_id_avm, + get_contract_instance_initialization_hash_avm + }; use dep::compressed_string::CompressedString; use dep::aztec::macros::{storage::storage, functions::{public, private}}; use dep::aztec::context::gas::GasOpts; @@ -274,33 +280,19 @@ contract AvmTest { * Contract instance ************************************************************************/ #[public] - fn test_get_contract_instance_raw() { - let fields = get_contract_instance_internal_avm(context.this_address()); - // The values here should match those in `avm_simulator.test.ts>Contract>GETCONTRACTINSTANCE deserializes correctly` - assert(fields.len() == CONTRACT_INSTANCE_LENGTH + 1); - assert(fields[0] == 0x1); - assert(fields[1] == 0x123); - assert(fields[2] == 0x456); - assert(fields[3] == 0x789); - assert(fields[4] == 0x101112); - assert(fields[5] == 0x131415); - assert(fields[6] == 0x161718); - assert(fields[7] == 0x00); - assert(fields[8] == 0x192021); - assert(fields[9] == 0x222324); - assert(fields[10] == 0x00); - assert(fields[11] == 0x252627); - assert(fields[12] == 0x282930); - assert(fields[13] == 0x00); - assert(fields[14] == 0x313233); - assert(fields[15] == 0x343536); - assert(fields[16] == 0x00); - } - - #[public] - fn test_get_contract_instance() { - let ci = get_contract_instance_avm(context.this_address()); - assert(ci.is_some(), "Contract instance not found!"); + fn test_get_contract_instance(address: AztecAddress) { + let deployer = get_contract_instance_deployer_avm(address); + let class_id = get_contract_instance_class_id_avm(address); + let initialization_hash = get_contract_instance_initialization_hash_avm(address); + + assert(deployer.is_some(), "Contract instance not found when getting DEPLOYER!"); + assert(class_id.is_some(), "Contract instance not found when getting CLASS_ID!"); + assert(initialization_hash.is_some(), "Contract instance not found when getting INIT_HASH!"); + + // The values here should match those in `avm_simulator.test.ts` + assert(deployer.unwrap().eq(AztecAddress::from_field(0x456))); + assert(class_id.unwrap().eq(ContractClassId::from_field(0x789))); + assert(initialization_hash.unwrap() == 0x101112); } /************************************************************************ @@ -539,7 +531,7 @@ 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"); - test_get_contract_instance(); + test_get_contract_instance(context.this_address()); dep::aztec::oracle::debug_log::debug_log("get_address"); let _ = get_address(); dep::aztec::oracle::debug_log::debug_log("get_storage_address"); diff --git a/yarn-project/bb-prover/src/avm_proving.test.ts b/yarn-project/bb-prover/src/avm_proving.test.ts index 3b3930aaa50..5a37c8701d5 100644 --- a/yarn-project/bb-prover/src/avm_proving.test.ts +++ b/yarn-project/bb-prover/src/avm_proving.test.ts @@ -65,6 +65,7 @@ const proveAndVerifyAvmTestContract = async ( const environment = initExecutionEnvironment({ functionSelector, calldata, globals }); const worldStateDB = mock(); + // The values here should match those in `avm_simulator.test.ts` const contractInstance = new SerializableContractInstance({ version: 1, salt: new Fr(0x123), diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index e2a8c481fd6..04fe21457c3 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -95,7 +95,7 @@ describe('e2e_avm_simulator', () => { describe('Contract instance', () => { it('Works', async () => { - const tx = await avmContract.methods.test_get_contract_instance().send().wait(); + const tx = await avmContract.methods.test_get_contract_instance(AztecAddress.fromBigInt(9090n)).send().wait(); expect(tx.status).toEqual(TxStatus.SUCCESS); }); }); diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index a4f36f95f3f..921a6979d05 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -1,4 +1,4 @@ -import { GasFees, PublicKeys } from '@aztec/circuits.js'; +import { GasFees, PublicKeys, SerializableContractInstance } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { computeVarArgsHash } from '@aztec/circuits.js/hash'; import { FunctionSelector } from '@aztec/foundation/abi'; @@ -830,10 +830,10 @@ describe('AVM simulator: transpiled Noir contracts', () => { describe('Contract Instance Retrieval', () => { it(`Can getContractInstance`, async () => { - const context = createContext(); + const calldata = [address]; + const context = createContext(calldata); // Contract instance must match noir - const contractInstance = { - address: AztecAddress.random(), + const contractInstance = new SerializableContractInstance({ version: 1 as const, salt: new Fr(0x123), deployer: AztecAddress.fromBigInt(0x456n), @@ -845,16 +845,16 @@ describe('AVM simulator: transpiled Noir contracts', () => { new Point(new Fr(0x252627), new Fr(0x282930), false), new Point(new Fr(0x313233), new Fr(0x343536), false), ), - }; - mockGetContractInstance(worldStateDB, contractInstance); + }); + mockGetContractInstance(worldStateDB, contractInstance.withAddress(address)); - const bytecode = getAvmTestContractBytecode('test_get_contract_instance_raw'); + const bytecode = getAvmTestContractBytecode('test_get_contract_instance'); const results = await new AvmSimulator(context).executeBytecode(bytecode); expect(results.reverted).toBe(false); - expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(1); - expect(trace.traceGetContractInstance).toHaveBeenCalledWith({ exists: true, ...contractInstance }); + expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(3); // called for each enum value + expect(trace.traceGetContractInstance).toHaveBeenCalledWith(address, /*exists=*/ true, contractInstance); }); }); diff --git a/yarn-project/simulator/src/avm/journal/journal.test.ts b/yarn-project/simulator/src/avm/journal/journal.test.ts index e0306adb81f..4247156a506 100644 --- a/yarn-project/simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/simulator/src/avm/journal/journal.test.ts @@ -1,4 +1,3 @@ -import { randomContractInstanceWithAddress } from '@aztec/circuit-types'; import { SerializableContractInstance } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; @@ -147,18 +146,18 @@ describe('journal', () => { describe('Getting contract instances', () => { it('Should get contract instance', async () => { - const contractInstance = randomContractInstanceWithAddress(/*(base instance) opts=*/ {}, /*address=*/ address); - mockGetContractInstance(worldStateDB, contractInstance); + const contractInstance = SerializableContractInstance.empty(); + mockGetContractInstance(worldStateDB, contractInstance.withAddress(address)); await persistableState.getContractInstance(address); expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(1); - expect(trace.traceGetContractInstance).toHaveBeenCalledWith({ exists: true, ...contractInstance }); + expect(trace.traceGetContractInstance).toHaveBeenCalledWith(address, /*exists=*/ true, contractInstance); }); it('Can get undefined contract instance', async () => { - const emptyContractInstance = SerializableContractInstance.empty().withAddress(address); + const emptyContractInstance = SerializableContractInstance.empty(); await persistableState.getContractInstance(address); expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(1); - expect(trace.traceGetContractInstance).toHaveBeenCalledWith({ exists: false, ...emptyContractInstance }); + expect(trace.traceGetContractInstance).toHaveBeenCalledWith(address, /*exists=*/ false, emptyContractInstance); }); }); diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 81ea9759ac3..eecf5290433 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -4,7 +4,6 @@ import { createDebugLogger } from '@aztec/foundation/log'; import { getPublicFunctionDebugName } from '../../common/debug_fn_name.js'; import { type WorldStateDB } from '../../public/public_db_sources.js'; -import { type TracedContractInstance } from '../../public/side_effect_trace.js'; import { type PublicSideEffectTraceInterface } from '../../public/side_effect_trace_interface.js'; import { type AvmContractCallResult } from '../avm_contract_call_result.js'; import { type AvmExecutionEnvironment } from '../avm_execution_environment.js'; @@ -212,20 +211,22 @@ export class AvmPersistableStateManager { * @param contractAddress - address of the contract instance to retrieve. * @returns the contract instance with an "exists" flag */ - public async getContractInstance(contractAddress: Fr): Promise { - let exists = true; + public async getContractInstance(contractAddress: Fr): Promise<[boolean, SerializableContractInstance]> { const aztecAddress = AztecAddress.fromField(contractAddress); - let instance = await this.worldStateDB.getContractInstance(aztecAddress); - if (instance === undefined) { - instance = SerializableContractInstance.empty().withAddress(aztecAddress); - exists = false; + const instanceWithAddress = await this.worldStateDB.getContractInstance(aztecAddress); + + let exists = false; + let instance = SerializableContractInstance.empty(); + if (instanceWithAddress !== undefined) { + exists = true; + instance = new SerializableContractInstance(instanceWithAddress); } + this.log.debug( `Get Contract instance (address=${contractAddress}): exists=${exists}, instance=${JSON.stringify(instance)}`, ); - const tracedInstance = { ...instance, exists }; - this.trace.traceGetContractInstance(tracedInstance); - return Promise.resolve(tracedInstance); + this.trace.traceGetContractInstance(contractAddress, exists, instance); + return Promise.resolve([exists, instance]); } /** diff --git a/yarn-project/simulator/src/avm/opcodes/contract.test.ts b/yarn-project/simulator/src/avm/opcodes/contract.test.ts index b5d25696006..d62ed79fdbc 100644 --- a/yarn-project/simulator/src/avm/opcodes/contract.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/contract.test.ts @@ -1,19 +1,23 @@ import { randomContractInstanceWithAddress } from '@aztec/circuit-types'; -import { AztecAddress, SerializableContractInstance } from '@aztec/circuits.js'; +import { AztecAddress, Fr, SerializableContractInstance } from '@aztec/circuits.js'; import { mock } from 'jest-mock-extended'; import { type WorldStateDB } from '../../public/public_db_sources.js'; import { type PublicSideEffectTraceInterface } from '../../public/side_effect_trace_interface.js'; import { type AvmContext } from '../avm_context.js'; -import { Field } from '../avm_memory_types.js'; +import { Field, TypeTag, Uint1 } from '../avm_memory_types.js'; import { initContext, initPersistableStateManager } from '../fixtures/index.js'; import { type AvmPersistableStateManager } from '../journal/journal.js'; import { mockGetContractInstance } from '../test_utils.js'; -import { GetContractInstance } from './contract.js'; +import { ContractInstanceMember, GetContractInstance } from './contract.js'; describe('Contract opcodes', () => { - const address = AztecAddress.random(); + const address = Fr.random(); + const contractInstance = SerializableContractInstance.random(); + const deployer = contractInstance.deployer; + const contractClassId = contractInstance.contractClassId; + const initializationHash = contractInstance.initializationHash; let worldStateDB: WorldStateDB; let trace: PublicSideEffectTraceInterface; @@ -32,59 +36,73 @@ describe('Contract opcodes', () => { const buf = Buffer.from([ GetContractInstance.opcode, // opcode 0x01, // indirect - ...Buffer.from('12345678', 'hex'), // addressOffset - ...Buffer.from('a2345678', 'hex'), // dstOffset + 0x02, // memberEnum (immediate) + ...Buffer.from('1234', 'hex'), // addressOffset + ...Buffer.from('a234', 'hex'), // dstOffset + ...Buffer.from('b234', 'hex'), // existsOffset ]); const inst = new GetContractInstance( /*indirect=*/ 0x01, - /*addressOffset=*/ 0x12345678, - /*dstOffset=*/ 0xa2345678, + /*memberEnum=*/ 0x02, + /*addressOffset=*/ 0x1234, + /*dstOffset=*/ 0xa234, + /*existsOffset=*/ 0xb234, ); expect(GetContractInstance.deserialize(buf)).toEqual(inst); expect(inst.serialize()).toEqual(buf); }); - it('should copy contract instance to memory if found', async () => { - const contractInstance = randomContractInstanceWithAddress(/*(base instance) opts=*/ {}, /*address=*/ address); - mockGetContractInstance(worldStateDB, contractInstance); - - context.machineState.memory.set(0, new Field(address.toField())); - await new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1).execute(context); - - const actual = context.machineState.memory.getSlice(1, 17); - - expect(actual).toEqual([ - new Field(1), // found - new Field(contractInstance.salt), - new Field(contractInstance.deployer), - new Field(contractInstance.contractClassId), - new Field(contractInstance.initializationHash), - ...contractInstance.publicKeys.toFields().map(f => new Field(f)), - ]); - - expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(1); - expect(trace.traceGetContractInstance).toHaveBeenCalledWith({ exists: true, ...contractInstance }); + describe.each([ + [ContractInstanceMember.DEPLOYER, deployer.toField()], + [ContractInstanceMember.CLASS_ID, contractClassId.toField()], + [ContractInstanceMember.INIT_HASH, initializationHash.toField()], + ])('GETCONTRACTINSTANCE member instruction ', (memberEnum: ContractInstanceMember, value: Fr) => { + it(`Should read '${ContractInstanceMember[memberEnum]}' correctly`, async () => { + mockGetContractInstance(worldStateDB, contractInstance.withAddress(address)); + + context.machineState.memory.set(0, new Field(address)); + await new GetContractInstance(/*indirect=*/ 0, memberEnum, /*addressOffset=*/ 0, /*dstOffset=*/ 1, /*existsOffset=*/ 2).execute(context); + + // value should be right + expect(context.machineState.memory.getTag(1)).toBe(TypeTag.FIELD); + const actual = context.machineState.memory.get(1); + expect(actual).toEqual(new Field(value)); + + // exists should be true + expect(context.machineState.memory.getTag(2)).toBe(TypeTag.UINT1); + const exists = context.machineState.memory.get(2); + expect(exists).toEqual(new Uint1(1)); + + expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(1); + expect(trace.traceGetContractInstance).toHaveBeenCalledWith(address, /*exists=*/ true, contractInstance); + }); }); - - it('should return zeroes if not found', async () => { - const emptyContractInstance = SerializableContractInstance.empty().withAddress(address); - context.machineState.memory.set(0, new Field(address.toField())); - - await new GetContractInstance(/*indirect=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1).execute(context); - - const actual = context.machineState.memory.getSlice(1, 6); - expect(actual).toEqual([ - new Field(0), // found - new Field(0), - new Field(0), - new Field(0), - new Field(0), - new Field(0), - ]); - - expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(1); - expect(trace.traceGetContractInstance).toHaveBeenCalledWith({ exists: false, ...emptyContractInstance }); + + describe.each([ + [ContractInstanceMember.DEPLOYER], + [ContractInstanceMember.CLASS_ID], + [ContractInstanceMember.INIT_HASH], + ])('GETCONTRACTINSTANCE member instruction works when contract does not exist', (memberEnum: ContractInstanceMember) => { + it(`'${ContractInstanceMember[memberEnum]}' should be 0 when contract does not exist `, async () => { + const emptyContractInstance = SerializableContractInstance.empty(); + + context.machineState.memory.set(0, new Field(address)); + await new GetContractInstance(/*indirect=*/ 0, memberEnum, /*addressOffset=*/ 0, /*dstOffset=*/ 1, /*existsOffset=*/ 2).execute(context); + + // value should be 0 + expect(context.machineState.memory.getTag(1)).toBe(TypeTag.FIELD); + const actual = context.machineState.memory.get(1); + expect(actual).toEqual(new Field(0)); + + // exists should be false + expect(context.machineState.memory.getTag(2)).toBe(TypeTag.UINT1); + const exists = context.machineState.memory.get(2); + expect(exists).toEqual(new Uint1(0)); + + expect(trace.traceGetContractInstance).toHaveBeenCalledTimes(1); + expect(trace.traceGetContractInstance).toHaveBeenCalledWith(address, /*exists=*/ false, emptyContractInstance); + }); }); }); }); diff --git a/yarn-project/simulator/src/avm/opcodes/contract.ts b/yarn-project/simulator/src/avm/opcodes/contract.ts index 4ee050e1c9e..ba7ac4fd72c 100644 --- a/yarn-project/simulator/src/avm/opcodes/contract.ts +++ b/yarn-project/simulator/src/avm/opcodes/contract.ts @@ -1,23 +1,29 @@ -import { Fr } from '@aztec/circuits.js'; - import type { AvmContext } from '../avm_context.js'; -import { Field, TypeTag } from '../avm_memory_types.js'; +import { Field, TypeTag, Uint1 } from '../avm_memory_types.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; +export enum ContractInstanceMember { + DEPLOYER, + CLASS_ID, + INIT_HASH, +} + export class GetContractInstance extends Instruction { static readonly type: string = 'GETCONTRACTINSTANCE'; static readonly opcode: Opcode = Opcode.GETCONTRACTINSTANCE; // Informs (de)serialization. See Instruction.deserialize. static readonly wireFormat: OperandType[] = [ - OperandType.UINT8, - OperandType.UINT8, - OperandType.UINT32, - OperandType.UINT32, + OperandType.UINT8, // opcode + OperandType.UINT8, // indirect bits + OperandType.UINT8, // member enum (immediate) + OperandType.UINT16, // addressOffset + OperandType.UINT16, // dstOffset + OperandType.UINT16, // existsOfsset ]; - constructor(private indirect: number, private addressOffset: number, private dstOffset: number) { + constructor(private indirect: number, private memberEnum: ContractInstanceMember, private addressOffset: number, private dstOffset: number, private existsOffset: number) { super(); } @@ -25,27 +31,25 @@ export class GetContractInstance extends Instruction { const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost()); - const operands = [this.addressOffset, this.dstOffset]; + const operands = [this.addressOffset, this.dstOffset, this.existsOffset]; const addressing = Addressing.fromWire(this.indirect, operands.length); - const [addressOffset, dstOffset] = addressing.resolve(operands, memory); + const [addressOffset, dstOffset, existsOffset] = addressing.resolve(operands, memory); memory.checkTag(TypeTag.FIELD, addressOffset); const address = memory.get(addressOffset).toFr(); - const instance = await context.persistableState.getContractInstance(address); + const [exists, instance] = await context.persistableState.getContractInstance(address); - const data = [ - new Fr(instance.exists), - instance.salt, - instance.deployer.toField(), - instance.contractClassId, - instance.initializationHash, - // This this okay ? - ...instance.publicKeys.toFields(), - ].map(f => new Field(f)); + const memberValue = this.memberEnum === ContractInstanceMember.DEPLOYER + ? instance.deployer.toField() + : this.memberEnum === ContractInstanceMember.CLASS_ID + ? instance.contractClassId + : instance.initializationHash; // memberEnum === ContractInstanceMember.INIT_HASH - memory.setSlice(dstOffset, data); + memory.set(existsOffset, new Uint1(exists ? 1 : 0)); + memory.set(dstOffset, exists ? new Field(memberValue) : new Field(0)); + // TODO(dbanks12): anything left to do here for tags? - memory.assert({ reads: 1, writes: 17, addressing }); + memory.assert({ reads: 1, writes: 2, addressing }); context.machineState.incrementPc(); } } diff --git a/yarn-project/simulator/src/public/dual_side_effect_trace.ts b/yarn-project/simulator/src/public/dual_side_effect_trace.ts index 483f0f188ff..4412a8d0d2b 100644 --- a/yarn-project/simulator/src/public/dual_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/dual_side_effect_trace.ts @@ -1,7 +1,7 @@ import { type CombinedConstantData, - type ContractInstanceWithAddress, type Gas, + type SerializableContractInstance, type VMCircuitPublicInputs, } from '@aztec/circuits.js'; import { type Fr } from '@aztec/foundation/fields'; @@ -15,8 +15,6 @@ import { type PublicExecutionResult } from './execution.js'; import { type PublicSideEffectTrace } from './side_effect_trace.js'; import { type PublicSideEffectTraceInterface } from './side_effect_trace_interface.js'; -export type TracedContractInstance = { exists: boolean } & ContractInstanceWithAddress; - export class DualSideEffectTrace implements PublicSideEffectTraceInterface { constructor( public readonly innerCallTrace: PublicSideEffectTrace, @@ -78,9 +76,9 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { this.enqueuedCallTrace.traceUnencryptedLog(contractAddress, log); } - public traceGetContractInstance(instance: TracedContractInstance) { - this.innerCallTrace.traceGetContractInstance(instance); - this.enqueuedCallTrace.traceGetContractInstance(instance); + public traceGetContractInstance(contractAddress: Fr, exists: boolean, instance: SerializableContractInstance) { + this.innerCallTrace.traceGetContractInstance(contractAddress, exists, instance); + this.enqueuedCallTrace.traceGetContractInstance(contractAddress, exists, instance); } /** diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts index 877543e77c8..5233f425f12 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts @@ -34,15 +34,9 @@ import { randomBytes, randomInt } from 'crypto'; import { AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { initExecutionEnvironment } from '../avm/fixtures/index.js'; -import { PublicEnqueuedCallSideEffectTrace, type TracedContractInstance } from './enqueued_call_side_effect_trace.js'; +import { PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; import { SideEffectLimitReachedError } from './side_effect_errors.js'; -function randomTracedContractInstance(): TracedContractInstance { - const instance = SerializableContractInstance.random(); - const address = AztecAddress.random(); - return { exists: true, ...instance, address }; -} - describe('Enqueued-call Side Effect Trace', () => { const address = Fr.random(); const utxo = Fr.random(); @@ -52,7 +46,7 @@ describe('Enqueued-call Side Effect Trace', () => { const recipient = Fr.random(); const content = Fr.random(); const log = [Fr.random(), Fr.random(), Fr.random()]; - const contractInstance = SerializableContractInstance.empty().withAddress(new Fr(42)); + const contractInstance = SerializableContractInstance.empty(); const startGasLeft = Gas.fromFields([new Fr(randomInt(10000)), new Fr(randomInt(10000))]); const endGasLeft = Gas.fromFields([new Fr(randomInt(10000)), new Fr(randomInt(10000))]); @@ -234,18 +228,19 @@ describe('Enqueued-call Side Effect Trace', () => { }); it('Should trace get contract instance', () => { - const instance = randomTracedContractInstance(); + const instance = SerializableContractInstance.random(); const { version: _, ...instanceWithoutVersion } = instance; - trace.traceGetContractInstance(instance); + const exists = true; + trace.traceGetContractInstance(address, exists, instance); expect(trace.getCounter()).toBe(startCounterPlus1); //const circuitPublicInputs = toVMCircuitPublicInputs(trace); - // TODO(dbanks12): once this emits nullifier read, check here expect(trace.getAvmCircuitHints().contractInstances.items).toEqual([ { // hint omits "version" and has "exists" as an Fr + address, + exists: new Fr(exists), ...instanceWithoutVersion, - exists: new Fr(instance.exists), }, ]); }); @@ -348,11 +343,11 @@ describe('Enqueued-call Side Effect Trace', () => { for (let i = 0; i < MAX_NULLIFIER_READ_REQUESTS_PER_TX; i++) { trace.traceNullifierCheck(new Fr(i), new Fr(i), new Fr(i), true, true); } - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: true })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ true, contractInstance)).toThrow( SideEffectLimitReachedError, ); // NOTE: also cannot do a existent check once non-existent checks have filled up - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: false })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ false, contractInstance)).toThrow( SideEffectLimitReachedError, ); }); @@ -361,11 +356,11 @@ describe('Enqueued-call Side Effect Trace', () => { for (let i = 0; i < MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX; i++) { trace.traceNullifierCheck(new Fr(i), new Fr(i), new Fr(i), false, true); } - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: false })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ false, contractInstance)).toThrow( SideEffectLimitReachedError, ); // NOTE: also cannot do a existent check once non-existent checks have filled up - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: true })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ true, contractInstance)).toThrow( SideEffectLimitReachedError, ); }); @@ -417,10 +412,10 @@ describe('Enqueued-call Side Effect Trace', () => { expect(() => trace.traceUnencryptedLog(new Fr(42), [new Fr(42), new Fr(42)])).toThrow( SideEffectLimitReachedError, ); - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: false })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ false, contractInstance)).toThrow( SideEffectLimitReachedError, ); - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: true })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ true, contractInstance)).toThrow( SideEffectLimitReachedError, ); }); @@ -454,9 +449,9 @@ describe('Enqueued-call Side Effect Trace', () => { testCounter++; nestedTrace.traceUnencryptedLog(address, log); testCounter++; - nestedTrace.traceGetContractInstance({ ...contractInstance, exists: true }); + nestedTrace.traceGetContractInstance(address, /*exists=*/ true, contractInstance); testCounter++; - nestedTrace.traceGetContractInstance({ ...contractInstance, exists: false }); + nestedTrace.traceGetContractInstance(address, /*exists=*/ false, contractInstance); testCounter++; trace.traceNestedCall(nestedTrace, avmEnvironment, startGasLeft, endGasLeft, bytecode, callResults); diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts index 547b719c93f..c7eec8736e0 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts @@ -7,7 +7,6 @@ import { AztecAddress, CallContext, type CombinedConstantData, - type ContractInstanceWithAddress, ContractStorageRead, ContractStorageUpdateRequest, EthAddress, @@ -44,6 +43,7 @@ import { ScopedNoteHash, type ScopedNullifier, ScopedReadRequest, + type SerializableContractInstance, TreeLeafReadRequest, VMCircuitPublicInputs, } from '@aztec/circuits.js'; @@ -58,8 +58,6 @@ import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.j import { SideEffectLimitReachedError } from './side_effect_errors.js'; import { type PublicSideEffectTraceInterface } from './side_effect_trace_interface.js'; -export type TracedContractInstance = { exists: boolean } & ContractInstanceWithAddress; - /** * A struct containing just the side effects as regular arrays * as opposed to "Tuple" arrays used by circuit public inputs. @@ -307,14 +305,13 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI this.incrementSideEffectCounter(); } - public traceGetContractInstance(instance: TracedContractInstance) { + public traceGetContractInstance(contractAddress: Fr, exists: boolean, instance: SerializableContractInstance) { this.enforceLimitOnNullifierChecks('(contract address nullifier from GETCONTRACTINSTANCE)'); - // TODO(dbanks12): should emit a nullifier read request this.avmCircuitHints.contractInstances.items.push( new AvmContractInstanceHint( - instance.address, - new Fr(instance.exists ? 1 : 0), + contractAddress, + new Fr(exists), instance.salt, instance.deployer, instance.contractClassId, diff --git a/yarn-project/simulator/src/public/side_effect_trace.test.ts b/yarn-project/simulator/src/public/side_effect_trace.test.ts index 4afd862e698..feddf1585f2 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.test.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.test.ts @@ -23,13 +23,7 @@ import { randomBytes, randomInt } from 'crypto'; import { AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { initExecutionEnvironment } from '../avm/fixtures/index.js'; import { SideEffectLimitReachedError } from './side_effect_errors.js'; -import { PublicSideEffectTrace, type TracedContractInstance } from './side_effect_trace.js'; - -function randomTracedContractInstance(): TracedContractInstance { - const instance = SerializableContractInstance.random(); - const address = AztecAddress.random(); - return { exists: true, ...instance, address }; -} +import { PublicSideEffectTrace } from './side_effect_trace.js'; describe('Side Effect Trace', () => { const address = Fr.random(); @@ -40,7 +34,7 @@ describe('Side Effect Trace', () => { const recipient = Fr.random(); const content = Fr.random(); const log = [Fr.random(), Fr.random(), Fr.random()]; - const contractInstance = SerializableContractInstance.empty().withAddress(new Fr(42)); + const contractInstance = SerializableContractInstance.empty(); const startGasLeft = Gas.fromFields([new Fr(randomInt(10000)), new Fr(randomInt(10000))]); const endGasLeft = Gas.fromFields([new Fr(randomInt(10000)), new Fr(randomInt(10000))]); @@ -232,18 +226,19 @@ describe('Side Effect Trace', () => { }); it('Should trace get contract instance', () => { - const instance = randomTracedContractInstance(); + const instance = SerializableContractInstance.random(); const { version: _, ...instanceWithoutVersion } = instance; - trace.traceGetContractInstance(instance); + const exists = true; + trace.traceGetContractInstance(address, exists, instance); expect(trace.getCounter()).toBe(startCounterPlus1); const pxResult = toPxResult(trace); - // TODO(dbanks12): once this emits nullifier read, check here expect(pxResult.avmCircuitHints.contractInstances.items).toEqual([ { // hint omits "version" and has "exists" as an Fr + address, + exists: new Fr(exists), ...instanceWithoutVersion, - exists: new Fr(instance.exists), }, ]); }); @@ -346,11 +341,11 @@ describe('Side Effect Trace', () => { for (let i = 0; i < MAX_NULLIFIER_READ_REQUESTS_PER_TX; i++) { trace.traceNullifierCheck(new Fr(i), new Fr(i), new Fr(i), true, true); } - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: true })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ true, contractInstance)).toThrow( SideEffectLimitReachedError, ); // NOTE: also cannot do a existent check once non-existent checks have filled up - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: false })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ false, contractInstance)).toThrow( SideEffectLimitReachedError, ); }); @@ -359,11 +354,11 @@ describe('Side Effect Trace', () => { for (let i = 0; i < MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX; i++) { trace.traceNullifierCheck(new Fr(i), new Fr(i), new Fr(i), false, true); } - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: false })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ false, contractInstance)).toThrow( SideEffectLimitReachedError, ); // NOTE: also cannot do a existent check once non-existent checks have filled up - expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: true })).toThrow( + expect(() => trace.traceGetContractInstance(address, /*exists=*/ true, contractInstance)).toThrow( SideEffectLimitReachedError, ); }); @@ -396,9 +391,9 @@ describe('Side Effect Trace', () => { testCounter++; nestedTrace.traceUnencryptedLog(address, log); testCounter++; - nestedTrace.traceGetContractInstance({ ...contractInstance, exists: true }); + nestedTrace.traceGetContractInstance(address, /*exists=*/ true, contractInstance); testCounter++; - nestedTrace.traceGetContractInstance({ ...contractInstance, exists: false }); + nestedTrace.traceGetContractInstance(address, /*exists=*/ false, contractInstance); testCounter++; trace.traceNestedCall(nestedTrace, avmEnvironment, startGasLeft, endGasLeft, bytecode, avmCallResults); diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index f023e09adbe..35ec7475a2b 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -27,6 +27,7 @@ import { Nullifier, type PublicInnerCallRequest, ReadRequest, + type SerializableContractInstance, TreeLeafReadRequest, } from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; @@ -210,14 +211,13 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { this.incrementSideEffectCounter(); } - public traceGetContractInstance(instance: TracedContractInstance) { + public traceGetContractInstance(contractAddress: Fr, exists: boolean, instance: SerializableContractInstance) { this.enforceLimitOnNullifierChecks('(contract address nullifier from GETCONTRACTINSTANCE)'); - // TODO(dbanks12): should emit a nullifier read request this.avmCircuitHints.contractInstances.items.push( new AvmContractInstanceHint( - instance.address, - new Fr(instance.exists ? 1 : 0), + contractAddress, + new Fr(exists), instance.salt, instance.deployer, instance.contractClassId, diff --git a/yarn-project/simulator/src/public/side_effect_trace_interface.ts b/yarn-project/simulator/src/public/side_effect_trace_interface.ts index 2d229d7f7b4..41fbd593296 100644 --- a/yarn-project/simulator/src/public/side_effect_trace_interface.ts +++ b/yarn-project/simulator/src/public/side_effect_trace_interface.ts @@ -1,9 +1,8 @@ -import { type Gas } from '@aztec/circuits.js'; +import { type SerializableContractInstance, type Gas } from '@aztec/circuits.js'; import { type Fr } from '@aztec/foundation/fields'; import { type AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; -import { type TracedContractInstance } from './side_effect_trace.js'; export interface PublicSideEffectTraceInterface { fork(): PublicSideEffectTraceInterface; @@ -18,8 +17,7 @@ export interface PublicSideEffectTraceInterface { traceL1ToL2MessageCheck(contractAddress: Fr, msgHash: Fr, msgLeafIndex: Fr, exists: boolean): void; traceNewL2ToL1Message(contractAddress: Fr, recipient: Fr, content: Fr): void; traceUnencryptedLog(contractAddress: Fr, log: Fr[]): void; - // TODO(dbanks12): odd that getContractInstance is a one-off in that it accepts an entire object instead of components - traceGetContractInstance(instance: TracedContractInstance): void; + traceGetContractInstance(contractAddress: Fr, exists: boolean, instance: SerializableContractInstance): void; traceNestedCall( /** The trace of the nested call. */ nestedCallTrace: PublicSideEffectTraceInterface, diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 63fad7601ae..30ee495e550 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -514,17 +514,36 @@ export class TXEService { ]); } - async avmOpcodeGetContractInstance(address: ForeignCallSingle) { + // TODO(dbanks12): fix + async avmOpcodeGetContractInstanceDeployer(address: ForeignCallSingle) { const instance = await this.typedOracle.getContractInstance(fromSingle(address)); return toForeignCallResult([ toArray([ + instance.deployer, // AVM requires an extra boolean indicating the instance was found new Fr(1), - instance.salt, - instance.deployer, + ]), + ]); + } + + async avmOpcodeGetContractInstanceClassId(address: ForeignCallSingle) { + const instance = await this.typedOracle.getContractInstance(fromSingle(address)); + return toForeignCallResult([ + toArray([ instance.contractClassId, + // AVM requires an extra boolean indicating the instance was found + new Fr(1), + ]), + ]); + } + + async avmOpcodeGetContractInstanceInitializationHash(address: ForeignCallSingle) { + const instance = await this.typedOracle.getContractInstance(fromSingle(address)); + return toForeignCallResult([ + toArray([ instance.initializationHash, - ...instance.publicKeys.toFields(), + // AVM requires an extra boolean indicating the instance was found + new Fr(1), ]), ]); }