Skip to content

Commit 29b692f

Browse files
authored
feat!: getcontractinstance instruction returns only a specified member (#9300)
`GETCONTRACTINSTANCE` now takes member enum as immediate operand and writes/returns a single field from the contract instance. Also writes/returns a u1/bool for "exists". Changed the trace to accept (separately) address, exists, contractInstance since the trace generally operates on lower-level types, not structs. Noir has a different oracle for each enum value (similar to the `GETENV` variations).
1 parent 493a3f3 commit 29b692f

30 files changed

+844
-475
lines changed

avm-transpiler/src/transpile.rs

+34-10
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,6 @@ fn handle_foreign_call(
395395
"avmOpcodeNullifierExists" => handle_nullifier_exists(avm_instrs, destinations, inputs),
396396
"avmOpcodeL1ToL2MsgExists" => handle_l1_to_l2_msg_exists(avm_instrs, destinations, inputs),
397397
"avmOpcodeSendL2ToL1Msg" => handle_send_l2_to_l1_msg(avm_instrs, destinations, inputs),
398-
"avmOpcodeGetContractInstance" => {
399-
handle_get_contract_instance(avm_instrs, destinations, inputs);
400-
}
401398
"avmOpcodeCalldataCopy" => handle_calldata_copy(avm_instrs, destinations, inputs),
402399
"avmOpcodeReturn" => handle_return(avm_instrs, destinations, inputs),
403400
"avmOpcodeRevert" => handle_revert(avm_instrs, destinations, inputs),
@@ -408,6 +405,10 @@ fn handle_foreign_call(
408405
_ if inputs.is_empty() && destinations.len() == 1 => {
409406
handle_getter_instruction(avm_instrs, function, destinations, inputs);
410407
}
408+
// Get contract instance variations.
409+
_ if function.starts_with("avmOpcodeGetContractInstance") => {
410+
handle_get_contract_instance(avm_instrs, function, destinations, inputs);
411+
}
411412
// Anything else.
412413
_ => panic!("Transpiler doesn't know how to process ForeignCall function {}", function),
413414
}
@@ -1304,35 +1305,58 @@ fn handle_storage_write(
13041305
/// Emit a GETCONTRACTINSTANCE opcode
13051306
fn handle_get_contract_instance(
13061307
avm_instrs: &mut Vec<AvmInstruction>,
1308+
function: &str,
13071309
destinations: &Vec<ValueOrArray>,
13081310
inputs: &Vec<ValueOrArray>,
13091311
) {
1312+
enum ContractInstanceMember {
1313+
DEPLOYER,
1314+
CLASS_ID,
1315+
INIT_HASH,
1316+
}
1317+
13101318
assert!(inputs.len() == 1);
1311-
assert!(destinations.len() == 1);
1319+
assert!(destinations.len() == 2);
1320+
1321+
let member_idx = match function {
1322+
"avmOpcodeGetContractInstanceDeployer" => ContractInstanceMember::DEPLOYER,
1323+
"avmOpcodeGetContractInstanceClassId" => ContractInstanceMember::CLASS_ID,
1324+
"avmOpcodeGetContractInstanceInitializationHash" => ContractInstanceMember::INIT_HASH,
1325+
_ => panic!("Transpiler doesn't know how to process function {:?}", function),
1326+
};
13121327

13131328
let address_offset_maybe = inputs[0];
13141329
let address_offset = match address_offset_maybe {
1315-
ValueOrArray::MemoryAddress(slot_offset) => slot_offset,
1330+
ValueOrArray::MemoryAddress(offset) => offset,
13161331
_ => panic!("GETCONTRACTINSTANCE address should be a single value"),
13171332
};
13181333

13191334
let dest_offset_maybe = destinations[0];
13201335
let dest_offset = match dest_offset_maybe {
1321-
ValueOrArray::HeapArray(HeapArray { pointer, .. }) => pointer,
1322-
_ => panic!("GETCONTRACTINSTANCE destination should be an array"),
1336+
ValueOrArray::MemoryAddress(offset) => offset,
1337+
_ => panic!("GETCONTRACTINSTANCE dst destination should be a single value"),
1338+
};
1339+
1340+
let exists_offset_maybe = destinations[1];
1341+
let exists_offset = match exists_offset_maybe {
1342+
ValueOrArray::MemoryAddress(offset) => offset,
1343+
_ => panic!("GETCONTRACTINSTANCE exists destination should be a single value"),
13231344
};
13241345

13251346
avm_instrs.push(AvmInstruction {
13261347
opcode: AvmOpcode::GETCONTRACTINSTANCE,
13271348
indirect: Some(
13281349
AddressingModeBuilder::default()
13291350
.direct_operand(&address_offset)
1330-
.indirect_operand(&dest_offset)
1351+
.direct_operand(&dest_offset)
1352+
.direct_operand(&exists_offset)
13311353
.build(),
13321354
),
13331355
operands: vec![
1334-
AvmOperand::U32 { value: address_offset.to_usize() as u32 },
1335-
AvmOperand::U32 { value: dest_offset.to_usize() as u32 },
1356+
AvmOperand::U8 { value: member_idx as u8 },
1357+
AvmOperand::U16 { value: address_offset.to_usize() as u16 },
1358+
AvmOperand::U16 { value: dest_offset.to_usize() as u16 },
1359+
AvmOperand::U16 { value: exists_offset.to_usize() as u16 },
13361360
],
13371361
..Default::default()
13381362
});

barretenberg/cpp/pil/avm/main.pil

+1-2
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,8 @@ namespace main(256);
351351
// op_err * (sel_op_fdiv + sel_op_XXX + ... - 1) == 0
352352
// Note that the above is even a stronger constraint, as it shows
353353
// that exactly one sel_op_XXX must be true.
354-
// At this time, we have only division producing an error.
355354
#[SUBOP_ERROR_RELEVANT_OP]
356-
op_err * ((sel_op_fdiv + sel_op_div) - 1) = 0;
355+
op_err * ((sel_op_fdiv + sel_op_div + sel_op_get_contract_instance) - 1) = 0;
357356

358357
// TODO: constraint that we stop execution at the first error (tag_err or op_err)
359358
// An error can only happen at the last sub-operation row.

barretenberg/cpp/src/barretenberg/vm/avm/generated/relations/main.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,9 @@ template <typename FF_> class mainImpl {
567567
}
568568
{
569569
using Accumulator = typename std::tuple_element_t<79, ContainerOverSubrelations>;
570-
auto tmp = (new_term.main_op_err * ((new_term.main_sel_op_fdiv + new_term.main_sel_op_div) - FF(1)));
570+
auto tmp = (new_term.main_op_err * (((new_term.main_sel_op_fdiv + new_term.main_sel_op_div) +
571+
new_term.main_sel_op_get_contract_instance) -
572+
FF(1)));
571573
tmp *= scaling_factor;
572574
std::get<79>(evals) += typename Accumulator::View(tmp);
573575
}

barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp

+115-40
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,7 @@ TEST_F(AvmExecutionTests, msmOpCode)
12451245
}
12461246

12471247
// Positive test for Kernel Input opcodes
1248-
TEST_F(AvmExecutionTests, kernelInputOpcodes)
1248+
TEST_F(AvmExecutionTests, getEnvOpcode)
12491249
{
12501250
std::string bytecode_hex =
12511251
to_hex(OpCode::GETENVVAR_16) + // opcode GETENVVAR_16
@@ -1497,6 +1497,32 @@ TEST_F(AvmExecutionTests, kernelInputOpcodes)
14971497
validate_trace(std::move(trace), convert_public_inputs(public_inputs_vec), calldata, returndata);
14981498
}
14991499

1500+
// TODO(9395): allow this intruction to raise error flag in main.pil
1501+
// TEST_F(AvmExecutionTests, getEnvOpcodeBadEnum)
1502+
//{
1503+
// std::string bytecode_hex =
1504+
// to_hex(OpCode::GETENVVAR_16) + // opcode GETENVVAR_16
1505+
// "00" // Indirect flag
1506+
// + to_hex(static_cast<uint8_t>(EnvironmentVariable::MAX_ENV_VAR)) + // envvar ADDRESS
1507+
// "0001"; // dst_offset
1508+
//
1509+
// auto bytecode = hex_to_bytes(bytecode_hex);
1510+
// auto instructions = Deserialization::parse(bytecode);
1511+
//
1512+
// // Public inputs for the circuit
1513+
// std::vector<FF> calldata;
1514+
// std::vector<FF> returndata;
1515+
// ExecutionHints execution_hints;
1516+
// auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
1517+
//
1518+
// // Bad enum should raise error flag
1519+
// auto address_row =
1520+
// std::ranges::find_if(trace.begin(), trace.end(), [](Row r) { return r.main_sel_op_address == 1; });
1521+
// EXPECT_EQ(address_row->main_op_err, FF(1));
1522+
//
1523+
// validate_trace(std::move(trace), convert_public_inputs(public_inputs_vec), calldata, returndata);
1524+
//}
1525+
15001526
// Positive test for L2GASLEFT opcode
15011527
TEST_F(AvmExecutionTests, l2GasLeft)
15021528
{
@@ -2110,43 +2136,10 @@ TEST_F(AvmExecutionTests, opCallOpcodes)
21102136
validate_trace(std::move(trace), public_inputs, calldata, returndata);
21112137
}
21122138

2113-
TEST_F(AvmExecutionTests, opGetContractInstanceOpcodes)
2139+
TEST_F(AvmExecutionTests, opGetContractInstanceOpcode)
21142140
{
2115-
std::string bytecode_hex = to_hex(OpCode::SET_8) + // opcode SET
2116-
"00" // Indirect flag
2117-
+ to_hex(AvmMemoryTag::U32) +
2118-
"00" // val
2119-
"00" // dst_offset
2120-
+ to_hex(OpCode::SET_8) + // opcode SET
2121-
"00" // Indirect flag
2122-
+ to_hex(AvmMemoryTag::U32) +
2123-
"01" // val
2124-
"01" +
2125-
to_hex(OpCode::CALLDATACOPY) + // opcode CALLDATACOPY for addr
2126-
"00" // Indirect flag
2127-
"0000" // cd_offset
2128-
"0001" // copy_size
2129-
"0001" // dst_offset, (i.e. where we store the addr)
2130-
+ to_hex(OpCode::SET_8) + // opcode SET for the indirect dst offset
2131-
"00" // Indirect flag
2132-
+ to_hex(AvmMemoryTag::U32) +
2133-
"03" // val i
2134-
"02" + // dst_offset 2
2135-
to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode CALL
2136-
"02" // Indirect flag
2137-
"00000001" // address offset
2138-
"00000002" // dst offset
2139-
+ to_hex(OpCode::RETURN) + // opcode RETURN
2140-
"00" // Indirect flag
2141-
"0003" // ret offset 3
2142-
"0006"; // ret size 6
2143-
2144-
auto bytecode = hex_to_bytes(bytecode_hex);
2145-
auto instructions = Deserialization::parse(bytecode);
2146-
2147-
FF address = 10;
2148-
std::vector<FF> calldata = { address };
2149-
std::vector<FF> returndata = {};
2141+
const uint8_t address_byte = 0x42;
2142+
const FF address(address_byte);
21502143

21512144
// Generate Hint for call operation
21522145
// Note: opcode does not write 'address' into memory
@@ -2158,14 +2151,96 @@ TEST_F(AvmExecutionTests, opGetContractInstanceOpcodes)
21582151
grumpkin::g1::affine_element::random_element(),
21592152
grumpkin::g1::affine_element::random_element(),
21602153
};
2161-
auto execution_hints =
2162-
ExecutionHints().with_contract_instance_hints({ { address, { address, 1, 2, 3, 4, 5, public_keys_hints } } });
2154+
const ContractInstanceHint instance = ContractInstanceHint{
2155+
.address = address,
2156+
.exists = true,
2157+
.salt = 2,
2158+
.deployer_addr = 42,
2159+
.contract_class_id = 66,
2160+
.initialisation_hash = 99,
2161+
.public_keys = public_keys_hints,
2162+
};
2163+
auto execution_hints = ExecutionHints().with_contract_instance_hints({ { address, instance } });
2164+
2165+
std::string bytecode_hex = to_hex(OpCode::SET_8) + // opcode SET
2166+
"00" // Indirect flag
2167+
+ to_hex(AvmMemoryTag::U8) + to_hex(address_byte) + // val
2168+
"01" // dst_offset 0
2169+
+ to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode GETCONTRACTINSTANCE
2170+
"00" // Indirect flag
2171+
+ to_hex(static_cast<uint8_t>(ContractInstanceMember::DEPLOYER)) + // member enum
2172+
"0001" // address offset
2173+
"0010" // dst offset
2174+
"0011" // exists offset
2175+
+ to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode GETCONTRACTINSTANCE
2176+
"00" // Indirect flag
2177+
+ to_hex(static_cast<uint8_t>(ContractInstanceMember::CLASS_ID)) + // member enum
2178+
"0001" // address offset
2179+
"0012" // dst offset
2180+
"0013" // exists offset
2181+
+ to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode GETCONTRACTINSTANCE
2182+
"00" // Indirect flag
2183+
+ to_hex(static_cast<uint8_t>(ContractInstanceMember::INIT_HASH)) + // member enum
2184+
"0001" // address offset
2185+
"0014" // dst offset
2186+
"0015" // exists offset
2187+
+ to_hex(OpCode::RETURN) + // opcode RETURN
2188+
"00" // Indirect flag
2189+
"0010" // ret offset 1
2190+
"0006"; // ret size 6 (dst & exists for all 3)
2191+
2192+
auto bytecode = hex_to_bytes(bytecode_hex);
2193+
auto instructions = Deserialization::parse(bytecode);
2194+
2195+
ASSERT_THAT(instructions, SizeIs(5));
2196+
2197+
std::vector<FF> const calldata{};
2198+
// alternating member value, exists bool
2199+
std::vector<FF> const expected_returndata = {
2200+
instance.deployer_addr, 1, instance.contract_class_id, 1, instance.initialisation_hash, 1,
2201+
};
21632202

2203+
std::vector<FF> returndata{};
21642204
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
2165-
EXPECT_EQ(returndata, std::vector<FF>({ 1, 2, 3, 4, 5, returned_point.x })); // The first one represents true
21662205

21672206
validate_trace(std::move(trace), public_inputs, calldata, returndata);
2207+
2208+
// Validate returndata
2209+
EXPECT_EQ(returndata, expected_returndata);
21682210
}
2211+
2212+
TEST_F(AvmExecutionTests, opGetContractInstanceOpcodeBadEnum)
2213+
{
2214+
const uint8_t address_byte = 0x42;
2215+
const FF address(address_byte);
2216+
2217+
std::string bytecode_hex = to_hex(OpCode::SET_8) + // opcode SET
2218+
"00" // Indirect flag
2219+
+ to_hex(AvmMemoryTag::U8) + to_hex(address_byte) + // val
2220+
"01" // dst_offset 0
2221+
+ to_hex(OpCode::GETCONTRACTINSTANCE) + // opcode GETCONTRACTINSTANCE
2222+
"00" // Indirect flag
2223+
+ to_hex(static_cast<uint8_t>(ContractInstanceMember::MAX_MEMBER)) + // member enum
2224+
"0001" // address offset
2225+
"0010" // dst offset
2226+
"0011"; // exists offset
2227+
2228+
auto bytecode = hex_to_bytes(bytecode_hex);
2229+
auto instructions = Deserialization::parse(bytecode);
2230+
2231+
std::vector<FF> calldata;
2232+
std::vector<FF> returndata;
2233+
ExecutionHints execution_hints;
2234+
auto trace = gen_trace(bytecode, calldata, public_inputs_vec, returndata, execution_hints);
2235+
2236+
// Bad enum should raise error flag
2237+
auto address_row = std::ranges::find_if(
2238+
trace.begin(), trace.end(), [](Row r) { return r.main_sel_op_get_contract_instance == 1; });
2239+
EXPECT_EQ(address_row->main_op_err, FF(1));
2240+
2241+
validate_trace(std::move(trace), public_inputs, calldata, returndata);
2242+
}
2243+
21692244
// Negative test detecting an invalid opcode byte.
21702245
TEST_F(AvmExecutionTests, invalidOpcode)
21712246
{

barretenberg/cpp/src/barretenberg/vm/avm/trace/deserialization.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ const std::unordered_map<OpCode, std::vector<OperandType>> OPCODE_WIRE_FORMAT =
136136
OperandType::UINT16,
137137
/*TODO: leafIndexOffset is not constrained*/ OperandType::UINT16,
138138
OperandType::UINT16 } },
139-
{ OpCode::GETCONTRACTINSTANCE, { OperandType::INDIRECT8, OperandType::UINT32, OperandType::UINT32 } },
139+
{ OpCode::GETCONTRACTINSTANCE,
140+
{ OperandType::INDIRECT8, OperandType::UINT8, OperandType::UINT16, OperandType::UINT16, OperandType::UINT16 } },
140141
{ OpCode::EMITUNENCRYPTEDLOG,
141142
{
142143
OperandType::INDIRECT8,

barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -604,8 +604,10 @@ std::vector<Row> Execution::gen_trace(std::vector<FF> const& calldata,
604604
break;
605605
case OpCode::GETCONTRACTINSTANCE:
606606
trace_builder.op_get_contract_instance(std::get<uint8_t>(inst.operands.at(0)),
607-
std::get<uint32_t>(inst.operands.at(1)),
608-
std::get<uint32_t>(inst.operands.at(2)));
607+
std::get<uint8_t>(inst.operands.at(1)),
608+
std::get<uint16_t>(inst.operands.at(2)),
609+
std::get<uint16_t>(inst.operands.at(3)),
610+
std::get<uint16_t>(inst.operands.at(4)));
609611
break;
610612

611613
// Accrued Substate

barretenberg/cpp/src/barretenberg/vm/avm/trace/opcode.hpp

+8
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ enum class EnvironmentVariable {
128128
MAX_ENV_VAR
129129
};
130130

131+
enum class ContractInstanceMember {
132+
DEPLOYER,
133+
CLASS_ID,
134+
INIT_HASH,
135+
// sentinel
136+
MAX_MEMBER,
137+
};
138+
131139
class Bytecode {
132140
public:
133141
static bool is_valid(uint8_t byte);

0 commit comments

Comments
 (0)