Skip to content

Commit 3d84fae

Browse files
author
jeanmon
committed
7211: unit tests for calldata copy gadget
1 parent d2a24da commit 3d84fae

File tree

4 files changed

+346
-5
lines changed

4 files changed

+346
-5
lines changed

barretenberg/cpp/src/barretenberg/vm/tests/avm_mem_opcodes.test.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
#include <gmock/gmock.h>
88
#include <gtest/gtest.h>
99

10-
#define MEM_ROW_FIELD_EQ(field_name, expression) Field(#field_name, &Row::mem_##field_name, expression)
11-
1210
namespace tests_avm {
1311

1412
using namespace bb;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
#include "avm_common.test.hpp"
2+
#include "barretenberg/vm/avm_trace/avm_common.hpp"
3+
#include "barretenberg/vm/tests/helpers.test.hpp"
4+
#include <gmock/gmock.h>
5+
#include <gtest/gtest.h>
6+
7+
#include <ranges>
8+
9+
#define SLICE_ROW_FIELD_EQ(field_name, expression) Field(#field_name, &Row::slice_##field_name, expression)
10+
11+
namespace tests_avm {
12+
13+
using namespace bb;
14+
using namespace bb::avm_trace;
15+
using namespace testing;
16+
17+
class AvmSliceTests : public ::testing::Test {
18+
public:
19+
AvmSliceTests()
20+
: public_inputs(generate_base_public_inputs())
21+
, trace_builder(AvmTraceBuilder(public_inputs))
22+
{
23+
srs::init_crs_factory("../srs_db/ignition");
24+
}
25+
26+
void gen_trace_builder(std::vector<FF> const& calldata)
27+
{
28+
trace_builder = AvmTraceBuilder(public_inputs, {}, 0, calldata);
29+
this->calldata = calldata;
30+
}
31+
32+
void gen_single_calldata_copy(
33+
bool indirect, uint32_t cd_size, uint32_t cd_offset, uint32_t copy_size, uint32_t dst_offset)
34+
{
35+
ASSERT_LE(cd_offset + copy_size, cd_size);
36+
std::vector<FF> calldata;
37+
for (size_t i = 0; i < cd_size; i++) {
38+
calldata.emplace_back(i * i);
39+
}
40+
41+
gen_trace_builder(calldata);
42+
trace_builder.op_calldata_copy(static_cast<uint8_t>(indirect), cd_offset, copy_size, dst_offset);
43+
trace_builder.op_return(0, 0, 0);
44+
trace = trace_builder.finalize();
45+
}
46+
47+
void validate_single_calldata_copy_trace(uint32_t cd_offset,
48+
uint32_t copy_size,
49+
uint32_t dst_offset,
50+
bool proof_verif = false)
51+
{
52+
// Find the first row enabling the calldata_copy selector
53+
auto row = std::ranges::find_if(
54+
trace.begin(), trace.end(), [](Row r) { return r.main_sel_op_calldata_copy == FF(1); });
55+
56+
ASSERT_TRUE(row != trace.end());
57+
58+
// Memory trace view pertaining to the calldata_copy operation.
59+
auto clk = row->main_clk;
60+
auto mem_view = std::views::filter(trace, [clk](Row r) {
61+
return r.mem_clk == clk && r.mem_rw == 1 && r.mem_sel_op_cd_cpy == 1 &&
62+
r.mem_tag == static_cast<uint32_t>(AvmMemoryTag::FF);
63+
});
64+
65+
// Check that the memory operations are as expected.
66+
size_t count = 0;
67+
for (auto const& mem_row : mem_view) {
68+
EXPECT_THAT(mem_row,
69+
AllOf(MEM_ROW_FIELD_EQ(val, (cd_offset + count) * (cd_offset + count)),
70+
MEM_ROW_FIELD_EQ(addr, dst_offset + count),
71+
MEM_ROW_FIELD_EQ(tag, static_cast<uint32_t>(AvmMemoryTag::FF)),
72+
MEM_ROW_FIELD_EQ(w_in_tag, static_cast<uint32_t>(AvmMemoryTag::FF)),
73+
MEM_ROW_FIELD_EQ(r_in_tag, static_cast<uint32_t>(AvmMemoryTag::U0)),
74+
MEM_ROW_FIELD_EQ(tag_err, 0)));
75+
count++;
76+
}
77+
78+
EXPECT_EQ(count, copy_size);
79+
80+
// Slice trace view pertaining to the calldata_copy operation.
81+
auto slice_view =
82+
std::views::filter(trace, [clk](Row r) { return r.slice_clk == clk && r.slice_sel_cd_cpy == 1; });
83+
84+
FF last_clk = 0;
85+
86+
// Check that the slice trace is as expected.
87+
count = 0;
88+
for (auto const& slice_row : slice_view) {
89+
EXPECT_THAT(slice_row,
90+
AllOf(SLICE_ROW_FIELD_EQ(val, (cd_offset + count) * (cd_offset + count)),
91+
SLICE_ROW_FIELD_EQ(addr, dst_offset + count),
92+
SLICE_ROW_FIELD_EQ(cd_offset, cd_offset + count),
93+
SLICE_ROW_FIELD_EQ(cnt, copy_size - count),
94+
SLICE_ROW_FIELD_EQ(sel_start_cd_cpy, static_cast<uint32_t>(count == 0))));
95+
count++;
96+
97+
if (count == copy_size) {
98+
last_clk = slice_row.main_clk;
99+
}
100+
}
101+
102+
// Check that the extra final row is well-formed.
103+
EXPECT_THAT(trace.at(static_cast<size_t>(last_clk + 1)),
104+
AllOf(SLICE_ROW_FIELD_EQ(addr, FF(dst_offset) + FF(copy_size)),
105+
SLICE_ROW_FIELD_EQ(cd_offset, cd_offset + copy_size),
106+
SLICE_ROW_FIELD_EQ(cnt, 0),
107+
SLICE_ROW_FIELD_EQ(clk, 0),
108+
SLICE_ROW_FIELD_EQ(sel_cd_cpy, 0),
109+
SLICE_ROW_FIELD_EQ(sel_start_cd_cpy, 0)));
110+
111+
if (proof_verif) {
112+
validate_trace(std::move(trace), public_inputs, calldata, true);
113+
} else {
114+
validate_trace(std::move(trace), public_inputs, calldata);
115+
}
116+
}
117+
118+
VmPublicInputs public_inputs;
119+
AvmTraceBuilder trace_builder;
120+
std::vector<FF> calldata;
121+
122+
std::vector<Row> trace;
123+
size_t main_row_idx;
124+
size_t alu_row_idx;
125+
size_t mem_row_idx;
126+
};
127+
128+
TEST_F(AvmSliceTests, simpleCopyAllCDValues)
129+
{
130+
gen_single_calldata_copy(false, 12, 0, 12, 25);
131+
validate_single_calldata_copy_trace(0, 12, 25, true);
132+
}
133+
134+
TEST_F(AvmSliceTests, singleCopyCDElement)
135+
{
136+
gen_single_calldata_copy(false, 12, 5, 1, 25);
137+
validate_single_calldata_copy_trace(5, 1, 25);
138+
}
139+
140+
TEST_F(AvmSliceTests, longCopyAllCDValues)
141+
{
142+
gen_single_calldata_copy(false, 2000, 0, 2000, 873);
143+
validate_single_calldata_copy_trace(0, 2000, 873);
144+
}
145+
146+
TEST_F(AvmSliceTests, copyFirstHalfCDValues)
147+
{
148+
gen_single_calldata_copy(false, 12, 0, 6, 98127);
149+
validate_single_calldata_copy_trace(0, 6, 98127);
150+
}
151+
152+
TEST_F(AvmSliceTests, copySecondHalfCDValues)
153+
{
154+
gen_single_calldata_copy(false, 12, 6, 6, 0);
155+
validate_single_calldata_copy_trace(6, 6, 0);
156+
}
157+
158+
TEST_F(AvmSliceTests, copyToHighestMemOffset)
159+
{
160+
gen_single_calldata_copy(false, 8, 2, 6, UINT32_MAX - 5);
161+
validate_single_calldata_copy_trace(2, 6, UINT32_MAX - 5);
162+
}
163+
164+
TEST_F(AvmSliceTests, twoCallsNoOverlap)
165+
{
166+
calldata = { 2, 3, 4, 5, 6 };
167+
168+
gen_trace_builder(calldata);
169+
trace_builder.op_calldata_copy(0, 0, 2, 34);
170+
trace_builder.op_calldata_copy(0, 3, 2, 2123);
171+
trace_builder.op_return(0, 0, 0);
172+
trace = trace_builder.finalize();
173+
174+
// Main trace views of rows enabling the calldata_copy selector
175+
auto main_view = std::views::filter(trace, [](Row r) { return r.main_sel_op_calldata_copy == FF(1); });
176+
177+
std::vector<Row> main_rows;
178+
for (auto const& row : main_view) {
179+
main_rows.push_back(row);
180+
}
181+
182+
EXPECT_EQ(main_rows.size(), 2);
183+
184+
EXPECT_THAT(main_rows.at(0),
185+
AllOf(MAIN_ROW_FIELD_EQ(ia, 0),
186+
MAIN_ROW_FIELD_EQ(ib, 2),
187+
MAIN_ROW_FIELD_EQ(mem_addr_c, 34),
188+
MAIN_ROW_FIELD_EQ(clk, 1)));
189+
EXPECT_THAT(main_rows.at(1),
190+
AllOf(MAIN_ROW_FIELD_EQ(ia, 3),
191+
MAIN_ROW_FIELD_EQ(ib, 2),
192+
MAIN_ROW_FIELD_EQ(mem_addr_c, 2123),
193+
MAIN_ROW_FIELD_EQ(clk, 2)));
194+
195+
validate_trace(std::move(trace), public_inputs, calldata);
196+
}
197+
198+
TEST_F(AvmSliceTests, indirectTwoCallsOverlap)
199+
{
200+
calldata = { 2, 3, 4, 5, 6 };
201+
202+
gen_trace_builder(calldata);
203+
trace_builder.op_set(0, 34, 100, AvmMemoryTag::U32); // indirect address 100 resolves to 34
204+
trace_builder.op_set(0, 2123, 101, AvmMemoryTag::U32); // indirect address 101 resolves to 2123
205+
trace_builder.op_calldata_copy(1, 1, 3, 100);
206+
trace_builder.op_calldata_copy(1, 2, 3, 101);
207+
trace_builder.op_return(0, 0, 0);
208+
trace = trace_builder.finalize();
209+
210+
// Main trace views of rows enabling the calldata_copy selector
211+
auto main_view = std::views::filter(trace, [](Row r) { return r.main_sel_op_calldata_copy == FF(1); });
212+
213+
std::vector<Row> main_rows;
214+
for (auto const& row : main_view) {
215+
main_rows.push_back(row);
216+
}
217+
218+
EXPECT_EQ(main_rows.size(), 2);
219+
220+
EXPECT_THAT(main_rows.at(0),
221+
AllOf(MAIN_ROW_FIELD_EQ(ia, 1),
222+
MAIN_ROW_FIELD_EQ(ib, 3),
223+
MAIN_ROW_FIELD_EQ(sel_resolve_ind_addr_c, 1),
224+
MAIN_ROW_FIELD_EQ(ind_addr_c, 100),
225+
MAIN_ROW_FIELD_EQ(mem_addr_c, 34),
226+
MAIN_ROW_FIELD_EQ(clk, 3)));
227+
EXPECT_THAT(main_rows.at(1),
228+
AllOf(MAIN_ROW_FIELD_EQ(ia, 2),
229+
MAIN_ROW_FIELD_EQ(ib, 3),
230+
MAIN_ROW_FIELD_EQ(sel_resolve_ind_addr_c, 1),
231+
MAIN_ROW_FIELD_EQ(ind_addr_c, 101),
232+
MAIN_ROW_FIELD_EQ(mem_addr_c, 2123),
233+
MAIN_ROW_FIELD_EQ(clk, 4)));
234+
235+
validate_trace(std::move(trace), public_inputs, calldata);
236+
}
237+
238+
TEST_F(AvmSliceTests, indirectFailedResolution)
239+
{
240+
calldata = { 2, 3, 4, 5, 6 };
241+
242+
gen_trace_builder(calldata);
243+
trace_builder.op_set(0, 34, 100, AvmMemoryTag::U16); // indirect address 100 resolves to 34
244+
trace_builder.op_calldata_copy(1, 1, 3, 100);
245+
trace_builder.halt();
246+
trace = trace_builder.finalize();
247+
248+
// Check that slice trace is empty
249+
auto slice_row = std::ranges::find_if(trace.begin(), trace.end(), [](Row r) { return r.slice_sel_cd_cpy == 1; });
250+
EXPECT_EQ(slice_row, trace.end());
251+
252+
auto count = std::ranges::count_if(trace.begin(), trace.end(), [](Row r) { return r.mem_sel_op_cd_cpy == 1; });
253+
// Check that MEM trace does not contain any entry related to calldata_copy write.
254+
EXPECT_EQ(count, 0);
255+
256+
// Find the first row enabling the calldata_copy selector
257+
auto row =
258+
std::ranges::find_if(trace.begin(), trace.end(), [](Row r) { return r.main_sel_op_calldata_copy == FF(1); });
259+
260+
ASSERT_TRUE(row != trace.end());
261+
auto clk = row->main_clk;
262+
auto mem_row = std::ranges::find_if(trace.begin(), trace.end(), [clk](Row r) { return r.mem_clk == clk; });
263+
264+
EXPECT_EQ(mem_row->mem_rw, 0);
265+
EXPECT_EQ(mem_row->mem_sel_resolve_ind_addr_c, 1);
266+
267+
validate_trace(std::move(trace), public_inputs, calldata);
268+
}
269+
270+
class AvmSliceNegativeTests : public AvmSliceTests {};
271+
272+
TEST_F(AvmSliceNegativeTests, wrongCDValueInSlice)
273+
{
274+
gen_single_calldata_copy(false, 10, 0, 10, 0);
275+
276+
trace.at(3).slice_val = 98;
277+
278+
// Adapt corresponding MEM trace entry in a consistent way.
279+
auto clk = trace.at(3).slice_clk;
280+
auto addr = trace.at(3).slice_addr;
281+
auto mem_row = std::ranges::find_if(
282+
trace.begin(), trace.end(), [clk, addr](Row r) { return r.mem_clk == clk && r.mem_addr == addr; });
283+
mem_row->mem_val = 98;
284+
285+
EXPECT_THROW_WITH_MESSAGE(validate_trace_check_circuit(std::move(trace)), "LOOKUP_CD_VALUE");
286+
}
287+
288+
TEST_F(AvmSliceNegativeTests, wrongCDValueInMemory)
289+
{
290+
gen_single_calldata_copy(false, 10, 0, 10, 0);
291+
292+
auto clk = trace.at(5).slice_clk;
293+
auto addr = trace.at(5).slice_addr;
294+
auto mem_row = std::ranges::find_if(
295+
trace.begin(), trace.end(), [clk, addr](Row r) { return r.mem_clk == clk && r.mem_addr == addr; });
296+
mem_row->mem_val = 98;
297+
298+
EXPECT_THROW_WITH_MESSAGE(validate_trace_check_circuit(std::move(trace)), "PERM_CD_MEM");
299+
}
300+
301+
TEST_F(AvmSliceNegativeTests, wrongCDValueInCalldataColumn)
302+
{
303+
gen_single_calldata_copy(false, 10, 0, 10, 0);
304+
305+
trace.at(2).main_calldata = 12;
306+
EXPECT_THROW_WITH_MESSAGE(validate_trace_check_circuit(std::move(trace)), "LOOKUP_CD_VALUE");
307+
}
308+
309+
TEST_F(AvmSliceNegativeTests, wrongCDValueInCalldataVerifier)
310+
{
311+
calldata = { 2, 3, 4, 5, 6 };
312+
313+
gen_trace_builder(calldata);
314+
trace_builder.op_calldata_copy(0, 1, 3, 100);
315+
trace_builder.op_return(0, 0, 0);
316+
trace = trace_builder.finalize();
317+
318+
validate_trace(std::move(trace), public_inputs, { 2, 3, 4, 5, 7 }, true, true);
319+
}
320+
321+
TEST_F(AvmSliceNegativeTests, disableMemWriteEntry)
322+
{
323+
gen_single_calldata_copy(false, 10, 0, 10, 0);
324+
325+
// Multiple adjustements to get valid MEM trace.
326+
trace.at(10).mem_sel_op_cd_cpy = 0;
327+
trace.at(10).mem_sel_mem = 0;
328+
trace.at(9).mem_last = 1;
329+
trace.at(10).mem_last = 0;
330+
trace.at(10).mem_tsp = 12;
331+
trace.at(9).mem_sel_rng_chk = 0;
332+
333+
EXPECT_THROW_WITH_MESSAGE(validate_trace_check_circuit(std::move(trace)), "PERM_CD_MEM");
334+
}
335+
336+
} // namespace tests_avm

barretenberg/cpp/src/barretenberg/vm/tests/helpers.test.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ void validate_trace_check_circuit(std::vector<Row>&& trace)
3737
void validate_trace(std::vector<Row>&& trace,
3838
VmPublicInputs const& public_inputs,
3939
std::vector<FF> const& calldata,
40-
bool with_proof)
40+
bool with_proof,
41+
bool expect_proof_failure)
4142
{
4243
auto circuit_builder = AvmCircuitBuilder();
4344
circuit_builder.set_trace(std::move(trace));
@@ -55,7 +56,11 @@ void validate_trace(std::vector<Row>&& trace,
5556

5657
bool verified = verifier.verify_proof(proof, { public_inputs_as_vec });
5758

58-
EXPECT_TRUE(verified);
59+
if (expect_proof_failure) {
60+
EXPECT_FALSE(verified);
61+
} else {
62+
EXPECT_TRUE(verified);
63+
}
5964
}
6065
};
6166

barretenberg/cpp/src/barretenberg/vm/tests/helpers.test.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
}
1616

1717
#define MAIN_ROW_FIELD_EQ(field_name, expression) Field(#field_name, &Row::main_##field_name, expression)
18+
#define MEM_ROW_FIELD_EQ(field_name, expression) Field(#field_name, &Row::mem_##field_name, expression)
1819

1920
namespace tests_avm {
2021

@@ -32,7 +33,8 @@ void validate_trace_check_circuit(std::vector<Row>&& trace);
3233
void validate_trace(std::vector<Row>&& trace,
3334
VmPublicInputs const& public_inputs = {},
3435
std::vector<FF> const& calldata = {},
35-
bool with_proof = bb::avm_trace::ENABLE_PROVING);
36+
bool with_proof = bb::avm_trace::ENABLE_PROVING,
37+
bool expect_proof_failure = false);
3638
void mutate_ic_in_trace(std::vector<Row>& trace,
3739
std::function<bool(Row)>&& selectRow,
3840
FF const& newValue,

0 commit comments

Comments
 (0)