Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
data_copy.test.cpp
Go to the documentation of this file.
2
3#include <cstdint>
4#include <gmock/gmock.h>
5#include <gtest/gtest.h>
6
31
32namespace bb::avm2::constraining {
33namespace {
34
35using namespace simulation;
36using ::testing::Return;
37using ::testing::ReturnRef;
38using ::testing::StrictMock;
39using tracegen::DataCopyTraceBuilder;
40using tracegen::ExecutionTraceBuilder;
41using tracegen::TestTraceContainer;
42
44using C = Column;
46
47class DataCopyConstrainingBuilderTest : public ::testing::Test {
48 protected:
49 DataCopyConstrainingBuilderTest() { EXPECT_CALL(context, get_memory()).WillRepeatedly(ReturnRef(mem)); }
50
51 ExecutionIdManager execution_id_manager = ExecutionIdManager(0);
52 EventEmitter<RangeCheckEvent> range_check_event_emitter;
53 RangeCheck range_check = RangeCheck(range_check_event_emitter);
54 EventEmitter<GreaterThanEvent> gt_event_emitter;
55 StrictMock<MockFieldGreaterThan> mock_field_gt;
56 GreaterThan gt = GreaterThan(mock_field_gt, range_check, gt_event_emitter);
57 EventEmitter<DataCopyEvent> event_emitter;
58 DataCopy copy_data = DataCopy(execution_id_manager, gt, event_emitter);
59 StrictMock<MockContext> context;
60
61 MemoryStore mem;
62
63 TestTraceContainer trace = TestTraceContainer({
64 {
65 { C::precomputed_first_row, 1 },
66 },
67 });
68
69 uint32_t dst_addr = 0; // Destination address in memory for the data.
71 MemoryValue::from<FF>(1), MemoryValue::from<FF>(2), MemoryValue::from<FF>(3), MemoryValue::from<FF>(4),
72 MemoryValue::from<FF>(5), MemoryValue::from<FF>(6), MemoryValue::from<FF>(7), MemoryValue::from<FF>(8),
73 };
74};
75
76class NestedCdConstrainingBuilderTest : public DataCopyConstrainingBuilderTest {
77 protected:
78 NestedCdConstrainingBuilderTest()
79 {
80 // Set up parent context
81 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
82 EXPECT_CALL(context, get_parent_id).WillRepeatedly(Return(1));
83 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(2));
84 EXPECT_CALL(context, get_parent_cd_size).WillRepeatedly(Return(data.size()));
85 EXPECT_CALL(context, get_parent_cd_addr).WillRepeatedly(Return(0));
86 }
87};
88
89TEST_F(NestedCdConstrainingBuilderTest, CdZeroCopy)
90{
91 uint32_t copy_size = 0;
92 uint32_t cd_offset = 0; // Offset into calldata
93
94 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
95
96 tracegen::DataCopyTraceBuilder builder;
97 builder.process(event_emitter.dump_events(), trace);
98
99 tracegen::GreaterThanTraceBuilder gt_builder;
100 gt_builder.process(gt_event_emitter.dump_events(), trace);
101
102 check_relation<data_copy>(trace);
103 check_interaction<DataCopyTraceBuilder,
108}
109
110TEST_F(NestedCdConstrainingBuilderTest, SimpleNestedCdCopy)
111{
112 uint32_t copy_size = static_cast<uint32_t>(data.size());
113 uint32_t cd_offset = 0; // Offset into calldata
114
115 EXPECT_CALL(context, get_calldata(cd_offset, copy_size)).WillOnce(Return(data));
116
117 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
118
119 DataCopyTraceBuilder builder;
120 builder.process(event_emitter.dump_events(), trace);
121
122 tracegen::GreaterThanTraceBuilder gt_builder;
123 gt_builder.process(gt_event_emitter.dump_events(), trace);
124
125 check_relation<data_copy>(trace);
126 check_interaction<DataCopyTraceBuilder,
131}
132
133// Copying one element tests the case where the trace populates a single row
134// where both start and end are toggled on but is a different code path
135// in tracegen than with copy_size == 0.
136TEST_F(NestedCdConstrainingBuilderTest, SimpleNestedCdCopySizeOneNoPadding)
137{
138 uint32_t copy_size = 1;
139 uint32_t cd_offset = static_cast<uint32_t>(data.size() - 1);
140
141 std::vector<MemoryValue> result_cd = { data.begin() + cd_offset, data.begin() + cd_offset + copy_size };
142
143 EXPECT_CALL(context, get_calldata(cd_offset, copy_size)).WillOnce(Return(result_cd));
144
145 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
146
147 DataCopyTraceBuilder builder;
148 builder.process(event_emitter.dump_events(), trace);
149
150 tracegen::GreaterThanTraceBuilder gt_builder;
151 gt_builder.process(gt_event_emitter.dump_events(), trace);
152
153 check_relation<data_copy>(trace);
154 check_interaction<DataCopyTraceBuilder,
159}
160
161TEST_F(NestedCdConstrainingBuilderTest, SimpleNestedCdCopySizeOneWithPadding)
162{
163 uint32_t copy_size = 1;
164 uint32_t cd_offset = static_cast<uint32_t>(data.size());
165
166 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
167
168 DataCopyTraceBuilder builder;
169 builder.process(event_emitter.dump_events(), trace);
170
171 tracegen::GreaterThanTraceBuilder gt_builder;
173
174 check_relation<data_copy>(trace);
175 check_interaction<DataCopyTraceBuilder,
180}
181
182TEST_F(NestedCdConstrainingBuilderTest, NestedCdCopyPadded)
183{
184 uint32_t cd_offset = 0;
185 uint32_t copy_size = 10; // Request more than available
186
187 // effective_reads = min(0+10, 8) - 0 = 8 (clamped by src_data_size)
188 uint32_t effective_reads = static_cast<uint32_t>(data.size());
189
190 EXPECT_CALL(context, get_calldata(cd_offset, effective_reads)).WillOnce(Return(data));
191
192 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
193
194 DataCopyTraceBuilder builder;
195 builder.process(event_emitter.dump_events(), trace);
196
197 tracegen::GreaterThanTraceBuilder gt_builder;
198 gt_builder.process(gt_event_emitter.dump_events(), trace);
199
200 check_relation<data_copy>(trace);
201 check_interaction<DataCopyTraceBuilder,
206}
207
208TEST_F(NestedCdConstrainingBuilderTest, NestedCdCopyPartial)
209{
210 uint32_t offset = 3;
211 uint32_t size = 4;
212
213 // Starting at offset = 3
214 std::vector<MemoryValue> result_cd = { data.begin() + offset, data.begin() + offset + size };
215
216 EXPECT_CALL(context, get_calldata(offset, size)).WillOnce(Return(result_cd));
217
218 copy_data.cd_copy(context, size, offset, dst_addr);
219
220 DataCopyTraceBuilder builder;
221 builder.process(event_emitter.dump_events(), trace);
222
223 tracegen::GreaterThanTraceBuilder gt_builder;
224 gt_builder.process(gt_event_emitter.dump_events(), trace);
225
226 check_relation<data_copy>(trace);
227 check_interaction<DataCopyTraceBuilder,
232}
233
234TEST_F(NestedCdConstrainingBuilderTest, ZeroCopySizeOffsetOOB)
235{
236 uint32_t offset = static_cast<uint32_t>(data.size()) + 1;
237 uint32_t size = 0;
238
239 // No call to get_calldata since offset is out of bounds
240 // Therefore, no need for an EXPECT_CALL(context, get_calldata(offset, size)).WillOnce(Return(result_cd));
241
242 copy_data.cd_copy(context, size, offset, dst_addr);
243
244 DataCopyTraceBuilder builder;
245 builder.process(event_emitter.dump_events(), trace);
246
247 tracegen::GreaterThanTraceBuilder gt_builder;
249
250 check_relation<data_copy>(trace);
251 check_interaction<DataCopyTraceBuilder,
256}
257
258TEST_F(NestedCdConstrainingBuilderTest, NonZeroCopySizeOffsetOOB)
259{
260 uint32_t offset = static_cast<uint32_t>(data.size()) + 1;
261 uint32_t size = 4;
262
263 // No call to get_calldata since offset is out of bounds
264 // Therefore, no need for an EXPECT_CALL(context, get_calldata(offset, size)).WillOnce(Return(result_cd));
265
266 copy_data.cd_copy(context, size, offset, dst_addr);
267
268 DataCopyTraceBuilder builder;
269 builder.process(event_emitter.dump_events(), trace);
270
271 tracegen::GreaterThanTraceBuilder gt_builder;
273
274 check_relation<data_copy>(trace);
275 check_interaction<DataCopyTraceBuilder,
280}
281
282TEST_F(NestedCdConstrainingBuilderTest, OutofRangeError)
283{
284 uint32_t offset = 10; // Offset beyond the size of calldata
285 uint32_t size = 4;
286
287 uint32_t big_dst_addr = AVM_HIGHEST_MEM_ADDRESS - 1;
288 EXPECT_THROW_WITH_MESSAGE(copy_data.cd_copy(context, size, offset, big_dst_addr),
289 "Attempting to write out of bounds memory");
290
291 DataCopyTraceBuilder builder;
292 builder.process(event_emitter.dump_events(), trace);
293
294 tracegen::GreaterThanTraceBuilder gt_builder;
295 gt_builder.process(gt_event_emitter.dump_events(), trace);
296
297 check_relation<data_copy>(trace);
298 check_interaction<DataCopyTraceBuilder,
303}
304
305TEST_F(NestedCdConstrainingBuilderTest, HighestMemoryAddressesWithPadding)
306{
307 uint32_t offset = static_cast<uint32_t>(data.size() - 1); // Last offset in calldata valid range
308 uint32_t size = 5; // Some padding will be needed
309
310 uint32_t high_dst_addr = AVM_HIGHEST_MEM_ADDRESS - size + 1;
311
312 // effective_reads = clamped - offset = data.size() - offset = 1
313 uint32_t effective_reads = static_cast<uint32_t>(data.size()) - offset;
314 std::vector<MemoryValue> result_cd = { data.at(offset) };
315
316 EXPECT_CALL(context, get_calldata(offset, effective_reads)).WillOnce(Return(result_cd));
317
318 copy_data.cd_copy(context, size, offset, high_dst_addr);
319
320 DataCopyTraceBuilder builder;
321 builder.process(event_emitter.dump_events(), trace);
322
323 tracegen::GreaterThanTraceBuilder gt_builder;
324 gt_builder.process(gt_event_emitter.dump_events(), trace);
325
326 check_relation<data_copy>(trace);
327 check_interaction<DataCopyTraceBuilder,
332}
333
334TEST_F(NestedCdConstrainingBuilderTest, HighestMemoryAddressesNoPadding)
335{
336 uint32_t offset = 0;
337 uint32_t size = static_cast<uint32_t>(data.size()) - 2;
338
339 uint32_t high_dst_addr = AVM_HIGHEST_MEM_ADDRESS - size + 1;
340 std::vector<MemoryValue> result_cd(data.begin(), data.begin() + size);
341
342 EXPECT_CALL(context, get_calldata(offset, size)).WillOnce(Return(result_cd));
343
344 copy_data.cd_copy(context, size, offset, high_dst_addr);
345
346 DataCopyTraceBuilder builder;
347 builder.process(event_emitter.dump_events(), trace);
348
349 tracegen::GreaterThanTraceBuilder gt_builder;
350 gt_builder.process(gt_event_emitter.dump_events(), trace);
351
352 check_relation<data_copy>(trace);
353 check_interaction<DataCopyTraceBuilder,
358}
359
360class HighCdAddressConstrainingBuilderTest : public DataCopyConstrainingBuilderTest {
361 protected:
362 HighCdAddressConstrainingBuilderTest()
363 {
364 // Set up parent context
365 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
366 EXPECT_CALL(context, get_parent_id).WillRepeatedly(Return(1));
367 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(2));
368 EXPECT_CALL(context, get_parent_cd_size).WillRepeatedly(Return(data.size()));
369 EXPECT_CALL(context, get_parent_cd_addr).WillRepeatedly(Return(AVM_HIGHEST_MEM_ADDRESS - data.size()));
370 }
371};
372
373TEST_F(HighCdAddressConstrainingBuilderTest, HighestMemoryAddressesWithPadding)
374{
375 uint32_t offset = static_cast<uint32_t>(data.size() - 1); // Last offset in calldata valid range
376 uint32_t size = 5; // Some padding will be needed
377
378 uint32_t high_dst_addr = AVM_HIGHEST_MEM_ADDRESS - size + 1;
379
380 // effective_reads = clamped - offset = data.size() - offset = 1
381 uint32_t effective_reads = static_cast<uint32_t>(data.size()) - offset;
382 std::vector<MemoryValue> result_cd = { data.at(offset) };
383
384 EXPECT_CALL(context, get_calldata(offset, effective_reads)).WillOnce(Return(result_cd));
385
386 copy_data.cd_copy(context, size, offset, high_dst_addr);
387
388 DataCopyTraceBuilder builder;
389 builder.process(event_emitter.dump_events(), trace);
390
391 tracegen::GreaterThanTraceBuilder gt_builder;
392 gt_builder.process(gt_event_emitter.dump_events(), trace);
393
394 check_relation<data_copy>(trace);
395 check_interaction<DataCopyTraceBuilder,
400}
401
402TEST_F(HighCdAddressConstrainingBuilderTest, HighestMemoryAddressesNoPadding)
403{
404 uint32_t offset = 0;
405 uint32_t size = static_cast<uint32_t>(data.size()) - 2;
406
407 uint32_t high_dst_addr = AVM_HIGHEST_MEM_ADDRESS - size + 1;
408 std::vector<MemoryValue> result_cd(data.begin(), data.begin() + size);
409
410 EXPECT_CALL(context, get_calldata(offset, size)).WillOnce(Return(result_cd));
411
412 copy_data.cd_copy(context, size, offset, high_dst_addr);
413
414 DataCopyTraceBuilder builder;
415 builder.process(event_emitter.dump_events(), trace);
416
417 tracegen::GreaterThanTraceBuilder gt_builder;
418 gt_builder.process(gt_event_emitter.dump_events(), trace);
419
420 check_relation<data_copy>(trace);
421 check_interaction<DataCopyTraceBuilder,
426}
427
428// Test that src reads exceeding memory are clamped (not an error).
429class SrcExceedsMemConstrainingBuilderTest : public DataCopyConstrainingBuilderTest {
430 protected:
431 SrcExceedsMemConstrainingBuilderTest()
432 {
433 // Set up parent context with cd_addr near end of memory space.
434 // src_addr + src_data_size > AVM_MEMORY_SIZE, but dst writes are fine.
435 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
436 EXPECT_CALL(context, get_parent_id).WillRepeatedly(Return(1));
437 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(2));
438 EXPECT_CALL(context, get_parent_cd_size).WillRepeatedly(Return(src_data_size));
439 EXPECT_CALL(context, get_parent_cd_addr).WillRepeatedly(Return(src_addr));
440 }
441
442 // 3 slots fit in memory before AVM_MEMORY_SIZE, but parent claims 8.
444 uint32_t src_data_size = 8;
445};
446
447TEST_F(SrcExceedsMemConstrainingBuilderTest, SrcOutOfRangeClampedNotAnError)
448{
449 uint32_t offset = 0;
450 uint32_t size = 5;
451
452 // read_index_upper_bound = min(5, 8) = 5
453 // read_addr_upper_bound = (AVM_HIGHEST_MEM_ADDRESS - 2) + 5 > AVM_MEMORY_SIZE
454 // clamped = AVM_MEMORY_SIZE - src_addr = 3
455 // effective_reads = 3 - 0 = 3
456 uint32_t effective_reads = 3;
457 std::vector<MemoryValue> result_cd(data.begin(), data.begin() + effective_reads);
458
459 EXPECT_CALL(context, get_calldata(offset, effective_reads)).WillOnce(Return(result_cd));
460
461 // Should NOT throw — src out of range is handled by clamping.
463
464 DataCopyTraceBuilder builder;
465 builder.process(event_emitter.dump_events(), trace);
466
467 tracegen::GreaterThanTraceBuilder gt_builder;
469
470 check_relation<data_copy>(trace);
471 check_interaction<DataCopyTraceBuilder,
476}
477
478// Src out-of-range with non-zero offset: offset interacts correctly with clamping.
479TEST_F(SrcExceedsMemConstrainingBuilderTest, SrcOutOfRangeWithOffset)
480{
481 uint32_t offset = 1;
482 uint32_t size = 4;
483
484 // read_index_upper_bound = min(1+4, 8) = 5
485 // read_addr_upper_bound = (AVM_HIGHEST_MEM_ADDRESS - 2) + 5 > AVM_MEMORY_SIZE
486 // clamped = AVM_MEMORY_SIZE - src_addr = 3
487 // effective_reads = 3 - 1 = 2
488 uint32_t effective_reads = 2;
489 std::vector<MemoryValue> result_cd(data.begin() + offset, data.begin() + offset + effective_reads);
490
491 EXPECT_CALL(context, get_calldata(offset, effective_reads)).WillOnce(Return(result_cd));
492
493 copy_data.cd_copy(context, size, offset, dst_addr);
494
495 DataCopyTraceBuilder builder;
496 builder.process(event_emitter.dump_events(), trace);
497
498 tracegen::GreaterThanTraceBuilder gt_builder;
499 gt_builder.process(gt_event_emitter.dump_events(), trace);
500
501 check_relation<data_copy>(trace);
502 check_interaction<DataCopyTraceBuilder,
507}
508
509// Src out-of-range where clamped < offset: all rows are padding (sel_has_reads = 0).
510TEST_F(SrcExceedsMemConstrainingBuilderTest, SrcOutOfRangeClampedBelowOffset)
511{
512 uint32_t offset = 5;
513 uint32_t size = 3;
514
515 // read_index_upper_bound = min(5+3, 8) = 8
516 // read_addr_upper_bound = (AVM_HIGHEST_MEM_ADDRESS - 2) + 8 > AVM_MEMORY_SIZE
517 // clamped = AVM_MEMORY_SIZE - src_addr = 3
518 // clamped(3) < offset(5), so reads_left = 0, all padding
519
520 // No get_calldata call — all padding
521 copy_data.cd_copy(context, size, offset, dst_addr);
522
523 DataCopyTraceBuilder builder;
524 builder.process(event_emitter.dump_events(), trace);
525
526 tracegen::GreaterThanTraceBuilder gt_builder;
527 gt_builder.process(gt_event_emitter.dump_events(), trace);
528
529 check_relation<data_copy>(trace);
530 check_interaction<DataCopyTraceBuilder,
535}
536
537// Src out-of-range AND dst out-of-range simultaneously: should still error on dst.
538TEST_F(SrcExceedsMemConstrainingBuilderTest, SrcAndDstBothOutOfRange)
539{
540 uint32_t offset = 0;
541 uint32_t size = 4;
542 uint32_t big_dst_addr = AVM_HIGHEST_MEM_ADDRESS - 1;
543
544 // src_addr + min(4,8) = (AVM_HIGHEST_MEM_ADDRESS-2) + 4 > AVM_MEMORY_SIZE (src overflow)
545 // big_dst_addr + 4 > AVM_MEMORY_SIZE (dst overflow)
546 // Should throw on dst overflow.
547 EXPECT_THROW_WITH_MESSAGE(copy_data.cd_copy(context, size, offset, big_dst_addr),
548 "Attempting to write out of bounds memory");
549
550 DataCopyTraceBuilder builder;
551 builder.process(event_emitter.dump_events(), trace);
552
553 tracegen::GreaterThanTraceBuilder gt_builder;
554 gt_builder.process(gt_event_emitter.dump_events(), trace);
555
556 check_relation<data_copy>(trace);
557 check_interaction<DataCopyTraceBuilder,
562}
563
564class EnqueuedCdConstrainingBuilderTest : public DataCopyConstrainingBuilderTest {
565 protected:
566 EnqueuedCdConstrainingBuilderTest()
567 {
568 // Set up for enqueued call
569 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(false));
570 EXPECT_CALL(context, get_parent_id).WillRepeatedly(Return(0));
571 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(1));
572 EXPECT_CALL(context, get_parent_cd_size).WillRepeatedly(Return(data.size()));
573 EXPECT_CALL(context, get_parent_cd_addr).WillRepeatedly(Return(0));
574
575 // Build Calldata Column
576 tracegen::CalldataTraceBuilder calldata_builder;
577 std::vector<FF> calldata_ff(data.size());
578 std::ranges::transform(
579 data.begin(), data.end(), calldata_ff.begin(), [](const MemoryValue& value) { return value.as_ff(); });
580
581 CalldataEvent cd_event = {
582 .context_id = 1,
583 .calldata = calldata_ff,
584 };
585 calldata_builder.process_retrieval({ cd_event }, trace);
586 }
587};
588
589TEST_F(EnqueuedCdConstrainingBuilderTest, CdZeroCopy)
590{
591 uint32_t copy_size = 0;
592 uint32_t cd_offset = 0; // Offset into calldata
593
594 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
595
596 tracegen::DataCopyTraceBuilder builder;
597 builder.process(event_emitter.dump_events(), trace);
598
599 tracegen::GreaterThanTraceBuilder gt_builder;
600 gt_builder.process(gt_event_emitter.dump_events(), trace);
601
602 check_relation<data_copy>(trace);
603 check_all_interactions<DataCopyTraceBuilder>(trace);
604}
605
606TEST_F(EnqueuedCdConstrainingBuilderTest, SimpleEnqueuedCdCopy)
607{
608 auto copy_size = static_cast<uint32_t>(data.size());
609 uint32_t cd_offset = 0;
610
611 EXPECT_CALL(context, get_calldata(cd_offset, copy_size)).WillOnce(Return(data));
612
613 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
614
615 DataCopyTraceBuilder builder;
616 builder.process(event_emitter.dump_events(), trace);
617
618 tracegen::GreaterThanTraceBuilder gt_builder;
619 gt_builder.process(gt_event_emitter.dump_events(), trace);
620
621 check_relation<data_copy>(trace);
622 check_all_interactions<DataCopyTraceBuilder>(trace);
623}
624
625TEST_F(EnqueuedCdConstrainingBuilderTest, EnqueuedCallCdCopyPadding)
626{
627 uint32_t cd_offset = 0;
628 uint32_t copy_size = 10; // Request more than available
629 // effective_reads = min(0+10, 8) - 0 = 8
630 uint32_t effective_reads = static_cast<uint32_t>(data.size());
631
632 EXPECT_CALL(context, get_calldata(cd_offset, effective_reads)).WillOnce(Return(data));
633
634 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
635
636 DataCopyTraceBuilder builder;
637 builder.process(event_emitter.dump_events(), trace);
638
639 tracegen::GreaterThanTraceBuilder gt_builder;
640 gt_builder.process(gt_event_emitter.dump_events(), trace);
641
642 check_relation<data_copy>(trace);
643 check_all_interactions<DataCopyTraceBuilder>(trace);
644}
645
646TEST_F(EnqueuedCdConstrainingBuilderTest, EnqueuedCallCdCopyPartial)
647{
648 uint32_t offset = 3;
649 uint32_t size = 4;
650
651 // Starting at offset = 3
652 std::vector<MemoryValue> result_cd = { data.begin() + offset, data.begin() + offset + size };
653
654 EXPECT_CALL(context, get_calldata(offset, size)).WillOnce(Return(result_cd));
655
656 copy_data.cd_copy(context, size, offset, dst_addr);
657
658 DataCopyTraceBuilder builder;
659 builder.process(event_emitter.dump_events(), trace);
660
661 tracegen::GreaterThanTraceBuilder gt_builder;
662 gt_builder.process(gt_event_emitter.dump_events(), trace);
663
664 check_relation<data_copy>(trace);
665 check_all_interactions<DataCopyTraceBuilder>(trace);
666}
667
668class EnqueuedEmptyCdConstrainingBuilderTest : public DataCopyConstrainingBuilderTest {
669 protected:
670 EnqueuedEmptyCdConstrainingBuilderTest()
671 {
672 // Set up for enqueued call
673 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(false));
674 EXPECT_CALL(context, get_parent_id).WillRepeatedly(Return(0));
675 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(1));
676 EXPECT_CALL(context, get_parent_cd_size).WillRepeatedly(Return(0));
677 EXPECT_CALL(context, get_parent_cd_addr).WillRepeatedly(Return(0));
678
679 // Build Calldata Column
680 tracegen::CalldataTraceBuilder calldata_builder;
681 CalldataEvent cd_event = {
682 .context_id = 1,
683 .calldata = {},
684 };
685 calldata_builder.process_retrieval({ cd_event }, trace);
686 }
687};
688
689TEST_F(EnqueuedEmptyCdConstrainingBuilderTest, CdZeroCopy)
690{
691 uint32_t copy_size = 0;
692 uint32_t cd_offset = 0; // Offset into calldata
693
694 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
695
696 tracegen::DataCopyTraceBuilder builder;
697 builder.process(event_emitter.dump_events(), trace);
698
699 tracegen::GreaterThanTraceBuilder gt_builder;
700 gt_builder.process(gt_event_emitter.dump_events(), trace);
701
702 check_relation<data_copy>(trace);
703 check_all_interactions<DataCopyTraceBuilder>(trace);
704}
705
706TEST_F(EnqueuedEmptyCdConstrainingBuilderTest, SimpleEnqueuedCdCopy)
707{
708 uint32_t copy_size = 4;
709 uint32_t cd_offset = 0;
710
711 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
712
713 DataCopyTraceBuilder builder;
714 builder.process(event_emitter.dump_events(), trace);
715
716 tracegen::GreaterThanTraceBuilder gt_builder;
717 gt_builder.process(gt_event_emitter.dump_events(), trace);
718
719 check_relation<data_copy>(trace);
720 check_all_interactions<DataCopyTraceBuilder>(trace);
721}
722
723TEST_F(EnqueuedEmptyCdConstrainingBuilderTest, EnqueuedCallCdCopyPadding)
724{
725 uint32_t cd_offset = 0;
726 std::vector<FF> result_cd = {};
727 result_cd.resize(10, 0); // Pad with zeros to 10 elements
728 auto copy_size = static_cast<uint32_t>(result_cd.size()); // Request more than available
729
730 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
731
732 DataCopyTraceBuilder builder;
733 builder.process(event_emitter.dump_events(), trace);
734
735 tracegen::GreaterThanTraceBuilder gt_builder;
737
738 check_relation<data_copy>(trace);
739 check_all_interactions<DataCopyTraceBuilder>(trace);
740}
741
743// DataCopy Tests with Execution Permutation
745
746TEST(DataCopyWithExecutionPerm, CdCopy)
747{
748 // Current Context
749 uint32_t context_id = 2;
750 uint32_t cd_offset = 3;
751 uint32_t copy_size = 4;
752 MemoryAddress dst_addr = 0xdeadbeef; // Destination address in memory for the data.
753 // Parent Context
754 uint32_t parent_context_id = 99; // Parent context ID
755 uint32_t parent_cd_addr = 0xc0ffee; // Parent calldata address in memory.
757 MemoryValue::from<FF>(8), MemoryValue::from<FF>(7), MemoryValue::from<FF>(6), MemoryValue::from<FF>(5),
758 MemoryValue::from<FF>(4), MemoryValue::from<FF>(3), MemoryValue::from<FF>(2), MemoryValue::from<FF>(1),
759 };
760
761 // Set up Memory
762 MemoryStore mem(static_cast<uint16_t>(context_id));
763
764 // Execution clk is 0 for this test
765 StrictMock<MockExecutionIdManager> execution_id_manager;
766 EXPECT_CALL(execution_id_manager, get_execution_id()).WillOnce(Return(0));
767
768 // Mock current context
769 StrictMock<MockContext> context;
770 EXPECT_CALL(context, get_memory()).WillRepeatedly(ReturnRef(mem));
771 EXPECT_CALL(context, get_parent_cd_size).WillRepeatedly(Return(data.size()));
772 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
773 EXPECT_CALL(context, get_parent_cd_addr).WillRepeatedly(Return(parent_cd_addr));
774 EXPECT_CALL(context, get_calldata(cd_offset, copy_size))
775 .WillRepeatedly(::testing::Invoke([&data, cd_offset, copy_size]() {
776 // Return a slice of data from the calldata
777 return std::vector<MemoryValue>(data.begin() + cd_offset, data.begin() + cd_offset + copy_size);
778 }));
779 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(context_id));
780 EXPECT_CALL(context, get_parent_id).WillRepeatedly(Return(parent_context_id));
781
782 PureGreaterThan gt;
783
784 EventEmitter<DataCopyEvent> event_emitter;
785 DataCopy copy_data = DataCopy(execution_id_manager, gt, event_emitter);
786 // Set up execution trace
787 TestTraceContainer trace({
788 {
789 { C::precomputed_first_row, 1 },
790 { C::execution_sel, 1 },
791 { C::execution_context_id, context_id },
792 { C::execution_parent_id, parent_context_id },
793 { C::execution_sel_exec_dispatch_calldata_copy, 1 },
794 { C::execution_register_0_, copy_size },
795 { C::execution_register_1_, cd_offset },
796 { C::execution_rop_2_, dst_addr },
797 { C::execution_sel_opcode_error, 0 },
798 { C::execution_parent_calldata_addr, parent_cd_addr },
799 { C::execution_parent_calldata_size, static_cast<uint32_t>(data.size()) },
800 },
801 });
802
803 copy_data.cd_copy(context, copy_size, cd_offset, dst_addr);
804
805 DataCopyTraceBuilder builder;
806 builder.process(event_emitter.dump_events(), trace);
807
808 check_relation<data_copy>(trace);
809 check_interaction<ExecutionTraceBuilder,
812}
813
814class NestedRdConstrainingBuilderTest : public DataCopyConstrainingBuilderTest {
815 protected:
816 NestedRdConstrainingBuilderTest()
817 {
818 // Set up parent context
819 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
820 EXPECT_CALL(context, get_last_child_id).WillRepeatedly(Return(2));
821 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(2));
822 EXPECT_CALL(context, get_last_rd_size).WillRepeatedly(Return(data.size()));
823 EXPECT_CALL(context, get_last_rd_addr).WillRepeatedly(Return(0));
824 }
825};
826
827TEST_F(NestedRdConstrainingBuilderTest, RdZeroCopy)
828{
829 uint32_t copy_size = 0;
830 uint32_t rd_offset = 0; // Offset into calldata
831
832 copy_data.rd_copy(context, copy_size, rd_offset, dst_addr);
833
834 tracegen::DataCopyTraceBuilder builder;
835 builder.process(event_emitter.dump_events(), trace);
836
837 tracegen::GreaterThanTraceBuilder gt_builder;
838 gt_builder.process(gt_event_emitter.dump_events(), trace);
839
840 check_relation<data_copy>(trace);
841 check_all_interactions<DataCopyTraceBuilder>(trace);
842}
843
844// Rd copy with src reads exceeding memory — clamped, not an error.
845class SrcExceedsMemRdConstrainingBuilderTest : public DataCopyConstrainingBuilderTest {
846 protected:
847 SrcExceedsMemRdConstrainingBuilderTest()
848 {
849 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
850 EXPECT_CALL(context, get_last_child_id).WillRepeatedly(Return(2));
851 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(2));
852 EXPECT_CALL(context, get_last_rd_size).WillRepeatedly(Return(rd_data_size));
853 EXPECT_CALL(context, get_last_rd_addr).WillRepeatedly(Return(rd_addr));
854 }
855
856 // 2 slots fit in memory before AVM_MEMORY_SIZE, but child claims 6.
858 uint32_t rd_data_size = 6;
859};
860
861TEST_F(SrcExceedsMemRdConstrainingBuilderTest, SrcOutOfRangeClampedNotAnError)
862{
863 uint32_t offset = 0;
864 uint32_t size = 4;
865
866 // read_index_upper_bound = min(4, 6) = 4
867 // read_addr_upper_bound = (AVM_HIGHEST_MEM_ADDRESS - 1) + 4 > AVM_MEMORY_SIZE
868 // clamped = AVM_MEMORY_SIZE - rd_addr = 2
869 // effective_reads = 2 - 0 = 2
870 uint32_t effective_reads = 2;
871 std::vector<MemoryValue> result_rd(data.begin(), data.begin() + effective_reads);
872
873 EXPECT_CALL(context, get_returndata(offset, effective_reads)).WillOnce(Return(result_rd));
874
876
877 DataCopyTraceBuilder builder;
878 builder.process(event_emitter.dump_events(), trace);
879
880 tracegen::GreaterThanTraceBuilder gt_builder;
882
883 check_relation<data_copy>(trace);
884 check_all_interactions<DataCopyTraceBuilder>(trace);
885}
886
887TEST(DataCopyWithExecutionPerm, RdCopy)
888{
889 // Current Context
890 uint32_t context_id = 2;
891 uint32_t rd_offset = 3;
892 uint32_t copy_size = 4;
893 MemoryAddress dst_addr = 0xdeadbeef; // Destination address in memory for the data.
894 // Child Context
895 uint32_t child_context_id = 1; // Child context ID
896 MemoryAddress child_rd_addr = 0xc0ffee; // Child returndata address in memory.
898 MemoryValue::from<FF>(1), MemoryValue::from<FF>(2), MemoryValue::from<FF>(3), MemoryValue::from<FF>(4),
899 MemoryValue::from<FF>(5), MemoryValue::from<FF>(6), MemoryValue::from<FF>(7), MemoryValue::from<FF>(8),
900 };
901
902 // Set up Memory
903 MemoryStore mem;
904
905 StrictMock<MockExecutionIdManager> execution_id_manager;
906 EXPECT_CALL(execution_id_manager, get_execution_id()).WillOnce(Return(0));
907 StrictMock<MockContext> context;
908 EXPECT_CALL(context, get_memory()).WillRepeatedly(ReturnRef(mem));
909 EXPECT_CALL(context, get_last_rd_size).WillRepeatedly(Return(data.size()));
910 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
911 EXPECT_CALL(context, get_last_rd_addr).WillRepeatedly(Return(child_rd_addr));
912 EXPECT_CALL(context, get_returndata(rd_offset, copy_size))
913 .WillRepeatedly(::testing::Invoke([&data, rd_offset, copy_size]() {
914 // Return a slice of data from the calldata
915 return std::vector<MemoryValue>(data.begin() + rd_offset, data.begin() + rd_offset + copy_size);
916 }));
917 EXPECT_CALL(context, get_last_child_id).WillRepeatedly(Return(child_context_id));
918 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(context_id));
919
920 PureGreaterThan gt;
921
922 EventEmitter<DataCopyEvent> event_emitter;
923 DataCopy copy_data = DataCopy(execution_id_manager, gt, event_emitter);
924 // Set up execution trace
925 TestTraceContainer trace({
926 {
927 { C::precomputed_first_row, 1 },
928 { C::execution_sel, 1 },
929 { C::execution_context_id, context_id },
930 { C::execution_last_child_id, child_context_id },
931 { C::execution_sel_exec_dispatch_returndata_copy, 1 },
932 { C::execution_register_0_, copy_size },
933 { C::execution_register_1_, rd_offset },
934 { C::execution_rop_2_, dst_addr },
935 { C::execution_sel_opcode_error, 0 },
936 { C::execution_last_child_returndata_addr, child_rd_addr },
937 { C::execution_last_child_returndata_size, static_cast<uint32_t>(data.size()) },
938 },
939 });
940
941 copy_data.rd_copy(context, copy_size, rd_offset, dst_addr);
942
943 DataCopyTraceBuilder builder;
944 builder.process(event_emitter.dump_events(), trace);
945
946 check_relation<data_copy>(trace);
947 check_interaction<ExecutionTraceBuilder,
950}
951
952TEST(DataCopyWithExecutionPerm, ErrorPropagation)
953{
954 // Current Context
955 uint32_t context_id = 2;
956 uint32_t rd_offset = 10;
957 uint32_t copy_size = 4;
958 MemoryAddress big_dst_addr = AVM_HIGHEST_MEM_ADDRESS - 1;
959
960 // Child context
961 uint32_t child_context_id = 3; // Child context ID
962 uint32_t child_rd_addr = 0xc0ffee; // Last child returndata address in memory.
963 uint32_t child_data_size = 10; // Size of the last child returndata.
964
965 MemoryStore mem;
966 StrictMock<MockContext> context;
967 EXPECT_CALL(context, get_memory()).WillRepeatedly(ReturnRef(mem));
968 EXPECT_CALL(context, get_last_rd_size).WillRepeatedly(Return(child_data_size));
969 EXPECT_CALL(context, has_parent).WillRepeatedly(Return(true));
970 EXPECT_CALL(context, get_last_rd_addr).WillRepeatedly(Return(child_rd_addr));
971 EXPECT_CALL(context, get_context_id).WillRepeatedly(Return(context_id));
972 EXPECT_CALL(context, get_last_child_id).WillRepeatedly(Return(child_context_id));
973
974 StrictMock<MockExecutionIdManager> execution_id_manager;
975 EXPECT_CALL(execution_id_manager, get_execution_id()).WillOnce(Return(0));
976
977 PureGreaterThan gt;
978
979 EventEmitter<DataCopyEvent> event_emitter;
980 DataCopy copy_data = DataCopy(execution_id_manager, gt, event_emitter);
981
982 TestTraceContainer trace({
983 {
984 { C::precomputed_first_row, 1 },
985 { C::execution_sel, 1 },
986 { C::execution_context_id, context_id },
987 { C::execution_last_child_id, child_context_id },
988 { C::execution_sel_exec_dispatch_returndata_copy, 1 },
989 { C::execution_register_0_, copy_size },
990 { C::execution_register_1_, rd_offset },
991 { C::execution_rop_2_, big_dst_addr },
992 { C::execution_sel_opcode_error, 1 }, // Error flag is on
993 { C::execution_last_child_returndata_addr, child_rd_addr },
994 { C::execution_last_child_returndata_size, child_data_size },
995 },
996 });
997
998 EXPECT_THROW_WITH_MESSAGE(copy_data.rd_copy(context, copy_size, rd_offset, big_dst_addr),
999 "Attempting to write out of bounds memory");
1000
1001 DataCopyTraceBuilder builder;
1002 builder.process(event_emitter.dump_events(), trace);
1003
1004 check_relation<data_copy>(trace);
1005 check_interaction<ExecutionTraceBuilder,
1008}
1009
1010} // namespace
1011} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define AVM_HIGHEST_MEM_ADDRESS
RangeCheck range_check
void rd_copy(ContextInterface &context, uint32_t copy_size, uint32_t offset, MemoryAddress dst_addr) override
Copies returndata from the last executed context to the dst_addr.
void cd_copy(ContextInterface &context, uint32_t copy_size, uint32_t offset, MemoryAddress dst_addr) override
Writes calldata into dst_addr. There is slight difference in how enqueued and nested contexts are han...
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
Process the ALU events and populate the ALU relevant columns in the trace.
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Process the greater-than events and populate the relevant columns in the trace.
Definition gt_trace.cpp:20
AluTraceBuilder builder
Definition alu.test.cpp:124
GreaterThanTraceBuilder gt_builder
Definition alu.test.cpp:123
DataCopy copy_data
EventEmitter< GreaterThanEvent > gt_event_emitter
ExecutionIdManager execution_id_manager
MemoryStore mem
uint32_t rd_data_size
uint32_t src_addr
EventEmitter< RangeCheckEvent > range_check_event_emitter
uint32_t rd_addr
StrictMock< MockFieldGreaterThan > mock_field_gt
EventEmitter< DataCopyEvent > event_emitter
uint32_t src_data_size
uint32_t dst_addr
const std::vector< MemoryValue > data
GreaterThan gt
TestTraceContainer trace
StrictMock< MockContext > context
ssize_t offset
Definition engine.cpp:62
void check_interaction(tracegen::TestTraceContainer &trace)
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
TEST_F(AvmRecursiveTests, TwoLayerAvmRecursionFailsWithWrongPIs)
TaggedValue MemoryValue
lookup_settings< lookup_data_copy_check_dst_addr_in_range_settings_ > lookup_data_copy_check_dst_addr_in_range_settings
permutation_settings< perm_execution_dispatch_to_cd_copy_settings_ > perm_execution_dispatch_to_cd_copy_settings
lookup_settings< lookup_data_copy_offset_plus_size_is_gt_data_size_settings_ > lookup_data_copy_offset_plus_size_is_gt_data_size_settings
lookup_settings< lookup_data_copy_sel_has_reads_settings_ > lookup_data_copy_sel_has_reads_settings
lookup_settings< lookup_data_copy_check_src_addr_in_range_settings_ > lookup_data_copy_check_src_addr_in_range_settings
uint32_t MemoryAddress
permutation_settings< perm_execution_dispatch_to_rd_copy_settings_ > perm_execution_dispatch_to_rd_copy_settings
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t context_id
uint32_t child_rd_addr
uint32_t parent_cd_addr
uint32_t child_context_id
DataCopy data_copy
uint32_t cd_offset