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 <vector>
4
10
11namespace bb::avm2::simulation {
12
13namespace {
14
19DataCopyEvent create_cd_event(ContextInterface& context,
20 uint32_t clk,
21 uint32_t copy_size,
22 uint32_t offset,
24 const std::vector<MemoryValue>& calldata = {})
25{
26 return DataCopyEvent{
27 .execution_clk = clk,
28 .operation = DataCopyOperation::CD_COPY,
29 .copying_data = calldata,
30 .write_context_id = context.get_context_id(),
31 .read_context_id = context.get_parent_id(),
32 .data_copy_size = copy_size,
33 .data_offset = offset,
34 .src_data_addr = context.get_parent_cd_addr(),
35 .src_data_size = context.get_parent_cd_size(),
36 .is_nested = context.has_parent(),
37 .dst_addr = dst_addr,
38 };
39}
40
45DataCopyEvent create_rd_event(ContextInterface& context,
46 uint32_t clk,
47 uint32_t copy_size,
48 uint32_t offset,
51{
52 return DataCopyEvent{
53 .execution_clk = clk,
54 .operation = DataCopyOperation::RD_COPY,
55 .copying_data = returndata,
56 .write_context_id = context.get_context_id(),
57 // This handles the case where there is no last child (i.e. new enqueued call)
58 .read_context_id = context.get_last_child_id(),
59 .data_copy_size = copy_size,
60 .data_offset = offset,
61 .src_data_addr = context.get_last_rd_addr(),
62 .src_data_size = context.get_last_rd_size(),
63 .is_nested = context.has_parent(),
64 .dst_addr = dst_addr,
65 };
66}
67
68} // namespace
69
70// This is std::min but creates the relevant greater than event
71uint64_t DataCopy::min(uint64_t a, uint64_t b)
72{
73 // Looks weird but ironically similar to the std::min implementation
74 // i.e if a == b, return a
75 if (gt.gt(a, b)) {
76 return b;
77 }
78 return a;
79}
80
99{
100 auto& memory = context.get_memory();
101 uint32_t clk = execution_id_manager.get_execution_id();
102
103 // This section is a bit leaky, but is necessary to ensure the correct gt events are generated.
104 // This work is duplicated in context.get_calldata() - but it avoids us having a gt there.
105
106 // Operations are performed over uint64_t in case the addition overflows, but the result in guaranteed to
107 // fit in 32 bits since get_parent_cd_size() returns a u32 (constrained by a CALL or 0 if an enqueued call).
108 uint64_t data_index_upper_bound = min(static_cast<uint64_t>(offset) + copy_size, context.get_parent_cd_size());
109
110 // Check that we will not access out of bounds memory.
111 uint64_t read_addr_upper_bound = data_index_upper_bound + context.get_parent_cd_addr();
112 uint64_t write_addr_upper_bound = static_cast<uint64_t>(dst_addr) + copy_size;
113
114 // Need all of this to happen regardless
115 bool read_out_of_range = gt.gt(read_addr_upper_bound, AVM_MEMORY_SIZE);
116 bool write_out_of_range = gt.gt(write_addr_upper_bound, AVM_MEMORY_SIZE);
117
118 if (read_out_of_range || write_out_of_range) {
119 const std::string error_msg = format("Attempting to access out of bounds memory: read_addr_upper_bound = ",
120 read_addr_upper_bound,
121 " write_addr_upper_bound = ",
122 write_addr_upper_bound);
123
124 events.emit(create_cd_event(context, clk, copy_size, offset, dst_addr));
125
126 // Throw something generic that execution will interpret as an opcode error.
127 throw DataCopyException(error_msg);
128 }
129
130 // If we get to this point, we know we will be error free
131 std::vector<MemoryValue> padded_calldata(copy_size, MemoryValue::from<FF>(0)); // Initialize with zeros
132 // Calldata is retrieved from [offset, data_index_upper_bound)
133 // If data_index_upper_bound > offset, we read the data.
134 if (gt.gt(data_index_upper_bound, static_cast<uint64_t>(offset))) {
135 padded_calldata = context.get_calldata(offset, copy_size);
136 }
137
138 // We do not enforce any tag check and upcast to FF transparently.
139 for (uint32_t i = 0; i < copy_size; i++) {
140 memory.set(dst_addr + i, MemoryValue::from<FF>(padded_calldata[i].as_ff()));
141 }
142
143 // We need to pass the original tags of the calldata to the circuit.
144 events.emit(create_cd_event(context, clk, copy_size, offset, dst_addr, padded_calldata));
145}
146
155{
156 auto& memory = context.get_memory();
157 uint32_t clk = execution_id_manager.get_execution_id();
158
159 // Check cd_copy for why we do this here even though it is in get_returndata()
160 uint64_t data_index_upper_bound = min(static_cast<uint64_t>(offset) + copy_size, context.get_last_rd_size());
161
162 uint64_t read_addr_upper_bound = data_index_upper_bound + context.get_last_rd_addr();
163 uint64_t write_addr_upper_bound = static_cast<uint64_t>(dst_addr) + copy_size;
164
165 // Need both of this to happen regardless
166 bool read_out_of_range = gt.gt(read_addr_upper_bound, AVM_MEMORY_SIZE);
167 bool write_out_of_range = gt.gt(write_addr_upper_bound, AVM_MEMORY_SIZE);
168
169 if (read_out_of_range || write_out_of_range) {
170 const std::string error_msg = format("Attempting to access out of bounds memory: read_addr_upper_bound = ",
171 read_addr_upper_bound,
172 " write_addr_upper_bound = ",
173 write_addr_upper_bound);
174
175 events.emit(create_rd_event(context, clk, copy_size, offset, dst_addr));
176
177 // Throw something generic that execution will interpret as an opcode error.
178 throw DataCopyException(error_msg);
179 }
180
181 // If we get to this point, we know we will be error free
182
183 // This is typically handled by the loop within get_returndata(), but we need to emit a range check in circuit
184 // so we need to be explicit about it.
185 // Returndata is retrieved from [offset, data_index_upper_bound), if data_index_upper_bound > offset, we will read
186 // the data.
187 std::vector<MemoryValue> padded_returndata(copy_size, MemoryValue::from<FF>(0)); // Initialize with zeros
188 if (gt.gt(data_index_upper_bound, static_cast<uint64_t>(offset))) {
189 padded_returndata = context.get_returndata(offset, copy_size);
190 }
191
192 // We do not enforce any tag check and upcast to FF transparently.
193 for (uint32_t i = 0; i < copy_size; i++) {
194 memory.set(dst_addr + i, MemoryValue::from<FF>(padded_returndata[i].as_ff()));
195 }
196
197 // We need to pass the original tags of the returndata to the circuit.
198 events.emit(create_rd_event(context, clk, copy_size, offset, dst_addr, padded_returndata));
199}
200
201} // namespace bb::avm2::simulation
#define AVM_MEMORY_SIZE
ExecutionIdGetterInterface & execution_id_manager
Definition data_copy.hpp:31
uint64_t min(uint64_t a, uint64_t b)
Definition data_copy.cpp:71
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...
Definition data_copy.cpp:98
EventEmitterInterface< DataCopyEvent > & events
Definition data_copy.hpp:33
virtual uint32_t get_execution_id() const =0
std::string format(Args... args)
Definition log.hpp:22
uint32_t dst_addr
StrictMock< MockContext > context
FF a
FF b
ssize_t offset
Definition engine.cpp:36
uint32_t MemoryAddress
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< MemoryValue > calldata
std::vector< MemoryValue > returndata