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
18
19namespace bb::avm2::simulation {
20namespace {
21
22using ::testing::ElementsAre;
23using ::testing::Return;
24using ::testing::ReturnRef;
25using ::testing::StrictMock;
26
27class DataCopySimulationTest : public ::testing::Test {
28 protected:
29 DataCopySimulationTest()
30 {
31 ON_CALL(context, get_memory()).WillByDefault(ReturnRef(mem));
32
33 // Standard EventEmitter Expectations
34 EXPECT_CALL(context, get_memory());
35 EXPECT_CALL(execution_id_manager, get_execution_id());
36 EXPECT_CALL(context, get_context_id());
37 }
38
39 MemoryStore mem;
40 StrictMock<MockExecutionIdManager> execution_id_manager;
41 PureGreaterThan gt;
42 StrictMock<MockContext> context;
43 EventEmitter<DataCopyEvent> event_emitter;
44 DataCopy data_copy = DataCopy(execution_id_manager, gt, event_emitter);
45
46 uint32_t dst_addr = 0; // Destination address in memory for the copied returndata.
47};
48
49class NestedCdCopySimulationTest : public DataCopySimulationTest {
50 protected:
51 NestedCdCopySimulationTest()
52 {
53 // Load up parent context
54 for (uint32_t i = 0; i < parent_cd_size; ++i) {
56 }
57 EXPECT_CALL(context, get_parent_cd_addr()).WillRepeatedly(Return(parent_cd_addr));
58 EXPECT_CALL(context, get_parent_cd_size()).WillRepeatedly(Return(parent_cd_size));
59 EXPECT_CALL(context, get_parent_id());
60 EXPECT_CALL(context, has_parent()).WillRepeatedly(Return(true));
61 }
62
63 std::vector<MemoryValue> calldata = { MemoryValue::from<FF>(1), MemoryValue::from<FF>(2), MemoryValue::from<FF>(3),
64 MemoryValue::from<FF>(4), MemoryValue::from<FF>(5), MemoryValue::from<FF>(6),
65 MemoryValue::from<FF>(7), MemoryValue::from<FF>(8) };
66 uint32_t parent_cd_addr = 100; // Address where the parent calldata is stored.
67 uint32_t parent_cd_size = static_cast<uint32_t>(calldata.size());
68 uint32_t cd_offset = 0;
69};
70
71TEST_F(NestedCdCopySimulationTest, CdZero)
72{
73 // Copy zero calldata from the parent context to memory
74 uint32_t cd_copy_size = 0;
75 uint32_t cd_offset = 0;
76
78
79 auto c = mem.get(dst_addr);
80 EXPECT_TRUE(c.as_ff().is_zero());
81}
82
83TEST_F(NestedCdCopySimulationTest, CdCopyAll)
84{
85 // Copy all calldata from the parent context to memory
86 uint32_t cd_copy_size = static_cast<uint32_t>(calldata.size());
87
88 EXPECT_CALL(context, get_calldata(cd_offset, cd_copy_size)).WillOnce(Return(calldata));
89
90 uint32_t dst_addr = 0;
91 data_copy.cd_copy(context, cd_copy_size, cd_offset, dst_addr);
92
93 // This should write all the calldata values
94 std::vector<FF> calldata_in_memory;
95 for (uint32_t i = 0; i < cd_copy_size; ++i) {
96 auto c = mem.get(dst_addr + i);
97 calldata_in_memory.emplace_back(c.as_ff());
98 }
99 EXPECT_THAT(calldata_in_memory, ElementsAre(1, 2, 3, 4, 5, 6, 7, 8));
100}
101
102TEST_F(NestedCdCopySimulationTest, CdCopyPartial)
103{
104 // Copy come calldata from the parent context to memory
105 uint32_t cd_copy_size = 2;
106
107 EXPECT_CALL(context, get_calldata(cd_offset, cd_copy_size))
108 .WillOnce(Return(std::vector<MemoryValue>{ MemoryValue::from<FF>(1),
109 MemoryValue::from<FF>(2) })); // Only copy first two values
110
111 data_copy.cd_copy(context, cd_copy_size, cd_offset, dst_addr);
112
113 // This should write all the calldata values
114 std::vector<FF> calldata_in_memory;
115 for (uint32_t i = 0; i < cd_copy_size; ++i) {
116 auto c = mem.get(dst_addr + i);
117 calldata_in_memory.emplace_back(c.as_ff());
118 }
119 EXPECT_THAT(calldata_in_memory, ElementsAre(1, 2));
120}
121
122TEST_F(NestedCdCopySimulationTest, CdFullWithPadding)
123{
124 // Copy some calldata from the parent context to memory, but with padding
125 uint32_t cd_copy_size = 10; // Request more than available
126 // effective_reads = min(0+10, 8) - 0 = 8 (clamped by src_data_size, not memory)
127 uint32_t effective_reads = parent_cd_size;
128
129 EXPECT_CALL(context, get_calldata(cd_offset, effective_reads)).WillOnce(Return(calldata));
130
131 data_copy.cd_copy(context, cd_copy_size, cd_offset, dst_addr);
132
133 // This should write all the calldata values and pad the rest with zeros
134 std::vector<FF> calldata_in_memory;
135 for (uint32_t i = 0; i < cd_copy_size; ++i) {
136 auto c = mem.get(dst_addr + i);
137 calldata_in_memory.emplace_back(c.as_ff());
138 }
139 EXPECT_THAT(calldata_in_memory, ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 0, 0));
140}
141
142TEST_F(NestedCdCopySimulationTest, CdPartialWithPadding)
143{
144 // Copy some calldata from the parent context to memory, but with padding
145 uint32_t cd_copy_size = 4; // Request more than available
146 uint32_t cd_offset = 6; // Offset into calldata
147 // effective_reads = min(6+4, 8) - 6 = 2
148 uint32_t effective_reads = 2;
149
150 std::vector<MemoryValue> expected_calldata = {
151 MemoryValue::from<FF>(7),
152 MemoryValue::from<FF>(8),
153 };
154
155 EXPECT_CALL(context, get_calldata(cd_offset, effective_reads)).WillOnce(Return(expected_calldata));
156
157 data_copy.cd_copy(context, cd_copy_size, cd_offset, dst_addr);
158
159 // This should write all the calldata values and pad the rest with zeros
160 std::vector<FF> calldata_in_memory;
161 for (uint32_t i = 0; i < cd_copy_size; ++i) {
162 auto c = mem.get(dst_addr + i);
163 calldata_in_memory.emplace_back(c.as_ff());
164 }
165 EXPECT_THAT(calldata_in_memory, ElementsAre(7, 8, 0, 0));
166}
167
168class RdCopySimulationTest : public DataCopySimulationTest {
169 protected:
170 RdCopySimulationTest()
171 {
172 // Set up the parent context address
173 EXPECT_CALL(context, get_last_rd_addr()).WillRepeatedly(Return(child_rd_addr));
174 EXPECT_CALL(context, get_last_rd_size()).WillRepeatedly(Return(child_rd_size));
175 EXPECT_CALL(context, get_last_child_id()).WillRepeatedly(Return(child_context_id));
176 EXPECT_CALL(context, has_parent()).WillRepeatedly(Return(true));
177 EXPECT_CALL(context, get_last_child_id()).WillRepeatedly(Return(2));
178 }
180 MemoryValue::from<FF>(9), MemoryValue::from<FF>(10), MemoryValue::from<FF>(11), MemoryValue::from<FF>(12)
181 }; // Example returndata to be copied.
182 uint32_t child_rd_size = static_cast<uint32_t>(returndata.size());
183 uint32_t child_rd_addr = 200; // Address where the parent returndata is stored.
184 uint32_t child_context_id = 2;
185};
186
187TEST_F(RdCopySimulationTest, RdZero)
188{
189 // Copy zero returndata from the last executed context to memory
190 uint32_t rd_copy_size = 0;
191 uint32_t rd_offset = 0;
192
193 data_copy.rd_copy(context, rd_copy_size, rd_offset, dst_addr);
194
195 auto c = mem.get(dst_addr);
196 EXPECT_TRUE(c.as_ff().is_zero());
197}
198
199TEST_F(RdCopySimulationTest, RdCopyAll)
200{
201 // Copy all returndata from the last executed context to memory
202 uint32_t rd_copy_size = static_cast<uint32_t>(returndata.size());
203 uint32_t rd_offset = 0;
204
205 EXPECT_CALL(context, get_returndata(rd_offset, rd_copy_size)).WillOnce(Return(returndata));
206
207 data_copy.rd_copy(context, rd_copy_size, rd_offset, dst_addr);
208
209 // This should write all the returndata values
210 std::vector<FF> returndata_in_memory;
211 for (uint32_t i = 0; i < rd_copy_size; ++i) {
212 auto c = mem.get(dst_addr + i);
213 returndata_in_memory.emplace_back(c.as_ff());
214 }
215 EXPECT_THAT(returndata_in_memory, ElementsAre(9, 10, 11, 12));
216}
217
218TEST_F(RdCopySimulationTest, RdCopyPartial)
219{
220 // Copy some returndata from the last executed context to memory
221 uint32_t rd_copy_size = 2;
222 uint32_t rd_offset = 1; // Start copying from second element
223
224 EXPECT_CALL(context, get_returndata(rd_offset, rd_copy_size))
225 .WillOnce(Return(std::vector<MemoryValue>{ MemoryValue::from<FF>(10),
226 MemoryValue::from<FF>(11) })); // Only copy second and third values
227
228 data_copy.rd_copy(context, rd_copy_size, rd_offset, dst_addr);
229
230 // This should write the selected returndata values
231 std::vector<FF> returndata_in_memory;
232 for (uint32_t i = 0; i < rd_copy_size; ++i) {
233 auto c = mem.get(dst_addr + i);
234 returndata_in_memory.emplace_back(c.as_ff());
235 }
236 EXPECT_THAT(returndata_in_memory, ElementsAre(10, 11));
237}
238
239TEST_F(RdCopySimulationTest, RdFullWithPadding)
240{
241 // Copy some returndata from the last executed context to memory, but with padding
242 uint32_t rd_copy_size = 10; // Request more than available
243 uint32_t rd_offset = 0; // Start copying from first element
244 // effective_reads = min(0+10, 4) - 0 = 4
245 uint32_t effective_reads = child_rd_size;
246
247 EXPECT_CALL(context, get_returndata(rd_offset, effective_reads)).WillOnce(Return(returndata));
248
249 data_copy.rd_copy(context, rd_copy_size, rd_offset, dst_addr);
250
251 // This should write all the returndata values and pad the rest with zeros
252 std::vector<FF> returndata_in_memory;
253 for (uint32_t i = 0; i < rd_copy_size; ++i) {
254 auto c = mem.get(dst_addr + i);
255 returndata_in_memory.emplace_back(c.as_ff());
256 }
257 EXPECT_THAT(returndata_in_memory, ElementsAre(9, 10, 11, 12, 0, 0, 0, 0, 0, 0));
258}
259
260TEST_F(RdCopySimulationTest, RdPartialWithPadding)
261{
262 // Copy some returndata from the last executed context to memory, but with padding
263 uint32_t rd_copy_size = 4; // Request more than available
264 uint32_t rd_offset = 2; // Start copying from third element
265 // effective_reads = min(2+4, 4) - 2 = 2
266 uint32_t effective_reads = 2;
267
268 std::vector<MemoryValue> expected_returndata = {
269 MemoryValue::from<FF>(11),
270 MemoryValue::from<FF>(12),
271 };
272
273 EXPECT_CALL(context, get_returndata(rd_offset, effective_reads)).WillOnce(Return(expected_returndata));
274
275 data_copy.rd_copy(context, rd_copy_size, rd_offset, dst_addr);
276
277 // This should write all the returndata values and pad the rest with zeros
278 std::vector<FF> returndata_in_memory;
279 for (uint32_t i = 0; i < rd_copy_size; ++i) {
280 auto c = mem.get(dst_addr + i);
281 returndata_in_memory.emplace_back(c.as_ff());
282 }
283 EXPECT_THAT(returndata_in_memory, ElementsAre(11, 12, 0, 0));
284}
285
286// Test that src out of range does NOT throw — reads are clamped and padded with zeros.
287class SrcOutOfRangeCdCopySimulationTest : public DataCopySimulationTest {
288 protected:
289 SrcOutOfRangeCdCopySimulationTest()
290 {
291 // Place calldata at near the end of the memory space so reads exceed AVM_MEMORY_SIZE.
292 for (uint32_t i = 0; i < parent_cd_size_in_range; ++i) {
293 mem.set(parent_cd_addr + i, MemoryValue::from(calldata[i]));
294 }
295 EXPECT_CALL(context, get_parent_cd_addr()).WillRepeatedly(Return(parent_cd_addr));
296 EXPECT_CALL(context, get_parent_cd_size()).WillRepeatedly(Return(parent_cd_size));
297 EXPECT_CALL(context, get_parent_id());
298 EXPECT_CALL(context, has_parent()).WillRepeatedly(Return(true));
299 }
300
301 std::vector<MemoryValue> calldata = { MemoryValue::from<FF>(1),
302 MemoryValue::from<FF>(2),
303 MemoryValue::from<FF>(3) };
304 // src_addr is 3 slots before AVM_MEMORY_SIZE, but src_data_size is 8 (exceeds memory).
305 uint32_t parent_cd_addr = AVM_HIGHEST_MEM_ADDRESS - 2; // 3 valid slots before end
306 uint32_t parent_cd_size = 8; // Claims 8 elements but only 3 fit in memory
307 uint32_t parent_cd_size_in_range = 3; // Only 3 actually fit
308};
309
310TEST_F(SrcOutOfRangeCdCopySimulationTest, SrcOutOfRangeClampedWithPadding)
311{
312 uint32_t cd_copy_size = 5;
313 uint32_t cd_offset = 0;
314 // read_index_upper_bound = min(0+5, 8) = 5
315 // read_addr_upper_bound = parent_cd_addr + 5 > AVM_MEMORY_SIZE (since parent_cd_addr = AVM_HIGHEST_MEM_ADDRESS-2)
316 // clamped = AVM_MEMORY_SIZE - parent_cd_addr = 3
317 // effective_reads = 3 - 0 = 3
318 uint32_t effective_reads = 3;
319
320 EXPECT_CALL(context, get_calldata(cd_offset, effective_reads)).WillOnce(Return(calldata));
321
322 // Should NOT throw — src out of range is not an error.
323 data_copy.cd_copy(context, cd_copy_size, cd_offset, dst_addr);
324
325 std::vector<FF> result;
326 for (uint32_t i = 0; i < cd_copy_size; ++i) {
327 result.emplace_back(mem.get(dst_addr + i).as_ff());
328 }
329 // 3 values from memory, 2 zeros from padding
330 EXPECT_THAT(result, ElementsAre(1, 2, 3, 0, 0));
331}
332
333class SrcOutOfRangeRdCopySimulationTest : public DataCopySimulationTest {
334 protected:
335 SrcOutOfRangeRdCopySimulationTest()
336 {
337 EXPECT_CALL(context, get_last_rd_addr()).WillRepeatedly(Return(child_rd_addr));
338 EXPECT_CALL(context, get_last_rd_size()).WillRepeatedly(Return(child_rd_size));
339 EXPECT_CALL(context, get_last_child_id()).WillRepeatedly(Return(2));
340 EXPECT_CALL(context, has_parent()).WillRepeatedly(Return(true));
341 }
342
343 std::vector<MemoryValue> returndata = { MemoryValue::from<FF>(10), MemoryValue::from<FF>(20) };
344 uint32_t child_rd_addr = AVM_HIGHEST_MEM_ADDRESS - 1; // 2 valid slots before end
345 uint32_t child_rd_size = 6; // Claims 6 but only 2 fit
346};
347
348TEST_F(SrcOutOfRangeRdCopySimulationTest, SrcOutOfRangeClampedWithPadding)
349{
350 uint32_t rd_copy_size = 4;
351 uint32_t rd_offset = 0;
352 // read_index_upper_bound = min(0+4, 6) = 4
353 // read_addr_upper_bound = child_rd_addr + 4 > AVM_MEMORY_SIZE
354 // clamped = AVM_MEMORY_SIZE - child_rd_addr = 2
355 // effective_reads = 2 - 0 = 2
356 uint32_t effective_reads = 2;
357
358 EXPECT_CALL(context, get_returndata(rd_offset, effective_reads)).WillOnce(Return(returndata));
359
360 // Should NOT throw
361 data_copy.rd_copy(context, rd_copy_size, rd_offset, dst_addr);
362
363 std::vector<FF> result;
364 for (uint32_t i = 0; i < rd_copy_size; ++i) {
365 result.emplace_back(mem.get(dst_addr + i).as_ff());
366 }
367 EXPECT_THAT(result, ElementsAre(10, 20, 0, 0));
368}
369
370// Src out-of-range with non-zero offset: offset interacts correctly with clamping.
371TEST_F(SrcOutOfRangeCdCopySimulationTest, SrcOutOfRangeWithOffset)
372{
373 uint32_t cd_copy_size = 4;
374 uint32_t cd_offset = 1;
375 // read_index_upper_bound = min(1+4, 8) = 5
376 // read_addr_upper_bound = parent_cd_addr + 5 > AVM_MEMORY_SIZE
377 // clamped = AVM_MEMORY_SIZE - parent_cd_addr = 3
378 // effective_reads = 3 - 1 = 2
379 uint32_t effective_reads = 2;
380
381 std::vector<MemoryValue> expected = { MemoryValue::from<FF>(2), MemoryValue::from<FF>(3) };
382 EXPECT_CALL(context, get_calldata(cd_offset, effective_reads)).WillOnce(Return(expected));
383
384 data_copy.cd_copy(context, cd_copy_size, cd_offset, dst_addr);
385
386 std::vector<FF> result;
387 for (uint32_t i = 0; i < cd_copy_size; ++i) {
388 result.emplace_back(mem.get(dst_addr + i).as_ff());
389 }
390 // 2 values from memory, 2 zeros from padding
391 EXPECT_THAT(result, ElementsAre(2, 3, 0, 0));
392}
393
394// Src out-of-range where clamped < offset: all rows are padding (sel_has_reads = 0).
395TEST_F(SrcOutOfRangeRdCopySimulationTest, SrcOutOfRangeClampedBelowOffset)
396{
397 uint32_t rd_copy_size = 3;
398 uint32_t rd_offset = 5;
399 // read_index_upper_bound = min(5+3, 6) = 6
400 // read_addr_upper_bound = child_rd_addr + 6 > AVM_MEMORY_SIZE
401 // clamped = AVM_MEMORY_SIZE - child_rd_addr = 2
402 // clamped(2) < offset(5), so reads_left = 0, all padding
403
404 // No get_returndata call expected — all padding
405 data_copy.rd_copy(context, rd_copy_size, rd_offset, dst_addr);
406
407 std::vector<FF> result;
408 for (uint32_t i = 0; i < rd_copy_size; ++i) {
409 result.emplace_back(mem.get(dst_addr + i).as_ff());
410 }
411 EXPECT_THAT(result, ElementsAre(0, 0, 0));
412}
413
414} // namespace
415} // namespace bb::avm2::simulation
#define AVM_HIGHEST_MEM_ADDRESS
static TaggedValue from(T value)
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...
const MemoryValue & get(MemoryAddress index) const override
ExecutionIdManager execution_id_manager
MemoryStore mem
EventEmitter< DataCopyEvent > event_emitter
uint32_t dst_addr
GreaterThan gt
StrictMock< MockContext > context
AVM range check gadget for witness generation.
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< MemoryValue > calldata
uint32_t child_rd_addr
uint32_t parent_cd_addr
uint32_t child_context_id
uint32_t child_rd_size
DataCopy data_copy
uint32_t parent_cd_size
std::vector< MemoryValue > returndata
uint32_t parent_cd_size_in_range
uint32_t cd_offset