Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
bytecode_manager.test.cpp
Go to the documentation of this file.
2
3#include <cstdint>
4#include <gmock/gmock.h>
5#include <gtest/gtest.h>
6#include <memory>
7#include <optional>
8#include <vector>
9
30
31using ::testing::_;
32using ::testing::Return;
33using ::testing::SizeIs;
34using ::testing::StrictMock;
35
36namespace bb::avm2::simulation {
37
38namespace {
39
40// Simple mock for ContractInstanceManagerInterface
41class MockContractInstanceManager : public ContractInstanceManagerInterface {
42 public:
43 MOCK_METHOD(std::optional<ContractInstance>, get_contract_instance, (const FF& contract_address), (override));
44};
45
46class BytecodeManagerTest : public ::testing::Test {
47 protected:
48 BytecodeManagerTest()
50 {}
51
52 StrictMock<MockContractDB> contract_db;
53 StrictMock<MockHighLevelMerkleDB> merkle_db;
54 StrictMock<MockPoseidon2> poseidon2;
55 StrictMock<MockRangeCheck> range_check;
56 StrictMock<MockContractInstanceManager> contract_instance_manager;
57 StrictMock<MockClassIdDerivation> class_id_derivation;
58 StrictMock<MockRetrievedBytecodesTreeCheck> retrieved_bytecodes_tree_check;
59
60 EventEmitter<BytecodeRetrievalEvent> retrieval_events;
61 EventEmitter<BytecodeDecompositionEvent> decomposition_events;
62 EventEmitter<InstructionFetchingEvent> instruction_fetching_events;
63 EventEmitter<BytecodeHashingEvent> hashing_events;
64 BytecodeHasher bytecode_hasher;
65};
66
67TEST_F(BytecodeManagerTest, RetrievalAndDeduplication)
68{
69 TxBytecodeManager tx_bytecode_manager(contract_db,
78
79 // Setup for base case
81 ContractInstance instance1 = testing::random_contract_instance();
82 ContractClass klass = testing::random_contract_class();
83 FF bytecode_commitment = FF::random_element();
84
85 // Expected interactions for first retrieval
86
87 EXPECT_CALL(retrieved_bytecodes_tree_check, get_snapshot()).Times(2);
88 EXPECT_CALL(contract_instance_manager, get_contract_instance(address1))
89 .WillOnce(Return(std::make_optional(instance1)));
90
91 EXPECT_CALL(retrieved_bytecodes_tree_check, contains(instance1.current_contract_class_id)).WillOnce(Return(false));
92 EXPECT_CALL(retrieved_bytecodes_tree_check, size()).WillOnce(Return(0));
93 EXPECT_CALL(retrieved_bytecodes_tree_check, insert(instance1.current_contract_class_id));
94
95 EXPECT_CALL(contract_db, get_contract_class(instance1.current_contract_class_id))
96 .WillOnce(Return(std::make_optional(klass)));
97 EXPECT_CALL(contract_db, get_bytecode_commitment(instance1.current_contract_class_id))
98 .WillRepeatedly(Return(std::make_optional(bytecode_commitment)));
99
100 // Let the real bytecode hasher run - it will emit hashing events
101 EXPECT_CALL(poseidon2, hash(_)).WillOnce(Return(bytecode_commitment));
102
103 TreeStates tree_states = {};
104 EXPECT_CALL(merkle_db, get_tree_state()).WillOnce(Return(tree_states));
105
106 // Base case: First retrieval - should do full processing
107 BytecodeId result1 = tx_bytecode_manager.get_bytecode(address1);
108 EXPECT_EQ(result1, bytecode_commitment);
109
110 // Verify events after first retrieval
111 // Verify retrieval events - should have exactly one retrieval event total
112 auto retrieval_events_dump = retrieval_events.dump_events();
113 EXPECT_THAT(retrieval_events_dump, SizeIs(1));
114 EXPECT_EQ(retrieval_events_dump[0].address, address1);
115 EXPECT_EQ(retrieval_events_dump[0].bytecode_id, bytecode_commitment);
116 EXPECT_TRUE(retrieval_events_dump[0].is_new_class);
117 EXPECT_FALSE(retrieval_events_dump[0].error.has_value());
118 // Verify hashing events - should have exactly one hashing event total
119 auto hashing_events_dump = hashing_events.dump_events();
120 EXPECT_THAT(hashing_events_dump, SizeIs(1));
121 EXPECT_EQ(hashing_events_dump[0].bytecode_id, bytecode_commitment);
122 // Verify decomposition events - should have exactly one decomposition event total
123 auto decomposition_events_dump = decomposition_events.dump_events();
124 EXPECT_THAT(decomposition_events_dump, SizeIs(1));
125 EXPECT_EQ(decomposition_events_dump[0].bytecode_id, bytecode_commitment);
126
127 // Deduplication case 1: Same address retrieval
128 // Expected interactions for second retrieval of same address
129 EXPECT_CALL(retrieved_bytecodes_tree_check, get_snapshot()).Times(2);
130 EXPECT_CALL(contract_instance_manager, get_contract_instance(address1))
131 .WillOnce(Return(std::make_optional(instance1)));
132 EXPECT_CALL(retrieved_bytecodes_tree_check, contains(instance1.current_contract_class_id)).WillOnce(Return(true));
133 EXPECT_CALL(retrieved_bytecodes_tree_check, size()).WillOnce(Return(1));
134 EXPECT_CALL(retrieved_bytecodes_tree_check, insert(instance1.current_contract_class_id));
135
136 EXPECT_CALL(contract_db, get_contract_class(instance1.current_contract_class_id))
137 .WillOnce(Return(std::make_optional(klass)));
138 // get_bytecode_commitment is called even for deduplicated retrievals
139 // (already set up with WillRepeatedly above)
140 // No hashing should occur for duplicate retrieval
141 EXPECT_CALL(merkle_db, get_tree_state()).WillOnce(Return(tree_states));
142
143 // Second retrieval of same address - should be deduplicated
144 BytecodeId result2 = tx_bytecode_manager.get_bytecode(address1);
145 EXPECT_EQ(result2, bytecode_commitment);
146
147 // Verify events after second retrieval - retrieval event emitted, but no hashing or decomposition
148 retrieval_events_dump = retrieval_events.dump_events();
149 EXPECT_THAT(retrieval_events_dump, SizeIs(1));
150 EXPECT_EQ(retrieval_events_dump[0].address, address1);
151 EXPECT_EQ(retrieval_events_dump[0].bytecode_id, bytecode_commitment);
152 EXPECT_FALSE(retrieval_events_dump[0].is_new_class);
153 hashing_events_dump = hashing_events.dump_events();
154 EXPECT_THAT(hashing_events_dump, SizeIs(0)); // No hashing for deduplicated bytecode
155 decomposition_events_dump = decomposition_events.dump_events();
156 EXPECT_THAT(decomposition_events_dump, SizeIs(0)); // No decomposition for deduplicated retrieval
157
158 // Deduplication case 2: Different address with same bytecode
159 AztecAddress address2 = address1 + 1; // force a different address
160 ContractInstance instance2 = testing::random_contract_instance();
161 instance2.current_contract_class_id = instance1.current_contract_class_id + 1; // force a different class id
162
163 // Expected interactions for different address with same bytecode
164 EXPECT_CALL(retrieved_bytecodes_tree_check, get_snapshot()).Times(2);
165 EXPECT_CALL(contract_instance_manager, get_contract_instance(address2))
166 .WillOnce(Return(std::make_optional(instance2)));
167 EXPECT_CALL(retrieved_bytecodes_tree_check, contains(instance2.current_contract_class_id)).WillOnce(Return(true));
168 EXPECT_CALL(retrieved_bytecodes_tree_check, size()).WillOnce(Return(1));
169 EXPECT_CALL(retrieved_bytecodes_tree_check, insert(instance2.current_contract_class_id));
170
171 EXPECT_CALL(contract_db, get_contract_class(instance2.current_contract_class_id))
172 .WillOnce(Return(std::make_optional(klass))); // Same class/bytecode
173 EXPECT_CALL(contract_db, get_bytecode_commitment(instance2.current_contract_class_id))
174 .WillOnce(Return(std::make_optional(bytecode_commitment)));
175 // No hashing should occur since we've already processed this bytecode
176 EXPECT_CALL(merkle_db, get_tree_state()).WillOnce(Return(tree_states));
177
178 // Third retrieval with different address but same bytecode - should be deduplicated
179 BytecodeId result3 = tx_bytecode_manager.get_bytecode(address2);
180 EXPECT_EQ(result3, bytecode_commitment);
181
182 // Verify events after third retrieval - retrieval event emitted, but no hashing or decomposition
183 retrieval_events_dump = retrieval_events.dump_events();
184 EXPECT_THAT(retrieval_events_dump, SizeIs(1));
185 EXPECT_EQ(retrieval_events_dump[0].address, address2);
186 EXPECT_EQ(retrieval_events_dump[0].bytecode_id, bytecode_commitment);
187 EXPECT_FALSE(retrieval_events_dump[0].is_new_class);
188 hashing_events_dump = hashing_events.dump_events();
189 EXPECT_THAT(hashing_events_dump, SizeIs(0)); // No hashing for deduplicated bytecode
190 decomposition_events_dump = decomposition_events.dump_events();
191 EXPECT_THAT(decomposition_events_dump, SizeIs(0)); // No decomposition for deduplicated bytecode
192}
193
194TEST_F(BytecodeManagerTest, TooManyBytecodes)
195{
196 TxBytecodeManager tx_bytecode_manager(contract_db,
197 merkle_db,
199 range_check,
205
207 ContractInstance instance1 = testing::random_contract_instance();
208 ContractClass klass = testing::random_contract_class();
209
210 EXPECT_CALL(retrieved_bytecodes_tree_check, get_snapshot());
211 EXPECT_CALL(merkle_db, get_tree_state());
212
213 EXPECT_CALL(contract_instance_manager, get_contract_instance(address1))
214 .WillOnce(Return(std::make_optional(instance1)));
215
216 EXPECT_CALL(retrieved_bytecodes_tree_check, contains(instance1.current_contract_class_id)).WillOnce(Return(false));
218
219 // Base case: First retrieval - should do full processing
220 EXPECT_THROW_WITH_MESSAGE(tx_bytecode_manager.get_bytecode(address1),
221 "Can't retrieve more than " +
223
224 auto retrieval_events_dump = retrieval_events.dump_events();
225 EXPECT_THAT(retrieval_events_dump, SizeIs(1));
226 EXPECT_EQ(retrieval_events_dump[0].address, address1);
227 EXPECT_EQ(retrieval_events_dump[0].bytecode_id, 0);
228 EXPECT_EQ(retrieval_events_dump[0].error, BytecodeRetrievalEventError::TOO_MANY_BYTECODES);
229}
230
231// Test about a contract address nullifier not found error (contract address not in nullifier tree)
232TEST_F(BytecodeManagerTest, ContractAddressNullifierNotFoundError)
233{
234 StrictMock<MockUpdateCheck> update_check;
235 StrictMock<MockFieldGreaterThan> field_gt;
236 ProtocolContracts protocol_contracts = {};
237 EventEmitter<ContractInstanceRetrievalEvent> contract_retrieval_events;
238
239 ContractInstanceManager real_contract_instance_manager(
240 contract_db, merkle_db, update_check, field_gt, protocol_contracts, contract_retrieval_events);
241
242 TxBytecodeManager tx_bytecode_manager(contract_db,
243 merkle_db,
245 range_check,
246 real_contract_instance_manager,
251
253 ContractInstance instance = testing::random_contract_instance();
254 EXPECT_CALL(contract_db, get_contract_instance(address)).WillOnce(Return(instance));
255 EXPECT_CALL(field_gt, ff_gt(FF(MAX_PROTOCOL_CONTRACTS), address - 1)).WillOnce(Return(false));
256 EXPECT_CALL(retrieved_bytecodes_tree_check, get_snapshot());
257 EXPECT_CALL(merkle_db, get_tree_state()).Times(2);
258 EXPECT_CALL(merkle_db, nullifier_exists(FF(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), address))
259 .WillOnce(Return(false));
260
261 EXPECT_THROW_WITH_MESSAGE(tx_bytecode_manager.get_bytecode(address),
262 "Contract " + field_to_string(address) + " is not deployed");
263
264 auto retrieval_events_dump = retrieval_events.dump_events();
265 EXPECT_THAT(retrieval_events_dump, SizeIs(1));
266 EXPECT_EQ(retrieval_events_dump[0].address, address);
267 EXPECT_EQ(retrieval_events_dump[0].bytecode_id, 0);
268 EXPECT_EQ(retrieval_events_dump[0].error, BytecodeRetrievalEventError::INSTANCE_NOT_FOUND);
269
270 auto contract_retrieval_events_dump = contract_retrieval_events.dump_events();
271 EXPECT_THAT(contract_retrieval_events_dump, SizeIs(1));
272 EXPECT_EQ(contract_retrieval_events_dump[0].address, address);
273 EXPECT_FALSE(contract_retrieval_events_dump[0].exists);
274 EXPECT_FALSE(contract_retrieval_events_dump[0].is_protocol_contract);
275 EXPECT_EQ(contract_retrieval_events_dump[0].deployment_nullifier, address);
276 EXPECT_EQ(contract_retrieval_events_dump[0].contract_instance, ContractInstance{});
277}
278
279TEST_F(BytecodeManagerTest, InstructionFetching)
280{
281 TxBytecodeManager tx_bytecode_manager(contract_db,
282 merkle_db,
284 range_check,
290
291 // Taken from /constraining/relations/instr_fetching.test.cpp:
292 Instruction add_8_instruction = {
293 .opcode = WireOpCode::ADD_8,
294 .addressing_mode = 3,
295 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
296 };
297
298 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
299 FF bytecode_commitment = FF::random_element();
300 PC pc = 0;
301
302 EXPECT_CALL(range_check, assert_range(bytecode.size() - pc - 1, AVM_PC_SIZE_IN_BITS));
303
304 // Base case - simple successful fetching.
305 Instruction result = tx_bytecode_manager.read_instruction(
306 bytecode_commitment, std::make_shared<std::vector<uint8_t>>((bytecode)), pc);
307
308 // Verify the decoded instruction.
309 EXPECT_EQ(result.opcode, WireOpCode::ADD_8);
310 EXPECT_EQ(result.addressing_mode, add_8_instruction.addressing_mode);
311 ASSERT_THAT(result.operands, SizeIs(3));
312 EXPECT_EQ(result.operands[0], add_8_instruction.operands[0]);
313 EXPECT_EQ(result.operands[1], add_8_instruction.operands[1]);
314 EXPECT_EQ(result.operands[2], add_8_instruction.operands[2]);
315
316 // Verify one InstructionFetchingEvent was emitted with no error.
317 auto fetching_events_dump = instruction_fetching_events.dump_events();
318 ASSERT_THAT(fetching_events_dump, SizeIs(1));
319 EXPECT_EQ(fetching_events_dump[0].bytecode_id, bytecode_commitment);
320 EXPECT_EQ(fetching_events_dump[0].pc, pc);
321 EXPECT_FALSE(fetching_events_dump[0].error.has_value());
322
323 // Error cases - PC_OUT_OF_RANGE (set pc to be above the bytecode size).
324 pc = static_cast<PC>(bytecode.size() + 2);
325 // The absolute diff between bytecode size and pc is now 2:
326 EXPECT_CALL(range_check, assert_range(2, AVM_PC_SIZE_IN_BITS));
327 EXPECT_THROW_WITH_MESSAGE(tx_bytecode_manager.read_instruction(
328 bytecode_commitment, std::make_shared<std::vector<uint8_t>>((bytecode)), pc),
329 "Instruction fetching error: .*");
330
331 // Error cases - OPCODE_OUT_OF_RANGE (set the opcode byte to be above LAST_OPCODE_SENTINEL).
332 pc = 0;
333 bytecode[0] = static_cast<uint8_t>(WireOpCode::LAST_OPCODE_SENTINEL) + 2;
334 EXPECT_CALL(range_check, assert_range(bytecode.size() - pc - 1, AVM_PC_SIZE_IN_BITS));
335 EXPECT_THROW_WITH_MESSAGE(tx_bytecode_manager.read_instruction(
336 bytecode_commitment, std::make_shared<std::vector<uint8_t>>((bytecode)), pc),
337 "Instruction fetching error: .*");
338
339 // Error cases - INSTRUCTION_OUT_OF_RANGE (set pc such that pc + instruction_size > bytecode_size, but pc <
340 // bytecode_size to avoid triggering PC_OUT_OF_RANGE).
341 bytecode[0] = static_cast<uint8_t>(add_8_instruction.opcode);
342 pc = static_cast<PC>(bytecode.size() - add_8_instruction.size_in_bytes() + 1);
343 EXPECT_CALL(range_check, assert_range(bytecode.size() - pc - 1, AVM_PC_SIZE_IN_BITS));
344 EXPECT_THROW_WITH_MESSAGE(tx_bytecode_manager.read_instruction(
345 bytecode_commitment, std::make_shared<std::vector<uint8_t>>((bytecode)), pc),
346 "Instruction fetching error: .*");
347
348 // Error cases - TAG_OUT_OF_RANGE (set the tag operand to be above the maximum value).
349
350 // Taken from /constraining/relations/instr_fetching.test.cpp (SET_16 has a tag operand at op2 = index 1):
351 Instruction set_16_instruction = {
352 .opcode = WireOpCode::SET_16,
353 .addressing_mode = 0,
354 .operands = { Operand::from<uint16_t>(0x1234),
355 Operand::from<uint8_t>(static_cast<uint8_t>(MemoryTag::MAX) + 1),
356 Operand::from<uint16_t>(0x5678) },
357 };
358
359 pc = 0;
360 bytecode = set_16_instruction.serialize();
361 EXPECT_CALL(range_check, assert_range(bytecode.size() - pc - 1, AVM_PC_SIZE_IN_BITS));
362 EXPECT_THROW_WITH_MESSAGE(tx_bytecode_manager.read_instruction(
363 bytecode_commitment, std::make_shared<std::vector<uint8_t>>((bytecode)), pc),
364 "Instruction fetching error.*");
365
366 fetching_events_dump = instruction_fetching_events.dump_events();
367 ASSERT_THAT(fetching_events_dump, SizeIs(4));
368
369 EXPECT_EQ(fetching_events_dump[0].error.value(), InstrDeserializationEventError::PC_OUT_OF_RANGE);
370 EXPECT_EQ(fetching_events_dump[1].error.value(), InstrDeserializationEventError::OPCODE_OUT_OF_RANGE);
371 EXPECT_EQ(fetching_events_dump[2].error.value(), InstrDeserializationEventError::INSTRUCTION_OUT_OF_RANGE);
372 EXPECT_EQ(fetching_events_dump[3].error.value(), InstrDeserializationEventError::TAG_OUT_OF_RANGE);
373}
374
375} // namespace
376} // namespace bb::avm2::simulation
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
std::shared_ptr< Napi::ThreadSafeFunction > instance
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
#define MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS
#define AVM_PC_SIZE_IN_BITS
#define MAX_PROTOCOL_CONTRACTS
#define CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS
FieldGreaterThan field_gt
RetrievedBytecodesTreeCheck retrieved_bytecodes_tree_check
RangeCheck range_check
BytecodeHasher bytecode_hasher
EventEmitter< BytecodeHashingEvent > hashing_events
EventEmitter< BytecodeDecompositionEvent > decomposition_events
StrictMock< MockHighLevelMerkleDB > merkle_db
EventEmitter< InstructionFetchingEvent > instruction_fetching_events
EventEmitter< BytecodeRetrievalEvent > retrieval_events
StrictMock< MockClassIdDerivation > class_id_derivation
StrictMock< MockContractDB > contract_db
StrictMock< MockContractInstanceManager > contract_instance_manager
Native Poseidon2 hash function implementation.
Definition poseidon2.hpp:22
StrictMock< MockUpdateCheck > update_check
AVM range check gadget for witness generation.
ContractClass random_contract_class(size_t bytecode_size)
Definition fixtures.cpp:175
ContractInstance random_contract_instance()
Definition fixtures.cpp:159
uint32_t PC
AvmFlavorSettings::FF FF
Definition field.hpp:10
std::string field_to_string(const FF &ff)
Definition stringify.cpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
Instruction
Enumeration of VM instructions that can be executed.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
static field random_element(numeric::RNG *engine=nullptr) noexcept