/src/solidity/libsolidity/codegen/ABIFunctions.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | /** |
19 | | * @author Christian <chris@ethereum.org> |
20 | | * @date 2017 |
21 | | * Routines that generate Yul code related to ABI encoding, decoding and type conversions. |
22 | | */ |
23 | | |
24 | | #include <libsolidity/codegen/ABIFunctions.h> |
25 | | |
26 | | #include <libsolidity/codegen/CompilerUtils.h> |
27 | | #include <libsolutil/CommonData.h> |
28 | | #include <libsolutil/Whiskers.h> |
29 | | #include <libsolutil/StringUtils.h> |
30 | | |
31 | | #include <boost/algorithm/string/join.hpp> |
32 | | |
33 | | using namespace solidity; |
34 | | using namespace solidity::util; |
35 | | using namespace solidity::frontend; |
36 | | |
37 | | std::string ABIFunctions::tupleEncoder( |
38 | | TypePointers const& _givenTypes, |
39 | | TypePointers _targetTypes, |
40 | | bool _encodeAsLibraryTypes, |
41 | | bool _reversed |
42 | | ) |
43 | 17.3k | { |
44 | 17.3k | solAssert(_givenTypes.size() == _targetTypes.size(), ""); |
45 | 17.3k | EncodingOptions options; |
46 | 17.3k | options.encodeAsLibraryTypes = _encodeAsLibraryTypes; |
47 | 17.3k | options.encodeFunctionFromStack = true; |
48 | 17.3k | options.padded = true; |
49 | 17.3k | options.dynamicInplace = false; |
50 | | |
51 | 17.3k | for (Type const*& t: _targetTypes) |
52 | 19.2k | { |
53 | 19.2k | solAssert(t, ""); |
54 | 19.2k | t = t->fullEncodingType(options.encodeAsLibraryTypes, true, !options.padded); |
55 | 19.2k | solAssert(t, ""); |
56 | 19.2k | } |
57 | | |
58 | 17.3k | std::string functionName = std::string("abi_encode_tuple_"); |
59 | 17.3k | for (auto const& t: _givenTypes) |
60 | 19.2k | functionName += t->identifier() + "_"; |
61 | 17.3k | functionName += "_to_"; |
62 | 17.3k | for (auto const& t: _targetTypes) |
63 | 19.2k | functionName += t->identifier() + "_"; |
64 | 17.3k | functionName += options.toFunctionNameSuffix(); |
65 | 17.3k | if (_reversed) |
66 | 10.7k | functionName += "_reversed"; |
67 | | |
68 | 17.3k | return createFunction(functionName, [&]() { |
69 | | // Note that the values are in reverse due to the difference in calling semantics. |
70 | 12.0k | Whiskers templ(R"( |
71 | 12.0k | function <functionName>(headStart <valueParams>) -> tail { |
72 | 12.0k | tail := add(headStart, <headSize>) |
73 | 12.0k | <encodeElements> |
74 | 12.0k | } |
75 | 12.0k | )"); |
76 | 12.0k | templ("functionName", functionName); |
77 | 12.0k | size_t const headSize_ = headSize(_targetTypes); |
78 | 12.0k | templ("headSize", std::to_string(headSize_)); |
79 | 12.0k | std::string encodeElements; |
80 | 12.0k | size_t headPos = 0; |
81 | 12.0k | size_t stackPos = 0; |
82 | 25.4k | for (size_t i = 0; i < _givenTypes.size(); ++i) |
83 | 13.3k | { |
84 | 13.3k | solAssert(_givenTypes[i], ""); |
85 | 13.3k | solAssert(_targetTypes[i], ""); |
86 | 13.3k | size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); |
87 | 13.3k | bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); |
88 | 13.3k | Whiskers elementTempl( |
89 | 13.3k | dynamic ? |
90 | 4.09k | std::string(R"( |
91 | 4.09k | mstore(add(headStart, <pos>), sub(tail, headStart)) |
92 | 4.09k | tail := <abiEncode>(<values> tail) |
93 | 4.09k | )") : |
94 | 13.3k | std::string(R"( |
95 | 9.29k | <abiEncode>(<values> add(headStart, <pos>)) |
96 | 9.29k | )") |
97 | 13.3k | ); |
98 | 13.3k | std::string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); |
99 | 13.3k | elementTempl("values", values.empty() ? "" : values + ", "); |
100 | 13.3k | elementTempl("pos", std::to_string(headPos)); |
101 | 13.3k | elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); |
102 | 13.3k | encodeElements += elementTempl.render(); |
103 | 13.3k | headPos += _targetTypes[i]->calldataHeadSize(); |
104 | 13.3k | stackPos += sizeOnStack; |
105 | 13.3k | } |
106 | 12.0k | solAssert(headPos == headSize_, ""); |
107 | 12.0k | std::string valueParams = |
108 | 12.0k | _reversed ? |
109 | 6.46k | suffixedVariableNameList("value", stackPos, 0) : |
110 | 12.0k | suffixedVariableNameList("value", 0, stackPos); |
111 | 12.0k | templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); |
112 | 12.0k | templ("encodeElements", encodeElements); |
113 | | |
114 | 12.0k | return templ.render(); |
115 | 12.0k | }); |
116 | 17.3k | } |
117 | | |
118 | | std::string ABIFunctions::tupleEncoderPacked( |
119 | | TypePointers const& _givenTypes, |
120 | | TypePointers _targetTypes, |
121 | | bool _reversed |
122 | | ) |
123 | 1.43k | { |
124 | 1.43k | EncodingOptions options; |
125 | 1.43k | options.encodeAsLibraryTypes = false; |
126 | 1.43k | options.encodeFunctionFromStack = true; |
127 | 1.43k | options.padded = false; |
128 | 1.43k | options.dynamicInplace = true; |
129 | | |
130 | 1.43k | for (Type const*& t: _targetTypes) |
131 | 1.78k | { |
132 | 1.78k | solAssert(t, ""); |
133 | 1.78k | t = t->fullEncodingType(options.encodeAsLibraryTypes, true, !options.padded); |
134 | 1.78k | solAssert(t, ""); |
135 | 1.78k | } |
136 | | |
137 | 1.43k | std::string functionName = std::string("abi_encode_tuple_packed_"); |
138 | 1.43k | for (auto const& t: _givenTypes) |
139 | 1.78k | functionName += t->identifier() + "_"; |
140 | 1.43k | functionName += "_to_"; |
141 | 1.43k | for (auto const& t: _targetTypes) |
142 | 1.78k | functionName += t->identifier() + "_"; |
143 | 1.43k | functionName += options.toFunctionNameSuffix(); |
144 | 1.43k | if (_reversed) |
145 | 1.05k | functionName += "_reversed"; |
146 | | |
147 | 1.43k | return createFunction(functionName, [&]() { |
148 | | // Note that the values are in reverse due to the difference in calling semantics. |
149 | 1.06k | Whiskers templ(R"( |
150 | 1.06k | function <functionName>(pos <valueParams>) -> end { |
151 | 1.06k | <encodeElements> |
152 | 1.06k | end := pos |
153 | 1.06k | } |
154 | 1.06k | )"); |
155 | 1.06k | templ("functionName", functionName); |
156 | 1.06k | std::string encodeElements; |
157 | 1.06k | size_t stackPos = 0; |
158 | 2.45k | for (size_t i = 0; i < _givenTypes.size(); ++i) |
159 | 1.39k | { |
160 | 1.39k | solAssert(_givenTypes[i], ""); |
161 | 1.39k | solAssert(_targetTypes[i], ""); |
162 | 1.39k | size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); |
163 | 1.39k | bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); |
164 | 1.39k | Whiskers elementTempl( |
165 | 1.39k | dynamic ? |
166 | 1.09k | std::string(R"( |
167 | 1.09k | pos := <abiEncode>(<values> pos) |
168 | 1.09k | )") : |
169 | 1.39k | std::string(R"( |
170 | 304 | <abiEncode>(<values> pos) |
171 | 304 | pos := add(pos, <calldataEncodedSize>) |
172 | 304 | )") |
173 | 1.39k | ); |
174 | 1.39k | std::string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); |
175 | 1.39k | elementTempl("values", values.empty() ? "" : values + ", "); |
176 | 1.39k | if (!dynamic) |
177 | 304 | elementTempl("calldataEncodedSize", std::to_string(_targetTypes[i]->calldataEncodedSize(false))); |
178 | 1.39k | elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); |
179 | 1.39k | encodeElements += elementTempl.render(); |
180 | 1.39k | stackPos += sizeOnStack; |
181 | 1.39k | } |
182 | 1.06k | std::string valueParams = |
183 | 1.06k | _reversed ? |
184 | 690 | suffixedVariableNameList("value", stackPos, 0) : |
185 | 1.06k | suffixedVariableNameList("value", 0, stackPos); |
186 | 1.06k | templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); |
187 | 1.06k | templ("encodeElements", encodeElements); |
188 | | |
189 | 1.06k | return templ.render(); |
190 | 1.06k | }); |
191 | 1.43k | } |
192 | | std::string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) |
193 | 17.1k | { |
194 | 17.1k | std::string functionName = std::string("abi_decode_tuple_"); |
195 | 17.1k | for (auto const& t: _types) |
196 | 18.5k | functionName += t->identifier(); |
197 | 17.1k | if (_fromMemory) |
198 | 3.33k | functionName += "_fromMemory"; |
199 | | |
200 | 17.1k | return createFunction(functionName, [&]() { |
201 | 10.2k | TypePointers decodingTypes; |
202 | 10.2k | for (auto const& t: _types) |
203 | 11.5k | decodingTypes.emplace_back(t->decodingType()); |
204 | | |
205 | 10.2k | Whiskers templ(R"( |
206 | 10.2k | function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> { |
207 | 10.2k | if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString>() } |
208 | 10.2k | <decodeElements> |
209 | 10.2k | } |
210 | 10.2k | )"); |
211 | 10.2k | templ("functionName", functionName); |
212 | 10.2k | templ("revertString", revertReasonIfDebugFunction("ABI decoding: tuple data too short")); |
213 | 10.2k | templ("minimumSize", std::to_string(headSize(decodingTypes))); |
214 | | |
215 | 10.2k | std::string decodeElements; |
216 | 10.2k | std::vector<std::string> valueReturnParams; |
217 | 10.2k | size_t headPos = 0; |
218 | 10.2k | size_t stackPos = 0; |
219 | 21.7k | for (size_t i = 0; i < _types.size(); ++i) |
220 | 11.5k | { |
221 | 11.5k | solAssert(_types[i], ""); |
222 | 11.5k | solAssert(decodingTypes[i], ""); |
223 | 11.5k | size_t sizeOnStack = _types[i]->sizeOnStack(); |
224 | 11.5k | solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), ""); |
225 | 11.5k | solAssert(sizeOnStack > 0, ""); |
226 | 11.5k | std::vector<std::string> valueNamesLocal; |
227 | 23.5k | for (size_t j = 0; j < sizeOnStack; j++) |
228 | 11.9k | { |
229 | 11.9k | valueNamesLocal.emplace_back("value" + std::to_string(stackPos)); |
230 | 11.9k | valueReturnParams.emplace_back("value" + std::to_string(stackPos)); |
231 | 11.9k | stackPos++; |
232 | 11.9k | } |
233 | 11.5k | Whiskers elementTempl(R"( |
234 | 11.5k | { |
235 | 11.5k | <?dynamic> |
236 | 11.5k | let offset := <load>(add(headStart, <pos>)) |
237 | 11.5k | if gt(offset, 0xffffffffffffffff) { <revertString>() } |
238 | 11.5k | <!dynamic> |
239 | 11.5k | let offset := <pos> |
240 | 11.5k | </dynamic> |
241 | 11.5k | <values> := <abiDecode>(add(headStart, offset), dataEnd) |
242 | 11.5k | } |
243 | 11.5k | )"); |
244 | 11.5k | elementTempl("dynamic", decodingTypes[i]->isDynamicallyEncoded()); |
245 | | // TODO add test |
246 | 11.5k | elementTempl("revertString", revertReasonIfDebugFunction("ABI decoding: invalid tuple offset")); |
247 | 11.5k | elementTempl("load", _fromMemory ? "mload" : "calldataload"); |
248 | 11.5k | elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); |
249 | 11.5k | elementTempl("pos", std::to_string(headPos)); |
250 | 11.5k | elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true)); |
251 | 11.5k | decodeElements += elementTempl.render(); |
252 | 11.5k | headPos += decodingTypes[i]->calldataHeadSize(); |
253 | 11.5k | } |
254 | 10.2k | templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); |
255 | 10.2k | templ("arrow", valueReturnParams.empty() ? "" : "->"); |
256 | 10.2k | templ("decodeElements", decodeElements); |
257 | | |
258 | 10.2k | return templ.render(); |
259 | 10.2k | }); |
260 | 17.1k | } |
261 | | |
262 | | std::string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const |
263 | 100k | { |
264 | 100k | std::string suffix; |
265 | 100k | if (!padded) |
266 | 3.85k | suffix += "_nonPadded"; |
267 | 100k | if (dynamicInplace) |
268 | 4.03k | suffix += "_inplace"; |
269 | 100k | if (encodeFunctionFromStack) |
270 | 37.9k | suffix += "_fromStack"; |
271 | 100k | if (encodeAsLibraryTypes) |
272 | 651 | suffix += "_library"; |
273 | 100k | return suffix; |
274 | 100k | } |
275 | | |
276 | | std::string ABIFunctions::abiEncodingFunction( |
277 | | Type const& _from, |
278 | | Type const& _to, |
279 | | EncodingOptions const& _options |
280 | | ) |
281 | 67.6k | { |
282 | 67.6k | Type const* toInterface = _to.fullEncodingType(_options.encodeAsLibraryTypes, true, false); |
283 | 67.6k | solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented."); |
284 | 67.6k | Type const& to = *toInterface; |
285 | | |
286 | 67.6k | if (_from.category() == Type::Category::StringLiteral) |
287 | 694 | return abiEncodingFunctionStringLiteral(_from, to, _options); |
288 | 66.9k | else if (auto toArray = dynamic_cast<ArrayType const*>(&to)) |
289 | 11.4k | { |
290 | 11.4k | ArrayType const* fromArray = nullptr; |
291 | 11.4k | switch (_from.category()) |
292 | 11.4k | { |
293 | 11.3k | case Type::Category::Array: |
294 | 11.3k | fromArray = dynamic_cast<ArrayType const*>(&_from); |
295 | 11.3k | break; |
296 | 20 | case Type::Category::ArraySlice: |
297 | 20 | fromArray = &dynamic_cast<ArraySliceType const*>(&_from)->arrayType(); |
298 | 20 | solAssert( |
299 | 20 | fromArray->dataStoredIn(DataLocation::CallData) && |
300 | 20 | fromArray->isDynamicallySized() && |
301 | 20 | !fromArray->baseType()->isDynamicallyEncoded(), |
302 | 20 | "" |
303 | 20 | ); |
304 | 20 | break; |
305 | 20 | default: |
306 | 0 | solAssert(false, ""); |
307 | 0 | break; |
308 | 11.4k | } |
309 | | |
310 | 11.4k | switch (fromArray->location()) |
311 | 11.4k | { |
312 | 2.00k | case DataLocation::CallData: |
313 | 2.00k | if ( |
314 | 2.00k | fromArray->isByteArrayOrString() || |
315 | 2.00k | *fromArray->baseType() == *TypeProvider::uint256() || |
316 | 2.00k | *fromArray->baseType() == FixedBytesType(32) |
317 | 2.00k | ) |
318 | 991 | return abiEncodingFunctionCalldataArrayWithoutCleanup(*fromArray, *toArray, _options); |
319 | 1.01k | else |
320 | 1.01k | return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options); |
321 | 7.88k | case DataLocation::Memory: |
322 | 7.88k | if (fromArray->isByteArrayOrString()) |
323 | 3.44k | return abiEncodingFunctionMemoryByteArray(*fromArray, *toArray, _options); |
324 | 4.43k | else |
325 | 4.43k | return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options); |
326 | 1.51k | case DataLocation::Storage: |
327 | 1.51k | if (fromArray->baseType()->storageBytes() <= 16) |
328 | 922 | return abiEncodingFunctionCompactStorageArray(*fromArray, *toArray, _options); |
329 | 594 | else |
330 | 594 | return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options); |
331 | 0 | default: |
332 | 0 | solAssert(false, ""); |
333 | 11.4k | } |
334 | 11.4k | } |
335 | 55.5k | else if (auto const* toStruct = dynamic_cast<StructType const*>(&to)) |
336 | 20.9k | { |
337 | 20.9k | StructType const* fromStruct = dynamic_cast<StructType const*>(&_from); |
338 | 20.9k | solAssert(fromStruct, ""); |
339 | 20.9k | return abiEncodingFunctionStruct(*fromStruct, *toStruct, _options); |
340 | 20.9k | } |
341 | 34.6k | else if (_from.category() == Type::Category::Function) |
342 | 119 | return abiEncodingFunctionFunctionType( |
343 | 119 | dynamic_cast<FunctionType const&>(_from), |
344 | 119 | to, |
345 | 119 | _options |
346 | 119 | ); |
347 | | |
348 | 34.5k | solAssert(_from.sizeOnStack() == 1, ""); |
349 | 34.5k | solAssert(to.isValueType(), ""); |
350 | 34.5k | solAssert(to.calldataEncodedSize() == 32, ""); |
351 | 34.5k | std::string functionName = |
352 | 34.5k | "abi_encode_" + |
353 | 34.5k | _from.identifier() + |
354 | 34.5k | "_to_" + |
355 | 34.5k | to.identifier() + |
356 | 34.5k | _options.toFunctionNameSuffix(); |
357 | 34.5k | return createFunction(functionName, [&]() { |
358 | 10.3k | solAssert(!to.isDynamicallyEncoded(), ""); |
359 | | |
360 | 10.3k | Whiskers templ(R"( |
361 | 10.3k | function <functionName>(value, pos) { |
362 | 10.3k | mstore(pos, <cleanupConvert>) |
363 | 10.3k | } |
364 | 10.3k | )"); |
365 | 10.3k | templ("functionName", functionName); |
366 | | |
367 | 10.3k | if (_from.dataStoredIn(DataLocation::Storage)) |
368 | 36 | { |
369 | | // special case: convert storage reference type to value type - this is only |
370 | | // possible for library calls where we just forward the storage reference |
371 | 36 | solAssert(_options.encodeAsLibraryTypes, ""); |
372 | 36 | solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested."); |
373 | 36 | solAssert(to == *TypeProvider::uint256(), ""); |
374 | 36 | templ("cleanupConvert", "value"); |
375 | 36 | } |
376 | 10.3k | else |
377 | 10.3k | { |
378 | 10.3k | std::string cleanupConvert; |
379 | 10.3k | if (_from == to) |
380 | 9.67k | cleanupConvert = m_utils.cleanupFunction(_from) + "(value)"; |
381 | 637 | else |
382 | 637 | cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)"; |
383 | 10.3k | if (!_options.padded) |
384 | 190 | cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; |
385 | 10.3k | templ("cleanupConvert", cleanupConvert); |
386 | 10.3k | } |
387 | 10.3k | return templ.render(); |
388 | 10.3k | }); |
389 | 34.5k | } |
390 | | |
391 | | std::string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction( |
392 | | Type const& _givenType, |
393 | | Type const& _targetType, |
394 | | ABIFunctions::EncodingOptions const& _options |
395 | | ) |
396 | 5.26k | { |
397 | 5.26k | std::string functionName = |
398 | 5.26k | "abi_encodeUpdatedPos_" + |
399 | 5.26k | _givenType.identifier() + |
400 | 5.26k | "_to_" + |
401 | 5.26k | _targetType.identifier() + |
402 | 5.26k | _options.toFunctionNameSuffix(); |
403 | 5.26k | return createFunction(functionName, [&]() { |
404 | 5.02k | std::string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options)); |
405 | 5.02k | std::string encoder = abiEncodingFunction(_givenType, _targetType, _options); |
406 | 5.02k | Type const* targetEncoding = _targetType.fullEncodingType(_options.encodeAsLibraryTypes, true, false); |
407 | 5.02k | solAssert(targetEncoding, ""); |
408 | 5.02k | if (targetEncoding->isDynamicallyEncoded()) |
409 | 2.87k | return Whiskers(R"( |
410 | 2.87k | function <functionName>(<values>, pos) -> updatedPos { |
411 | 2.87k | updatedPos := <encode>(<values>, pos) |
412 | 2.87k | } |
413 | 2.87k | )") |
414 | 2.87k | ("functionName", functionName) |
415 | 2.87k | ("encode", encoder) |
416 | 2.87k | ("values", values) |
417 | 2.87k | .render(); |
418 | 2.15k | else |
419 | 2.15k | { |
420 | 2.15k | unsigned encodedSize = targetEncoding->calldataEncodedSize(_options.padded); |
421 | 2.15k | solAssert(encodedSize != 0, "Invalid encoded size."); |
422 | 2.15k | return Whiskers(R"( |
423 | 2.15k | function <functionName>(<values>, pos) -> updatedPos { |
424 | 2.15k | <encode>(<values>, pos) |
425 | 2.15k | updatedPos := add(pos, <encodedSize>) |
426 | 2.15k | } |
427 | 2.15k | )") |
428 | 2.15k | ("functionName", functionName) |
429 | 2.15k | ("encode", encoder) |
430 | 2.15k | ("encodedSize", toCompactHexWithPrefix(encodedSize)) |
431 | 2.15k | ("values", values) |
432 | 2.15k | .render(); |
433 | 2.15k | } |
434 | 5.02k | }); |
435 | 5.26k | } |
436 | | |
437 | | std::string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( |
438 | | Type const& _from, |
439 | | Type const& _to, |
440 | | EncodingOptions const& _options |
441 | | ) |
442 | 991 | { |
443 | 991 | solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type."); |
444 | 991 | solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type."); |
445 | 991 | auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from); |
446 | 991 | auto const& toArrayType = dynamic_cast<ArrayType const&>(_to); |
447 | | |
448 | 991 | solAssert(fromArrayType.location() == DataLocation::CallData, ""); |
449 | 991 | solAssert( |
450 | 991 | fromArrayType.isByteArrayOrString() || |
451 | 991 | *fromArrayType.baseType() == *TypeProvider::uint256() || |
452 | 991 | *fromArrayType.baseType() == FixedBytesType(32), |
453 | 991 | "" |
454 | 991 | ); |
455 | 991 | solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), ""); |
456 | | |
457 | 991 | solAssert( |
458 | 991 | *fromArrayType.copyForLocation(DataLocation::Memory, true) == |
459 | 991 | *toArrayType.copyForLocation(DataLocation::Memory, true), |
460 | 991 | "" |
461 | 991 | ); |
462 | | |
463 | 991 | std::string functionName = |
464 | 991 | "abi_encode_" + |
465 | 991 | _from.identifier() + |
466 | 991 | "_to_" + |
467 | 991 | _to.identifier() + |
468 | 991 | _options.toFunctionNameSuffix(); |
469 | 991 | return createFunction(functionName, [&]() { |
470 | 290 | bool bytesOrString = fromArrayType.isByteArrayOrString(); |
471 | 290 | bool needsPadding = _options.padded && bytesOrString; |
472 | 290 | if (fromArrayType.isDynamicallySized()) |
473 | 282 | { |
474 | 282 | Whiskers templ(R"( |
475 | 282 | // <readableTypeNameFrom> -> <readableTypeNameTo> |
476 | 282 | function <functionName>(start, length, pos) -> end { |
477 | 282 | pos := <storeLength>(pos, length) |
478 | 282 | <scaleLengthByStride> |
479 | 282 | <copyFun>(start, pos, length) |
480 | 282 | end := add(pos, <lengthPadded>) |
481 | 282 | } |
482 | 282 | )"); |
483 | 282 | templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options)); |
484 | 282 | templ("functionName", functionName); |
485 | 282 | if (fromArrayType.isByteArrayOrString() || fromArrayType.calldataStride() == 1) |
486 | 256 | templ("scaleLengthByStride", ""); |
487 | 26 | else |
488 | 26 | templ("scaleLengthByStride", |
489 | 26 | Whiskers(R"( |
490 | 26 | if gt(length, <maxLength>) { <revertString>() } |
491 | 26 | length := mul(length, <stride>) |
492 | 26 | )") |
493 | 26 | ("stride", toCompactHexWithPrefix(fromArrayType.calldataStride())) |
494 | 26 | ("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride())) |
495 | 26 | ("revertString", revertReasonIfDebugFunction("ABI encoding: array data too long")) |
496 | 26 | .render() |
497 | | // TODO add revert test |
498 | 26 | ); |
499 | 282 | templ("readableTypeNameFrom", _from.toString(true)); |
500 | 282 | templ("readableTypeNameTo", _to.toString(true)); |
501 | 282 | templ("copyFun", m_utils.copyToMemoryFunction(true, /*cleanup*/bytesOrString)); |
502 | 282 | templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length"); |
503 | 282 | return templ.render(); |
504 | 282 | } |
505 | 8 | else |
506 | 8 | { |
507 | 8 | solAssert(fromArrayType.calldataStride() == 32, ""); |
508 | 8 | Whiskers templ(R"( |
509 | 8 | // <readableTypeNameFrom> -> <readableTypeNameTo> |
510 | 8 | function <functionName>(start, pos) { |
511 | 8 | <copyFun>(start, pos, <byteLength>) |
512 | 8 | } |
513 | 8 | )"); |
514 | 8 | templ("functionName", functionName); |
515 | 8 | templ("readableTypeNameFrom", _from.toString(true)); |
516 | 8 | templ("readableTypeNameTo", _to.toString(true)); |
517 | 8 | templ("copyFun", m_utils.copyToMemoryFunction(true, /*cleanup*/bytesOrString)); |
518 | 8 | templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride())); |
519 | 8 | return templ.render(); |
520 | 8 | } |
521 | 290 | }); |
522 | 991 | } |
523 | | |
524 | | std::string ABIFunctions::abiEncodingFunctionSimpleArray( |
525 | | ArrayType const& _from, |
526 | | ArrayType const& _to, |
527 | | EncodingOptions const& _options |
528 | | ) |
529 | 6.04k | { |
530 | 6.04k | std::string functionName = |
531 | 6.04k | "abi_encode_" + |
532 | 6.04k | _from.identifier() + |
533 | 6.04k | "_to_" + |
534 | 6.04k | _to.identifier() + |
535 | 6.04k | _options.toFunctionNameSuffix(); |
536 | | |
537 | 6.04k | solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); |
538 | 6.04k | solAssert(_from.length() == _to.length(), ""); |
539 | 6.04k | solAssert(!_from.isByteArrayOrString(), ""); |
540 | 6.04k | if (_from.dataStoredIn(DataLocation::Storage)) |
541 | 6.04k | solAssert(_from.baseType()->storageBytes() > 16, ""); |
542 | | |
543 | 6.04k | return createFunction(functionName, [&]() { |
544 | 5.05k | bool dynamic = _to.isDynamicallyEncoded(); |
545 | 5.05k | bool dynamicBase = _to.baseType()->isDynamicallyEncoded(); |
546 | 5.05k | bool const usesTail = dynamicBase && !_options.dynamicInplace; |
547 | 5.05k | EncodingOptions subOptions(_options); |
548 | 5.05k | subOptions.encodeFunctionFromStack = false; |
549 | 5.05k | subOptions.padded = true; |
550 | 5.05k | std::string elementValues = suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions)); |
551 | 5.05k | Whiskers templ( |
552 | 5.05k | usesTail ? |
553 | 2.72k | R"( |
554 | 2.72k | // <readableTypeNameFrom> -> <readableTypeNameTo> |
555 | 2.72k | function <functionName>(value,<maybeLength> pos) <return> { |
556 | 2.72k | <declareLength> |
557 | 2.72k | pos := <storeLength>(pos, length) |
558 | 2.72k | let headStart := pos |
559 | 2.72k | let tail := add(pos, mul(length, 0x20)) |
560 | 2.72k | let baseRef := <dataAreaFun>(value) |
561 | 2.72k | let srcPtr := baseRef |
562 | 2.72k | for { let i := 0 } lt(i, length) { i := add(i, 1) } |
563 | 2.72k | { |
564 | 2.72k | mstore(pos, sub(tail, headStart)) |
565 | 2.72k | let <elementValues> := <arrayElementAccess> |
566 | 2.72k | tail := <encodeToMemoryFun>(<elementValues>, tail) |
567 | 2.72k | srcPtr := <nextArrayElement>(srcPtr) |
568 | 2.72k | pos := add(pos, 0x20) |
569 | 2.72k | } |
570 | 2.72k | pos := tail |
571 | 2.72k | <assignEnd> |
572 | 2.72k | } |
573 | 2.72k | )" : |
574 | 5.05k | R"( |
575 | 2.33k | // <readableTypeNameFrom> -> <readableTypeNameTo> |
576 | 2.33k | function <functionName>(value,<maybeLength> pos) <return> { |
577 | 2.33k | <declareLength> |
578 | 2.33k | pos := <storeLength>(pos, length) |
579 | 2.33k | let baseRef := <dataAreaFun>(value) |
580 | 2.33k | let srcPtr := baseRef |
581 | 2.33k | for { let i := 0 } lt(i, length) { i := add(i, 1) } |
582 | 2.33k | { |
583 | 2.33k | let <elementValues> := <arrayElementAccess> |
584 | 2.33k | pos := <encodeToMemoryFun>(<elementValues>, pos) |
585 | 2.33k | srcPtr := <nextArrayElement>(srcPtr) |
586 | 2.33k | } |
587 | 2.33k | <assignEnd> |
588 | 2.33k | } |
589 | 2.33k | )" |
590 | 5.05k | ); |
591 | 5.05k | templ("functionName", functionName); |
592 | 5.05k | templ("elementValues", elementValues); |
593 | 5.05k | bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized(); |
594 | 5.05k | if (lengthAsArgument) |
595 | 525 | { |
596 | 525 | templ("maybeLength", " length,"); |
597 | 525 | templ("declareLength", ""); |
598 | 525 | } |
599 | 4.52k | else |
600 | 4.52k | { |
601 | 4.52k | templ("maybeLength", ""); |
602 | 4.52k | templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)"); |
603 | 4.52k | } |
604 | 5.05k | templ("readableTypeNameFrom", _from.toString(true)); |
605 | 5.05k | templ("readableTypeNameTo", _to.toString(true)); |
606 | 5.05k | templ("return", dynamic ? " -> end " : ""); |
607 | 5.05k | templ("assignEnd", dynamic ? "end := pos" : ""); |
608 | 5.05k | templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); |
609 | 5.05k | templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from)); |
610 | | |
611 | 5.05k | templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); |
612 | 5.05k | switch (_from.location()) |
613 | 5.05k | { |
614 | 3.96k | case DataLocation::Memory: |
615 | 3.96k | templ("arrayElementAccess", "mload(srcPtr)"); |
616 | 3.96k | break; |
617 | 435 | case DataLocation::Storage: |
618 | 435 | if (_from.baseType()->isValueType()) |
619 | 65 | templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false, VariableDeclaration::Location::Unspecified) + "(srcPtr)"); |
620 | 370 | else |
621 | 370 | templ("arrayElementAccess", "srcPtr"); |
622 | 435 | break; |
623 | 658 | case DataLocation::CallData: |
624 | 658 | templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)"); |
625 | 658 | break; |
626 | 0 | default: |
627 | 0 | solAssert(false, ""); |
628 | 5.05k | } |
629 | 5.05k | templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); |
630 | 5.05k | return templ.render(); |
631 | 5.05k | }); |
632 | 6.04k | } |
633 | | |
634 | | std::string ABIFunctions::abiEncodingFunctionMemoryByteArray( |
635 | | ArrayType const& _from, |
636 | | ArrayType const& _to, |
637 | | EncodingOptions const& _options |
638 | | ) |
639 | 3.44k | { |
640 | 3.44k | std::string functionName = |
641 | 3.44k | "abi_encode_" + |
642 | 3.44k | _from.identifier() + |
643 | 3.44k | "_to_" + |
644 | 3.44k | _to.identifier() + |
645 | 3.44k | _options.toFunctionNameSuffix(); |
646 | | |
647 | 3.44k | solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); |
648 | 3.44k | solAssert(_from.length() == _to.length(), ""); |
649 | 3.44k | solAssert(_from.dataStoredIn(DataLocation::Memory), ""); |
650 | 3.44k | solAssert(_from.isByteArrayOrString(), ""); |
651 | | |
652 | 3.44k | return createFunction(functionName, [&]() { |
653 | 2.32k | solAssert(_to.isByteArrayOrString(), ""); |
654 | 2.32k | Whiskers templ(R"( |
655 | 2.32k | function <functionName>(value, pos) -> end { |
656 | 2.32k | let length := <lengthFun>(value) |
657 | 2.32k | pos := <storeLength>(pos, length) |
658 | 2.32k | <copyFun>(add(value, 0x20), pos, length) |
659 | 2.32k | end := add(pos, <lengthPadded>) |
660 | 2.32k | } |
661 | 2.32k | )"); |
662 | 2.32k | templ("functionName", functionName); |
663 | 2.32k | templ("lengthFun", m_utils.arrayLengthFunction(_from)); |
664 | 2.32k | templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); |
665 | 2.32k | templ("copyFun", m_utils.copyToMemoryFunction(false, /*cleanup*/true)); |
666 | 2.32k | templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); |
667 | 2.32k | return templ.render(); |
668 | 2.32k | }); |
669 | 3.44k | } |
670 | | |
671 | | std::string ABIFunctions::abiEncodingFunctionCompactStorageArray( |
672 | | ArrayType const& _from, |
673 | | ArrayType const& _to, |
674 | | EncodingOptions const& _options |
675 | | ) |
676 | 922 | { |
677 | 922 | std::string functionName = |
678 | 922 | "abi_encode_" + |
679 | 922 | _from.identifier() + |
680 | 922 | "_to_" + |
681 | 922 | _to.identifier() + |
682 | 922 | _options.toFunctionNameSuffix(); |
683 | | |
684 | 922 | solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); |
685 | 922 | solAssert(_from.length() == _to.length(), ""); |
686 | 922 | solAssert(_from.dataStoredIn(DataLocation::Storage), ""); |
687 | | |
688 | 922 | return createFunction(functionName, [&]() { |
689 | 432 | if (_from.isByteArrayOrString()) |
690 | 328 | { |
691 | 328 | solAssert(_to.isByteArrayOrString(), ""); |
692 | 328 | Whiskers templ(R"( |
693 | 328 | // <readableTypeNameFrom> -> <readableTypeNameTo> |
694 | 328 | function <functionName>(value, pos) -> ret { |
695 | 328 | let slotValue := sload(value) |
696 | 328 | let length := <byteArrayLengthFunction>(slotValue) |
697 | 328 | pos := <storeLength>(pos, length) |
698 | 328 | switch and(slotValue, 1) |
699 | 328 | case 0 { |
700 | 328 | // short byte array |
701 | 328 | mstore(pos, and(slotValue, not(0xff))) |
702 | 328 | ret := add(pos, mul(<lengthPaddedShort>, iszero(iszero(length)))) |
703 | 328 | } |
704 | 328 | case 1 { |
705 | 328 | // long byte array |
706 | 328 | let dataPos := <arrayDataSlot>(value) |
707 | 328 | let i := 0 |
708 | 328 | for { } lt(i, length) { i := add(i, 0x20) } { |
709 | 328 | mstore(add(pos, i), sload(dataPos)) |
710 | 328 | dataPos := add(dataPos, 1) |
711 | 328 | } |
712 | 328 | ret := add(pos, <lengthPaddedLong>) |
713 | 328 | } |
714 | 328 | } |
715 | 328 | )"); |
716 | 328 | templ("functionName", functionName); |
717 | 328 | templ("readableTypeNameFrom", _from.toString(true)); |
718 | 328 | templ("readableTypeNameTo", _to.toString(true)); |
719 | 328 | templ("byteArrayLengthFunction", m_utils.extractByteArrayLengthFunction()); |
720 | 328 | templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); |
721 | 328 | templ("lengthPaddedShort", _options.padded ? "0x20" : "length"); |
722 | 328 | templ("lengthPaddedLong", _options.padded ? "i" : "length"); |
723 | 328 | templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from)); |
724 | 328 | return templ.render(); |
725 | 328 | } |
726 | 104 | else |
727 | 104 | { |
728 | | // Multiple items per slot |
729 | 104 | solAssert(_from.baseType()->storageBytes() <= 16, ""); |
730 | 104 | solAssert(!_from.baseType()->isDynamicallyEncoded(), ""); |
731 | 104 | solAssert(!_to.baseType()->isDynamicallyEncoded(), ""); |
732 | 104 | solAssert(_from.baseType()->isValueType(), ""); |
733 | 104 | bool dynamic = _to.isDynamicallyEncoded(); |
734 | 104 | size_t storageBytes = _from.baseType()->storageBytes(); |
735 | 104 | size_t itemsPerSlot = 32 / storageBytes; |
736 | 104 | solAssert(itemsPerSlot > 0, ""); |
737 | | // The number of elements we need to handle manually after the loop. |
738 | 104 | size_t spill = static_cast<size_t>(_from.length() % itemsPerSlot); |
739 | 104 | Whiskers templ( |
740 | 104 | R"( |
741 | 104 | // <readableTypeNameFrom> -> <readableTypeNameTo> |
742 | 104 | function <functionName>(value, pos) <return> { |
743 | 104 | let length := <lengthFun>(value) |
744 | 104 | pos := <storeLength>(pos, length) |
745 | 104 | let originalPos := pos |
746 | 104 | let srcPtr := <dataArea>(value) |
747 | 104 | let itemCounter := 0 |
748 | 104 | if <useLoop> { |
749 | 104 | // Run the loop over all full slots |
750 | 104 | for { } lt(add(itemCounter, sub(<itemsPerSlot>, 1)), length) |
751 | 104 | { itemCounter := add(itemCounter, <itemsPerSlot>) } |
752 | 104 | { |
753 | 104 | let data := sload(srcPtr) |
754 | 104 | <#items> |
755 | 104 | <encodeToMemoryFun>(<extractFromSlot>(data), pos) |
756 | 104 | pos := add(pos, <stride>) |
757 | 104 | </items> |
758 | 104 | srcPtr := add(srcPtr, 1) |
759 | 104 | } |
760 | 104 | } |
761 | 104 | // Handle the last (not necessarily full) slot specially |
762 | 104 | if <useSpill> { |
763 | 104 | let data := sload(srcPtr) |
764 | 104 | <#items> |
765 | 104 | if <inRange> { |
766 | 104 | <encodeToMemoryFun>(<extractFromSlot>(data), pos) |
767 | 104 | pos := add(pos, <stride>) |
768 | 104 | itemCounter := add(itemCounter, 1) |
769 | 104 | } |
770 | 104 | </items> |
771 | 104 | } |
772 | 104 | <assignEnd> |
773 | 104 | } |
774 | 104 | )" |
775 | 104 | ); |
776 | 104 | templ("functionName", functionName); |
777 | 104 | templ("readableTypeNameFrom", _from.toString(true)); |
778 | 104 | templ("readableTypeNameTo", _to.toString(true)); |
779 | 104 | templ("return", dynamic ? " -> end " : ""); |
780 | 104 | templ("assignEnd", dynamic ? "end := pos" : ""); |
781 | 104 | templ("lengthFun", m_utils.arrayLengthFunction(_from)); |
782 | 104 | templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); |
783 | 104 | templ("dataArea", m_utils.arrayDataAreaFunction(_from)); |
784 | | // We skip the loop for arrays that fit a single slot. |
785 | 104 | if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot) |
786 | 69 | templ("useLoop", "1"); |
787 | 35 | else |
788 | 35 | templ("useLoop", "0"); |
789 | 104 | if (_from.isDynamicallySized() || spill != 0) |
790 | 103 | templ("useSpill", "1"); |
791 | 1 | else |
792 | 1 | templ("useSpill", "0"); |
793 | 104 | templ("itemsPerSlot", std::to_string(itemsPerSlot)); |
794 | 104 | templ("stride", toCompactHexWithPrefix(_to.calldataStride())); |
795 | | |
796 | 104 | EncodingOptions subOptions(_options); |
797 | 104 | subOptions.encodeFunctionFromStack = false; |
798 | 104 | subOptions.padded = true; |
799 | 104 | std::string encodeToMemoryFun = abiEncodingFunction( |
800 | 104 | *_from.baseType(), |
801 | 104 | *_to.baseType(), |
802 | 104 | subOptions |
803 | 104 | ); |
804 | 104 | templ("encodeToMemoryFun", encodeToMemoryFun); |
805 | 104 | std::vector<std::map<std::string, std::string>> items(itemsPerSlot); |
806 | 2.72k | for (size_t i = 0; i < itemsPerSlot; ++i) |
807 | 2.62k | { |
808 | 2.62k | if (_from.isDynamicallySized()) |
809 | 1.63k | items[i]["inRange"] = "lt(itemCounter, length)"; |
810 | 986 | else if (i < spill) |
811 | 97 | items[i]["inRange"] = "1"; |
812 | 889 | else |
813 | 889 | items[i]["inRange"] = "0"; |
814 | 2.62k | items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes); |
815 | 2.62k | } |
816 | 104 | templ("items", items); |
817 | 104 | return templ.render(); |
818 | 104 | } |
819 | 432 | }); |
820 | 922 | } |
821 | | |
822 | | std::string ABIFunctions::abiEncodingFunctionStruct( |
823 | | StructType const& _from, |
824 | | StructType const& _to, |
825 | | EncodingOptions const& _options |
826 | | ) |
827 | 20.9k | { |
828 | 20.9k | std::string functionName = |
829 | 20.9k | "abi_encode_" + |
830 | 20.9k | _from.identifier() + |
831 | 20.9k | "_to_" + |
832 | 20.9k | _to.identifier() + |
833 | 20.9k | _options.toFunctionNameSuffix(); |
834 | | |
835 | 20.9k | solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); |
836 | | |
837 | 20.9k | return createFunction(functionName, [&]() { |
838 | 20.8k | bool dynamic = _to.isDynamicallyEncoded(); |
839 | 20.8k | Whiskers templ(R"( |
840 | 20.8k | // <readableTypeNameFrom> -> <readableTypeNameTo> |
841 | 20.8k | function <functionName>(value, pos) <return> { |
842 | 20.8k | let tail := add(pos, <headSize>) |
843 | 20.8k | <init> |
844 | 20.8k | <#members> |
845 | 20.8k | { |
846 | 20.8k | // <memberName> |
847 | 20.8k | <preprocess> |
848 | 20.8k | let <memberValues> := <retrieveValue> |
849 | 20.8k | <encode> |
850 | 20.8k | } |
851 | 20.8k | </members> |
852 | 20.8k | <assignEnd> |
853 | 20.8k | } |
854 | 20.8k | )"); |
855 | 20.8k | templ("functionName", functionName); |
856 | 20.8k | templ("readableTypeNameFrom", _from.toString(true)); |
857 | 20.8k | templ("readableTypeNameTo", _to.toString(true)); |
858 | 20.8k | templ("return", dynamic ? " -> end " : ""); |
859 | 20.8k | if (dynamic && _options.dynamicInplace) |
860 | 8 | templ("assignEnd", "end := pos"); |
861 | 20.8k | else if (dynamic && !_options.dynamicInplace) |
862 | 4.75k | templ("assignEnd", "end := tail"); |
863 | 16.0k | else |
864 | 16.0k | templ("assignEnd", ""); |
865 | | // to avoid multiple loads from the same slot for subsequent members |
866 | 20.8k | templ("init", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : ""); |
867 | 20.8k | u256 previousSlotOffset(-1); |
868 | 20.8k | u256 encodingOffset = 0; |
869 | 20.8k | std::vector<std::map<std::string, std::string>> members; |
870 | 20.8k | for (auto const& member: _to.members(nullptr)) |
871 | 47.8k | { |
872 | 47.8k | solAssert(member.type, ""); |
873 | 47.8k | solAssert(!member.type->containsNestedMapping(), ""); |
874 | 47.8k | Type const* memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false); |
875 | 47.8k | solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented."); |
876 | 47.8k | auto memberTypeFrom = _from.memberType(member.name); |
877 | 47.8k | solAssert(memberTypeFrom, ""); |
878 | 47.8k | bool dynamicMember = memberTypeTo->isDynamicallyEncoded(); |
879 | 47.8k | if (dynamicMember) |
880 | 47.8k | solAssert(dynamic, ""); |
881 | | |
882 | 47.8k | members.emplace_back(); |
883 | 47.8k | members.back()["preprocess"] = ""; |
884 | | |
885 | 47.8k | switch (_from.location()) |
886 | 47.8k | { |
887 | 12.0k | case DataLocation::Storage: |
888 | 12.0k | { |
889 | 12.0k | solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); |
890 | 12.0k | u256 storageSlotOffset; |
891 | 12.0k | size_t intraSlotOffset; |
892 | 12.0k | std::tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); |
893 | 12.0k | if (memberTypeFrom->isValueType()) |
894 | 6.09k | { |
895 | 6.09k | if (storageSlotOffset != previousSlotOffset) |
896 | 3.91k | { |
897 | 3.91k | members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; |
898 | 3.91k | previousSlotOffset = storageSlotOffset; |
899 | 3.91k | } |
900 | 6.09k | members.back()["retrieveValue"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset) + "(slotValue)"; |
901 | 6.09k | } |
902 | 5.90k | else |
903 | 5.90k | { |
904 | 5.90k | solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); |
905 | 5.90k | solAssert(intraSlotOffset == 0, ""); |
906 | 5.90k | members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; |
907 | 5.90k | } |
908 | 12.0k | break; |
909 | 12.0k | } |
910 | 19.1k | case DataLocation::Memory: |
911 | 19.1k | { |
912 | 19.1k | std::string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); |
913 | 19.1k | members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; |
914 | 19.1k | break; |
915 | 12.0k | } |
916 | 16.6k | case DataLocation::CallData: |
917 | 16.6k | { |
918 | 16.6k | std::string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name)); |
919 | 16.6k | members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))"; |
920 | 16.6k | break; |
921 | 12.0k | } |
922 | 0 | default: |
923 | 0 | solAssert(false, ""); |
924 | 47.8k | } |
925 | | |
926 | 47.8k | EncodingOptions subOptions(_options); |
927 | 47.8k | subOptions.encodeFunctionFromStack = false; |
928 | | // Like with arrays, struct members are always padded. |
929 | 47.8k | subOptions.padded = true; |
930 | | |
931 | 47.8k | std::string memberValues = suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions)); |
932 | 47.8k | members.back()["memberValues"] = memberValues; |
933 | | |
934 | 47.8k | std::string encode; |
935 | 47.8k | if (_options.dynamicInplace) |
936 | 42 | encode = Whiskers{"pos := <encode>(<memberValues>, pos)"} |
937 | 42 | ("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions)) |
938 | 42 | ("memberValues", memberValues) |
939 | 42 | .render(); |
940 | 47.7k | else |
941 | 47.7k | { |
942 | 47.7k | Whiskers encodeTempl( |
943 | 47.7k | dynamicMember ? |
944 | 8.18k | std::string(R"( |
945 | 8.18k | mstore(add(pos, <encodingOffset>), sub(tail, pos)) |
946 | 8.18k | tail := <abiEncode>(<memberValues>, tail) |
947 | 8.18k | )") : |
948 | 47.7k | "<abiEncode>(<memberValues>, add(pos, <encodingOffset>))" |
949 | 47.7k | ); |
950 | 47.7k | encodeTempl("memberValues", memberValues); |
951 | 47.7k | encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); |
952 | 47.7k | encodingOffset += memberTypeTo->calldataHeadSize(); |
953 | 47.7k | encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); |
954 | 47.7k | encode = encodeTempl.render(); |
955 | 47.7k | } |
956 | 47.8k | members.back()["encode"] = encode; |
957 | | |
958 | 47.8k | members.back()["memberName"] = member.name; |
959 | 47.8k | } |
960 | 20.8k | templ("members", members); |
961 | 20.8k | if (_options.dynamicInplace) |
962 | 20.8k | solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size."); |
963 | 20.8k | templ("headSize", toCompactHexWithPrefix(encodingOffset)); |
964 | 20.8k | return templ.render(); |
965 | 20.8k | }); |
966 | 20.9k | } |
967 | | |
968 | | std::string ABIFunctions::abiEncodingFunctionStringLiteral( |
969 | | Type const& _from, |
970 | | Type const& _to, |
971 | | EncodingOptions const& _options |
972 | | ) |
973 | 694 | { |
974 | 694 | solAssert(_from.category() == Type::Category::StringLiteral, ""); |
975 | | |
976 | 694 | std::string functionName = |
977 | 694 | "abi_encode_" + |
978 | 694 | _from.identifier() + |
979 | 694 | "_to_" + |
980 | 694 | _to.identifier() + |
981 | 694 | _options.toFunctionNameSuffix(); |
982 | 694 | return createFunction(functionName, [&]() { |
983 | 658 | auto const& strType = dynamic_cast<StringLiteralType const&>(_from); |
984 | 658 | std::string const& value = strType.value(); |
985 | 658 | solAssert(_from.sizeOnStack() == 0, ""); |
986 | | |
987 | 658 | if (_to.isDynamicallySized()) |
988 | 619 | { |
989 | 619 | solAssert(_to.category() == Type::Category::Array, ""); |
990 | 619 | Whiskers templ(R"( |
991 | 619 | function <functionName>(pos) -> end { |
992 | 619 | pos := <storeLength>(pos, <length>) |
993 | 619 | <storeLiteralInMemory>(pos) |
994 | 619 | end := add(pos, <overallSize>) |
995 | 619 | } |
996 | 619 | )"); |
997 | 619 | templ("functionName", functionName); |
998 | | |
999 | | // TODO this can make use of CODECOPY for large strings once we have that in Yul |
1000 | 619 | templ("length", std::to_string(value.size())); |
1001 | 619 | templ("storeLength", arrayStoreLengthForEncodingFunction(dynamic_cast<ArrayType const&>(_to), _options)); |
1002 | 619 | if (_options.padded) |
1003 | 115 | templ("overallSize", std::to_string(((value.size() + 31) / 32) * 32)); |
1004 | 504 | else |
1005 | 504 | templ("overallSize", std::to_string(value.size())); |
1006 | 619 | templ("storeLiteralInMemory", m_utils.storeLiteralInMemoryFunction(value)); |
1007 | 619 | return templ.render(); |
1008 | 619 | } |
1009 | 39 | else |
1010 | 39 | { |
1011 | 39 | solAssert(_to.category() == Type::Category::FixedBytes, ""); |
1012 | 39 | solAssert(value.size() <= 32, ""); |
1013 | 39 | Whiskers templ(R"( |
1014 | 39 | function <functionName>(pos) { |
1015 | 39 | mstore(pos, <wordValue>) |
1016 | 39 | } |
1017 | 39 | )"); |
1018 | 39 | templ("functionName", functionName); |
1019 | 39 | templ("wordValue", formatAsStringOrNumber(value)); |
1020 | 39 | return templ.render(); |
1021 | 39 | } |
1022 | 658 | }); |
1023 | 694 | } |
1024 | | |
1025 | | std::string ABIFunctions::abiEncodingFunctionFunctionType( |
1026 | | FunctionType const& _from, |
1027 | | Type const& _to, |
1028 | | EncodingOptions const& _options |
1029 | | ) |
1030 | 119 | { |
1031 | 119 | solAssert( |
1032 | 119 | _from.kind() == FunctionType::Kind::External && |
1033 | 119 | _from.isImplicitlyConvertibleTo(_to) && |
1034 | 119 | _from.sizeOnStack() == _to.sizeOnStack(), |
1035 | 119 | "Invalid function type conversion requested" |
1036 | 119 | ); |
1037 | | |
1038 | 119 | std::string functionName = |
1039 | 119 | "abi_encode_" + |
1040 | 119 | _from.identifier() + |
1041 | 119 | "_to_" + |
1042 | 119 | _to.identifier() + |
1043 | 119 | _options.toFunctionNameSuffix(); |
1044 | | |
1045 | 119 | if (_options.encodeFunctionFromStack) |
1046 | 108 | return createFunction(functionName, [&]() { |
1047 | 98 | return Whiskers(R"( |
1048 | 98 | function <functionName>(addr, function_id, pos) { |
1049 | 98 | addr, function_id := <convert>(addr, function_id) |
1050 | 98 | mstore(pos, <combineExtFun>(addr, function_id)) |
1051 | 98 | } |
1052 | 98 | )") |
1053 | 98 | ("functionName", functionName) |
1054 | 98 | ("combineExtFun", m_utils.combineExternalFunctionIdFunction()) |
1055 | 98 | ("convert", m_utils.conversionFunction(_from, _to)) |
1056 | 98 | .render(); |
1057 | 98 | }); |
1058 | 11 | else |
1059 | 11 | return createFunction(functionName, [&]() { |
1060 | 9 | return Whiskers(R"( |
1061 | 9 | function <functionName>(addr_and_function_id, pos) { |
1062 | 9 | mstore(pos, <cleanExtFun>(addr_and_function_id)) |
1063 | 9 | } |
1064 | 9 | )") |
1065 | 9 | ("functionName", functionName) |
1066 | 9 | ("cleanExtFun", m_utils.cleanupFunction(_to)) |
1067 | 9 | .render(); |
1068 | 9 | }); |
1069 | 119 | } |
1070 | | |
1071 | | std::string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) |
1072 | 32.4k | { |
1073 | | // The decoding function has to perform bounds checks unless it decodes a value type. |
1074 | | // Conversely, bounds checks have to be performed before the decoding function |
1075 | | // of a value type is called. |
1076 | | |
1077 | 32.4k | Type const* decodingType = _type.decodingType(); |
1078 | 32.4k | solAssert(decodingType, ""); |
1079 | | |
1080 | 32.4k | if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType)) |
1081 | 4.71k | { |
1082 | 4.71k | if (arrayType->dataStoredIn(DataLocation::CallData)) |
1083 | 433 | { |
1084 | 433 | solAssert(!_fromMemory, ""); |
1085 | 433 | return abiDecodingFunctionCalldataArray(*arrayType); |
1086 | 433 | } |
1087 | 4.27k | else |
1088 | 4.27k | return abiDecodingFunctionArray(*arrayType, _fromMemory); |
1089 | 4.71k | } |
1090 | 27.7k | else if (auto const* structType = dynamic_cast<StructType const*>(decodingType)) |
1091 | 9.13k | { |
1092 | 9.13k | if (structType->dataStoredIn(DataLocation::CallData)) |
1093 | 437 | { |
1094 | 437 | solAssert(!_fromMemory, ""); |
1095 | 437 | return abiDecodingFunctionCalldataStruct(*structType); |
1096 | 437 | } |
1097 | 8.70k | else |
1098 | 8.70k | return abiDecodingFunctionStruct(*structType, _fromMemory); |
1099 | 9.13k | } |
1100 | 18.6k | else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType)) |
1101 | 97 | return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); |
1102 | 18.5k | else |
1103 | 18.5k | return abiDecodingFunctionValueType(_type, _fromMemory); |
1104 | 32.4k | } |
1105 | | |
1106 | | std::string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromMemory) |
1107 | 20.0k | { |
1108 | 20.0k | Type const* decodingType = _type.decodingType(); |
1109 | 20.0k | solAssert(decodingType, ""); |
1110 | 20.0k | solAssert(decodingType->sizeOnStack() == 1, ""); |
1111 | 20.0k | solAssert(decodingType->isValueType(), ""); |
1112 | 20.0k | solAssert(!decodingType->isDynamicallyEncoded(), ""); |
1113 | 20.0k | solAssert(decodingType->calldataEncodedSize() == 32, ""); |
1114 | | |
1115 | 20.0k | std::string functionName = |
1116 | 20.0k | "abi_decode_" + |
1117 | 20.0k | _type.identifier() + |
1118 | 20.0k | (_fromMemory ? "_fromMemory" : ""); |
1119 | 20.0k | return createFunction(functionName, [&]() { |
1120 | 8.85k | Whiskers templ(R"( |
1121 | 8.85k | function <functionName>(offset, end) -> value { |
1122 | 8.85k | value := <load>(offset) |
1123 | 8.85k | <validator>(value) |
1124 | 8.85k | } |
1125 | 8.85k | )"); |
1126 | 8.85k | templ("functionName", functionName); |
1127 | 8.85k | templ("load", _fromMemory ? "mload" : "calldataload"); |
1128 | | // Validation should use the type and not decodingType, because e.g. |
1129 | | // the decoding type of an enum is a plain int. |
1130 | 8.85k | templ("validator", m_utils.validatorFunction(_type, true)); |
1131 | 8.85k | return templ.render(); |
1132 | 8.85k | }); |
1133 | | |
1134 | 20.0k | } |
1135 | | |
1136 | | std::string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) |
1137 | 4.27k | { |
1138 | 4.27k | solAssert(_type.dataStoredIn(DataLocation::Memory), ""); |
1139 | | |
1140 | 4.27k | std::string functionName = |
1141 | 4.27k | "abi_decode_" + |
1142 | 4.27k | _type.identifier() + |
1143 | 4.27k | (_fromMemory ? "_fromMemory" : ""); |
1144 | | |
1145 | 4.27k | return createFunction(functionName, [&]() { |
1146 | 2.45k | std::string load = _fromMemory ? "mload" : "calldataload"; |
1147 | 2.45k | Whiskers templ( |
1148 | 2.45k | R"( |
1149 | 2.45k | // <readableTypeName> |
1150 | 2.45k | function <functionName>(offset, end) -> array { |
1151 | 2.45k | if iszero(slt(add(offset, 0x1f), end)) { <revertString>() } |
1152 | 2.45k | let length := <retrieveLength> |
1153 | 2.45k | array := <abiDecodeAvailableLen>(<offset>, length, end) |
1154 | 2.45k | } |
1155 | 2.45k | )" |
1156 | 2.45k | ); |
1157 | | // TODO add test |
1158 | 2.45k | templ("revertString", revertReasonIfDebugFunction("ABI decoding: invalid calldata array offset")); |
1159 | 2.45k | templ("functionName", functionName); |
1160 | 2.45k | templ("readableTypeName", _type.toString(true)); |
1161 | 2.45k | templ("retrieveLength", _type.isDynamicallySized() ? (load + "(offset)") : toCompactHexWithPrefix(_type.length())); |
1162 | 2.45k | templ("offset", _type.isDynamicallySized() ? "add(offset, 0x20)" : "offset"); |
1163 | 2.45k | templ("abiDecodeAvailableLen", abiDecodingFunctionArrayAvailableLength(_type, _fromMemory)); |
1164 | 2.45k | return templ.render(); |
1165 | 2.45k | }); |
1166 | 4.27k | } |
1167 | | |
1168 | | std::string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, bool _fromMemory) |
1169 | 2.48k | { |
1170 | 2.48k | solAssert(_type.dataStoredIn(DataLocation::Memory), ""); |
1171 | 2.48k | if (_type.isByteArrayOrString()) |
1172 | 799 | return abiDecodingFunctionByteArrayAvailableLength(_type, _fromMemory); |
1173 | 1.69k | solAssert(_type.calldataStride() > 0, ""); |
1174 | | |
1175 | 1.69k | std::string functionName = |
1176 | 1.69k | "abi_decode_available_length_" + |
1177 | 1.69k | _type.identifier() + |
1178 | 1.69k | (_fromMemory ? "_fromMemory" : ""); |
1179 | | |
1180 | 1.69k | return createFunction(functionName, [&]() { |
1181 | 1.68k | Whiskers templ(R"( |
1182 | 1.68k | // <readableTypeName> |
1183 | 1.68k | function <functionName>(offset, length, end) -> array { |
1184 | 1.68k | array := <allocate>(<allocationSize>(length)) |
1185 | 1.68k | let dst := array |
1186 | 1.68k | <?dynamic> |
1187 | 1.68k | mstore(array, length) |
1188 | 1.68k | dst := add(array, 0x20) |
1189 | 1.68k | </dynamic> |
1190 | 1.68k | let srcEnd := add(offset, mul(length, <stride>)) |
1191 | 1.68k | if gt(srcEnd, end) { |
1192 | 1.68k | <revertInvalidStride>() |
1193 | 1.68k | } |
1194 | 1.68k | for { let src := offset } lt(src, srcEnd) { src := add(src, <stride>) } |
1195 | 1.68k | { |
1196 | 1.68k | <?dynamicBase> |
1197 | 1.68k | let innerOffset := <load>(src) |
1198 | 1.68k | if gt(innerOffset, 0xffffffffffffffff) { <revertStringOffset>() } |
1199 | 1.68k | let elementPos := add(offset, innerOffset) |
1200 | 1.68k | <!dynamicBase> |
1201 | 1.68k | let elementPos := src |
1202 | 1.68k | </dynamicBase> |
1203 | 1.68k | mstore(dst, <decodingFun>(elementPos, end)) |
1204 | 1.68k | dst := add(dst, 0x20) |
1205 | 1.68k | } |
1206 | 1.68k | } |
1207 | 1.68k | )"); |
1208 | 1.68k | templ("functionName", functionName); |
1209 | 1.68k | templ("readableTypeName", _type.toString(true)); |
1210 | 1.68k | templ("allocate", m_utils.allocationFunction()); |
1211 | 1.68k | templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); |
1212 | 1.68k | templ("stride", toCompactHexWithPrefix(_type.calldataStride())); |
1213 | 1.68k | templ("dynamic", _type.isDynamicallySized()); |
1214 | 1.68k | templ("load", _fromMemory ? "mload" : "calldataload"); |
1215 | 1.68k | templ("dynamicBase", _type.baseType()->isDynamicallyEncoded()); |
1216 | 1.68k | templ( |
1217 | 1.68k | "revertInvalidStride", |
1218 | 1.68k | revertReasonIfDebugFunction("ABI decoding: invalid calldata array stride") |
1219 | 1.68k | ); |
1220 | 1.68k | templ("revertStringOffset", revertReasonIfDebugFunction("ABI decoding: invalid calldata array offset")); |
1221 | 1.68k | templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); |
1222 | 1.68k | return templ.render(); |
1223 | 1.68k | }); |
1224 | 1.69k | } |
1225 | | |
1226 | | std::string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) |
1227 | 433 | { |
1228 | 433 | solAssert(_type.dataStoredIn(DataLocation::CallData), ""); |
1229 | 433 | if (!_type.isDynamicallySized()) |
1230 | 433 | solAssert(_type.length() < u256("0xffffffffffffffff"), ""); |
1231 | 433 | solAssert(_type.calldataStride() > 0, ""); |
1232 | 433 | solAssert(_type.calldataStride() < u256("0xffffffffffffffff"), ""); |
1233 | | |
1234 | 433 | std::string functionName = |
1235 | 433 | "abi_decode_" + |
1236 | 433 | _type.identifier(); |
1237 | 433 | return createFunction(functionName, [&]() { |
1238 | 411 | Whiskers w; |
1239 | 411 | if (_type.isDynamicallySized()) |
1240 | 335 | { |
1241 | 335 | w = Whiskers(R"( |
1242 | 335 | // <readableTypeName> |
1243 | 335 | function <functionName>(offset, end) -> arrayPos, length { |
1244 | 335 | if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset>() } |
1245 | 335 | length := calldataload(offset) |
1246 | 335 | if gt(length, 0xffffffffffffffff) { <revertStringLength>() } |
1247 | 335 | arrayPos := add(offset, 0x20) |
1248 | 335 | if gt(add(arrayPos, mul(length, <stride>)), end) { <revertStringPos>() } |
1249 | 335 | } |
1250 | 335 | )"); |
1251 | 335 | w("revertStringOffset", revertReasonIfDebugFunction("ABI decoding: invalid calldata array offset")); |
1252 | 335 | w("revertStringLength", revertReasonIfDebugFunction("ABI decoding: invalid calldata array length")); |
1253 | 335 | } |
1254 | 76 | else |
1255 | 76 | { |
1256 | 76 | w = Whiskers(R"( |
1257 | 76 | // <readableTypeName> |
1258 | 76 | function <functionName>(offset, end) -> arrayPos { |
1259 | 76 | arrayPos := offset |
1260 | 76 | if gt(add(arrayPos, mul(<length>, <stride>)), end) { <revertStringPos>() } |
1261 | 76 | } |
1262 | 76 | )"); |
1263 | 76 | w("length", toCompactHexWithPrefix(_type.length())); |
1264 | 76 | } |
1265 | 411 | w("revertStringPos", revertReasonIfDebugFunction("ABI decoding: invalid calldata array stride")); |
1266 | 411 | w("functionName", functionName); |
1267 | 411 | w("readableTypeName", _type.toString(true)); |
1268 | 411 | w("stride", toCompactHexWithPrefix(_type.calldataStride())); |
1269 | | |
1270 | | // TODO add test |
1271 | 411 | return w.render(); |
1272 | 411 | }); |
1273 | 433 | } |
1274 | | |
1275 | | std::string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, bool _fromMemory) |
1276 | 799 | { |
1277 | 799 | solAssert(_type.dataStoredIn(DataLocation::Memory), ""); |
1278 | 799 | solAssert(_type.isByteArrayOrString(), ""); |
1279 | | |
1280 | 799 | std::string functionName = |
1281 | 799 | "abi_decode_available_length_" + |
1282 | 799 | _type.identifier() + |
1283 | 799 | (_fromMemory ? "_fromMemory" : ""); |
1284 | | |
1285 | 799 | return createFunction(functionName, [&]() { |
1286 | 799 | Whiskers templ(R"( |
1287 | 799 | function <functionName>(src, length, end) -> array { |
1288 | 799 | array := <allocate>(<allocationSize>(length)) |
1289 | 799 | mstore(array, length) |
1290 | 799 | let dst := add(array, 0x20) |
1291 | 799 | if gt(add(src, length), end) { <revertStringLength>() } |
1292 | 799 | <copyToMemFun>(src, dst, length) |
1293 | 799 | } |
1294 | 799 | )"); |
1295 | 799 | templ("revertStringLength", revertReasonIfDebugFunction("ABI decoding: invalid byte array length")); |
1296 | 799 | templ("functionName", functionName); |
1297 | 799 | templ("allocate", m_utils.allocationFunction()); |
1298 | 799 | templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); |
1299 | 799 | templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory, /*cleanup*/true)); |
1300 | 799 | return templ.render(); |
1301 | 799 | }); |
1302 | 799 | } |
1303 | | |
1304 | | std::string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) |
1305 | 437 | { |
1306 | 437 | solAssert(_type.dataStoredIn(DataLocation::CallData), ""); |
1307 | 437 | std::string functionName = |
1308 | 437 | "abi_decode_" + |
1309 | 437 | _type.identifier(); |
1310 | | |
1311 | 437 | return createFunction(functionName, [&]() { |
1312 | 427 | Whiskers w{R"( |
1313 | 427 | // <readableTypeName> |
1314 | 427 | function <functionName>(offset, end) -> value { |
1315 | 427 | if slt(sub(end, offset), <minimumSize>) { <revertString>() } |
1316 | 427 | value := offset |
1317 | 427 | } |
1318 | 427 | )"}; |
1319 | | // TODO add test |
1320 | 427 | w("revertString", revertReasonIfDebugFunction("ABI decoding: struct calldata too short")); |
1321 | 427 | w("functionName", functionName); |
1322 | 427 | w("readableTypeName", _type.toString(true)); |
1323 | 427 | w("minimumSize", std::to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true))); |
1324 | 427 | return w.render(); |
1325 | 427 | }); |
1326 | 437 | } |
1327 | | |
1328 | | std::string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) |
1329 | 8.71k | { |
1330 | 8.71k | solAssert(!_type.dataStoredIn(DataLocation::CallData), ""); |
1331 | 8.71k | std::string functionName = |
1332 | 8.71k | "abi_decode_" + |
1333 | 8.71k | _type.identifier() + |
1334 | 8.71k | (_fromMemory ? "_fromMemory" : ""); |
1335 | | |
1336 | 8.71k | return createFunction(functionName, [&]() { |
1337 | 8.46k | Whiskers templ(R"( |
1338 | 8.46k | // <readableTypeName> |
1339 | 8.46k | function <functionName>(headStart, end) -> value { |
1340 | 8.46k | if slt(sub(end, headStart), <minimumSize>) { <revertString>() } |
1341 | 8.46k | value := <allocate>(<memorySize>) |
1342 | 8.46k | <#members> |
1343 | 8.46k | { |
1344 | 8.46k | // <memberName> |
1345 | 8.46k | <decode> |
1346 | 8.46k | } |
1347 | 8.46k | </members> |
1348 | 8.46k | } |
1349 | 8.46k | )"); |
1350 | | // TODO add test |
1351 | 8.46k | templ("revertString", revertReasonIfDebugFunction("ABI decoding: struct data too short")); |
1352 | 8.46k | templ("functionName", functionName); |
1353 | 8.46k | templ("readableTypeName", _type.toString(true)); |
1354 | 8.46k | templ("allocate", m_utils.allocationFunction()); |
1355 | 8.46k | solAssert(_type.memoryDataSize() < u256("0xffffffffffffffff"), ""); |
1356 | 8.46k | templ("memorySize", toCompactHexWithPrefix(_type.memoryDataSize())); |
1357 | 8.46k | size_t headPos = 0; |
1358 | 8.46k | std::vector<std::map<std::string, std::string>> members; |
1359 | 8.46k | for (auto const& member: _type.members(nullptr)) |
1360 | 19.2k | { |
1361 | 19.2k | solAssert(member.type, ""); |
1362 | 19.2k | solAssert(!member.type->containsNestedMapping(), ""); |
1363 | 19.2k | auto decodingType = member.type->decodingType(); |
1364 | 19.2k | solAssert(decodingType, ""); |
1365 | 19.2k | Whiskers memberTempl(R"( |
1366 | 19.2k | <?dynamic> |
1367 | 19.2k | let offset := <load>(add(headStart, <pos>)) |
1368 | 19.2k | if gt(offset, 0xffffffffffffffff) { <revertString>() } |
1369 | 19.2k | <!dynamic> |
1370 | 19.2k | let offset := <pos> |
1371 | 19.2k | </dynamic> |
1372 | 19.2k | mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end)) |
1373 | 19.2k | )"); |
1374 | 19.2k | memberTempl("dynamic", decodingType->isDynamicallyEncoded()); |
1375 | | // TODO add test |
1376 | 19.2k | memberTempl("revertString", revertReasonIfDebugFunction("ABI decoding: invalid struct offset")); |
1377 | 19.2k | memberTempl("load", _fromMemory ? "mload" : "calldataload"); |
1378 | 19.2k | memberTempl("pos", std::to_string(headPos)); |
1379 | 19.2k | memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); |
1380 | 19.2k | memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false)); |
1381 | | |
1382 | 19.2k | members.emplace_back(); |
1383 | 19.2k | members.back()["decode"] = memberTempl.render(); |
1384 | 19.2k | members.back()["memberName"] = member.name; |
1385 | 19.2k | headPos += decodingType->calldataHeadSize(); |
1386 | 19.2k | } |
1387 | 8.46k | templ("members", members); |
1388 | 8.46k | templ("minimumSize", toCompactHexWithPrefix(headPos)); |
1389 | 8.46k | return templ.render(); |
1390 | 8.46k | }); |
1391 | 8.71k | } |
1392 | | |
1393 | | std::string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack) |
1394 | 172 | { |
1395 | 172 | solAssert(_type.kind() == FunctionType::Kind::External, ""); |
1396 | | |
1397 | 172 | std::string functionName = |
1398 | 172 | "abi_decode_" + |
1399 | 172 | _type.identifier() + |
1400 | 172 | (_fromMemory ? "_fromMemory" : "") + |
1401 | 172 | (_forUseOnStack ? "_onStack" : ""); |
1402 | | |
1403 | 172 | return createFunction(functionName, [&]() { |
1404 | 160 | if (_forUseOnStack) |
1405 | 75 | { |
1406 | 75 | return Whiskers(R"( |
1407 | 75 | function <functionName>(offset, end) -> addr, function_selector { |
1408 | 75 | addr, function_selector := <splitExtFun>(<decodeFun>(offset, end)) |
1409 | 75 | } |
1410 | 75 | )") |
1411 | 75 | ("functionName", functionName) |
1412 | 75 | ("decodeFun", abiDecodingFunctionFunctionType(_type, _fromMemory, false)) |
1413 | 75 | ("splitExtFun", m_utils.splitExternalFunctionIdFunction()) |
1414 | 75 | .render(); |
1415 | 75 | } |
1416 | 85 | else |
1417 | 85 | { |
1418 | 85 | return Whiskers(R"( |
1419 | 85 | function <functionName>(offset, end) -> fun { |
1420 | 85 | fun := <load>(offset) |
1421 | 85 | <validateExtFun>(fun) |
1422 | 85 | } |
1423 | 85 | )") |
1424 | 85 | ("functionName", functionName) |
1425 | 85 | ("load", _fromMemory ? "mload" : "calldataload") |
1426 | 85 | ("validateExtFun", m_utils.validatorFunction(_type, true)) |
1427 | 85 | .render(); |
1428 | 85 | } |
1429 | 160 | }); |
1430 | 172 | } |
1431 | | |
1432 | | std::string ABIFunctions::calldataAccessFunction(Type const& _type) |
1433 | 17.3k | { |
1434 | 17.3k | solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), ""); |
1435 | 17.3k | std::string functionName = "calldata_access_" + _type.identifier(); |
1436 | 17.3k | return createFunction(functionName, [&]() { |
1437 | 9.17k | if (_type.isDynamicallyEncoded()) |
1438 | 2.16k | { |
1439 | 2.16k | unsigned int tailSize = _type.calldataEncodedTailSize(); |
1440 | 2.16k | solAssert(tailSize > 1, ""); |
1441 | 2.16k | Whiskers w(R"( |
1442 | 2.16k | function <functionName>(base_ref, ptr) -> <return> { |
1443 | 2.16k | let rel_offset_of_tail := calldataload(ptr) |
1444 | 2.16k | if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertStringOffset>() } |
1445 | 2.16k | value := add(rel_offset_of_tail, base_ref) |
1446 | 2.16k | <handleLength> |
1447 | 2.16k | } |
1448 | 2.16k | )"); |
1449 | 2.16k | if (_type.isDynamicallySized()) |
1450 | 716 | { |
1451 | 716 | auto const* arrayType = dynamic_cast<ArrayType const*>(&_type); |
1452 | 716 | solAssert(!!arrayType, ""); |
1453 | 716 | w("handleLength", Whiskers(R"( |
1454 | 716 | length := calldataload(value) |
1455 | 716 | value := add(value, 0x20) |
1456 | 716 | if gt(length, 0xffffffffffffffff) { <revertStringLength>() } |
1457 | 716 | if sgt(value, sub(calldatasize(), mul(length, <calldataStride>))) { <revertStringStride>() } |
1458 | 716 | )") |
1459 | 716 | ("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())) |
1460 | | // TODO add test |
1461 | 716 | ("revertStringLength", revertReasonIfDebugFunction("Invalid calldata access length")) |
1462 | | // TODO add test |
1463 | 716 | ("revertStringStride", revertReasonIfDebugFunction("Invalid calldata access stride")) |
1464 | 716 | .render()); |
1465 | 716 | w("return", "value, length"); |
1466 | 716 | } |
1467 | 1.44k | else |
1468 | 1.44k | { |
1469 | 1.44k | w("handleLength", ""); |
1470 | 1.44k | w("return", "value"); |
1471 | 1.44k | } |
1472 | 2.16k | w("neededLength", toCompactHexWithPrefix(tailSize)); |
1473 | 2.16k | w("functionName", functionName); |
1474 | 2.16k | w("revertStringOffset", revertReasonIfDebugFunction("Invalid calldata access offset")); |
1475 | 2.16k | return w.render(); |
1476 | 2.16k | } |
1477 | 7.01k | else if (_type.isValueType()) |
1478 | 1.50k | { |
1479 | 1.50k | std::string decodingFunction; |
1480 | 1.50k | if (auto const* functionType = dynamic_cast<FunctionType const*>(&_type)) |
1481 | 0 | decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false); |
1482 | 1.50k | else |
1483 | 1.50k | decodingFunction = abiDecodingFunctionValueType(_type, false); |
1484 | | // Note that the second argument to the decoding function should be discarded after inlining. |
1485 | 1.50k | return Whiskers(R"( |
1486 | 1.50k | function <functionName>(baseRef, ptr) -> value { |
1487 | 1.50k | value := <decodingFunction>(ptr, add(ptr, 32)) |
1488 | 1.50k | } |
1489 | 1.50k | )") |
1490 | 1.50k | ("functionName", functionName) |
1491 | 1.50k | ("decodingFunction", decodingFunction) |
1492 | 1.50k | .render(); |
1493 | 1.50k | } |
1494 | 5.51k | else |
1495 | 5.51k | { |
1496 | 5.51k | solAssert( |
1497 | 5.51k | _type.category() == Type::Category::Array || |
1498 | 5.51k | _type.category() == Type::Category::Struct, |
1499 | 5.51k | "" |
1500 | 5.51k | ); |
1501 | 5.51k | return Whiskers(R"( |
1502 | 5.51k | function <functionName>(baseRef, ptr) -> value { |
1503 | 5.51k | value := ptr |
1504 | 5.51k | } |
1505 | 5.51k | )") |
1506 | 5.51k | ("functionName", functionName) |
1507 | 5.51k | .render(); |
1508 | 5.51k | } |
1509 | 9.17k | }); |
1510 | 17.3k | } |
1511 | | |
1512 | | std::string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) |
1513 | 8.71k | { |
1514 | 8.71k | std::string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); |
1515 | 8.71k | return createFunction(functionName, [&]() { |
1516 | 7.05k | if (_type.isDynamicallySized() && !_options.dynamicInplace) |
1517 | 5.60k | return Whiskers(R"( |
1518 | 5.60k | function <functionName>(pos, length) -> updated_pos { |
1519 | 5.60k | mstore(pos, length) |
1520 | 5.60k | updated_pos := add(pos, 0x20) |
1521 | 5.60k | } |
1522 | 5.60k | )") |
1523 | 5.60k | ("functionName", functionName) |
1524 | 5.60k | .render(); |
1525 | 1.44k | else |
1526 | 1.44k | return Whiskers(R"( |
1527 | 1.44k | function <functionName>(pos, length) -> updated_pos { |
1528 | 1.44k | updated_pos := pos |
1529 | 1.44k | } |
1530 | 1.44k | )") |
1531 | 1.44k | ("functionName", functionName) |
1532 | 1.44k | .render(); |
1533 | 7.05k | }); |
1534 | 8.71k | } |
1535 | | |
1536 | | std::string ABIFunctions::createFunction(std::string const& _name, std::function<std::string ()> const& _creator) |
1537 | 171k | { |
1538 | 171k | return m_functionCollector.createFunction(_name, _creator); |
1539 | 171k | } |
1540 | | |
1541 | | size_t ABIFunctions::headSize(TypePointers const& _targetTypes) |
1542 | 22.3k | { |
1543 | 22.3k | size_t headSize = 0; |
1544 | 22.3k | for (auto const& t: _targetTypes) |
1545 | 24.9k | headSize += t->calldataHeadSize(); |
1546 | | |
1547 | 22.3k | return headSize; |
1548 | 22.3k | } |
1549 | | |
1550 | | size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions const& _options) |
1551 | 57.8k | { |
1552 | 57.8k | if (_type.category() == Type::Category::Function && !_options.encodeFunctionFromStack) |
1553 | 11 | return 1; |
1554 | 57.8k | else |
1555 | 57.8k | return _type.sizeOnStack(); |
1556 | 57.8k | } |
1557 | | |
1558 | | std::string ABIFunctions::revertReasonIfDebugFunction(std::string const& _message) |
1559 | 61.2k | { |
1560 | 61.2k | return m_utils.revertReasonIfDebugFunction(_message); |
1561 | 61.2k | } |