Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
data_copy.cpp
Go to the documentation of this file.
2
3#include <stdexcept>
4
9
10namespace bb::avm2::simulation {
11
12namespace {
13
14DataCopyEvent create_cd_event(ContextInterface& context,
15 const uint32_t clk,
16 const uint32_t cd_copy_size,
17 const uint32_t cd_offset,
19 const std::vector<FF>& calldata = {})
20{
21 return DataCopyEvent{ .execution_clk = clk,
22 .operation = DataCopyOperation::CD_COPY,
23 .calldata = calldata,
24 .write_context_id = context.get_context_id(),
25 .read_context_id = context.get_parent_id(),
26 .data_copy_size = cd_copy_size,
27 .data_offset = cd_offset,
28 .data_addr = context.get_parent_cd_addr(),
29 .data_size = context.get_parent_cd_size(),
30 .is_nested = context.has_parent(),
31 .dst_addr = dst_addr };
32}
33
34DataCopyEvent create_rd_event(ContextInterface& context,
35 const uint32_t clk,
36 const uint32_t rd_copy_size,
37 const uint32_t rd_offset,
39 const std::vector<FF>& returndata = {})
40{
41 return DataCopyEvent{ .execution_clk = clk,
42 .operation = DataCopyOperation::RD_COPY,
43 .calldata = returndata,
44 .write_context_id = context.get_context_id(),
45 // This handles the case where there is no last child (i.e. new enqueued call)
46 .read_context_id = context.get_last_child_id(),
47 .data_copy_size = rd_copy_size,
48 .data_offset = rd_offset,
49 .data_addr = context.get_last_rd_addr(),
50 .data_size = context.get_last_rd_size(),
51 .is_nested = context.has_parent(),
52 .dst_addr = dst_addr };
53}
54
55} // namespace
56
57// This is std::min but creates the relevant greater than event
58uint64_t DataCopy::min(uint64_t a, uint64_t b)
59{
60 // Looks weird but ironically similar to the std::min implementation
61 // i.e if a == b, return a
62 if (gt.gt(a, b)) {
63 return b;
64 }
65 return a;
66}
67
86 const uint32_t copy_size,
87 const uint32_t offset,
89{
90 auto& memory = context.get_memory();
92
93 try {
94 // This section is a bit leaky, but is necessary to ensure the correct gt events are generated.
95 // This work is duplicated in context.get_calldata() - but it avoids us having a gt there.
96
97 // Operations are performed over uint64_t in case the addition overflows, but the result in guaranteed to
98 // fit in 32 bits since get_parent_cd_size() returns a u32 (constrained by a CALL or 0 if an enqueued call).
99 uint64_t max_read_index = min(static_cast<uint64_t>(offset) + copy_size, context.get_parent_cd_size());
100
101 // Check that we will not access out of bounds memory.
102 // todo(ilyas): think of a way to not need to leak enqueued/nested context information here.
103 uint64_t max_read_addr = max_read_index + context.get_parent_cd_addr();
104 uint64_t max_write_addr = static_cast<uint64_t>(dst_addr) + copy_size;
105
106 // Need all of this to happen regardless
107 bool read_out_of_range = gt.gt(max_read_addr, AVM_HIGHEST_MEM_ADDRESS);
108 bool write_out_of_range = gt.gt(max_write_addr, AVM_HIGHEST_MEM_ADDRESS);
109
110 if (read_out_of_range || write_out_of_range) {
111 throw std::runtime_error("Attempting to access out of bounds memory");
112 }
113
114 // If we get to this point, we know we will be error free
115 std::vector<FF> padded_calldata(copy_size, 0); // Initialize with zeros
116 // Calldata is retrieved from [offset, max_read_index]
117 // if offset > max_read_index, we will read nothing
118 if (!gt.gt(static_cast<uint64_t>(offset), max_read_index)) {
119 padded_calldata = context.get_calldata(offset, copy_size);
120 }
121
122 for (uint32_t i = 0; i < copy_size; i++) {
123 memory.set(dst_addr + i, MemoryValue::from<FF>(padded_calldata[i]));
124 }
125
126 events.emit(create_cd_event(context, clk, copy_size, offset, dst_addr, padded_calldata));
127 } catch (const std::exception& e) {
128 debug("CD_COPY exception: ", e.what());
129 events.emit(create_cd_event(context, clk, copy_size, offset, dst_addr));
130
131 // Re-throw something generic that execution will interpret as an opcode error.
132 throw DataCopyException();
133 }
134}
135
144 const uint32_t copy_size,
145 const uint32_t offset,
147{
148 auto& memory = context.get_memory();
149 uint32_t clk = execution_id_manager.get_execution_id();
150
151 try {
152 // Check cd_copy for why we do this here even though it is in get_returndata()
153 uint64_t max_read_index = min(static_cast<uint64_t>(offset) + copy_size, context.get_last_rd_size());
154
155 uint64_t max_read_addr = max_read_index + context.get_last_rd_addr();
156 uint64_t max_write_addr = static_cast<uint64_t>(dst_addr) + copy_size;
157
158 // Need both of this to happen regardless
159 bool read_out_of_range = gt.gt(max_read_addr, AVM_HIGHEST_MEM_ADDRESS);
160 bool write_out_of_range = gt.gt(max_write_addr, AVM_HIGHEST_MEM_ADDRESS);
161
162 if (read_out_of_range || write_out_of_range) {
163 throw std::runtime_error("Attempting to access out of bounds memory");
164 }
165
166 // If we get to this point, we know we will be error free
167
168 // This is typically handled by the loop within get_returndata(), but we need to emit a range check in circuit
169 // so we need to be explicit about it.
170 // Returndata is retrieved from [offset, max_read_index], if offset > max_read_index, we will read nothing.
171 std::vector<FF> padded_returndata(copy_size, 0); // Initialize with zeros
172 if (!gt.gt(static_cast<uint64_t>(offset), max_read_index)) {
173 padded_returndata = context.get_returndata(offset, copy_size);
174 }
175
176 for (uint32_t i = 0; i < copy_size; i++) {
177 memory.set(dst_addr + i, MemoryValue::from<FF>(padded_returndata[i]));
178 }
179
180 events.emit(create_rd_event(context, clk, copy_size, offset, dst_addr, padded_returndata));
181
182 } catch (const std::exception& e) {
183 debug("RD_COPY exception: ", e.what());
184 events.emit(create_rd_event(context, clk, copy_size, offset, dst_addr));
185
186 // Re-throw something generic that execution will interpret as an opcode error.
187 throw DataCopyException();
188 }
189}
190
191} // namespace bb::avm2::simulation
#define AVM_HIGHEST_MEM_ADDRESS
ExecutionIdGetterInterface & execution_id_manager
Definition data_copy.hpp:35
uint64_t min(uint64_t a, uint64_t b)
Definition data_copy.cpp:58
EventEmitterInterface< DataCopyEvent > & events
Definition data_copy.hpp:37
void cd_copy(ContextInterface &context, const uint32_t cd_copy_size, const uint32_t cd_offset, const MemoryAddress dst_addr) override
Writes calldata into dst_addr. There is slight difference in how enqueued and nested contexts,...
Definition data_copy.cpp:85
void rd_copy(ContextInterface &context, const uint32_t rd_copy_size, const uint32_t rd_offset, const MemoryAddress dst_addr) override
Copies returndata from the last executed context to the dst_addr.
virtual uint32_t get_execution_id() const =0
#define debug(...)
Definition log.hpp:61
uint32_t dst_addr
StrictMock< MockContext > context
FF a
FF b
ssize_t offset
Definition engine.cpp:36
uint32_t MemoryAddress
std::vector< FF > calldata
std::vector< FF > returndata
uint32_t cd_offset