Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
call_stack_metadata_collector.test.cpp
Go to the documentation of this file.
2
3#include <gmock/gmock.h>
4#include <gtest/gtest.h>
5
11
12namespace bb::avm2::simulation {
13namespace {
14
15using ::testing::_;
16using ::testing::ElementsAre;
17using ::testing::Return;
18using ::testing::ReturnRef;
19using ::testing::StrictMock;
20
21CollectionLimitsConfig limits = {
22 .max_calldata_size_in_fields = 1024,
23 .max_returndata_size_in_fields = 1024,
24};
25
26TEST(CallStackMetadataCollectorTest, SingleCallEnterAndExit)
27{
28 CallStackMetadataCollector collector(limits);
29 AztecAddress contract_addr(0x1234);
30 PC caller_pc = 100;
31 PC exit_pc = 200;
32 Gas gas_limit{ 1000, 2000 };
33 std::vector<FF> calldata = { FF(0xabcd), FF(0xef01) };
34 std::vector<FF> return_data = { FF(0x5678), FF(0x9abc) };
35
36 // Set phase
37 collector.set_phase(CoarseTransactionPhase::APP_LOGIC);
38
39 // Enter call
40 CalldataProvider calldata_provider = [&calldata](uint32_t /*max_size*/) -> std::vector<FF> { return calldata; };
41
42 collector.notify_enter_call(contract_addr, caller_pc, calldata_provider, false, gas_limit);
43
44 // Exit call
45 ReturnDataProvider return_data_provider = [&return_data](uint32_t /*max_size*/) -> std::vector<FF> {
46 return return_data;
47 };
48 InternalCallStackProvider internal_call_stack_provider = []() -> std::vector<PC> {
49 // This means that there where 2 internal calls, one at pc 0 and one at pc 10.
50 // We are currently at PC exit_pc but the provider does not provide this.
51 return { 0, 10 };
52 };
53
54 collector.notify_exit_call(true, exit_pc, std::nullopt, return_data_provider, internal_call_stack_provider);
55
56 // Dump and verify
57 auto metadata = collector.dump_call_stack_metadata();
58 ASSERT_EQ(metadata.size(), 1U);
59
60 const auto& call_metadata = metadata[0];
61 EXPECT_EQ(call_metadata.timestamp, 0U);
62 EXPECT_EQ(call_metadata.phase, CoarseTransactionPhase::APP_LOGIC);
63 EXPECT_EQ(call_metadata.contract_address, contract_addr);
64 EXPECT_EQ(call_metadata.caller_pc, caller_pc);
65 EXPECT_EQ(call_metadata.internal_call_stack_at_exit, std::vector<PC>({ 0, 10, exit_pc }));
66 EXPECT_EQ(call_metadata.calldata, calldata);
67 EXPECT_EQ(call_metadata.output, return_data);
68 EXPECT_EQ(call_metadata.is_static_call, false);
69 EXPECT_EQ(call_metadata.gas_limit, gas_limit);
70 EXPECT_EQ(call_metadata.reverted, false);
71 EXPECT_EQ(call_metadata.halting_message, std::nullopt);
72 EXPECT_EQ(call_metadata.num_nested_calls, 0U);
73 EXPECT_TRUE(call_metadata.nested.empty());
74}
75
76TEST(CallStackMetadataCollectorTest, NestedCalls)
77{
78 CallStackMetadataCollector collector(limits);
79 AztecAddress contract1(0x1111);
80 AztecAddress contract2(0x2222);
81 AztecAddress contract3(0x3333);
82
83 collector.set_phase(CoarseTransactionPhase::APP_LOGIC);
84
85 // Enter first call
86 std::vector<FF> calldata1 = { FF(0xaaaa) };
87 CalldataProvider calldata_provider1 = [&calldata1](uint32_t /*max_size*/) -> std::vector<FF> { return calldata1; };
88
89 collector.notify_enter_call(contract1, 100, calldata_provider1, false, Gas{ 1000, 2000 });
90
91 // Enter second call (nested)
92 std::vector<FF> calldata2 = { FF(0xbbbb) };
93 CalldataProvider calldata_provider2 = [&calldata2](uint32_t /*max_size*/) -> std::vector<FF> { return calldata2; };
94
95 collector.notify_enter_call(contract2, 200, calldata_provider2, false, Gas{ 500, 1000 });
96
97 // Enter third call (nested in nested)
98 std::vector<FF> calldata3 = { FF(0xcccc) };
99 CalldataProvider calldata_provider3 = [&calldata3](uint32_t /*max_size*/) -> std::vector<FF> { return calldata3; };
100
101 collector.notify_enter_call(contract3, 300, calldata_provider3, true, Gas{ 200, 400 });
102
103 // Exit third call
104 std::vector<FF> return_data3 = { FF(0x3333) };
105 ReturnDataProvider return_data_provider3 = [&return_data3](uint32_t /*max_size*/) -> std::vector<FF> {
106 return return_data3;
107 };
108 InternalCallStackProvider internal_call_stack_provider3 = []() -> std::vector<PC> { return { 300 }; };
109
110 collector.notify_exit_call(true, 399, std::nullopt, return_data_provider3, internal_call_stack_provider3);
111
112 // Exit second call
113 std::vector<FF> return_data2 = { FF(0x2222) };
114 ReturnDataProvider return_data_provider2 = [&return_data2](uint32_t /*max_size*/) -> std::vector<FF> {
115 return return_data2;
116 };
117 InternalCallStackProvider internal_call_stack_provider2 = []() -> std::vector<PC> { return { 200 }; };
118
119 collector.notify_exit_call(
120 false, 299, "Nested call reverted", return_data_provider2, internal_call_stack_provider2);
121
122 // Exit first call
123 std::vector<FF> return_data1 = { FF(0x1111) };
124 ReturnDataProvider return_data_provider1 = [&return_data1](uint32_t /*max_size*/) -> std::vector<FF> {
125 return return_data1;
126 };
127 InternalCallStackProvider internal_call_stack_provider1 = []() -> std::vector<PC> { return { 100 }; };
128
129 collector.notify_exit_call(true, 199, std::nullopt, return_data_provider1, internal_call_stack_provider1);
130
131 // Dump and verify
132 auto metadata = collector.dump_call_stack_metadata();
133 ASSERT_EQ(metadata.size(), 1U);
134
135 const auto& outer_call = metadata[0];
136 EXPECT_EQ(outer_call.timestamp, 0U);
137 EXPECT_EQ(outer_call.num_nested_calls, 1U);
138 EXPECT_EQ(outer_call.nested.size(), 1U);
139 EXPECT_EQ(outer_call.output, return_data1);
140 EXPECT_EQ(outer_call.reverted, false);
141 EXPECT_EQ(outer_call.halting_message, std::nullopt);
142 EXPECT_EQ(outer_call.internal_call_stack_at_exit, std::vector<PC>({ 100, 199 }));
143
144 const auto& middle_call = outer_call.nested[0];
145 EXPECT_EQ(middle_call.timestamp, 1U);
146 EXPECT_EQ(middle_call.num_nested_calls, 1U);
147 EXPECT_EQ(middle_call.nested.size(), 1U);
148 EXPECT_EQ(middle_call.output, return_data2);
149 EXPECT_EQ(middle_call.reverted, true);
150 EXPECT_EQ(middle_call.halting_message, "Nested call reverted");
151 EXPECT_EQ(middle_call.internal_call_stack_at_exit, std::vector<PC>({ 200, 299 }));
152
153 const auto& inner_call = middle_call.nested[0];
154 EXPECT_EQ(inner_call.timestamp, 2U);
155 EXPECT_EQ(inner_call.num_nested_calls, 0U);
156 EXPECT_EQ(inner_call.nested.size(), 0U);
157 EXPECT_EQ(inner_call.output, return_data3);
158 EXPECT_EQ(inner_call.reverted, false);
159 EXPECT_EQ(inner_call.halting_message, std::nullopt);
160 EXPECT_EQ(inner_call.is_static_call, true);
161 EXPECT_EQ(inner_call.internal_call_stack_at_exit, std::vector<PC>({ 300, 399 }));
162}
163
164TEST(CallStackMetadataCollectorTest, MultipleSiblingCalls)
165{
166 CallStackMetadataCollector collector(limits);
167 AztecAddress contract0(0x0000);
168 AztecAddress contract1(0xaaaa);
169 AztecAddress contract2(0xbbbb);
170
171 collector.set_phase(CoarseTransactionPhase::APP_LOGIC);
172
173 // Outer call.
174 std::vector<FF> calldata0 = { FF(0x0000) };
175 CalldataProvider calldata_provider0 = [&calldata0](uint32_t /*max_size*/) -> std::vector<FF> { return calldata0; };
176 collector.notify_enter_call(contract0, 0, calldata_provider0, false, Gas{ 0, 0 });
177
178 // First call
179 std::vector<FF> calldata1 = { FF(0x1111) };
180 CalldataProvider calldata_provider1 = [&calldata1](uint32_t /*max_size*/) -> std::vector<FF> { return calldata1; };
181
182 collector.notify_enter_call(contract1, 100, calldata_provider1, false, Gas{ 1000, 2000 });
183
184 ReturnDataProvider return_data_provider1 = [](uint32_t /*max_size*/) -> std::vector<FF> { return { FF(0xaaaa) }; };
185 InternalCallStackProvider internal_call_stack_provider1 = []() -> std::vector<PC> { return { 100, 200 }; };
186
187 collector.notify_exit_call(true, 150, std::nullopt, return_data_provider1, internal_call_stack_provider1);
188
189 // Second call (sibling)
190 std::vector<FF> calldata2 = { FF(0x2222) };
191 CalldataProvider calldata_provider2 = [&calldata2](uint32_t /*max_size*/) -> std::vector<FF> { return calldata2; };
192
193 collector.notify_enter_call(contract2, 200, calldata_provider2, false, Gas{ 500, 1000 });
194
195 ReturnDataProvider return_data_provider2 = [](uint32_t /*max_size*/) -> std::vector<FF> { return { FF(0xbbbb) }; };
196 InternalCallStackProvider internal_call_stack_provider2 = []() -> std::vector<PC> { return { 200, 300 }; };
197
198 collector.notify_exit_call(true, 250, std::nullopt, return_data_provider2, internal_call_stack_provider2);
199
200 // Outer call (return).
201 ReturnDataProvider return_data_provider0 = [](uint32_t /*max_size*/) -> std::vector<FF> { return { FF(0x0000) }; };
202 InternalCallStackProvider internal_call_stack_provider0 = []() -> std::vector<PC> { return { 0, 100 }; };
203 collector.notify_exit_call(true, 0, std::nullopt, return_data_provider0, internal_call_stack_provider0);
204
205 // Verify both calls are present
206 auto metadata = collector.dump_call_stack_metadata();
207 ASSERT_EQ(metadata.size(), 1U);
208
209 const auto& outer_call = metadata[0];
210 EXPECT_EQ(outer_call.timestamp, 0U);
211 EXPECT_EQ(outer_call.num_nested_calls, 2U);
212 EXPECT_EQ(outer_call.halting_message, std::nullopt);
213 ASSERT_EQ(outer_call.nested.size(), 2U);
214 EXPECT_EQ(outer_call.nested[0].timestamp, 1U);
215 EXPECT_EQ(outer_call.nested[0].halting_message, std::nullopt);
216 EXPECT_EQ(outer_call.nested[1].timestamp, 2U);
217 EXPECT_EQ(outer_call.nested[1].halting_message, std::nullopt);
218 EXPECT_EQ(outer_call.nested[0].contract_address, contract1);
219 EXPECT_EQ(outer_call.nested[1].contract_address, contract2);
220}
221
222TEST(CallStackMetadataCollectorTest, CalldataSizeLimit)
223{
224 CallStackMetadataCollector collector(limits);
225 AztecAddress contract_addr(0x1234);
226 std::vector<FF> large_calldata(2000, FF(0xabcd)); // Larger than max_calldata_size (1024)
227
228 collector.set_phase(CoarseTransactionPhase::APP_LOGIC);
229
230 CalldataProvider calldata_provider = [&large_calldata](uint32_t max_size) -> std::vector<FF> {
231 // Provider should respect max_size
232 if (large_calldata.size() > max_size) {
233 return std::vector<FF>(large_calldata.begin(), large_calldata.begin() + max_size);
234 }
235 return large_calldata;
236 };
237
238 collector.notify_enter_call(contract_addr, 100, calldata_provider, false, Gas{ 1000, 2000 });
239
240 ReturnDataProvider return_data_provider = [](uint32_t /*max_size*/) -> std::vector<FF> { return {}; };
241 InternalCallStackProvider internal_call_stack_provider = []() -> std::vector<PC> { return { 100, 200 }; };
242
243 collector.notify_exit_call(true, 200, std::nullopt, return_data_provider, internal_call_stack_provider);
244
245 auto metadata = collector.dump_call_stack_metadata();
246 ASSERT_EQ(metadata.size(), 1U);
247 // Calldata should be limited to max_calldata_size (1024)
248 EXPECT_EQ(metadata[0].calldata.size(), 1024U);
249 EXPECT_EQ(metadata[0].halting_message, std::nullopt);
250}
251
252TEST(CallStackMetadataCollectorTest, ReturnDataSizeLimit)
253{
254 CallStackMetadataCollector collector(limits);
255 AztecAddress contract_addr(0x5678);
256 std::vector<FF> calldata = { FF(0x1234) };
257
258 collector.set_phase(CoarseTransactionPhase::APP_LOGIC);
259
260 CalldataProvider calldata_provider = [&calldata](uint32_t /*max_size*/) -> std::vector<FF> { return calldata; };
261
262 collector.notify_enter_call(contract_addr, 100, calldata_provider, false, Gas{ 1000, 2000 });
263
264 std::vector<FF> large_return_data(2000, FF(0xef01)); // Larger than max_return_data_size (1024)
265 ReturnDataProvider return_data_provider = [&large_return_data](uint32_t max_size) -> std::vector<FF> {
266 // Provider should respect max_size
267 if (large_return_data.size() > max_size) {
268 return std::vector<FF>(large_return_data.begin(), large_return_data.begin() + max_size);
269 }
270 return large_return_data;
271 };
272 InternalCallStackProvider internal_call_stack_provider = []() -> std::vector<PC> { return { 100, 200 }; };
273
274 collector.notify_exit_call(true, 200, std::nullopt, return_data_provider, internal_call_stack_provider);
275
276 auto metadata = collector.dump_call_stack_metadata();
277 ASSERT_EQ(metadata.size(), 1U);
278 // Return data should be limited to max_return_data_size (1024)
279 EXPECT_EQ(metadata[0].output.size(), 1024U);
280 EXPECT_EQ(metadata[0].halting_message, std::nullopt);
281}
282
283TEST(CallStackMetadataCollectorTest, PhaseTracking)
284{
285 CallStackMetadataCollector collector(limits);
286 AztecAddress contract1(0x1111);
287 AztecAddress contract2(0x2222);
288 AztecAddress contract3(0x3333);
289
290 // First call in SETUP phase
291 collector.set_phase(CoarseTransactionPhase::SETUP);
292 std::vector<FF> calldata1 = { FF(0xaaaa) };
293 CalldataProvider calldata_provider1 = [&calldata1](uint32_t /*max_size*/) -> std::vector<FF> { return calldata1; };
294
295 collector.notify_enter_call(contract1, 100, calldata_provider1, false, Gas{ 1000, 2000 });
296 collector.notify_exit_call(
297 true,
298 150,
300 [](uint32_t) -> std::vector<FF> { return {}; },
301 []() -> std::vector<PC> { return { 100, 200 }; });
302
303 // Second call in APP_LOGIC phase
304 collector.set_phase(CoarseTransactionPhase::APP_LOGIC);
305 std::vector<FF> calldata2 = { FF(0xbbbb) };
306 CalldataProvider calldata_provider2 = [&calldata2](uint32_t /*max_size*/) -> std::vector<FF> { return calldata2; };
307
308 collector.notify_enter_call(contract2, 200, calldata_provider2, false, Gas{ 500, 1000 });
309 collector.notify_exit_call(
310 true,
311 250,
313 [](uint32_t) -> std::vector<FF> { return {}; },
314 []() -> std::vector<PC> { return { 200, 300 }; });
315
316 // Third call in TEARDOWN phase
317 collector.set_phase(CoarseTransactionPhase::TEARDOWN);
318 std::vector<FF> calldata3 = { FF(0xcccc) };
319 CalldataProvider calldata_provider3 = [&calldata3](uint32_t /*max_size*/) -> std::vector<FF> { return calldata3; };
320
321 collector.notify_enter_call(contract3, 300, calldata_provider3, false, Gas{ 200, 400 });
322 collector.notify_exit_call(
323 true,
324 350,
326 [](uint32_t) -> std::vector<FF> { return {}; },
327 []() -> std::vector<PC> { return { 300, 400 }; });
328
329 auto metadata = collector.dump_call_stack_metadata();
330 ASSERT_EQ(metadata.size(), 3U);
331
332 EXPECT_EQ(metadata[0].phase, CoarseTransactionPhase::SETUP);
333 EXPECT_EQ(metadata[0].halting_message, std::nullopt);
334 EXPECT_EQ(metadata[1].phase, CoarseTransactionPhase::APP_LOGIC);
335 EXPECT_EQ(metadata[1].halting_message, std::nullopt);
336 EXPECT_EQ(metadata[2].phase, CoarseTransactionPhase::TEARDOWN);
337 EXPECT_EQ(metadata[2].halting_message, std::nullopt);
338}
339
340// Test helper functions
341class MakeProviderTest : public ::testing::Test {
342 protected:
343 StrictMock<MockContext> mock_context;
344};
345
346TEST_F(MakeProviderTest, MakeCalldataProviderSuccess)
347{
348 uint32_t cd_size = 5;
349 std::vector<MemoryValue> calldata_values = {
350 MemoryValue::from<FF>(FF(0xaaaa)), MemoryValue::from<FF>(FF(0xbbbb)), MemoryValue::from<FF>(FF(0xcccc)),
351 MemoryValue::from<FF>(FF(0xdddd)), MemoryValue::from<FF>(FF(0xeeee)),
352 };
353
354 // This is called when creating the provider
355 EXPECT_CALL(mock_context, get_parent_cd_size()).WillOnce(Return(cd_size));
356
357 auto provider = make_calldata_provider(mock_context);
358
359 // This is called when invoking the provider.
360 // get_calldata handles offsetting into parent memory for nested contexts internally.
361 EXPECT_CALL(mock_context, get_calldata(0, cd_size)).WillOnce(Return(calldata_values));
362
363 auto result = provider(1024);
364
365 EXPECT_THAT(result, ElementsAre(FF(0xaaaa), FF(0xbbbb), FF(0xcccc), FF(0xdddd), FF(0xeeee)));
366}
367
368TEST_F(MakeProviderTest, MakeCalldataProviderRespectsMaxSize)
369{
370 uint32_t cd_size = 2000; // Larger than max_size
371 uint32_t max_size = 10;
372 std::vector<MemoryValue> calldata_values(2000);
373 for (size_t i = 0; i < 2000; ++i) {
374 calldata_values[i] = MemoryValue::from<FF>(FF(i));
375 }
376
377 // This is called when creating the provider
378 EXPECT_CALL(mock_context, get_parent_cd_size()).WillOnce(Return(cd_size));
379
380 auto provider = make_calldata_provider(mock_context);
381
382 // This is called when invoking the provider
383 EXPECT_CALL(mock_context, get_calldata(0, max_size)).WillOnce([&calldata_values, max_size](uint32_t, uint32_t) {
384 return std::vector<MemoryValue>(calldata_values.begin(), calldata_values.begin() + max_size);
385 });
386
387 auto result = provider(max_size);
388
389 ASSERT_EQ(result.size(), max_size);
390}
391
392TEST_F(MakeProviderTest, MakeReturnDataProviderSuccess)
393{
394 uint32_t rd_addr = 200;
395 uint32_t rd_size = 3;
396 std::vector<MemoryValue> return_data_values = {
397 MemoryValue::from<FF>(FF(0x1111)),
398 MemoryValue::from<FF>(FF(0x2222)),
399 MemoryValue::from<FF>(FF(0x3333)),
400 };
401 auto zero = MemoryValue::from<FF>(FF(0));
402
403 auto provider = make_return_data_provider(mock_context, rd_addr, rd_size);
404
405 // This is called when invoking the provider
406 StrictMock<MockMemory> memory;
407 EXPECT_CALL(::testing::Const(mock_context), get_memory()).WillOnce(ReturnRef(memory));
408 ON_CALL(memory, get)
409 .WillByDefault([&return_data_values, rd_addr, rd_size, &zero](uint32_t i) -> const MemoryValue& {
410 // if offset is not in range [rd_addr, rd_addr + rd_size], return 0
411 if (i < rd_addr || i >= rd_addr + rd_size) {
412 return zero;
413 }
414 const auto offset_into_rd = i - rd_addr;
415 return return_data_values[offset_into_rd];
416 });
417 EXPECT_CALL(memory, get).Times(static_cast<int>(rd_size));
418
419 auto result = provider(1024);
420
421 EXPECT_THAT(result, ElementsAre(FF(0x1111), FF(0x2222), FF(0x3333)));
422}
423
424TEST_F(MakeProviderTest, MakeReturnDataProviderRespectsMaxSize)
425{
426 uint32_t rd_addr = 0;
427 uint32_t rd_size = 2000; // Larger than max_size
428 uint32_t max_size = 15;
429 std::vector<MemoryValue> return_data_values(2000);
430 for (size_t i = 0; i < 2000; ++i) {
431 return_data_values[i] = MemoryValue::from<FF>(FF(i * 2));
432 }
433
434 auto provider = make_return_data_provider(mock_context, rd_addr, rd_size);
435
436 // This is called when invoking the provider
437 StrictMock<MockMemory> memory;
438 EXPECT_CALL(::testing::Const(mock_context), get_memory()).WillOnce(ReturnRef(memory));
439 ON_CALL(memory, get).WillByDefault([&return_data_values](uint32_t i) -> const MemoryValue& {
440 return return_data_values[i];
441 });
442 EXPECT_CALL(memory, get).Times(static_cast<int>(max_size));
443
444 auto result = provider(max_size);
445
446 ASSERT_EQ(result.size(), max_size);
447}
448
449} // namespace
450} // namespace bb::avm2::simulation
StrictMock< MockContext > mock_context
std::function< std::vector< PC >()> InternalCallStackProvider
std::function< std::vector< FF >(uint32_t max_size)> CalldataProvider
std::function< std::vector< FF >(uint32_t max_size)> ReturnDataProvider
CalldataProvider make_calldata_provider(const ContextInterface &context)
ReturnDataProvider make_return_data_provider(const ContextInterface &context, uint32_t rd_mem_offset_in_child, uint32_t rd_size)
uint32_t PC
TaggedValue MemoryValue
AvmFlavorSettings::FF FF
Definition field.hpp:10
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:142
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< MemoryValue > calldata
MemoryStore memory