Skip to content

Commit

Permalink
feat!: getcontractinstance instruction returns only a specified member
Browse files Browse the repository at this point in the history
  • Loading branch information
dbanks12 committed Oct 23, 2024
1 parent cb58490 commit 76e88dc
Show file tree
Hide file tree
Showing 25 changed files with 464 additions and 296 deletions.
45 changes: 35 additions & 10 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
}
Expand Down Expand Up @@ -1284,35 +1286,58 @@ fn handle_storage_write(
/// Emit a GETCONTRACTINSTANCE opcode
fn handle_get_contract_instance(
avm_instrs: &mut Vec<AvmInstruction>,
function: &str,
destinations: &Vec<ValueOrArray>,
inputs: &Vec<ValueOrArray>,
) {
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 {
opcode: AvmOpcode::GETCONTRACTINSTANCE,
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()
});
Expand Down
91 changes: 52 additions & 39 deletions barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2055,43 +2055,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<FF> calldata = { address };
std::vector<FF> 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
Expand All @@ -2103,14 +2074,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<uint8_t>(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<uint8_t>(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<uint8_t>(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<FF> const calldata{};
// alternating member value, exists bool
std::vector<FF> const expected_returndata = {
deployer, 1, class_id, 1, init_hash, 1,
};

std::vector<FF> returndata{};
auto trace = Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec, execution_hints);
EXPECT_EQ(returndata, std::vector<FF>({ 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ const std::unordered_map<OpCode, std::vector<OperandType>> 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,
Expand Down
6 changes: 4 additions & 2 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,10 @@ std::vector<Row> Execution::gen_trace(std::vector<Instruction> const& instructio
break;
case OpCode::GETCONTRACTINSTANCE:
trace_builder.op_get_contract_instance(std::get<uint8_t>(inst.operands.at(0)),
std::get<uint32_t>(inst.operands.at(1)),
std::get<uint32_t>(inst.operands.at(2)));
std::get<uint8_t>(inst.operands.at(1)),
std::get<uint16_t>(inst.operands.at(2)),
std::get<uint16_t>(inst.operands.at(3)),
std::get<uint16_t>(inst.operands.at(4)));
break;

// Accrued Substate
Expand Down
8 changes: 8 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,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);
Expand Down
54 changes: 40 additions & 14 deletions barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2357,45 +2357,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<int>(ContractInstanceMember::MAX_MEMBER));
ContractInstanceMember chosen_member = static_cast<ContractInstanceMember>(member_enum);

auto clk = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(read_address.is_indirect)),
//.main_sel_resolve_ind_addr_c = FF(static_cast<uint32_t>(write_dst.is_indirect)),
//.main_sel_resolve_ind_addr_d = FF(static_cast<uint32_t>(write_exists.is_indirect)),
.main_tag_err = FF(static_cast<uint32_t>(!tag_match)),
});

// Read the contract instance
ContractInstanceHint contract_instance = execution_hints.contract_instance_hints.at(read_address.val);
std::vector<FF> 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<FF> 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++;
Expand Down
3 changes: 2 additions & 1 deletion barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,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);
Expand Down
8 changes: 0 additions & 8 deletions noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,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()
}
Expand Down Expand Up @@ -317,10 +313,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 {}

Expand Down
Loading

0 comments on commit 76e88dc

Please sign in to comment.