|
| 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 |
0 commit comments