/src/solidity/libsolidity/codegen/YulUtilFunctions.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 | | * Component that can generate various useful Yul functions. |
20 | | */ |
21 | | |
22 | | #include <libsolidity/codegen/YulUtilFunctions.h> |
23 | | |
24 | | #include <libsolidity/codegen/MultiUseYulFunctionCollector.h> |
25 | | #include <libsolidity/ast/AST.h> |
26 | | #include <libsolidity/codegen/CompilerUtils.h> |
27 | | #include <libsolidity/codegen/ir/IRVariable.h> |
28 | | |
29 | | #include <libsolutil/CommonData.h> |
30 | | #include <libsolutil/FunctionSelector.h> |
31 | | #include <libsolutil/Whiskers.h> |
32 | | #include <libsolutil/StringUtils.h> |
33 | | #include <libsolidity/ast/TypeProvider.h> |
34 | | |
35 | | #include <range/v3/algorithm/all_of.hpp> |
36 | | |
37 | | using namespace solidity; |
38 | | using namespace solidity::util; |
39 | | using namespace solidity::frontend; |
40 | | using namespace std::string_literals; |
41 | | |
42 | | namespace |
43 | | { |
44 | | |
45 | | std::optional<size_t> staticEncodingSize(std::vector<Type const*> const& _parameterTypes) |
46 | 29 | { |
47 | 29 | size_t encodedSize = 0; |
48 | 29 | for (auto* type: _parameterTypes) |
49 | 29 | { |
50 | 29 | if (type->isDynamicallyEncoded()) |
51 | 29 | return std::nullopt; |
52 | 0 | encodedSize += type->calldataHeadSize(); |
53 | 0 | } |
54 | 0 | return encodedSize; |
55 | 29 | } |
56 | | |
57 | | } |
58 | | |
59 | | std::string YulUtilFunctions::identityFunction() |
60 | 16.5k | { |
61 | 16.5k | std::string functionName = "identity"; |
62 | 16.5k | return m_functionCollector.createFunction("identity", [&](std::vector<std::string>& _args, std::vector<std::string>& _rets) { |
63 | 4.69k | _args.push_back("value"); |
64 | 4.69k | _rets.push_back("ret"); |
65 | 4.69k | return "ret := value"; |
66 | 4.69k | }); |
67 | 16.5k | } |
68 | | |
69 | | std::string YulUtilFunctions::combineExternalFunctionIdFunction() |
70 | 98 | { |
71 | 98 | std::string functionName = "combine_external_function_id"; |
72 | 98 | return m_functionCollector.createFunction(functionName, [&]() { |
73 | 96 | return Whiskers(R"( |
74 | 96 | function <functionName>(addr, selector) -> combined { |
75 | 96 | combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff))) |
76 | 96 | } |
77 | 96 | )") |
78 | 96 | ("functionName", functionName) |
79 | 96 | ("shl32", shiftLeftFunction(32)) |
80 | 96 | ("shl64", shiftLeftFunction(64)) |
81 | 96 | .render(); |
82 | 96 | }); |
83 | 98 | } |
84 | | |
85 | | std::string YulUtilFunctions::splitExternalFunctionIdFunction() |
86 | 75 | { |
87 | 75 | std::string functionName = "split_external_function_id"; |
88 | 75 | return m_functionCollector.createFunction(functionName, [&]() { |
89 | 73 | return Whiskers(R"( |
90 | 73 | function <functionName>(combined) -> addr, selector { |
91 | 73 | combined := <shr64>(combined) |
92 | 73 | selector := and(combined, 0xffffffff) |
93 | 73 | addr := <shr32>(combined) |
94 | 73 | } |
95 | 73 | )") |
96 | 73 | ("functionName", functionName) |
97 | 73 | ("shr32", shiftRightFunction(32)) |
98 | 73 | ("shr64", shiftRightFunction(64)) |
99 | 73 | .render(); |
100 | 73 | }); |
101 | 75 | } |
102 | | |
103 | | std::string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata, bool _cleanup) |
104 | 3.41k | { |
105 | 3.41k | std::string functionName = |
106 | 3.41k | "copy_"s + |
107 | 3.41k | (_fromCalldata ? "calldata"s : "memory"s) + |
108 | 3.41k | "_to_memory"s + |
109 | 3.41k | (_cleanup ? "_with_cleanup"s : ""s); |
110 | | |
111 | 3.41k | return m_functionCollector.createFunction(functionName, [&](std::vector<std::string>& _args, std::vector<std::string>&) { |
112 | 2.80k | _args = {"src", "dst", "length"}; |
113 | | |
114 | 2.80k | if (_fromCalldata) |
115 | 742 | return Whiskers(R"( |
116 | 742 | calldatacopy(dst, src, length) |
117 | 742 | <?cleanup>mstore(add(dst, length), 0)</cleanup> |
118 | 742 | )") |
119 | 742 | ("cleanup", _cleanup) |
120 | 742 | .render(); |
121 | 2.06k | else |
122 | 2.06k | { |
123 | 2.06k | if (m_evmVersion.hasMcopy()) |
124 | 694 | return Whiskers(R"( |
125 | 694 | mcopy(dst, src, length) |
126 | 694 | <?cleanup>mstore(add(dst, length), 0)</cleanup> |
127 | 694 | )") |
128 | 694 | ("cleanup", _cleanup) |
129 | 694 | .render(); |
130 | 1.36k | else |
131 | 1.36k | return Whiskers(R"( |
132 | 1.36k | let i := 0 |
133 | 1.36k | for { } lt(i, length) { i := add(i, 32) } |
134 | 1.36k | { |
135 | 1.36k | mstore(add(dst, i), mload(add(src, i))) |
136 | 1.36k | } |
137 | 1.36k | <?cleanup>mstore(add(dst, length), 0)</cleanup> |
138 | 1.36k | )") |
139 | 1.36k | ("cleanup", _cleanup) |
140 | 1.36k | .render(); |
141 | 2.06k | } |
142 | 2.80k | }); |
143 | 3.41k | } |
144 | | |
145 | | std::string YulUtilFunctions::copyLiteralToMemoryFunction(std::string const& _literal) |
146 | 619 | { |
147 | 619 | std::string functionName = "copy_literal_to_memory_" + util::toHex(util::keccak256(_literal).asBytes()); |
148 | | |
149 | 619 | return m_functionCollector.createFunction(functionName, [&]() { |
150 | 619 | return Whiskers(R"( |
151 | 619 | function <functionName>() -> memPtr { |
152 | 619 | memPtr := <arrayAllocationFunction>(<size>) |
153 | 619 | <storeLiteralInMem>(add(memPtr, 32)) |
154 | 619 | } |
155 | 619 | )") |
156 | 619 | ("functionName", functionName) |
157 | 619 | ("arrayAllocationFunction", allocateMemoryArrayFunction(*TypeProvider::array(DataLocation::Memory, true))) |
158 | 619 | ("size", std::to_string(_literal.size())) |
159 | 619 | ("storeLiteralInMem", storeLiteralInMemoryFunction(_literal)) |
160 | 619 | .render(); |
161 | 619 | }); |
162 | 619 | } |
163 | | |
164 | | std::string YulUtilFunctions::storeLiteralInMemoryFunction(std::string const& _literal) |
165 | 1.23k | { |
166 | 1.23k | std::string functionName = "store_literal_in_memory_" + util::toHex(util::keccak256(_literal).asBytes()); |
167 | | |
168 | 1.23k | return m_functionCollector.createFunction(functionName, [&]() { |
169 | 1.23k | size_t words = (_literal.length() + 31) / 32; |
170 | 1.23k | std::vector<std::map<std::string, std::string>> wordParams(words); |
171 | 4.62k | for (size_t i = 0; i < words; ++i) |
172 | 3.39k | { |
173 | 3.39k | wordParams[i]["offset"] = std::to_string(i * 32); |
174 | 3.39k | wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32)); |
175 | 3.39k | } |
176 | | |
177 | 1.23k | return Whiskers(R"( |
178 | 1.23k | function <functionName>(memPtr) { |
179 | 1.23k | <#word> |
180 | 1.23k | mstore(add(memPtr, <offset>), <wordValue>) |
181 | 1.23k | </word> |
182 | 1.23k | } |
183 | 1.23k | )") |
184 | 1.23k | ("functionName", functionName) |
185 | 1.23k | ("word", wordParams) |
186 | 1.23k | .render(); |
187 | 1.23k | }); |
188 | 1.23k | } |
189 | | |
190 | | std::string YulUtilFunctions::copyLiteralToStorageFunction(std::string const& _literal) |
191 | 212 | { |
192 | 212 | std::string functionName = "copy_literal_to_storage_" + util::toHex(util::keccak256(_literal).asBytes()); |
193 | | |
194 | 212 | return m_functionCollector.createFunction(functionName, [&](std::vector<std::string>& _args, std::vector<std::string>&) { |
195 | 212 | _args = {"slot"}; |
196 | | |
197 | 212 | if (_literal.size() >= 32) |
198 | 43 | { |
199 | 43 | size_t words = (_literal.length() + 31) / 32; |
200 | 43 | std::vector<std::map<std::string, std::string>> wordParams(words); |
201 | 141 | for (size_t i = 0; i < words; ++i) |
202 | 98 | { |
203 | 98 | wordParams[i]["offset"] = std::to_string(i); |
204 | 98 | wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32)); |
205 | 98 | } |
206 | 43 | return Whiskers(R"( |
207 | 43 | let oldLen := <byteArrayLength>(sload(slot)) |
208 | 43 | <cleanUpArrayEnd>(slot, oldLen, <length>) |
209 | 43 | sstore(slot, <encodedLen>) |
210 | 43 | let dstPtr := <dataArea>(slot) |
211 | 43 | <#word> |
212 | 43 | sstore(add(dstPtr, <offset>), <wordValue>) |
213 | 43 | </word> |
214 | 43 | )") |
215 | 43 | ("byteArrayLength", extractByteArrayLengthFunction()) |
216 | 43 | ("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage())) |
217 | 43 | ("dataArea", arrayDataAreaFunction(*TypeProvider::bytesStorage())) |
218 | 43 | ("word", wordParams) |
219 | 43 | ("length", std::to_string(_literal.size())) |
220 | 43 | ("encodedLen", std::to_string(2 * _literal.size() + 1)) |
221 | 43 | .render(); |
222 | 43 | } |
223 | 169 | else |
224 | 169 | return Whiskers(R"( |
225 | 169 | let oldLen := <byteArrayLength>(sload(slot)) |
226 | 169 | <cleanUpArrayEnd>(slot, oldLen, <length>) |
227 | 169 | sstore(slot, add(<wordValue>, <encodedLen>)) |
228 | 169 | )") |
229 | 169 | ("byteArrayLength", extractByteArrayLengthFunction()) |
230 | 169 | ("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage())) |
231 | 169 | ("wordValue", formatAsStringOrNumber(_literal)) |
232 | 169 | ("length", std::to_string(_literal.size())) |
233 | 169 | ("encodedLen", std::to_string(2 * _literal.size())) |
234 | 169 | .render(); |
235 | 212 | }); |
236 | 212 | } |
237 | | |
238 | | std::string YulUtilFunctions::revertWithError( |
239 | | std::string const& _signature, |
240 | | std::vector<Type const*> const& _parameterTypes, |
241 | | std::vector<ASTPointer<Expression const>> const& _errorArguments, |
242 | | std::string const& _posVar, |
243 | | std::string const& _endVar |
244 | | ) |
245 | 29 | { |
246 | 29 | solAssert((!_posVar.empty() && !_endVar.empty()) || (_posVar.empty() && _endVar.empty())); |
247 | 29 | bool const needsNewVariable = !_posVar.empty() && !_endVar.empty(); |
248 | 29 | bool needsAllocation = true; |
249 | | |
250 | 29 | if (std::optional<size_t> size = staticEncodingSize(_parameterTypes)) |
251 | 0 | if ( |
252 | 0 | ranges::all_of(_parameterTypes, [](auto const* type) { |
253 | 0 | solAssert(!dynamic_cast<InaccessibleDynamicType const*>(type)); |
254 | 0 | return type && type->isValueType(); |
255 | 0 | }) |
256 | 0 | ) |
257 | 0 | { |
258 | 0 | constexpr size_t errorSelectorSize = 4; |
259 | 0 | needsAllocation = *size + errorSelectorSize > CompilerUtils::generalPurposeMemoryStart; |
260 | 0 | } |
261 | | |
262 | 29 | Whiskers templ(R"({ |
263 | 29 | <?needsAllocation> |
264 | 29 | let <pos> := <allocateUnbounded>() |
265 | 29 | <!needsAllocation> |
266 | 29 | let <pos> := 0 |
267 | 29 | </needsAllocation> |
268 | 29 | mstore(<pos>, <hash>) |
269 | 29 | let <end> := <encode>(add(<pos>, 4) <argumentVars>) |
270 | 29 | revert(<pos>, sub(<end>, <pos>)) |
271 | 29 | })"); |
272 | 29 | templ("pos", needsNewVariable ? _posVar : "memPtr"); |
273 | 29 | templ("end", needsNewVariable ? _endVar : "end"); |
274 | 29 | templ("hash", formatNumber(util::selectorFromSignatureU256(_signature))); |
275 | 29 | templ("needsAllocation", needsAllocation); |
276 | 29 | if (needsAllocation) |
277 | 29 | templ("allocateUnbounded", allocateUnboundedFunction()); |
278 | | |
279 | 29 | std::vector<std::string> errorArgumentVars; |
280 | 29 | std::vector<Type const*> errorArgumentTypes; |
281 | 29 | for (ASTPointer<Expression const> const& arg: _errorArguments) |
282 | 37 | { |
283 | 37 | errorArgumentVars += IRVariable(*arg).stackSlots(); |
284 | 37 | solAssert(arg->annotation().type); |
285 | 37 | errorArgumentTypes.push_back(arg->annotation().type); |
286 | 37 | } |
287 | 29 | templ("argumentVars", joinHumanReadablePrefixed(errorArgumentVars)); |
288 | 29 | templ("encode", ABIFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector).tupleEncoder(errorArgumentTypes, _parameterTypes)); |
289 | | |
290 | 29 | return templ.render(); |
291 | 29 | } |
292 | | |
293 | | std::string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType, ASTPointer<Expression const> _stringArgumentExpression) |
294 | 101 | { |
295 | 101 | std::string functionName = |
296 | 101 | std::string(_assert ? "assert_helper" : "require_helper") + |
297 | 101 | (_messageType ? ("_" + _messageType->identifier()) : ""); |
298 | | |
299 | 101 | solAssert(!_assert || !_messageType, "Asserts can't have messages!"); |
300 | | |
301 | 101 | return m_functionCollector.createFunction(functionName, [&]() { |
302 | 66 | if (!_messageType) |
303 | 60 | return Whiskers(R"( |
304 | 60 | function <functionName>(condition) { |
305 | 60 | if iszero(condition) { <error> } |
306 | 60 | } |
307 | 60 | )") |
308 | 60 | ("error", _assert ? panicFunction(PanicCode::Assert) + "()" : "revert(0, 0)") |
309 | 60 | ("functionName", functionName) |
310 | 60 | .render(); |
311 | | |
312 | 6 | solAssert(_stringArgumentExpression, "Require with string must have a string argument"); |
313 | 6 | solAssert(_stringArgumentExpression->annotation().type); |
314 | 6 | std::vector<std::string> functionParameterNames = IRVariable(*_stringArgumentExpression).stackSlots(); |
315 | | |
316 | 6 | return Whiskers(R"( |
317 | 6 | function <functionName>(condition <functionParameterNames>) { |
318 | 6 | if iszero(condition) |
319 | 6 | <revertWithError> |
320 | 6 | } |
321 | 6 | )") |
322 | 6 | ("functionName", functionName) |
323 | 6 | ("revertWithError", revertWithError("Error(string)", {TypeProvider::stringMemory()}, {_stringArgumentExpression})) |
324 | 6 | ("functionParameterNames", joinHumanReadablePrefixed(functionParameterNames)) |
325 | 6 | .render(); |
326 | 6 | }); |
327 | 101 | } |
328 | | |
329 | | std::string YulUtilFunctions::requireWithErrorFunction(FunctionCall const& errorConstructorCall) |
330 | 0 | { |
331 | 0 | ErrorDefinition const* errorDefinition = dynamic_cast<ErrorDefinition const*>(ASTNode::referencedDeclaration(errorConstructorCall.expression())); |
332 | 0 | solAssert(errorDefinition); |
333 | | |
334 | 0 | std::string const errorSignature = errorDefinition->functionType(true)->externalSignature(); |
335 | | // Note that in most cases we'll always generate one function per error definition, |
336 | | // because types in the constructor call will match the ones in the definition. The only |
337 | | // exception are calls with types, where each instance has its own type (e.g. literals). |
338 | 0 | std::string functionName = "require_helper_t_error_" + std::to_string(errorDefinition->id()) + "_" + errorDefinition->name(); |
339 | 0 | for (ASTPointer<Expression const> const& argument: errorConstructorCall.sortedArguments()) |
340 | 0 | { |
341 | 0 | solAssert(argument->annotation().type); |
342 | 0 | functionName += ("_" + argument->annotation().type->identifier()); |
343 | 0 | } |
344 | | |
345 | 0 | std::vector<std::string> functionParameterNames; |
346 | 0 | for (ASTPointer<Expression const> const& arg: errorConstructorCall.sortedArguments()) |
347 | 0 | { |
348 | 0 | solAssert(arg->annotation().type); |
349 | 0 | if (arg->annotation().type->sizeOnStack() > 0) |
350 | 0 | functionParameterNames += IRVariable(*arg).stackSlots(); |
351 | 0 | } |
352 | | |
353 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
354 | 0 | return Whiskers(R"( |
355 | 0 | function <functionName>(condition <functionParameterNames>) { |
356 | 0 | if iszero(condition) |
357 | 0 | <revertWithError> |
358 | 0 | } |
359 | 0 | )") |
360 | 0 | ("functionName", functionName) |
361 | 0 | ("functionParameterNames", joinHumanReadablePrefixed(functionParameterNames)) |
362 | | // We're creating parameter names from the expressions passed into the constructor call, |
363 | | // which will result in odd names like `expr_29` that would normally be used for locals. |
364 | | // Note that this is the naming expected by `revertWithError()`. |
365 | 0 | ("revertWithError", revertWithError(errorSignature, errorDefinition->functionType(true)->parameterTypes(), errorConstructorCall.sortedArguments())) |
366 | 0 | .render(); |
367 | 0 | }); |
368 | 0 | } |
369 | | |
370 | | std::string YulUtilFunctions::leftAlignFunction(Type const& _type) |
371 | 192 | { |
372 | 192 | std::string functionName = std::string("leftAlign_") + _type.identifier(); |
373 | 192 | return m_functionCollector.createFunction(functionName, [&]() { |
374 | 192 | Whiskers templ(R"( |
375 | 192 | function <functionName>(value) -> aligned { |
376 | 192 | <body> |
377 | 192 | } |
378 | 192 | )"); |
379 | 192 | templ("functionName", functionName); |
380 | 192 | switch (_type.category()) |
381 | 192 | { |
382 | 2 | case Type::Category::Address: |
383 | 2 | templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)"); |
384 | 2 | break; |
385 | 169 | case Type::Category::Integer: |
386 | 169 | { |
387 | 169 | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
388 | 169 | if (type.numBits() == 256) |
389 | 150 | templ("body", "aligned := value"); |
390 | 19 | else |
391 | 19 | templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)"); |
392 | 169 | break; |
393 | 0 | } |
394 | 0 | case Type::Category::RationalNumber: |
395 | 0 | solAssert(false, "Left align requested for rational number."); |
396 | 0 | break; |
397 | 0 | case Type::Category::Bool: |
398 | 0 | templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); |
399 | 0 | break; |
400 | 0 | case Type::Category::FixedPoint: |
401 | 0 | solUnimplemented("Fixed point types not implemented."); |
402 | 0 | break; |
403 | 0 | case Type::Category::Array: |
404 | 0 | case Type::Category::Struct: |
405 | 0 | solAssert(false, "Left align requested for non-value type."); |
406 | 0 | break; |
407 | 21 | case Type::Category::FixedBytes: |
408 | 21 | templ("body", "aligned := value"); |
409 | 21 | break; |
410 | 0 | case Type::Category::Contract: |
411 | 0 | templ("body", "aligned := " + leftAlignFunction(*TypeProvider::address()) + "(value)"); |
412 | 0 | break; |
413 | 0 | case Type::Category::Enum: |
414 | 0 | { |
415 | 0 | solAssert(dynamic_cast<EnumType const&>(_type).storageBytes() == 1, ""); |
416 | 0 | templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); |
417 | 0 | break; |
418 | 0 | } |
419 | 0 | case Type::Category::InaccessibleDynamic: |
420 | 0 | solAssert(false, "Left align requested for inaccessible dynamic type."); |
421 | 0 | break; |
422 | 0 | default: |
423 | 0 | solAssert(false, "Left align of type " + _type.identifier() + " requested."); |
424 | 192 | } |
425 | | |
426 | 192 | return templ.render(); |
427 | 192 | }); |
428 | 192 | } |
429 | | |
430 | | std::string YulUtilFunctions::shiftLeftFunction(size_t _numBits) |
431 | 1.79k | { |
432 | 1.79k | solAssert(_numBits < 256, ""); |
433 | | |
434 | 1.79k | std::string functionName = "shift_left_" + std::to_string(_numBits); |
435 | 1.79k | return m_functionCollector.createFunction(functionName, [&]() { |
436 | 1.64k | return |
437 | 1.64k | Whiskers(R"( |
438 | 1.64k | function <functionName>(value) -> newValue { |
439 | 1.64k | newValue := |
440 | 1.64k | <?hasShifts> |
441 | 1.64k | shl(<numBits>, value) |
442 | 1.64k | <!hasShifts> |
443 | 1.64k | mul(value, <multiplier>) |
444 | 1.64k | </hasShifts> |
445 | 1.64k | } |
446 | 1.64k | )") |
447 | 1.64k | ("functionName", functionName) |
448 | 1.64k | ("numBits", std::to_string(_numBits)) |
449 | 1.64k | ("hasShifts", m_evmVersion.hasBitwiseShifting()) |
450 | 1.64k | ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) |
451 | 1.64k | .render(); |
452 | 1.64k | }); |
453 | 1.79k | } |
454 | | |
455 | | std::string YulUtilFunctions::shiftLeftFunctionDynamic() |
456 | 1.79k | { |
457 | 1.79k | std::string functionName = "shift_left_dynamic"; |
458 | 1.79k | return m_functionCollector.createFunction(functionName, [&]() { |
459 | 1.48k | return |
460 | 1.48k | Whiskers(R"( |
461 | 1.48k | function <functionName>(bits, value) -> newValue { |
462 | 1.48k | newValue := |
463 | 1.48k | <?hasShifts> |
464 | 1.48k | shl(bits, value) |
465 | 1.48k | <!hasShifts> |
466 | 1.48k | mul(value, exp(2, bits)) |
467 | 1.48k | </hasShifts> |
468 | 1.48k | } |
469 | 1.48k | )") |
470 | 1.48k | ("functionName", functionName) |
471 | 1.48k | ("hasShifts", m_evmVersion.hasBitwiseShifting()) |
472 | 1.48k | .render(); |
473 | 1.48k | }); |
474 | 1.79k | } |
475 | | |
476 | | std::string YulUtilFunctions::shiftRightFunction(size_t _numBits) |
477 | 10.6k | { |
478 | 10.6k | solAssert(_numBits < 256, ""); |
479 | | |
480 | | // Note that if this is extended with signed shifts, |
481 | | // the opcodes SAR and SDIV behave differently with regards to rounding! |
482 | | |
483 | 10.6k | std::string functionName = "shift_right_" + std::to_string(_numBits) + "_unsigned"; |
484 | 10.6k | return m_functionCollector.createFunction(functionName, [&]() { |
485 | 9.48k | return |
486 | 9.48k | Whiskers(R"( |
487 | 9.48k | function <functionName>(value) -> newValue { |
488 | 9.48k | newValue := |
489 | 9.48k | <?hasShifts> |
490 | 9.48k | shr(<numBits>, value) |
491 | 9.48k | <!hasShifts> |
492 | 9.48k | div(value, <multiplier>) |
493 | 9.48k | </hasShifts> |
494 | 9.48k | } |
495 | 9.48k | )") |
496 | 9.48k | ("functionName", functionName) |
497 | 9.48k | ("hasShifts", m_evmVersion.hasBitwiseShifting()) |
498 | 9.48k | ("numBits", std::to_string(_numBits)) |
499 | 9.48k | ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) |
500 | 9.48k | .render(); |
501 | 9.48k | }); |
502 | 10.6k | } |
503 | | |
504 | | std::string YulUtilFunctions::shiftRightFunctionDynamic() |
505 | 2.18k | { |
506 | 2.18k | std::string const functionName = "shift_right_unsigned_dynamic"; |
507 | 2.18k | return m_functionCollector.createFunction(functionName, [&]() { |
508 | 1.92k | return |
509 | 1.92k | Whiskers(R"( |
510 | 1.92k | function <functionName>(bits, value) -> newValue { |
511 | 1.92k | newValue := |
512 | 1.92k | <?hasShifts> |
513 | 1.92k | shr(bits, value) |
514 | 1.92k | <!hasShifts> |
515 | 1.92k | div(value, exp(2, bits)) |
516 | 1.92k | </hasShifts> |
517 | 1.92k | } |
518 | 1.92k | )") |
519 | 1.92k | ("functionName", functionName) |
520 | 1.92k | ("hasShifts", m_evmVersion.hasBitwiseShifting()) |
521 | 1.92k | .render(); |
522 | 1.92k | }); |
523 | 2.18k | } |
524 | | |
525 | | std::string YulUtilFunctions::shiftRightSignedFunctionDynamic() |
526 | 0 | { |
527 | 0 | std::string const functionName = "shift_right_signed_dynamic"; |
528 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
529 | 0 | return |
530 | 0 | Whiskers(R"( |
531 | 0 | function <functionName>(bits, value) -> result { |
532 | 0 | <?hasShifts> |
533 | 0 | result := sar(bits, value) |
534 | 0 | <!hasShifts> |
535 | 0 | let divisor := exp(2, bits) |
536 | 0 | let xor_mask := sub(0, slt(value, 0)) |
537 | 0 | result := xor(div(xor(value, xor_mask), divisor), xor_mask) |
538 | 0 | // combined version of |
539 | 0 | // switch slt(value, 0) |
540 | 0 | // case 0 { result := div(value, divisor) } |
541 | 0 | // default { result := not(div(not(value), divisor)) } |
542 | 0 | </hasShifts> |
543 | 0 | } |
544 | 0 | )") |
545 | 0 | ("functionName", functionName) |
546 | 0 | ("hasShifts", m_evmVersion.hasBitwiseShifting()) |
547 | 0 | .render(); |
548 | 0 | }); |
549 | 0 | } |
550 | | |
551 | | |
552 | | std::string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _amountType) |
553 | 2 | { |
554 | 2 | solUnimplementedAssert(_type.category() != Type::Category::FixedPoint, "Not yet implemented - FixedPointType."); |
555 | 2 | solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, ""); |
556 | 2 | solAssert(_amountType.category() == Type::Category::Integer, ""); |
557 | 2 | solAssert(!dynamic_cast<IntegerType const&>(_amountType).isSigned(), ""); |
558 | 2 | std::string const functionName = "shift_left_" + _type.identifier() + "_" + _amountType.identifier(); |
559 | 2 | return m_functionCollector.createFunction(functionName, [&]() { |
560 | 2 | return |
561 | 2 | Whiskers(R"( |
562 | 2 | function <functionName>(value, bits) -> result { |
563 | 2 | bits := <cleanAmount>(bits) |
564 | 2 | result := <cleanup>(<shift>(bits, <cleanup>(value))) |
565 | 2 | } |
566 | 2 | )") |
567 | 2 | ("functionName", functionName) |
568 | 2 | ("cleanAmount", cleanupFunction(_amountType)) |
569 | 2 | ("shift", shiftLeftFunctionDynamic()) |
570 | 2 | ("cleanup", cleanupFunction(_type)) |
571 | 2 | .render(); |
572 | 2 | }); |
573 | 2 | } |
574 | | |
575 | | std::string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const& _amountType) |
576 | 56 | { |
577 | 56 | solUnimplementedAssert(_type.category() != Type::Category::FixedPoint, "Not yet implemented - FixedPointType."); |
578 | 56 | solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, ""); |
579 | 56 | solAssert(_amountType.category() == Type::Category::Integer, ""); |
580 | 56 | solAssert(!dynamic_cast<IntegerType const&>(_amountType).isSigned(), ""); |
581 | 56 | IntegerType const* integerType = dynamic_cast<IntegerType const*>(&_type); |
582 | 56 | bool valueSigned = integerType && integerType->isSigned(); |
583 | | |
584 | 56 | std::string const functionName = "shift_right_" + _type.identifier() + "_" + _amountType.identifier(); |
585 | 56 | return m_functionCollector.createFunction(functionName, [&]() { |
586 | 52 | return |
587 | 52 | Whiskers(R"( |
588 | 52 | function <functionName>(value, bits) -> result { |
589 | 52 | bits := <cleanAmount>(bits) |
590 | 52 | result := <cleanup>(<shift>(bits, <cleanup>(value))) |
591 | 52 | } |
592 | 52 | )") |
593 | 52 | ("functionName", functionName) |
594 | 52 | ("cleanAmount", cleanupFunction(_amountType)) |
595 | 52 | ("shift", valueSigned ? shiftRightSignedFunctionDynamic() : shiftRightFunctionDynamic()) |
596 | 52 | ("cleanup", cleanupFunction(_type)) |
597 | 52 | .render(); |
598 | 52 | }); |
599 | 56 | } |
600 | | |
601 | | std::string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) |
602 | 1.05k | { |
603 | 1.05k | solAssert(_numBytes <= 32, ""); |
604 | 1.05k | solAssert(_shiftBytes <= 32, ""); |
605 | 1.05k | size_t numBits = _numBytes * 8; |
606 | 1.05k | size_t shiftBits = _shiftBytes * 8; |
607 | 1.05k | std::string functionName = "update_byte_slice_" + std::to_string(_numBytes) + "_shift_" + std::to_string(_shiftBytes); |
608 | 1.05k | return m_functionCollector.createFunction(functionName, [&]() { |
609 | 985 | return |
610 | 985 | Whiskers(R"( |
611 | 985 | function <functionName>(value, toInsert) -> result { |
612 | 985 | let mask := <mask> |
613 | 985 | toInsert := <shl>(toInsert) |
614 | 985 | value := and(value, not(mask)) |
615 | 985 | result := or(value, and(toInsert, mask)) |
616 | 985 | } |
617 | 985 | )") |
618 | 985 | ("functionName", functionName) |
619 | 985 | ("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits)) |
620 | 985 | ("shl", shiftLeftFunction(shiftBits)) |
621 | 985 | .render(); |
622 | 985 | }); |
623 | 1.05k | } |
624 | | |
625 | | std::string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes) |
626 | 1.57k | { |
627 | 1.57k | solAssert(_numBytes <= 32, ""); |
628 | 1.57k | size_t numBits = _numBytes * 8; |
629 | 1.57k | std::string functionName = "update_byte_slice_dynamic" + std::to_string(_numBytes); |
630 | 1.57k | return m_functionCollector.createFunction(functionName, [&]() { |
631 | 1.52k | return |
632 | 1.52k | Whiskers(R"( |
633 | 1.52k | function <functionName>(value, shiftBytes, toInsert) -> result { |
634 | 1.52k | let shiftBits := mul(shiftBytes, 8) |
635 | 1.52k | let mask := <shl>(shiftBits, <mask>) |
636 | 1.52k | toInsert := <shl>(shiftBits, toInsert) |
637 | 1.52k | value := and(value, not(mask)) |
638 | 1.52k | result := or(value, and(toInsert, mask)) |
639 | 1.52k | } |
640 | 1.52k | )") |
641 | 1.52k | ("functionName", functionName) |
642 | 1.52k | ("mask", formatNumber((bigint(1) << numBits) - 1)) |
643 | 1.52k | ("shl", shiftLeftFunctionDynamic()) |
644 | 1.52k | .render(); |
645 | 1.52k | }); |
646 | 1.57k | } |
647 | | |
648 | | std::string YulUtilFunctions::maskBytesFunctionDynamic() |
649 | 2.36k | { |
650 | 2.36k | std::string functionName = "mask_bytes_dynamic"; |
651 | 2.36k | return m_functionCollector.createFunction(functionName, [&]() { |
652 | 1.13k | return Whiskers(R"( |
653 | 1.13k | function <functionName>(data, bytes) -> result { |
654 | 1.13k | let mask := not(<shr>(mul(8, bytes), not(0))) |
655 | 1.13k | result := and(data, mask) |
656 | 1.13k | })") |
657 | 1.13k | ("functionName", functionName) |
658 | 1.13k | ("shr", shiftRightFunctionDynamic()) |
659 | 1.13k | .render(); |
660 | 1.13k | }); |
661 | 2.36k | } |
662 | | |
663 | | std::string YulUtilFunctions::maskLowerOrderBytesFunction(size_t _bytes) |
664 | 49 | { |
665 | 49 | std::string functionName = "mask_lower_order_bytes_" + std::to_string(_bytes); |
666 | 49 | solAssert(_bytes <= 32, ""); |
667 | 49 | return m_functionCollector.createFunction(functionName, [&]() { |
668 | 48 | return Whiskers(R"( |
669 | 48 | function <functionName>(data) -> result { |
670 | 48 | result := and(data, <mask>) |
671 | 48 | })") |
672 | 48 | ("functionName", functionName) |
673 | 48 | ("mask", formatNumber((~u256(0)) >> (256 - 8 * _bytes))) |
674 | 48 | .render(); |
675 | 48 | }); |
676 | 49 | } |
677 | | |
678 | | std::string YulUtilFunctions::maskLowerOrderBytesFunctionDynamic() |
679 | 49 | { |
680 | 49 | std::string functionName = "mask_lower_order_bytes_dynamic"; |
681 | 49 | return m_functionCollector.createFunction(functionName, [&]() { |
682 | 48 | return Whiskers(R"( |
683 | 48 | function <functionName>(data, bytes) -> result { |
684 | 48 | let mask := not(<shl>(mul(8, bytes), not(0))) |
685 | 48 | result := and(data, mask) |
686 | 48 | })") |
687 | 48 | ("functionName", functionName) |
688 | 48 | ("shl", shiftLeftFunctionDynamic()) |
689 | 48 | .render(); |
690 | 48 | }); |
691 | 49 | } |
692 | | |
693 | | std::string YulUtilFunctions::roundUpFunction() |
694 | 9.70k | { |
695 | 9.70k | std::string functionName = "round_up_to_mul_of_32"; |
696 | 9.70k | return m_functionCollector.createFunction(functionName, [&]() { |
697 | 3.99k | return |
698 | 3.99k | Whiskers(R"( |
699 | 3.99k | function <functionName>(value) -> result { |
700 | 3.99k | result := and(add(value, 31), not(31)) |
701 | 3.99k | } |
702 | 3.99k | )") |
703 | 3.99k | ("functionName", functionName) |
704 | 3.99k | .render(); |
705 | 3.99k | }); |
706 | 9.70k | } |
707 | | |
708 | | std::string YulUtilFunctions::divide32CeilFunction() |
709 | 1.24k | { |
710 | 1.24k | return m_functionCollector.createFunction( |
711 | 1.24k | "divide_by_32_ceil", |
712 | 1.24k | [&](std::vector<std::string>& _args, std::vector<std::string>& _ret) { |
713 | 1.23k | _args = {"value"}; |
714 | 1.23k | _ret = {"result"}; |
715 | 1.23k | return "result := div(add(value, 31), 32)"; |
716 | 1.23k | } |
717 | 1.24k | ); |
718 | 1.24k | } |
719 | | |
720 | | std::string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type) |
721 | 3.62k | { |
722 | 3.62k | std::string functionName = "checked_add_" + _type.identifier(); |
723 | 3.62k | return m_functionCollector.createFunction(functionName, [&]() { |
724 | 1.50k | return |
725 | 1.50k | Whiskers(R"( |
726 | 1.50k | function <functionName>(x, y) -> sum { |
727 | 1.50k | x := <cleanupFunction>(x) |
728 | 1.50k | y := <cleanupFunction>(y) |
729 | 1.50k | sum := add(x, y) |
730 | 1.50k | <?signed> |
731 | 1.50k | <?256bit> |
732 | 1.50k | // overflow, if x >= 0 and sum < y |
733 | 1.50k | // underflow, if x < 0 and sum >= y |
734 | 1.50k | if or( |
735 | 1.50k | and(iszero(slt(x, 0)), slt(sum, y)), |
736 | 1.50k | and(slt(x, 0), iszero(slt(sum, y))) |
737 | 1.50k | ) { <panic>() } |
738 | 1.50k | <!256bit> |
739 | 1.50k | if or( |
740 | 1.50k | sgt(sum, <maxValue>), |
741 | 1.50k | slt(sum, <minValue>) |
742 | 1.50k | ) { <panic>() } |
743 | 1.50k | </256bit> |
744 | 1.50k | <!signed> |
745 | 1.50k | <?256bit> |
746 | 1.50k | if gt(x, sum) { <panic>() } |
747 | 1.50k | <!256bit> |
748 | 1.50k | if gt(sum, <maxValue>) { <panic>() } |
749 | 1.50k | </256bit> |
750 | 1.50k | </signed> |
751 | 1.50k | } |
752 | 1.50k | )") |
753 | 1.50k | ("functionName", functionName) |
754 | 1.50k | ("signed", _type.isSigned()) |
755 | 1.50k | ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) |
756 | 1.50k | ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) |
757 | 1.50k | ("cleanupFunction", cleanupFunction(_type)) |
758 | 1.50k | ("panic", panicFunction(PanicCode::UnderOverflow)) |
759 | 1.50k | ("256bit", _type.numBits() == 256) |
760 | 1.50k | .render(); |
761 | 1.50k | }); |
762 | 3.62k | } |
763 | | |
764 | | std::string YulUtilFunctions::wrappingIntAddFunction(IntegerType const& _type) |
765 | 0 | { |
766 | 0 | std::string functionName = "wrapping_add_" + _type.identifier(); |
767 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
768 | 0 | return |
769 | 0 | Whiskers(R"( |
770 | 0 | function <functionName>(x, y) -> sum { |
771 | 0 | sum := <cleanupFunction>(add(x, y)) |
772 | 0 | } |
773 | 0 | )") |
774 | 0 | ("functionName", functionName) |
775 | 0 | ("cleanupFunction", cleanupFunction(_type)) |
776 | 0 | .render(); |
777 | 0 | }); |
778 | 0 | } |
779 | | |
780 | | std::string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) |
781 | 4.66k | { |
782 | 4.66k | std::string functionName = "checked_mul_" + _type.identifier(); |
783 | 4.66k | return m_functionCollector.createFunction(functionName, [&]() { |
784 | 1.52k | return |
785 | | // Multiplication by zero could be treated separately and directly return zero. |
786 | 1.52k | Whiskers(R"( |
787 | 1.52k | function <functionName>(x, y) -> product { |
788 | 1.52k | x := <cleanupFunction>(x) |
789 | 1.52k | y := <cleanupFunction>(y) |
790 | 1.52k | let product_raw := mul(x, y) |
791 | 1.52k | product := <cleanupFunction>(product_raw) |
792 | 1.52k | <?signed> |
793 | 1.52k | <?gt128bit> |
794 | 1.52k | <?256bit> |
795 | 1.52k | // special case |
796 | 1.52k | if and(slt(x, 0), eq(y, <minValue>)) { <panic>() } |
797 | 1.52k | </256bit> |
798 | 1.52k | // overflow, if x != 0 and y != product/x |
799 | 1.52k | if iszero( |
800 | 1.52k | or( |
801 | 1.52k | iszero(x), |
802 | 1.52k | eq(y, sdiv(product, x)) |
803 | 1.52k | ) |
804 | 1.52k | ) { <panic>() } |
805 | 1.52k | <!gt128bit> |
806 | 1.52k | if iszero(eq(product, product_raw)) { <panic>() } |
807 | 1.52k | </gt128bit> |
808 | 1.52k | <!signed> |
809 | 1.52k | <?gt128bit> |
810 | 1.52k | // overflow, if x != 0 and y != product/x |
811 | 1.52k | if iszero( |
812 | 1.52k | or( |
813 | 1.52k | iszero(x), |
814 | 1.52k | eq(y, div(product, x)) |
815 | 1.52k | ) |
816 | 1.52k | ) { <panic>() } |
817 | 1.52k | <!gt128bit> |
818 | 1.52k | if iszero(eq(product, product_raw)) { <panic>() } |
819 | 1.52k | </gt128bit> |
820 | 1.52k | </signed> |
821 | 1.52k | } |
822 | 1.52k | )") |
823 | 1.52k | ("functionName", functionName) |
824 | 1.52k | ("signed", _type.isSigned()) |
825 | 1.52k | ("cleanupFunction", cleanupFunction(_type)) |
826 | 1.52k | ("panic", panicFunction(PanicCode::UnderOverflow)) |
827 | 1.52k | ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) |
828 | 1.52k | ("256bit", _type.numBits() == 256) |
829 | 1.52k | ("gt128bit", _type.numBits() > 128) |
830 | 1.52k | .render(); |
831 | 1.52k | }); |
832 | 4.66k | } |
833 | | |
834 | | std::string YulUtilFunctions::wrappingIntMulFunction(IntegerType const& _type) |
835 | 0 | { |
836 | 0 | std::string functionName = "wrapping_mul_" + _type.identifier(); |
837 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
838 | 0 | return |
839 | 0 | Whiskers(R"( |
840 | 0 | function <functionName>(x, y) -> product { |
841 | 0 | product := <cleanupFunction>(mul(x, y)) |
842 | 0 | } |
843 | 0 | )") |
844 | 0 | ("functionName", functionName) |
845 | 0 | ("cleanupFunction", cleanupFunction(_type)) |
846 | 0 | .render(); |
847 | 0 | }); |
848 | 0 | } |
849 | | |
850 | | std::string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) |
851 | 693 | { |
852 | 693 | std::string functionName = "checked_div_" + _type.identifier(); |
853 | 693 | return m_functionCollector.createFunction(functionName, [&]() { |
854 | 528 | return |
855 | 528 | Whiskers(R"( |
856 | 528 | function <functionName>(x, y) -> r { |
857 | 528 | x := <cleanupFunction>(x) |
858 | 528 | y := <cleanupFunction>(y) |
859 | 528 | if iszero(y) { <panicDivZero>() } |
860 | 528 | <?signed> |
861 | 528 | // overflow for minVal / -1 |
862 | 528 | if and( |
863 | 528 | eq(x, <minVal>), |
864 | 528 | eq(y, sub(0, 1)) |
865 | 528 | ) { <panicOverflow>() } |
866 | 528 | </signed> |
867 | 528 | r := <?signed>s</signed>div(x, y) |
868 | 528 | } |
869 | 528 | )") |
870 | 528 | ("functionName", functionName) |
871 | 528 | ("signed", _type.isSigned()) |
872 | 528 | ("minVal", toCompactHexWithPrefix(u256(_type.minValue()))) |
873 | 528 | ("cleanupFunction", cleanupFunction(_type)) |
874 | 528 | ("panicDivZero", panicFunction(PanicCode::DivisionByZero)) |
875 | 528 | ("panicOverflow", panicFunction(PanicCode::UnderOverflow)) |
876 | 528 | .render(); |
877 | 528 | }); |
878 | 693 | } |
879 | | |
880 | | std::string YulUtilFunctions::wrappingIntDivFunction(IntegerType const& _type) |
881 | 0 | { |
882 | 0 | std::string functionName = "wrapping_div_" + _type.identifier(); |
883 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
884 | 0 | return |
885 | 0 | Whiskers(R"( |
886 | 0 | function <functionName>(x, y) -> r { |
887 | 0 | x := <cleanupFunction>(x) |
888 | 0 | y := <cleanupFunction>(y) |
889 | 0 | if iszero(y) { <error>() } |
890 | 0 | r := <?signed>s</signed>div(x, y) |
891 | 0 | } |
892 | 0 | )") |
893 | 0 | ("functionName", functionName) |
894 | 0 | ("cleanupFunction", cleanupFunction(_type)) |
895 | 0 | ("signed", _type.isSigned()) |
896 | 0 | ("error", panicFunction(PanicCode::DivisionByZero)) |
897 | 0 | .render(); |
898 | 0 | }); |
899 | 0 | } |
900 | | |
901 | | std::string YulUtilFunctions::intModFunction(IntegerType const& _type) |
902 | 1.02k | { |
903 | 1.02k | std::string functionName = "mod_" + _type.identifier(); |
904 | 1.02k | return m_functionCollector.createFunction(functionName, [&]() { |
905 | 622 | return |
906 | 622 | Whiskers(R"( |
907 | 622 | function <functionName>(x, y) -> r { |
908 | 622 | x := <cleanupFunction>(x) |
909 | 622 | y := <cleanupFunction>(y) |
910 | 622 | if iszero(y) { <panic>() } |
911 | 622 | r := <?signed>s</signed>mod(x, y) |
912 | 622 | } |
913 | 622 | )") |
914 | 622 | ("functionName", functionName) |
915 | 622 | ("signed", _type.isSigned()) |
916 | 622 | ("cleanupFunction", cleanupFunction(_type)) |
917 | 622 | ("panic", panicFunction(PanicCode::DivisionByZero)) |
918 | 622 | .render(); |
919 | 622 | }); |
920 | 1.02k | } |
921 | | |
922 | | std::string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type) |
923 | 5.65k | { |
924 | 5.65k | std::string functionName = "checked_sub_" + _type.identifier(); |
925 | 5.65k | return m_functionCollector.createFunction(functionName, [&] { |
926 | 1.64k | return |
927 | 1.64k | Whiskers(R"( |
928 | 1.64k | function <functionName>(x, y) -> diff { |
929 | 1.64k | x := <cleanupFunction>(x) |
930 | 1.64k | y := <cleanupFunction>(y) |
931 | 1.64k | diff := sub(x, y) |
932 | 1.64k | <?signed> |
933 | 1.64k | <?256bit> |
934 | 1.64k | // underflow, if y >= 0 and diff > x |
935 | 1.64k | // overflow, if y < 0 and diff < x |
936 | 1.64k | if or( |
937 | 1.64k | and(iszero(slt(y, 0)), sgt(diff, x)), |
938 | 1.64k | and(slt(y, 0), slt(diff, x)) |
939 | 1.64k | ) { <panic>() } |
940 | 1.64k | <!256bit> |
941 | 1.64k | if or( |
942 | 1.64k | slt(diff, <minValue>), |
943 | 1.64k | sgt(diff, <maxValue>) |
944 | 1.64k | ) { <panic>() } |
945 | 1.64k | </256bit> |
946 | 1.64k | <!signed> |
947 | 1.64k | <?256bit> |
948 | 1.64k | if gt(diff, x) { <panic>() } |
949 | 1.64k | <!256bit> |
950 | 1.64k | if gt(diff, <maxValue>) { <panic>() } |
951 | 1.64k | </256bit> |
952 | 1.64k | </signed> |
953 | 1.64k | } |
954 | 1.64k | )") |
955 | 1.64k | ("functionName", functionName) |
956 | 1.64k | ("signed", _type.isSigned()) |
957 | 1.64k | ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) |
958 | 1.64k | ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) |
959 | 1.64k | ("cleanupFunction", cleanupFunction(_type)) |
960 | 1.64k | ("panic", panicFunction(PanicCode::UnderOverflow)) |
961 | 1.64k | ("256bit", _type.numBits() == 256) |
962 | 1.64k | .render(); |
963 | 1.64k | }); |
964 | 5.65k | } |
965 | | |
966 | | std::string YulUtilFunctions::wrappingIntSubFunction(IntegerType const& _type) |
967 | 0 | { |
968 | 0 | std::string functionName = "wrapping_sub_" + _type.identifier(); |
969 | 0 | return m_functionCollector.createFunction(functionName, [&] { |
970 | 0 | return |
971 | 0 | Whiskers(R"( |
972 | 0 | function <functionName>(x, y) -> diff { |
973 | 0 | diff := <cleanupFunction>(sub(x, y)) |
974 | 0 | } |
975 | 0 | )") |
976 | 0 | ("functionName", functionName) |
977 | 0 | ("cleanupFunction", cleanupFunction(_type)) |
978 | 0 | .render(); |
979 | 0 | }); |
980 | 0 | } |
981 | | |
982 | | std::string YulUtilFunctions::overflowCheckedIntExpFunction( |
983 | | IntegerType const& _type, |
984 | | IntegerType const& _exponentType |
985 | | ) |
986 | 3.40k | { |
987 | 3.40k | solAssert(!_exponentType.isSigned(), ""); |
988 | | |
989 | 3.40k | std::string functionName = "checked_exp_" + _type.identifier() + "_" + _exponentType.identifier(); |
990 | 3.40k | return m_functionCollector.createFunction(functionName, [&]() { |
991 | 2.22k | return |
992 | 2.22k | Whiskers(R"( |
993 | 2.22k | function <functionName>(base, exponent) -> power { |
994 | 2.22k | base := <baseCleanupFunction>(base) |
995 | 2.22k | exponent := <exponentCleanupFunction>(exponent) |
996 | 2.22k | <?signed> |
997 | 2.22k | power := <exp>(base, exponent, <minValue>, <maxValue>) |
998 | 2.22k | <!signed> |
999 | 2.22k | power := <exp>(base, exponent, <maxValue>) |
1000 | 2.22k | </signed> |
1001 | 2.22k | |
1002 | 2.22k | } |
1003 | 2.22k | )") |
1004 | 2.22k | ("functionName", functionName) |
1005 | 2.22k | ("signed", _type.isSigned()) |
1006 | 2.22k | ("exp", _type.isSigned() ? overflowCheckedSignedExpFunction() : overflowCheckedUnsignedExpFunction()) |
1007 | 2.22k | ("maxValue", toCompactHexWithPrefix(_type.max())) |
1008 | 2.22k | ("minValue", toCompactHexWithPrefix(_type.min())) |
1009 | 2.22k | ("baseCleanupFunction", cleanupFunction(_type)) |
1010 | 2.22k | ("exponentCleanupFunction", cleanupFunction(_exponentType)) |
1011 | 2.22k | .render(); |
1012 | 2.22k | }); |
1013 | 3.40k | } |
1014 | | |
1015 | | std::string YulUtilFunctions::overflowCheckedIntLiteralExpFunction( |
1016 | | RationalNumberType const& _baseType, |
1017 | | IntegerType const& _exponentType, |
1018 | | IntegerType const& _commonType |
1019 | | ) |
1020 | 178 | { |
1021 | 178 | solAssert(!_exponentType.isSigned(), ""); |
1022 | 178 | solAssert(_baseType.isNegative() == _commonType.isSigned(), ""); |
1023 | 178 | solAssert(_commonType.numBits() == 256, ""); |
1024 | | |
1025 | 178 | std::string functionName = "checked_exp_" + _baseType.richIdentifier() + "_" + _exponentType.identifier(); |
1026 | | |
1027 | 178 | return m_functionCollector.createFunction(functionName, [&]() |
1028 | 178 | { |
1029 | | // Converts a bigint number into u256 (negative numbers represented in two's complement form.) |
1030 | | // We assume that `_v` fits in 256 bits. |
1031 | 175 | auto bigint2u = [&](bigint const& _v) -> u256 |
1032 | 175 | { |
1033 | 175 | if (_v < 0) |
1034 | 0 | return s2u(s256(_v)); |
1035 | 175 | return u256(_v); |
1036 | 175 | }; |
1037 | | |
1038 | | // Calculates the upperbound for exponentiation, that is, calculate `b`, such that |
1039 | | // _base**b <= _maxValue and _base**(b + 1) > _maxValue |
1040 | 175 | auto findExponentUpperbound = [](bigint const _base, bigint const _maxValue) -> unsigned |
1041 | 175 | { |
1042 | | // There is no overflow for these cases |
1043 | 175 | if (_base == 0 || _base == -1 || _base == 1) |
1044 | 52 | return 0; |
1045 | | |
1046 | 123 | unsigned first = 0; |
1047 | 123 | unsigned last = 255; |
1048 | 123 | unsigned middle; |
1049 | | |
1050 | 849 | while (first < last) |
1051 | 836 | { |
1052 | 836 | middle = (first + last) / 2; |
1053 | | |
1054 | 836 | if ( |
1055 | | // The condition on msb is a shortcut that avoids computing large powers in |
1056 | | // arbitrary precision. |
1057 | 836 | boost::multiprecision::msb(_base) * middle <= boost::multiprecision::msb(_maxValue) && |
1058 | 836 | boost::multiprecision::pow(_base, middle) <= _maxValue |
1059 | 836 | ) |
1060 | 361 | { |
1061 | 361 | if (boost::multiprecision::pow(_base, middle + 1) > _maxValue) |
1062 | 110 | return middle; |
1063 | 251 | else |
1064 | 251 | first = middle + 1; |
1065 | 361 | } |
1066 | 475 | else |
1067 | 475 | last = middle; |
1068 | 836 | } |
1069 | | |
1070 | 13 | return last; |
1071 | 123 | }; |
1072 | | |
1073 | 175 | bigint baseValue = _baseType.isNegative() ? |
1074 | 0 | u2s(_baseType.literalValue(nullptr)) : |
1075 | 175 | _baseType.literalValue(nullptr); |
1076 | 175 | bool needsOverflowCheck = !((baseValue == 0) || (baseValue == -1) || (baseValue == 1)); |
1077 | 175 | unsigned exponentUpperbound; |
1078 | | |
1079 | 175 | if (_baseType.isNegative()) |
1080 | 0 | { |
1081 | | // Only checks for underflow. The only case where this can be a problem is when, for a |
1082 | | // negative base, say `b`, and an even exponent, say `e`, `b**e = 2**255` (which is an |
1083 | | // overflow.) But this never happens because, `255 = 3*5*17`, and therefore there is no even |
1084 | | // number `e` such that `b**e = 2**255`. |
1085 | 0 | exponentUpperbound = findExponentUpperbound(abs(baseValue), abs(_commonType.minValue())); |
1086 | |
|
1087 | 0 | bigint power = boost::multiprecision::pow(baseValue, exponentUpperbound); |
1088 | 0 | bigint overflowedPower = boost::multiprecision::pow(baseValue, exponentUpperbound + 1); |
1089 | |
|
1090 | 0 | if (needsOverflowCheck) |
1091 | 0 | solAssert( |
1092 | 0 | (power <= _commonType.maxValue()) && (power >= _commonType.minValue()) && |
1093 | 0 | !((overflowedPower <= _commonType.maxValue()) && (overflowedPower >= _commonType.minValue())), |
1094 | 0 | "Incorrect exponent upper bound calculated." |
1095 | 0 | ); |
1096 | 0 | } |
1097 | 175 | else |
1098 | 175 | { |
1099 | 175 | exponentUpperbound = findExponentUpperbound(baseValue, _commonType.maxValue()); |
1100 | | |
1101 | 175 | if (needsOverflowCheck) |
1102 | 175 | solAssert( |
1103 | 175 | boost::multiprecision::pow(baseValue, exponentUpperbound) <= _commonType.maxValue() && |
1104 | 175 | boost::multiprecision::pow(baseValue, exponentUpperbound + 1) > _commonType.maxValue(), |
1105 | 175 | "Incorrect exponent upper bound calculated." |
1106 | 175 | ); |
1107 | 175 | } |
1108 | | |
1109 | 175 | return Whiskers(R"( |
1110 | 175 | function <functionName>(exponent) -> power { |
1111 | 175 | exponent := <exponentCleanupFunction>(exponent) |
1112 | 175 | <?needsOverflowCheck> |
1113 | 175 | if gt(exponent, <exponentUpperbound>) { <panic>() } |
1114 | 175 | </needsOverflowCheck> |
1115 | 175 | power := exp(<base>, exponent) |
1116 | 175 | } |
1117 | 175 | )") |
1118 | 175 | ("functionName", functionName) |
1119 | 175 | ("exponentCleanupFunction", cleanupFunction(_exponentType)) |
1120 | 175 | ("needsOverflowCheck", needsOverflowCheck) |
1121 | 175 | ("exponentUpperbound", std::to_string(exponentUpperbound)) |
1122 | 175 | ("panic", panicFunction(PanicCode::UnderOverflow)) |
1123 | 175 | ("base", bigint2u(baseValue).str()) |
1124 | 175 | .render(); |
1125 | 175 | }); |
1126 | 178 | } |
1127 | | |
1128 | | std::string YulUtilFunctions::overflowCheckedUnsignedExpFunction() |
1129 | 2.20k | { |
1130 | | // Checks for the "small number specialization" below. |
1131 | 2.20k | using namespace boost::multiprecision; |
1132 | 2.20k | solAssert(pow(bigint(10), 77) < pow(bigint(2), 256), ""); |
1133 | 2.20k | solAssert(pow(bigint(11), 77) >= pow(bigint(2), 256), ""); |
1134 | 2.20k | solAssert(pow(bigint(10), 78) >= pow(bigint(2), 256), ""); |
1135 | | |
1136 | 2.20k | solAssert(pow(bigint(306), 31) < pow(bigint(2), 256), ""); |
1137 | 2.20k | solAssert(pow(bigint(307), 31) >= pow(bigint(2), 256), ""); |
1138 | 2.20k | solAssert(pow(bigint(306), 32) >= pow(bigint(2), 256), ""); |
1139 | | |
1140 | 2.20k | std::string functionName = "checked_exp_unsigned"; |
1141 | 2.20k | return m_functionCollector.createFunction(functionName, [&]() { |
1142 | 1.17k | return |
1143 | 1.17k | Whiskers(R"( |
1144 | 1.17k | function <functionName>(base, exponent, max) -> power { |
1145 | 1.17k | // This function currently cannot be inlined because of the |
1146 | 1.17k | // "leave" statements. We have to improve the optimizer. |
1147 | 1.17k | |
1148 | 1.17k | // Note that 0**0 == 1 |
1149 | 1.17k | if iszero(exponent) { power := 1 leave } |
1150 | 1.17k | if iszero(base) { power := 0 leave } |
1151 | 1.17k | |
1152 | 1.17k | // Specializations for small bases |
1153 | 1.17k | switch base |
1154 | 1.17k | // 0 is handled above |
1155 | 1.17k | case 1 { power := 1 leave } |
1156 | 1.17k | case 2 |
1157 | 1.17k | { |
1158 | 1.17k | if gt(exponent, 255) { <panic>() } |
1159 | 1.17k | power := exp(2, exponent) |
1160 | 1.17k | if gt(power, max) { <panic>() } |
1161 | 1.17k | leave |
1162 | 1.17k | } |
1163 | 1.17k | if or( |
1164 | 1.17k | and(lt(base, 11), lt(exponent, 78)), |
1165 | 1.17k | and(lt(base, 307), lt(exponent, 32)) |
1166 | 1.17k | ) |
1167 | 1.17k | { |
1168 | 1.17k | power := exp(base, exponent) |
1169 | 1.17k | if gt(power, max) { <panic>() } |
1170 | 1.17k | leave |
1171 | 1.17k | } |
1172 | 1.17k | |
1173 | 1.17k | power, base := <expLoop>(1, base, exponent, max) |
1174 | 1.17k | |
1175 | 1.17k | if gt(power, div(max, base)) { <panic>() } |
1176 | 1.17k | power := mul(power, base) |
1177 | 1.17k | } |
1178 | 1.17k | )") |
1179 | 1.17k | ("functionName", functionName) |
1180 | 1.17k | ("panic", panicFunction(PanicCode::UnderOverflow)) |
1181 | 1.17k | ("expLoop", overflowCheckedExpLoopFunction()) |
1182 | 1.17k | .render(); |
1183 | 1.17k | }); |
1184 | 2.20k | } |
1185 | | |
1186 | | std::string YulUtilFunctions::overflowCheckedSignedExpFunction() |
1187 | 16 | { |
1188 | 16 | std::string functionName = "checked_exp_signed"; |
1189 | 16 | return m_functionCollector.createFunction(functionName, [&]() { |
1190 | 10 | return |
1191 | 10 | Whiskers(R"( |
1192 | 10 | function <functionName>(base, exponent, min, max) -> power { |
1193 | 10 | // Currently, `leave` avoids this function being inlined. |
1194 | 10 | // We have to improve the optimizer. |
1195 | 10 | |
1196 | 10 | // Note that 0**0 == 1 |
1197 | 10 | switch exponent |
1198 | 10 | case 0 { power := 1 leave } |
1199 | 10 | case 1 { power := base leave } |
1200 | 10 | if iszero(base) { power := 0 leave } |
1201 | 10 | |
1202 | 10 | power := 1 |
1203 | 10 | |
1204 | 10 | // We pull out the first iteration because it is the only one in which |
1205 | 10 | // base can be negative. |
1206 | 10 | // Exponent is at least 2 here. |
1207 | 10 | |
1208 | 10 | // overflow check for base * base |
1209 | 10 | switch sgt(base, 0) |
1210 | 10 | case 1 { if gt(base, div(max, base)) { <panic>() } } |
1211 | 10 | case 0 { if slt(base, sdiv(max, base)) { <panic>() } } |
1212 | 10 | if and(exponent, 1) |
1213 | 10 | { |
1214 | 10 | power := base |
1215 | 10 | } |
1216 | 10 | base := mul(base, base) |
1217 | 10 | exponent := <shr_1>(exponent) |
1218 | 10 | |
1219 | 10 | // Below this point, base is always positive. |
1220 | 10 | |
1221 | 10 | power, base := <expLoop>(power, base, exponent, max) |
1222 | 10 | |
1223 | 10 | if and(sgt(power, 0), gt(power, div(max, base))) { <panic>() } |
1224 | 10 | if and(slt(power, 0), slt(power, sdiv(min, base))) { <panic>() } |
1225 | 10 | power := mul(power, base) |
1226 | 10 | } |
1227 | 10 | )") |
1228 | 10 | ("functionName", functionName) |
1229 | 10 | ("panic", panicFunction(PanicCode::UnderOverflow)) |
1230 | 10 | ("expLoop", overflowCheckedExpLoopFunction()) |
1231 | 10 | ("shr_1", shiftRightFunction(1)) |
1232 | 10 | .render(); |
1233 | 10 | }); |
1234 | 16 | } |
1235 | | |
1236 | | std::string YulUtilFunctions::overflowCheckedExpLoopFunction() |
1237 | 1.18k | { |
1238 | | // We use this loop for both signed and unsigned exponentiation |
1239 | | // because we pull out the first iteration in the signed case which |
1240 | | // results in the base always being positive. |
1241 | | |
1242 | | // This function does not include the final multiplication. |
1243 | | |
1244 | 1.18k | std::string functionName = "checked_exp_helper"; |
1245 | 1.18k | return m_functionCollector.createFunction(functionName, [&]() { |
1246 | 1.18k | return |
1247 | 1.18k | Whiskers(R"( |
1248 | 1.18k | function <functionName>(_power, _base, exponent, max) -> power, base { |
1249 | 1.18k | power := _power |
1250 | 1.18k | base := _base |
1251 | 1.18k | for { } gt(exponent, 1) {} |
1252 | 1.18k | { |
1253 | 1.18k | // overflow check for base * base |
1254 | 1.18k | if gt(base, div(max, base)) { <panic>() } |
1255 | 1.18k | if and(exponent, 1) |
1256 | 1.18k | { |
1257 | 1.18k | // No checks for power := mul(power, base) needed, because the check |
1258 | 1.18k | // for base * base above is sufficient, since: |
1259 | 1.18k | // |power| <= base (proof by induction) and thus: |
1260 | 1.18k | // |power * base| <= base * base <= max <= |min| (for signed) |
1261 | 1.18k | // (this is equally true for signed and unsigned exp) |
1262 | 1.18k | power := mul(power, base) |
1263 | 1.18k | } |
1264 | 1.18k | base := mul(base, base) |
1265 | 1.18k | exponent := <shr_1>(exponent) |
1266 | 1.18k | } |
1267 | 1.18k | } |
1268 | 1.18k | )") |
1269 | 1.18k | ("functionName", functionName) |
1270 | 1.18k | ("panic", panicFunction(PanicCode::UnderOverflow)) |
1271 | 1.18k | ("shr_1", shiftRightFunction(1)) |
1272 | 1.18k | .render(); |
1273 | 1.18k | }); |
1274 | 1.18k | } |
1275 | | |
1276 | | std::string YulUtilFunctions::wrappingIntExpFunction( |
1277 | | IntegerType const& _type, |
1278 | | IntegerType const& _exponentType |
1279 | | ) |
1280 | 0 | { |
1281 | 0 | solAssert(!_exponentType.isSigned(), ""); |
1282 | | |
1283 | 0 | std::string functionName = "wrapping_exp_" + _type.identifier() + "_" + _exponentType.identifier(); |
1284 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
1285 | 0 | return |
1286 | 0 | Whiskers(R"( |
1287 | 0 | function <functionName>(base, exponent) -> power { |
1288 | 0 | base := <baseCleanupFunction>(base) |
1289 | 0 | exponent := <exponentCleanupFunction>(exponent) |
1290 | 0 | power := <baseCleanupFunction>(exp(base, exponent)) |
1291 | 0 | } |
1292 | 0 | )") |
1293 | 0 | ("functionName", functionName) |
1294 | 0 | ("baseCleanupFunction", cleanupFunction(_type)) |
1295 | 0 | ("exponentCleanupFunction", cleanupFunction(_exponentType)) |
1296 | 0 | .render(); |
1297 | 0 | }); |
1298 | 0 | } |
1299 | | |
1300 | | std::string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) |
1301 | 12.9k | { |
1302 | 12.9k | std::string functionName = "array_length_" + _type.identifier(); |
1303 | 12.9k | return m_functionCollector.createFunction(functionName, [&]() { |
1304 | 10.0k | Whiskers w(R"( |
1305 | 10.0k | function <functionName>(value<?dynamic><?calldata>, len</calldata></dynamic>) -> length { |
1306 | 10.0k | <?dynamic> |
1307 | 10.0k | <?memory> |
1308 | 10.0k | length := mload(value) |
1309 | 10.0k | </memory> |
1310 | 10.0k | <?storage> |
1311 | 10.0k | length := sload(value) |
1312 | 10.0k | <?byteArray> |
1313 | 10.0k | length := <extractByteArrayLength>(length) |
1314 | 10.0k | </byteArray> |
1315 | 10.0k | </storage> |
1316 | 10.0k | <?calldata> |
1317 | 10.0k | length := len |
1318 | 10.0k | </calldata> |
1319 | 10.0k | <!dynamic> |
1320 | 10.0k | length := <length> |
1321 | 10.0k | </dynamic> |
1322 | 10.0k | } |
1323 | 10.0k | )"); |
1324 | 10.0k | w("functionName", functionName); |
1325 | 10.0k | w("dynamic", _type.isDynamicallySized()); |
1326 | 10.0k | if (!_type.isDynamicallySized()) |
1327 | 1.47k | w("length", toCompactHexWithPrefix(_type.length())); |
1328 | 10.0k | w("memory", _type.location() == DataLocation::Memory); |
1329 | 10.0k | w("storage", _type.location() == DataLocation::Storage); |
1330 | 10.0k | w("calldata", _type.location() == DataLocation::CallData); |
1331 | 10.0k | if (_type.location() == DataLocation::Storage) |
1332 | 1.38k | { |
1333 | 1.38k | w("byteArray", _type.isByteArrayOrString()); |
1334 | 1.38k | if (_type.isByteArrayOrString()) |
1335 | 145 | w("extractByteArrayLength", extractByteArrayLengthFunction()); |
1336 | 1.38k | } |
1337 | | |
1338 | 10.0k | return w.render(); |
1339 | 10.0k | }); |
1340 | 12.9k | } |
1341 | | |
1342 | | std::string YulUtilFunctions::extractByteArrayLengthFunction() |
1343 | 4.70k | { |
1344 | 4.70k | std::string functionName = "extract_byte_array_length"; |
1345 | 4.70k | return m_functionCollector.createFunction(functionName, [&]() { |
1346 | 1.66k | Whiskers w(R"( |
1347 | 1.66k | function <functionName>(data) -> length { |
1348 | 1.66k | length := div(data, 2) |
1349 | 1.66k | let outOfPlaceEncoding := and(data, 1) |
1350 | 1.66k | if iszero(outOfPlaceEncoding) { |
1351 | 1.66k | length := and(length, 0x7f) |
1352 | 1.66k | } |
1353 | 1.66k | |
1354 | 1.66k | if eq(outOfPlaceEncoding, lt(length, 32)) { |
1355 | 1.66k | <panic>() |
1356 | 1.66k | } |
1357 | 1.66k | } |
1358 | 1.66k | )"); |
1359 | 1.66k | w("functionName", functionName); |
1360 | 1.66k | w("panic", panicFunction(PanicCode::StorageEncodingError)); |
1361 | 1.66k | return w.render(); |
1362 | 1.66k | }); |
1363 | 4.70k | } |
1364 | | |
1365 | | std::string YulUtilFunctions::resizeArrayFunction(ArrayType const& _type) |
1366 | 196 | { |
1367 | 196 | solAssert(_type.location() == DataLocation::Storage, ""); |
1368 | 196 | solUnimplementedAssert(_type.baseType()->storageBytes() <= 32); |
1369 | | |
1370 | 196 | if (_type.isByteArrayOrString()) |
1371 | 0 | return resizeDynamicByteArrayFunction(_type); |
1372 | | |
1373 | 196 | std::string functionName = "resize_array_" + _type.identifier(); |
1374 | 196 | return m_functionCollector.createFunction(functionName, [&]() { |
1375 | 175 | Whiskers templ(R"( |
1376 | 175 | function <functionName>(array, newLen) { |
1377 | 175 | if gt(newLen, <maxArrayLength>) { |
1378 | 175 | <panic>() |
1379 | 175 | } |
1380 | 175 | |
1381 | 175 | let oldLen := <fetchLength>(array) |
1382 | 175 | |
1383 | 175 | <?isDynamic> |
1384 | 175 | // Store new length |
1385 | 175 | sstore(array, newLen) |
1386 | 175 | </isDynamic> |
1387 | 175 | |
1388 | 175 | <?needsClearing> |
1389 | 175 | <cleanUpArrayEnd>(array, oldLen, newLen) |
1390 | 175 | </needsClearing> |
1391 | 175 | })"); |
1392 | 175 | templ("functionName", functionName); |
1393 | 175 | templ("maxArrayLength", (u256(1) << 64).str()); |
1394 | 175 | templ("panic", panicFunction(util::PanicCode::ResourceError)); |
1395 | 175 | templ("fetchLength", arrayLengthFunction(_type)); |
1396 | 175 | templ("isDynamic", _type.isDynamicallySized()); |
1397 | 175 | bool isMappingBase = _type.baseType()->category() == Type::Category::Mapping; |
1398 | 175 | templ("needsClearing", !isMappingBase); |
1399 | 175 | if (!isMappingBase) |
1400 | 175 | templ("cleanUpArrayEnd", cleanUpStorageArrayEndFunction(_type)); |
1401 | 175 | return templ.render(); |
1402 | 175 | }); |
1403 | 196 | } |
1404 | | |
1405 | | std::string YulUtilFunctions::cleanUpStorageArrayEndFunction(ArrayType const& _type) |
1406 | 175 | { |
1407 | 175 | solAssert(_type.location() == DataLocation::Storage, ""); |
1408 | 175 | solAssert(_type.baseType()->category() != Type::Category::Mapping, ""); |
1409 | 175 | solAssert(!_type.isByteArrayOrString(), ""); |
1410 | 175 | solUnimplementedAssert(_type.baseType()->storageBytes() <= 32); |
1411 | | |
1412 | 175 | std::string functionName = "cleanup_storage_array_end_" + _type.identifier(); |
1413 | 175 | return m_functionCollector.createFunction(functionName, [&](std::vector<std::string>& _args, std::vector<std::string>&) { |
1414 | 175 | _args = {"array", "len", "startIndex"}; |
1415 | 175 | return Whiskers(R"( |
1416 | 175 | if lt(startIndex, len) { |
1417 | 175 | // Size was reduced, clear end of array |
1418 | 175 | let oldSlotCount := <convertToSize>(len) |
1419 | 175 | let newSlotCount := <convertToSize>(startIndex) |
1420 | 175 | let arrayDataStart := <dataPosition>(array) |
1421 | 175 | let deleteStart := add(arrayDataStart, newSlotCount) |
1422 | 175 | let deleteEnd := add(arrayDataStart, oldSlotCount) |
1423 | 175 | <?packed> |
1424 | 175 | // if we are dealing with packed array and offset is greater than zero |
1425 | 175 | // we have to partially clear last slot that is still used, so decreasing start by one |
1426 | 175 | let offset := mul(mod(startIndex, <itemsPerSlot>), <storageBytes>) |
1427 | 175 | if gt(offset, 0) { <partialClearStorageSlot>(sub(deleteStart, 1), offset) } |
1428 | 175 | </packed> |
1429 | 175 | <clearStorageRange>(deleteStart, deleteEnd) |
1430 | 175 | } |
1431 | 175 | )") |
1432 | 175 | ("convertToSize", arrayConvertLengthToSize(_type)) |
1433 | 175 | ("dataPosition", arrayDataAreaFunction(_type)) |
1434 | 175 | ("clearStorageRange", clearStorageRangeFunction(*_type.baseType())) |
1435 | 175 | ("packed", _type.baseType()->storageBytes() <= 16) |
1436 | 175 | ("itemsPerSlot", std::to_string(32 / _type.baseType()->storageBytes())) |
1437 | 175 | ("storageBytes", std::to_string(_type.baseType()->storageBytes())) |
1438 | 175 | ("partialClearStorageSlot", partialClearStorageSlotFunction()) |
1439 | 175 | .render(); |
1440 | 175 | }); |
1441 | 175 | } |
1442 | | |
1443 | | std::string YulUtilFunctions::resizeDynamicByteArrayFunction(ArrayType const& _type) |
1444 | 0 | { |
1445 | 0 | std::string functionName = "resize_array_" + _type.identifier(); |
1446 | 0 | return m_functionCollector.createFunction(functionName, [&](std::vector<std::string>& _args, std::vector<std::string>&) { |
1447 | 0 | _args = {"array", "newLen"}; |
1448 | 0 | return Whiskers(R"( |
1449 | 0 | let data := sload(array) |
1450 | 0 | let oldLen := <extractLength>(data) |
1451 | 0 |
|
1452 | 0 | if gt(newLen, oldLen) { |
1453 | 0 | <increaseSize>(array, data, oldLen, newLen) |
1454 | 0 | } |
1455 | 0 |
|
1456 | 0 | if lt(newLen, oldLen) { |
1457 | 0 | <decreaseSize>(array, data, oldLen, newLen) |
1458 | 0 | } |
1459 | 0 | )") |
1460 | 0 | ("extractLength", extractByteArrayLengthFunction()) |
1461 | 0 | ("decreaseSize", decreaseByteArraySizeFunction(_type)) |
1462 | 0 | ("increaseSize", increaseByteArraySizeFunction(_type)) |
1463 | 0 | .render(); |
1464 | 0 | }); |
1465 | 0 | } |
1466 | | |
1467 | | std::string YulUtilFunctions::cleanUpDynamicByteArrayEndSlotsFunction(ArrayType const& _type) |
1468 | 1.44k | { |
1469 | 1.44k | solAssert(_type.isByteArrayOrString(), ""); |
1470 | 1.44k | solAssert(_type.isDynamicallySized(), ""); |
1471 | | |
1472 | 1.44k | std::string functionName = "clean_up_bytearray_end_slots_" + _type.identifier(); |
1473 | 1.44k | return m_functionCollector.createFunction(functionName, [&](std::vector<std::string>& _args, std::vector<std::string>&) { |
1474 | 1.24k | _args = {"array", "len", "startIndex"}; |
1475 | 1.24k | return Whiskers(R"( |
1476 | 1.24k | if gt(len, 31) { |
1477 | 1.24k | let dataArea := <dataLocation>(array) |
1478 | 1.24k | let deleteStart := add(dataArea, <div32Ceil>(startIndex)) |
1479 | 1.24k | // If we are clearing array to be short byte array, we want to clear only data starting from array data area. |
1480 | 1.24k | if lt(startIndex, 32) { deleteStart := dataArea } |
1481 | 1.24k | <clearStorageRange>(deleteStart, add(dataArea, <div32Ceil>(len))) |
1482 | 1.24k | } |
1483 | 1.24k | )") |
1484 | 1.24k | ("dataLocation", arrayDataAreaFunction(_type)) |
1485 | 1.24k | ("div32Ceil", divide32CeilFunction()) |
1486 | 1.24k | ("clearStorageRange", clearStorageRangeFunction(*_type.baseType())) |
1487 | 1.24k | .render(); |
1488 | 1.24k | }); |
1489 | 1.44k | } |
1490 | | |
1491 | | std::string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _type) |
1492 | 0 | { |
1493 | 0 | std::string functionName = "byte_array_decrease_size_" + _type.identifier(); |
1494 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
1495 | 0 | return Whiskers(R"( |
1496 | 0 | function <functionName>(array, data, oldLen, newLen) { |
1497 | 0 | switch lt(newLen, 32) |
1498 | 0 | case 0 { |
1499 | 0 | let arrayDataStart := <dataPosition>(array) |
1500 | 0 | let deleteStart := add(arrayDataStart, <div32Ceil>(newLen)) |
1501 | 0 |
|
1502 | 0 | // we have to partially clear last slot that is still used |
1503 | 0 | let offset := and(newLen, 0x1f) |
1504 | 0 | if offset { <partialClearStorageSlot>(sub(deleteStart, 1), offset) } |
1505 | 0 |
|
1506 | 0 | <clearStorageRange>(deleteStart, add(arrayDataStart, <div32Ceil>(oldLen))) |
1507 | 0 |
|
1508 | 0 | sstore(array, or(mul(2, newLen), 1)) |
1509 | 0 | } |
1510 | 0 | default { |
1511 | 0 | switch gt(oldLen, 31) |
1512 | 0 | case 1 { |
1513 | 0 | let arrayDataStart := <dataPosition>(array) |
1514 | 0 | // clear whole old array, as we are transforming to short bytes array |
1515 | 0 | <clearStorageRange>(add(arrayDataStart, 1), add(arrayDataStart, <div32Ceil>(oldLen))) |
1516 | 0 | <transitLongToShort>(array, newLen) |
1517 | 0 | } |
1518 | 0 | default { |
1519 | 0 | sstore(array, <encodeUsedSetLen>(data, newLen)) |
1520 | 0 | } |
1521 | 0 | } |
1522 | 0 | })") |
1523 | 0 | ("functionName", functionName) |
1524 | 0 | ("dataPosition", arrayDataAreaFunction(_type)) |
1525 | 0 | ("partialClearStorageSlot", partialClearStorageSlotFunction()) |
1526 | 0 | ("clearStorageRange", clearStorageRangeFunction(*_type.baseType())) |
1527 | 0 | ("transitLongToShort", byteArrayTransitLongToShortFunction(_type)) |
1528 | 0 | ("div32Ceil", divide32CeilFunction()) |
1529 | 0 | ("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction()) |
1530 | 0 | .render(); |
1531 | 0 | }); |
1532 | 0 | } |
1533 | | |
1534 | | std::string YulUtilFunctions::increaseByteArraySizeFunction(ArrayType const& _type) |
1535 | 0 | { |
1536 | 0 | std::string functionName = "byte_array_increase_size_" + _type.identifier(); |
1537 | 0 | return m_functionCollector.createFunction(functionName, [&](std::vector<std::string>& _args, std::vector<std::string>&) { |
1538 | 0 | _args = {"array", "data", "oldLen", "newLen"}; |
1539 | 0 | return Whiskers(R"( |
1540 | 0 | if gt(newLen, <maxArrayLength>) { <panic>() } |
1541 | 0 |
|
1542 | 0 | switch lt(oldLen, 32) |
1543 | 0 | case 0 { |
1544 | 0 | // in this case array stays unpacked, so we just set new length |
1545 | 0 | sstore(array, add(mul(2, newLen), 1)) |
1546 | 0 | } |
1547 | 0 | default { |
1548 | 0 | switch lt(newLen, 32) |
1549 | 0 | case 0 { |
1550 | 0 | // we need to copy elements to data area as we changed array from packed to unpacked |
1551 | 0 | data := and(not(0xff), data) |
1552 | 0 | sstore(<dataPosition>(array), data) |
1553 | 0 | sstore(array, add(mul(2, newLen), 1)) |
1554 | 0 | } |
1555 | 0 | default { |
1556 | 0 | // here array stays packed, we just need to increase length |
1557 | 0 | sstore(array, <encodeUsedSetLen>(data, newLen)) |
1558 | 0 | } |
1559 | 0 | } |
1560 | 0 | )") |
1561 | 0 | ("panic", panicFunction(PanicCode::ResourceError)) |
1562 | 0 | ("maxArrayLength", (u256(1) << 64).str()) |
1563 | 0 | ("dataPosition", arrayDataAreaFunction(_type)) |
1564 | 0 | ("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction()) |
1565 | 0 | .render(); |
1566 | 0 | }); |
1567 | 0 | } |
1568 | | |
1569 | | std::string YulUtilFunctions::byteArrayTransitLongToShortFunction(ArrayType const& _type) |
1570 | 18 | { |
1571 | 18 | std::string functionName = "transit_byte_array_long_to_short_" + _type.identifier(); |
1572 | 18 | return m_functionCollector.createFunction(functionName, [&]() { |
1573 | 18 | return Whiskers(R"( |
1574 | 18 | function <functionName>(array, len) { |
1575 | 18 | // we need to copy elements from old array to new |
1576 | 18 | // we want to copy only elements that are part of the array after resizing |
1577 | 18 | let dataPos := <dataPosition>(array) |
1578 | 18 | let data := <extractUsedApplyLen>(sload(dataPos), len) |
1579 | 18 | sstore(array, data) |
1580 | 18 | sstore(dataPos, 0) |
1581 | 18 | })") |
1582 | 18 | ("functionName", functionName) |
1583 | 18 | ("dataPosition", arrayDataAreaFunction(_type)) |
1584 | 18 | ("extractUsedApplyLen", shortByteArrayEncodeUsedAreaSetLengthFunction()) |
1585 | 18 | .render(); |
1586 | 18 | }); |
1587 | 18 | } |
1588 | | |
1589 | | std::string YulUtilFunctions::shortByteArrayEncodeUsedAreaSetLengthFunction() |
1590 | 1.26k | { |
1591 | 1.26k | std::string functionName = "extract_used_part_and_set_length_of_short_byte_array"; |
1592 | 1.26k | return m_functionCollector.createFunction(functionName, [&]() { |
1593 | 1.13k | return Whiskers(R"( |
1594 | 1.13k | function <functionName>(data, len) -> used { |
1595 | 1.13k | // we want to save only elements that are part of the array after resizing |
1596 | 1.13k | // others should be set to zero |
1597 | 1.13k | data := <maskBytes>(data, len) |
1598 | 1.13k | used := or(data, mul(2, len)) |
1599 | 1.13k | })") |
1600 | 1.13k | ("functionName", functionName) |
1601 | 1.13k | ("maskBytes", maskBytesFunctionDynamic()) |
1602 | 1.13k | .render(); |
1603 | 1.13k | }); |
1604 | 1.26k | } |
1605 | | |
1606 | | std::string YulUtilFunctions::longByteArrayStorageIndexAccessNoCheckFunction() |
1607 | 707 | { |
1608 | 707 | return m_functionCollector.createFunction( |
1609 | 707 | "long_byte_array_index_access_no_checks", |
1610 | 707 | [&](std::vector<std::string>& _args, std::vector<std::string>& _returnParams) { |
1611 | 470 | _args = {"array", "index"}; |
1612 | 470 | _returnParams = {"slot", "offset"}; |
1613 | 470 | return Whiskers(R"( |
1614 | 470 | offset := sub(31, mod(index, 0x20)) |
1615 | 470 | let dataArea := <dataAreaFunc>(array) |
1616 | 470 | slot := add(dataArea, div(index, 0x20)) |
1617 | 470 | )") |
1618 | 470 | ("dataAreaFunc", arrayDataAreaFunction(*TypeProvider::bytesStorage())) |
1619 | 470 | .render(); |
1620 | 470 | } |
1621 | 707 | ); |
1622 | 707 | } |
1623 | | |
1624 | | std::string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type) |
1625 | 37 | { |
1626 | 37 | solAssert(_type.location() == DataLocation::Storage, ""); |
1627 | 37 | solAssert(_type.isDynamicallySized(), ""); |
1628 | 37 | solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); |
1629 | 37 | if (_type.isByteArrayOrString()) |
1630 | 30 | return storageByteArrayPopFunction(_type); |
1631 | | |
1632 | 7 | std::string functionName = "array_pop_" + _type.identifier(); |
1633 | 7 | return m_functionCollector.createFunction(functionName, [&]() { |
1634 | 4 | return Whiskers(R"( |
1635 | 4 | function <functionName>(array) { |
1636 | 4 | let oldLen := <fetchLength>(array) |
1637 | 4 | if iszero(oldLen) { <panic>() } |
1638 | 4 | let newLen := sub(oldLen, 1) |
1639 | 4 | let slot, offset := <indexAccess>(array, newLen) |
1640 | 4 | <?+setToZero><setToZero>(slot, offset)</+setToZero> |
1641 | 4 | sstore(array, newLen) |
1642 | 4 | })") |
1643 | 4 | ("functionName", functionName) |
1644 | 4 | ("panic", panicFunction(PanicCode::EmptyArrayPop)) |
1645 | 4 | ("fetchLength", arrayLengthFunction(_type)) |
1646 | 4 | ("indexAccess", storageArrayIndexAccessFunction(_type)) |
1647 | 4 | ( |
1648 | 4 | "setToZero", |
1649 | 4 | _type.baseType()->category() != Type::Category::Mapping ? storageSetToZeroFunction(*_type.baseType(), VariableDeclaration::Location::Unspecified) : "" |
1650 | 4 | ) |
1651 | 4 | .render(); |
1652 | 4 | }); |
1653 | 37 | } |
1654 | | |
1655 | | std::string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type) |
1656 | 30 | { |
1657 | 30 | solAssert(_type.location() == DataLocation::Storage, ""); |
1658 | 30 | solAssert(_type.isDynamicallySized(), ""); |
1659 | 30 | solAssert(_type.isByteArrayOrString(), ""); |
1660 | | |
1661 | 30 | std::string functionName = "byte_array_pop_" + _type.identifier(); |
1662 | 30 | return m_functionCollector.createFunction(functionName, [&]() { |
1663 | 18 | return Whiskers(R"( |
1664 | 18 | function <functionName>(array) { |
1665 | 18 | let data := sload(array) |
1666 | 18 | let oldLen := <extractByteArrayLength>(data) |
1667 | 18 | if iszero(oldLen) { <panic>() } |
1668 | 18 | |
1669 | 18 | switch oldLen |
1670 | 18 | case 32 { |
1671 | 18 | // Here we have a special case where array transitions to shorter than 32 |
1672 | 18 | // So we need to copy data |
1673 | 18 | <transitLongToShort>(array, 31) |
1674 | 18 | } |
1675 | 18 | default { |
1676 | 18 | let newLen := sub(oldLen, 1) |
1677 | 18 | switch lt(oldLen, 32) |
1678 | 18 | case 1 { |
1679 | 18 | sstore(array, <encodeUsedSetLen>(data, newLen)) |
1680 | 18 | } |
1681 | 18 | default { |
1682 | 18 | let slot, offset := <indexAccessNoChecks>(array, newLen) |
1683 | 18 | <setToZero>(slot, offset) |
1684 | 18 | sstore(array, sub(data, 2)) |
1685 | 18 | } |
1686 | 18 | } |
1687 | 18 | })") |
1688 | 18 | ("functionName", functionName) |
1689 | 18 | ("panic", panicFunction(PanicCode::EmptyArrayPop)) |
1690 | 18 | ("extractByteArrayLength", extractByteArrayLengthFunction()) |
1691 | 18 | ("transitLongToShort", byteArrayTransitLongToShortFunction(_type)) |
1692 | 18 | ("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction()) |
1693 | 18 | ("indexAccessNoChecks", longByteArrayStorageIndexAccessNoCheckFunction()) |
1694 | 18 | ("setToZero", storageSetToZeroFunction(*_type.baseType(), VariableDeclaration::Location::Unspecified)) |
1695 | 18 | .render(); |
1696 | 18 | }); |
1697 | 30 | } |
1698 | | |
1699 | | std::string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type, Type const* _fromType) |
1700 | 326 | { |
1701 | 326 | solAssert(_type.location() == DataLocation::Storage, ""); |
1702 | 326 | solAssert(_type.isDynamicallySized(), ""); |
1703 | 326 | if (!_fromType) |
1704 | 0 | _fromType = _type.baseType(); |
1705 | 326 | else if (_fromType->isValueType()) |
1706 | 326 | solUnimplementedAssert(*_fromType == *_type.baseType()); |
1707 | | |
1708 | 326 | std::string functionName = |
1709 | 326 | std::string{"array_push_from_"} + |
1710 | 326 | _fromType->identifier() + |
1711 | 326 | "_to_" + |
1712 | 326 | _type.identifier(); |
1713 | 326 | return m_functionCollector.createFunction(functionName, [&]() { |
1714 | 204 | return Whiskers(R"( |
1715 | 204 | function <functionName>(array <values>) { |
1716 | 204 | <?isByteArrayOrString> |
1717 | 204 | let data := sload(array) |
1718 | 204 | let oldLen := <extractByteArrayLength>(data) |
1719 | 204 | if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() } |
1720 | 204 | |
1721 | 204 | switch gt(oldLen, 31) |
1722 | 204 | case 0 { |
1723 | 204 | let value := byte(0 <values>) |
1724 | 204 | switch oldLen |
1725 | 204 | case 31 { |
1726 | 204 | // Here we have special case when array switches from short array to long array |
1727 | 204 | // We need to copy data |
1728 | 204 | let dataArea := <dataAreaFunction>(array) |
1729 | 204 | data := and(data, not(0xff)) |
1730 | 204 | sstore(dataArea, or(and(0xff, value), data)) |
1731 | 204 | // New length is 32, encoded as (32 * 2 + 1) |
1732 | 204 | sstore(array, 65) |
1733 | 204 | } |
1734 | 204 | default { |
1735 | 204 | data := add(data, 2) |
1736 | 204 | let shiftBits := mul(8, sub(31, oldLen)) |
1737 | 204 | let valueShifted := <shl>(shiftBits, and(0xff, value)) |
1738 | 204 | let mask := <shl>(shiftBits, 0xff) |
1739 | 204 | data := or(and(data, not(mask)), valueShifted) |
1740 | 204 | sstore(array, data) |
1741 | 204 | } |
1742 | 204 | } |
1743 | 204 | default { |
1744 | 204 | sstore(array, add(data, 2)) |
1745 | 204 | let slot, offset := <indexAccess>(array, oldLen) |
1746 | 204 | <storeValue>(slot, offset <values>) |
1747 | 204 | } |
1748 | 204 | <!isByteArrayOrString> |
1749 | 204 | let oldLen := sload(array) |
1750 | 204 | if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() } |
1751 | 204 | sstore(array, add(oldLen, 1)) |
1752 | 204 | let slot, offset := <indexAccess>(array, oldLen) |
1753 | 204 | <storeValue>(slot, offset <values>) |
1754 | 204 | </isByteArrayOrString> |
1755 | 204 | })") |
1756 | 204 | ("functionName", functionName) |
1757 | 204 | ("values", _fromType->sizeOnStack() == 0 ? "" : ", " + suffixedVariableNameList("value", 0, _fromType->sizeOnStack())) |
1758 | 204 | ("panic", panicFunction(PanicCode::ResourceError)) |
1759 | 204 | ("extractByteArrayLength", _type.isByteArrayOrString() ? extractByteArrayLengthFunction() : "") |
1760 | 204 | ("dataAreaFunction", arrayDataAreaFunction(_type)) |
1761 | 204 | ("isByteArrayOrString", _type.isByteArrayOrString()) |
1762 | 204 | ("indexAccess", storageArrayIndexAccessFunction(_type)) |
1763 | 204 | ("storeValue", updateStorageValueFunction(*_fromType, *_type.baseType(), VariableDeclaration::Location::Unspecified)) |
1764 | 204 | ("maxArrayLength", (u256(1) << 64).str()) |
1765 | 204 | ("shl", shiftLeftFunctionDynamic()) |
1766 | 204 | .render(); |
1767 | 204 | }); |
1768 | 326 | } |
1769 | | |
1770 | | std::string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type) |
1771 | 3 | { |
1772 | 3 | solAssert(_type.location() == DataLocation::Storage, ""); |
1773 | 3 | solAssert(_type.isDynamicallySized(), ""); |
1774 | 3 | solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); |
1775 | | |
1776 | 3 | std::string functionName = "array_push_zero_" + _type.identifier(); |
1777 | 3 | return m_functionCollector.createFunction(functionName, [&]() { |
1778 | 3 | return Whiskers(R"( |
1779 | 3 | function <functionName>(array) -> slot, offset { |
1780 | 3 | <?isBytes> |
1781 | 3 | let data := sload(array) |
1782 | 3 | let oldLen := <extractLength>(data) |
1783 | 3 | <increaseBytesSize>(array, data, oldLen, add(oldLen, 1)) |
1784 | 3 | <!isBytes> |
1785 | 3 | let oldLen := <fetchLength>(array) |
1786 | 3 | if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() } |
1787 | 3 | sstore(array, add(oldLen, 1)) |
1788 | 3 | </isBytes> |
1789 | 3 | slot, offset := <indexAccess>(array, oldLen) |
1790 | 3 | })") |
1791 | 3 | ("functionName", functionName) |
1792 | 3 | ("isBytes", _type.isByteArrayOrString()) |
1793 | 3 | ("increaseBytesSize", _type.isByteArrayOrString() ? increaseByteArraySizeFunction(_type) : "") |
1794 | 3 | ("extractLength", _type.isByteArrayOrString() ? extractByteArrayLengthFunction() : "") |
1795 | 3 | ("panic", panicFunction(PanicCode::ResourceError)) |
1796 | 3 | ("fetchLength", arrayLengthFunction(_type)) |
1797 | 3 | ("indexAccess", storageArrayIndexAccessFunction(_type)) |
1798 | 3 | ("maxArrayLength", (u256(1) << 64).str()) |
1799 | 3 | .render(); |
1800 | 3 | }); |
1801 | 3 | } |
1802 | | |
1803 | | std::string YulUtilFunctions::partialClearStorageSlotFunction() |
1804 | 175 | { |
1805 | 175 | std::string functionName = "partial_clear_storage_slot"; |
1806 | 175 | return m_functionCollector.createFunction(functionName, [&]() { |
1807 | 135 | return Whiskers(R"( |
1808 | 135 | function <functionName>(slot, offset) { |
1809 | 135 | let mask := <shr>(mul(8, sub(32, offset)), <ones>) |
1810 | 135 | sstore(slot, and(mask, sload(slot))) |
1811 | 135 | } |
1812 | 135 | )") |
1813 | 135 | ("functionName", functionName) |
1814 | 135 | ("ones", formatNumber((bigint(1) << 256) - 1)) |
1815 | 135 | ("shr", shiftRightFunctionDynamic()) |
1816 | 135 | .render(); |
1817 | 135 | }); |
1818 | 175 | } |
1819 | | |
1820 | | std::string YulUtilFunctions::clearStorageRangeFunction(Type const& _type) |
1821 | 1.45k | { |
1822 | 1.45k | if (_type.storageBytes() < 32) |
1823 | 1.45k | solAssert(_type.isValueType(), ""); |
1824 | | |
1825 | 1.45k | std::string functionName = "clear_storage_range_" + _type.identifier(); |
1826 | | |
1827 | 1.45k | return m_functionCollector.createFunction(functionName, [&]() { |
1828 | 1.43k | return Whiskers(R"( |
1829 | 1.43k | function <functionName>(start, end) { |
1830 | 1.43k | for {} lt(start, end) { start := add(start, <increment>) } |
1831 | 1.43k | { |
1832 | 1.43k | <setToZero>(start, 0) |
1833 | 1.43k | } |
1834 | 1.43k | } |
1835 | 1.43k | )") |
1836 | 1.43k | ("functionName", functionName) |
1837 | 1.43k | ("setToZero", storageSetToZeroFunction(_type.storageBytes() < 32 ? *TypeProvider::uint256() : _type, VariableDeclaration::Location::Unspecified)) |
1838 | 1.43k | ("increment", _type.storageSize().str()) |
1839 | 1.43k | .render(); |
1840 | 1.43k | }); |
1841 | 1.45k | } |
1842 | | |
1843 | | std::string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type) |
1844 | 36 | { |
1845 | 36 | solAssert(_type.location() == DataLocation::Storage, ""); |
1846 | | |
1847 | 36 | if (_type.baseType()->storageBytes() < 32) |
1848 | 18 | { |
1849 | 18 | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
1850 | 18 | solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); |
1851 | 18 | } |
1852 | | |
1853 | 36 | if (_type.baseType()->isValueType()) |
1854 | 36 | solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); |
1855 | | |
1856 | 36 | std::string functionName = "clear_storage_array_" + _type.identifier(); |
1857 | | |
1858 | 36 | return m_functionCollector.createFunction(functionName, [&]() { |
1859 | 36 | return Whiskers(R"( |
1860 | 36 | function <functionName>(slot) { |
1861 | 36 | <?dynamic> |
1862 | 36 | <resizeArray>(slot, 0) |
1863 | 36 | <!dynamic> |
1864 | 36 | <?+clearRange><clearRange>(slot, add(slot, <lenToSize>(<len>)))</+clearRange> |
1865 | 36 | </dynamic> |
1866 | 36 | } |
1867 | 36 | )") |
1868 | 36 | ("functionName", functionName) |
1869 | 36 | ("dynamic", _type.isDynamicallySized()) |
1870 | 36 | ("resizeArray", _type.isDynamicallySized() ? resizeArrayFunction(_type) : "") |
1871 | 36 | ( |
1872 | 36 | "clearRange", |
1873 | 36 | _type.baseType()->category() != Type::Category::Mapping ? |
1874 | 36 | clearStorageRangeFunction((_type.baseType()->storageBytes() < 32) ? *TypeProvider::uint256() : *_type.baseType()) : |
1875 | 36 | "" |
1876 | 36 | ) |
1877 | 36 | ("lenToSize", arrayConvertLengthToSize(_type)) |
1878 | 36 | ("len", _type.length().str()) |
1879 | 36 | .render(); |
1880 | 36 | }); |
1881 | 36 | } |
1882 | | |
1883 | | std::string YulUtilFunctions::clearStorageStructFunction(StructType const& _type) |
1884 | 31 | { |
1885 | 31 | solAssert(_type.location() == DataLocation::Storage, ""); |
1886 | | |
1887 | 31 | std::string functionName = "clear_struct_storage_" + _type.identifier(); |
1888 | | |
1889 | 31 | return m_functionCollector.createFunction(functionName, [&] { |
1890 | 31 | MemberList::MemberMap structMembers = _type.nativeMembers(nullptr); |
1891 | 31 | std::vector<std::map<std::string, std::string>> memberSetValues; |
1892 | | |
1893 | 31 | std::set<u256> slotsCleared; |
1894 | 31 | for (auto const& member: structMembers) |
1895 | 84 | { |
1896 | 84 | if (member.type->category() == Type::Category::Mapping) |
1897 | 0 | continue; |
1898 | 84 | if (member.type->storageBytes() < 32) |
1899 | 74 | { |
1900 | 74 | auto const& slotDiff = _type.storageOffsetsOfMember(member.name).first; |
1901 | 74 | if (!slotsCleared.count(slotDiff)) |
1902 | 47 | { |
1903 | 47 | memberSetValues.emplace_back().emplace("clearMember", "sstore(add(slot, " + slotDiff.str() + "), 0)"); |
1904 | 47 | slotsCleared.emplace(slotDiff); |
1905 | 47 | } |
1906 | 74 | } |
1907 | 10 | else |
1908 | 10 | { |
1909 | 10 | auto const& [memberSlotDiff, memberStorageOffset] = _type.storageOffsetsOfMember(member.name); |
1910 | 10 | solAssert(memberStorageOffset == 0, ""); |
1911 | | |
1912 | 10 | memberSetValues.emplace_back().emplace("clearMember", Whiskers(R"( |
1913 | 10 | <setZero>(add(slot, <memberSlotDiff>), <memberStorageOffset>) |
1914 | 10 | )") |
1915 | 10 | ("setZero", storageSetToZeroFunction(*member.type, VariableDeclaration::Location::Unspecified)) |
1916 | 10 | ("memberSlotDiff", memberSlotDiff.str()) |
1917 | 10 | ("memberStorageOffset", std::to_string(memberStorageOffset)) |
1918 | 10 | .render() |
1919 | 10 | ); |
1920 | 10 | } |
1921 | 84 | } |
1922 | | |
1923 | 31 | return Whiskers(R"( |
1924 | 31 | function <functionName>(slot) { |
1925 | 31 | <#member> |
1926 | 31 | <clearMember> |
1927 | 31 | </member> |
1928 | 31 | } |
1929 | 31 | )") |
1930 | 31 | ("functionName", functionName) |
1931 | 31 | ("member", memberSetValues) |
1932 | 31 | .render(); |
1933 | 31 | }); |
1934 | 31 | } |
1935 | | |
1936 | | std::string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType) |
1937 | 199 | { |
1938 | 199 | solAssert( |
1939 | 199 | (*_fromType.copyForLocation(_toType.location(), _toType.isPointer())).equals(dynamic_cast<ReferenceType const&>(_toType)), |
1940 | 199 | "" |
1941 | 199 | ); |
1942 | 199 | if (!_toType.isDynamicallySized()) |
1943 | 199 | solAssert(!_fromType.isDynamicallySized() && _fromType.length() <= _toType.length(), ""); |
1944 | | |
1945 | 199 | if (_fromType.isByteArrayOrString()) |
1946 | 27 | return copyByteArrayToStorageFunction(_fromType, _toType); |
1947 | 172 | if (_toType.baseType()->isValueType()) |
1948 | 123 | return copyValueArrayToStorageFunction(_fromType, _toType); |
1949 | | |
1950 | 49 | solAssert(_toType.storageStride() == 32); |
1951 | 49 | solAssert(!_fromType.baseType()->isValueType()); |
1952 | | |
1953 | 49 | std::string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); |
1954 | 49 | return m_functionCollector.createFunction(functionName, [&](){ |
1955 | 49 | Whiskers templ(R"( |
1956 | 49 | function <functionName>(slot, value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) { |
1957 | 49 | <?fromStorage> if eq(slot, value) { leave } </fromStorage> |
1958 | 49 | let length := <arrayLength>(value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) |
1959 | 49 | |
1960 | 49 | <resizeArray>(slot, length) |
1961 | 49 | |
1962 | 49 | let srcPtr := <srcDataLocation>(value) |
1963 | 49 | |
1964 | 49 | let elementSlot := <dstDataLocation>(slot) |
1965 | 49 | |
1966 | 49 | for { let i := 0 } lt(i, length) {i := add(i, 1)} { |
1967 | 49 | <?fromCalldata> |
1968 | 49 | let <stackItems> := |
1969 | 49 | <?dynamicallyEncodedBase> |
1970 | 49 | <accessCalldataTail>(value, srcPtr) |
1971 | 49 | <!dynamicallyEncodedBase> |
1972 | 49 | srcPtr |
1973 | 49 | </dynamicallyEncodedBase> |
1974 | 49 | </fromCalldata> |
1975 | 49 | |
1976 | 49 | <?fromMemory> |
1977 | 49 | let <stackItems> := <readFromMemoryOrCalldata>(srcPtr) |
1978 | 49 | </fromMemory> |
1979 | 49 | |
1980 | 49 | <?fromStorage> |
1981 | 49 | let <stackItems> := srcPtr |
1982 | 49 | </fromStorage> |
1983 | 49 | |
1984 | 49 | <updateStorageValue>(elementSlot, <stackItems>) |
1985 | 49 | |
1986 | 49 | srcPtr := add(srcPtr, <srcStride>) |
1987 | 49 | |
1988 | 49 | elementSlot := add(elementSlot, <storageSize>) |
1989 | 49 | } |
1990 | 49 | } |
1991 | 49 | )"); |
1992 | 49 | if (_fromType.dataStoredIn(DataLocation::Storage)) |
1993 | 49 | solAssert(!_fromType.isValueType(), ""); |
1994 | 49 | templ("functionName", functionName); |
1995 | 49 | bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData); |
1996 | 49 | templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata); |
1997 | 49 | templ("fromStorage", _fromType.dataStoredIn(DataLocation::Storage)); |
1998 | 49 | bool fromMemory = _fromType.dataStoredIn(DataLocation::Memory); |
1999 | 49 | templ("fromMemory", fromMemory); |
2000 | 49 | templ("fromCalldata", fromCalldata); |
2001 | 49 | templ("srcDataLocation", arrayDataAreaFunction(_fromType)); |
2002 | 49 | if (fromCalldata) |
2003 | 32 | { |
2004 | 32 | templ("dynamicallyEncodedBase", _fromType.baseType()->isDynamicallyEncoded()); |
2005 | 32 | if (_fromType.baseType()->isDynamicallyEncoded()) |
2006 | 18 | templ("accessCalldataTail", accessCalldataTailFunction(*_fromType.baseType())); |
2007 | 32 | } |
2008 | 49 | templ("resizeArray", resizeArrayFunction(_toType)); |
2009 | 49 | templ("arrayLength",arrayLengthFunction(_fromType)); |
2010 | 49 | templ("dstDataLocation", arrayDataAreaFunction(_toType)); |
2011 | 49 | if (fromMemory || (fromCalldata && _fromType.baseType()->isValueType())) |
2012 | 8 | templ("readFromMemoryOrCalldata", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata)); |
2013 | 49 | templ("stackItems", suffixedVariableNameList( |
2014 | 49 | "stackItem_", |
2015 | 49 | 0, |
2016 | 49 | _fromType.baseType()->stackItems().size() |
2017 | 49 | )); |
2018 | 49 | templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType(), VariableDeclaration::Location::Unspecified, 0)); |
2019 | 49 | templ("srcStride", |
2020 | 49 | fromCalldata ? |
2021 | 32 | std::to_string(_fromType.calldataStride()) : |
2022 | 49 | fromMemory ? |
2023 | 8 | std::to_string(_fromType.memoryStride()) : |
2024 | 17 | formatNumber(_fromType.baseType()->storageSize()) |
2025 | 49 | ); |
2026 | 49 | templ("storageSize", _toType.baseType()->storageSize().str()); |
2027 | | |
2028 | 49 | return templ.render(); |
2029 | 49 | }); |
2030 | 49 | } |
2031 | | |
2032 | | |
2033 | | std::string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType) |
2034 | 6.89k | { |
2035 | 6.89k | solAssert( |
2036 | 6.89k | (*_fromType.copyForLocation(_toType.location(), _toType.isPointer())).equals(dynamic_cast<ReferenceType const&>(_toType)), |
2037 | 6.89k | "" |
2038 | 6.89k | ); |
2039 | 6.89k | solAssert(_fromType.isByteArrayOrString(), ""); |
2040 | 6.89k | solAssert(_toType.isByteArrayOrString(), ""); |
2041 | | |
2042 | 6.89k | std::string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); |
2043 | 6.89k | return m_functionCollector.createFunction(functionName, [&](){ |
2044 | 1.22k | Whiskers templ(R"( |
2045 | 1.22k | function <functionName>(slot, src<?fromCalldata>, len</fromCalldata>) { |
2046 | 1.22k | <?fromStorage> if eq(slot, src) { leave } </fromStorage> |
2047 | 1.22k | |
2048 | 1.22k | let newLen := <arrayLength>(src<?fromCalldata>, len</fromCalldata>) |
2049 | 1.22k | // Make sure array length is sane |
2050 | 1.22k | if gt(newLen, 0xffffffffffffffff) { <panic>() } |
2051 | 1.22k | |
2052 | 1.22k | let oldLen := <byteArrayLength>(sload(slot)) |
2053 | 1.22k | |
2054 | 1.22k | // potentially truncate data |
2055 | 1.22k | <cleanUpEndArray>(slot, oldLen, newLen) |
2056 | 1.22k | |
2057 | 1.22k | let srcOffset := 0 |
2058 | 1.22k | <?fromMemory> |
2059 | 1.22k | srcOffset := 0x20 |
2060 | 1.22k | </fromMemory> |
2061 | 1.22k | |
2062 | 1.22k | switch gt(newLen, 31) |
2063 | 1.22k | case 1 { |
2064 | 1.22k | let loopEnd := and(newLen, not(0x1f)) |
2065 | 1.22k | <?fromStorage> src := <srcDataLocation>(src) </fromStorage> |
2066 | 1.22k | let dstPtr := <dstDataLocation>(slot) |
2067 | 1.22k | let i := 0 |
2068 | 1.22k | for { } lt(i, loopEnd) { i := add(i, 0x20) } { |
2069 | 1.22k | sstore(dstPtr, <read>(add(src, srcOffset))) |
2070 | 1.22k | dstPtr := add(dstPtr, 1) |
2071 | 1.22k | srcOffset := add(srcOffset, <srcIncrement>) |
2072 | 1.22k | } |
2073 | 1.22k | if lt(loopEnd, newLen) { |
2074 | 1.22k | let lastValue := <read>(add(src, srcOffset)) |
2075 | 1.22k | sstore(dstPtr, <maskBytes>(lastValue, and(newLen, 0x1f))) |
2076 | 1.22k | } |
2077 | 1.22k | sstore(slot, add(mul(newLen, 2), 1)) |
2078 | 1.22k | } |
2079 | 1.22k | default { |
2080 | 1.22k | let value := 0 |
2081 | 1.22k | if newLen { |
2082 | 1.22k | value := <read>(add(src, srcOffset)) |
2083 | 1.22k | } |
2084 | 1.22k | sstore(slot, <byteArrayCombineShort>(value, newLen)) |
2085 | 1.22k | } |
2086 | 1.22k | } |
2087 | 1.22k | )"); |
2088 | 1.22k | templ("functionName", functionName); |
2089 | 1.22k | bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage); |
2090 | 1.22k | templ("fromStorage", fromStorage); |
2091 | 1.22k | bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData); |
2092 | 1.22k | templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory)); |
2093 | 1.22k | templ("fromCalldata", fromCalldata); |
2094 | 1.22k | templ("arrayLength", arrayLengthFunction(_fromType)); |
2095 | 1.22k | templ("panic", panicFunction(PanicCode::ResourceError)); |
2096 | 1.22k | templ("byteArrayLength", extractByteArrayLengthFunction()); |
2097 | 1.22k | templ("dstDataLocation", arrayDataAreaFunction(_toType)); |
2098 | 1.22k | if (fromStorage) |
2099 | 106 | templ("srcDataLocation", arrayDataAreaFunction(_fromType)); |
2100 | 1.22k | templ("cleanUpEndArray", cleanUpDynamicByteArrayEndSlotsFunction(_toType)); |
2101 | 1.22k | templ("srcIncrement", std::to_string(fromStorage ? 1 : 0x20)); |
2102 | 1.22k | templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload"); |
2103 | 1.22k | templ("maskBytes", maskBytesFunctionDynamic()); |
2104 | 1.22k | templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction()); |
2105 | | |
2106 | 1.22k | return templ.render(); |
2107 | 1.22k | }); |
2108 | 6.89k | } |
2109 | | |
2110 | | std::string YulUtilFunctions::copyValueArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType) |
2111 | 123 | { |
2112 | 123 | solAssert(_fromType.baseType()->isValueType(), ""); |
2113 | 123 | solAssert(_toType.baseType()->isValueType(), ""); |
2114 | 123 | solAssert(_fromType.baseType()->isImplicitlyConvertibleTo(*_toType.baseType()), ""); |
2115 | | |
2116 | 123 | solAssert(!_fromType.isByteArrayOrString(), ""); |
2117 | 123 | solAssert(!_toType.isByteArrayOrString(), ""); |
2118 | 123 | solAssert(_toType.dataStoredIn(DataLocation::Storage), ""); |
2119 | | |
2120 | 123 | solAssert(_fromType.storageStride() <= _toType.storageStride(), ""); |
2121 | 123 | solAssert(_toType.storageStride() <= 32, ""); |
2122 | | |
2123 | 123 | std::string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); |
2124 | 123 | return m_functionCollector.createFunction(functionName, [&](){ |
2125 | 123 | Whiskers templ(R"( |
2126 | 123 | function <functionName>(dst, src<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) { |
2127 | 123 | <?isFromStorage> |
2128 | 123 | if eq(dst, src) { leave } |
2129 | 123 | </isFromStorage> |
2130 | 123 | let length := <arrayLength>(src<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) |
2131 | 123 | // Make sure array length is sane |
2132 | 123 | if gt(length, 0xffffffffffffffff) { <panic>() } |
2133 | 123 | <resizeArray>(dst, length) |
2134 | 123 | |
2135 | 123 | let srcPtr := <srcDataLocation>(src) |
2136 | 123 | let dstSlot := <dstDataLocation>(dst) |
2137 | 123 | |
2138 | 123 | let fullSlots := div(length, <itemsPerSlot>) |
2139 | 123 | |
2140 | 123 | <?isFromStorage> |
2141 | 123 | let srcSlotValue := sload(srcPtr) |
2142 | 123 | let srcItemIndexInSlot := 0 |
2143 | 123 | </isFromStorage> |
2144 | 123 | |
2145 | 123 | for { let i := 0 } lt(i, fullSlots) { i := add(i, 1) } { |
2146 | 123 | let dstSlotValue := 0 |
2147 | 123 | <?sameTypeFromStorage> |
2148 | 123 | dstSlotValue := <maskFull>(srcSlotValue) |
2149 | 123 | <updateSrcPtr> |
2150 | 123 | <!sameTypeFromStorage> |
2151 | 123 | <?multipleItemsPerSlotDst>for { let j := 0 } lt(j, <itemsPerSlot>) { j := add(j, 1) } </multipleItemsPerSlotDst> |
2152 | 123 | { |
2153 | 123 | <?isFromStorage> |
2154 | 123 | let <stackItems> := <convert>( |
2155 | 123 | <extractFromSlot>(srcSlotValue, mul(<srcStride>, srcItemIndexInSlot)) |
2156 | 123 | ) |
2157 | 123 | <!isFromStorage> |
2158 | 123 | let <stackItems> := <readFromMemoryOrCalldata>(srcPtr) |
2159 | 123 | </isFromStorage> |
2160 | 123 | let itemValue := <prepareStore>(<stackItems>) |
2161 | 123 | dstSlotValue := |
2162 | 123 | <?multipleItemsPerSlotDst> |
2163 | 123 | <updateByteSlice>(dstSlotValue, mul(<dstStride>, j), itemValue) |
2164 | 123 | <!multipleItemsPerSlotDst> |
2165 | 123 | itemValue |
2166 | 123 | </multipleItemsPerSlotDst> |
2167 | 123 | |
2168 | 123 | <updateSrcPtr> |
2169 | 123 | } |
2170 | 123 | </sameTypeFromStorage> |
2171 | 123 | |
2172 | 123 | sstore(add(dstSlot, i), dstSlotValue) |
2173 | 123 | } |
2174 | 123 | |
2175 | 123 | <?multipleItemsPerSlotDst> |
2176 | 123 | let spill := sub(length, mul(fullSlots, <itemsPerSlot>)) |
2177 | 123 | if gt(spill, 0) { |
2178 | 123 | let dstSlotValue := 0 |
2179 | 123 | <?sameTypeFromStorage> |
2180 | 123 | dstSlotValue := <maskBytes>(srcSlotValue, mul(spill, <srcStride>)) |
2181 | 123 | <updateSrcPtr> |
2182 | 123 | <!sameTypeFromStorage> |
2183 | 123 | for { let j := 0 } lt(j, spill) { j := add(j, 1) } { |
2184 | 123 | <?isFromStorage> |
2185 | 123 | let <stackItems> := <convert>( |
2186 | 123 | <extractFromSlot>(srcSlotValue, mul(<srcStride>, srcItemIndexInSlot)) |
2187 | 123 | ) |
2188 | 123 | <!isFromStorage> |
2189 | 123 | let <stackItems> := <readFromMemoryOrCalldata>(srcPtr) |
2190 | 123 | </isFromStorage> |
2191 | 123 | let itemValue := <prepareStore>(<stackItems>) |
2192 | 123 | dstSlotValue := <updateByteSlice>(dstSlotValue, mul(<dstStride>, j), itemValue) |
2193 | 123 | |
2194 | 123 | <updateSrcPtr> |
2195 | 123 | } |
2196 | 123 | </sameTypeFromStorage> |
2197 | 123 | sstore(add(dstSlot, fullSlots), dstSlotValue) |
2198 | 123 | } |
2199 | 123 | </multipleItemsPerSlotDst> |
2200 | 123 | } |
2201 | 123 | )"); |
2202 | 123 | if (_fromType.dataStoredIn(DataLocation::Storage)) |
2203 | 123 | solAssert(!_fromType.isValueType(), ""); |
2204 | | |
2205 | 123 | bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData); |
2206 | 123 | bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage); |
2207 | 123 | templ("functionName", functionName); |
2208 | 123 | templ("resizeArray", resizeArrayFunction(_toType)); |
2209 | 123 | templ("arrayLength", arrayLengthFunction(_fromType)); |
2210 | 123 | templ("panic", panicFunction(PanicCode::ResourceError)); |
2211 | 123 | templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata); |
2212 | 123 | templ("isFromStorage", fromStorage); |
2213 | 123 | templ("readFromMemoryOrCalldata", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata)); |
2214 | 123 | templ("srcDataLocation", arrayDataAreaFunction(_fromType)); |
2215 | 123 | templ("dstDataLocation", arrayDataAreaFunction(_toType)); |
2216 | 123 | templ("srcStride", std::to_string(_fromType.storageStride())); |
2217 | 123 | templ("stackItems", suffixedVariableNameList( |
2218 | 123 | "stackItem_", |
2219 | 123 | 0, |
2220 | 123 | _fromType.baseType()->stackItems().size() |
2221 | 123 | )); |
2222 | 123 | unsigned itemsPerSlot = 32 / _toType.storageStride(); |
2223 | 123 | templ("itemsPerSlot", std::to_string(itemsPerSlot)); |
2224 | 123 | templ("multipleItemsPerSlotDst", itemsPerSlot > 1); |
2225 | 123 | bool sameTypeFromStorage = fromStorage && (*_fromType.baseType() == *_toType.baseType()); |
2226 | 123 | if (auto functionType = dynamic_cast<FunctionType const*>(_fromType.baseType())) |
2227 | 0 | { |
2228 | 0 | solAssert(functionType->equalExcludingStateMutability( |
2229 | 0 | dynamic_cast<FunctionType const&>(*_toType.baseType()) |
2230 | 0 | )); |
2231 | 0 | sameTypeFromStorage = fromStorage; |
2232 | 0 | } |
2233 | 123 | templ("sameTypeFromStorage", sameTypeFromStorage); |
2234 | 123 | if (sameTypeFromStorage) |
2235 | 49 | { |
2236 | 49 | templ("maskFull", maskLowerOrderBytesFunction(itemsPerSlot * _toType.storageStride())); |
2237 | 49 | templ("maskBytes", maskLowerOrderBytesFunctionDynamic()); |
2238 | 49 | } |
2239 | 74 | else |
2240 | 74 | { |
2241 | 74 | templ("dstStride", std::to_string(_toType.storageStride())); |
2242 | 74 | templ("extractFromSlot", extractFromStorageValueDynamic(*_fromType.baseType())); |
2243 | 74 | templ("updateByteSlice", updateByteSliceFunctionDynamic(_toType.storageStride())); |
2244 | 74 | templ("convert", conversionFunction(*_fromType.baseType(), *_toType.baseType())); |
2245 | 74 | templ("prepareStore", prepareStoreFunction(*_toType.baseType())); |
2246 | 74 | } |
2247 | 123 | if (fromStorage) |
2248 | 63 | templ("updateSrcPtr", Whiskers(R"( |
2249 | 63 | <?srcReadMultiPerSlot> |
2250 | 63 | srcItemIndexInSlot := add(srcItemIndexInSlot, 1) |
2251 | 63 | if eq(srcItemIndexInSlot, <srcItemsPerSlot>) { |
2252 | 63 | // here we are done with this slot, we need to read next one |
2253 | 63 | srcPtr := add(srcPtr, 1) |
2254 | 63 | srcSlotValue := sload(srcPtr) |
2255 | 63 | srcItemIndexInSlot := 0 |
2256 | 63 | } |
2257 | 63 | <!srcReadMultiPerSlot> |
2258 | 63 | srcPtr := add(srcPtr, 1) |
2259 | 63 | srcSlotValue := sload(srcPtr) |
2260 | 63 | </srcReadMultiPerSlot> |
2261 | 63 | )") |
2262 | 63 | ("srcReadMultiPerSlot", !sameTypeFromStorage && _fromType.storageStride() <= 16) |
2263 | 63 | ("srcItemsPerSlot", std::to_string(32 / _fromType.storageStride())) |
2264 | 63 | .render() |
2265 | 63 | ); |
2266 | 60 | else |
2267 | 60 | templ("updateSrcPtr", Whiskers(R"( |
2268 | 60 | srcPtr := add(srcPtr, <srcStride>) |
2269 | 60 | )") |
2270 | 60 | ("srcStride", fromCalldata ? std::to_string(_fromType.calldataStride()) : std::to_string(_fromType.memoryStride())) |
2271 | 60 | .render() |
2272 | 60 | ); |
2273 | | |
2274 | 123 | return templ.render(); |
2275 | 123 | }); |
2276 | 123 | } |
2277 | | |
2278 | | |
2279 | | std::string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) |
2280 | 211 | { |
2281 | 211 | std::string functionName = "array_convert_length_to_size_" + _type.identifier(); |
2282 | 211 | return m_functionCollector.createFunction(functionName, [&]() { |
2283 | 185 | Type const& baseType = *_type.baseType(); |
2284 | | |
2285 | 185 | switch (_type.location()) |
2286 | 185 | { |
2287 | 185 | case DataLocation::Storage: |
2288 | 185 | { |
2289 | 185 | unsigned const baseStorageBytes = baseType.storageBytes(); |
2290 | 185 | solAssert(baseStorageBytes > 0, ""); |
2291 | 185 | solAssert(32 / baseStorageBytes > 0, ""); |
2292 | | |
2293 | 185 | return Whiskers(R"( |
2294 | 185 | function <functionName>(length) -> size { |
2295 | 185 | size := length |
2296 | 185 | <?multiSlot> |
2297 | 185 | size := <mul>(<storageSize>, length) |
2298 | 185 | <!multiSlot> |
2299 | 185 | // Number of slots rounded up |
2300 | 185 | size := div(add(length, sub(<itemsPerSlot>, 1)), <itemsPerSlot>) |
2301 | 185 | </multiSlot> |
2302 | 185 | })") |
2303 | 185 | ("functionName", functionName) |
2304 | 185 | ("multiSlot", baseType.storageSize() > 1) |
2305 | 185 | ("itemsPerSlot", std::to_string(32 / baseStorageBytes)) |
2306 | 185 | ("storageSize", baseType.storageSize().str()) |
2307 | 185 | ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) |
2308 | 185 | .render(); |
2309 | 185 | } |
2310 | 0 | case DataLocation::CallData: // fallthrough |
2311 | 0 | case DataLocation::Memory: |
2312 | 0 | return Whiskers(R"( |
2313 | 0 | function <functionName>(length) -> size { |
2314 | 0 | <?byteArray> |
2315 | 0 | size := length |
2316 | 0 | <!byteArray> |
2317 | 0 | size := <mul>(length, <stride>) |
2318 | 0 | </byteArray> |
2319 | 0 | })") |
2320 | 0 | ("functionName", functionName) |
2321 | 0 | ("stride", std::to_string(_type.location() == DataLocation::Memory ? _type.memoryStride() : _type.calldataStride())) |
2322 | 0 | ("byteArray", _type.isByteArrayOrString()) |
2323 | 0 | ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) |
2324 | 0 | .render(); |
2325 | 0 | default: |
2326 | 0 | solAssert(false, ""); |
2327 | 185 | } |
2328 | | |
2329 | 185 | }); |
2330 | 211 | } |
2331 | | |
2332 | | std::string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) |
2333 | 5.39k | { |
2334 | 5.39k | solAssert(_type.dataStoredIn(DataLocation::Memory), ""); |
2335 | 5.39k | std::string functionName = "array_allocation_size_" + _type.identifier(); |
2336 | 5.39k | return m_functionCollector.createFunction(functionName, [&]() { |
2337 | 4.35k | Whiskers w(R"( |
2338 | 4.35k | function <functionName>(length) -> size { |
2339 | 4.35k | // Make sure we can allocate memory without overflow |
2340 | 4.35k | if gt(length, 0xffffffffffffffff) { <panic>() } |
2341 | 4.35k | <?byteArray> |
2342 | 4.35k | size := <roundUp>(length) |
2343 | 4.35k | <!byteArray> |
2344 | 4.35k | size := mul(length, 0x20) |
2345 | 4.35k | </byteArray> |
2346 | 4.35k | <?dynamic> |
2347 | 4.35k | // add length slot |
2348 | 4.35k | size := add(size, 0x20) |
2349 | 4.35k | </dynamic> |
2350 | 4.35k | } |
2351 | 4.35k | )"); |
2352 | 4.35k | w("functionName", functionName); |
2353 | 4.35k | w("panic", panicFunction(PanicCode::ResourceError)); |
2354 | 4.35k | w("byteArray", _type.isByteArrayOrString()); |
2355 | 4.35k | w("roundUp", roundUpFunction()); |
2356 | 4.35k | w("dynamic", _type.isDynamicallySized()); |
2357 | 4.35k | return w.render(); |
2358 | 4.35k | }); |
2359 | 5.39k | } |
2360 | | |
2361 | | std::string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) |
2362 | 10.0k | { |
2363 | 10.0k | std::string functionName = "array_dataslot_" + _type.identifier(); |
2364 | 10.0k | return m_functionCollector.createFunction(functionName, [&]() { |
2365 | | // No special processing for calldata arrays, because they are stored as |
2366 | | // offset of the data area and length on the stack, so the offset already |
2367 | | // points to the data area. |
2368 | | // This might change, if calldata arrays are stored in a single |
2369 | | // stack slot at some point. |
2370 | 7.89k | return Whiskers(R"( |
2371 | 7.89k | function <functionName>(ptr) -> data { |
2372 | 7.89k | data := ptr |
2373 | 7.89k | <?dynamic> |
2374 | 7.89k | <?memory> |
2375 | 7.89k | data := add(ptr, 0x20) |
2376 | 7.89k | </memory> |
2377 | 7.89k | <?storage> |
2378 | 7.89k | mstore(0, ptr) |
2379 | 7.89k | data := keccak256(0, 0x20) |
2380 | 7.89k | </storage> |
2381 | 7.89k | </dynamic> |
2382 | 7.89k | } |
2383 | 7.89k | )") |
2384 | 7.89k | ("functionName", functionName) |
2385 | 7.89k | ("dynamic", _type.isDynamicallySized()) |
2386 | 7.89k | ("memory", _type.location() == DataLocation::Memory) |
2387 | 7.89k | ("storage", _type.location() == DataLocation::Storage) |
2388 | 7.89k | .render(); |
2389 | 7.89k | }); |
2390 | 10.0k | } |
2391 | | |
2392 | | std::string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type) |
2393 | 1.21k | { |
2394 | 1.21k | std::string functionName = "storage_array_index_access_" + _type.identifier(); |
2395 | 1.21k | return m_functionCollector.createFunction(functionName, [&]() { |
2396 | 689 | return Whiskers(R"( |
2397 | 689 | function <functionName>(array, index) -> slot, offset { |
2398 | 689 | let arrayLength := <arrayLen>(array) |
2399 | 689 | if iszero(lt(index, arrayLength)) { <panic>() } |
2400 | 689 | |
2401 | 689 | <?multipleItemsPerSlot> |
2402 | 689 | <?isBytesArray> |
2403 | 689 | switch lt(arrayLength, 0x20) |
2404 | 689 | case 0 { |
2405 | 689 | slot, offset := <indexAccessNoChecks>(array, index) |
2406 | 689 | } |
2407 | 689 | default { |
2408 | 689 | offset := sub(31, mod(index, 0x20)) |
2409 | 689 | slot := array |
2410 | 689 | } |
2411 | 689 | <!isBytesArray> |
2412 | 689 | let dataArea := <dataAreaFunc>(array) |
2413 | 689 | slot := add(dataArea, div(index, <itemsPerSlot>)) |
2414 | 689 | offset := mul(mod(index, <itemsPerSlot>), <storageBytes>) |
2415 | 689 | </isBytesArray> |
2416 | 689 | <!multipleItemsPerSlot> |
2417 | 689 | let dataArea := <dataAreaFunc>(array) |
2418 | 689 | slot := add(dataArea, mul(index, <storageSize>)) |
2419 | 689 | offset := 0 |
2420 | 689 | </multipleItemsPerSlot> |
2421 | 689 | } |
2422 | 689 | )") |
2423 | 689 | ("functionName", functionName) |
2424 | 689 | ("panic", panicFunction(PanicCode::ArrayOutOfBounds)) |
2425 | 689 | ("arrayLen", arrayLengthFunction(_type)) |
2426 | 689 | ("dataAreaFunc", arrayDataAreaFunction(_type)) |
2427 | 689 | ("indexAccessNoChecks", longByteArrayStorageIndexAccessNoCheckFunction()) |
2428 | 689 | ("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16) |
2429 | 689 | ("isBytesArray", _type.isByteArrayOrString()) |
2430 | 689 | ("storageSize", _type.baseType()->storageSize().str()) |
2431 | 689 | ("storageBytes", toString(_type.baseType()->storageBytes())) |
2432 | 689 | ("itemsPerSlot", std::to_string(32 / _type.baseType()->storageBytes())) |
2433 | 689 | .render(); |
2434 | 689 | }); |
2435 | 1.21k | } |
2436 | | |
2437 | | std::string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type) |
2438 | 12.3k | { |
2439 | 12.3k | std::string functionName = "memory_array_index_access_" + _type.identifier(); |
2440 | 12.3k | return m_functionCollector.createFunction(functionName, [&]() { |
2441 | 3.14k | return Whiskers(R"( |
2442 | 3.14k | function <functionName>(baseRef, index) -> addr { |
2443 | 3.14k | if iszero(lt(index, <arrayLen>(baseRef))) { |
2444 | 3.14k | <panic>() |
2445 | 3.14k | } |
2446 | 3.14k | |
2447 | 3.14k | let offset := mul(index, <stride>) |
2448 | 3.14k | <?dynamicallySized> |
2449 | 3.14k | offset := add(offset, 32) |
2450 | 3.14k | </dynamicallySized> |
2451 | 3.14k | addr := add(baseRef, offset) |
2452 | 3.14k | } |
2453 | 3.14k | )") |
2454 | 3.14k | ("functionName", functionName) |
2455 | 3.14k | ("panic", panicFunction(PanicCode::ArrayOutOfBounds)) |
2456 | 3.14k | ("arrayLen", arrayLengthFunction(_type)) |
2457 | 3.14k | ("stride", std::to_string(_type.memoryStride())) |
2458 | 3.14k | ("dynamicallySized", _type.isDynamicallySized()) |
2459 | 3.14k | .render(); |
2460 | 3.14k | }); |
2461 | 12.3k | } |
2462 | | |
2463 | | std::string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type) |
2464 | 5 | { |
2465 | 5 | solAssert(_type.dataStoredIn(DataLocation::CallData), ""); |
2466 | 5 | std::string functionName = "calldata_array_index_access_" + _type.identifier(); |
2467 | 5 | return m_functionCollector.createFunction(functionName, [&]() { |
2468 | 5 | return Whiskers(R"( |
2469 | 5 | function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> { |
2470 | 5 | if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { <panic>() } |
2471 | 5 | addr := add(base_ref, mul(index, <stride>)) |
2472 | 5 | <?dynamicallyEncodedBase> |
2473 | 5 | addr<?dynamicallySizedBase>, len</dynamicallySizedBase> := <accessCalldataTail>(base_ref, addr) |
2474 | 5 | </dynamicallyEncodedBase> |
2475 | 5 | } |
2476 | 5 | )") |
2477 | 5 | ("functionName", functionName) |
2478 | 5 | ("panic", panicFunction(PanicCode::ArrayOutOfBounds)) |
2479 | 5 | ("stride", std::to_string(_type.calldataStride())) |
2480 | 5 | ("dynamicallySized", _type.isDynamicallySized()) |
2481 | 5 | ("dynamicallyEncodedBase", _type.baseType()->isDynamicallyEncoded()) |
2482 | 5 | ("dynamicallySizedBase", _type.baseType()->isDynamicallySized()) |
2483 | 5 | ("arrayLen", toCompactHexWithPrefix(_type.length())) |
2484 | 5 | ("accessCalldataTail", _type.baseType()->isDynamicallyEncoded() ? accessCalldataTailFunction(*_type.baseType()): "") |
2485 | 5 | .render(); |
2486 | 5 | }); |
2487 | 5 | } |
2488 | | |
2489 | | std::string YulUtilFunctions::calldataArrayIndexRangeAccess(ArrayType const& _type) |
2490 | 370 | { |
2491 | 370 | solAssert(_type.dataStoredIn(DataLocation::CallData), ""); |
2492 | 370 | solAssert(_type.isDynamicallySized(), ""); |
2493 | 370 | std::string functionName = "calldata_array_index_range_access_" + _type.identifier(); |
2494 | 370 | return m_functionCollector.createFunction(functionName, [&]() { |
2495 | 96 | return Whiskers(R"( |
2496 | 96 | function <functionName>(offset, length, startIndex, endIndex) -> offsetOut, lengthOut { |
2497 | 96 | if gt(startIndex, endIndex) { <revertSliceStartAfterEnd>() } |
2498 | 96 | if gt(endIndex, length) { <revertSliceGreaterThanLength>() } |
2499 | 96 | offsetOut := add(offset, mul(startIndex, <stride>)) |
2500 | 96 | lengthOut := sub(endIndex, startIndex) |
2501 | 96 | } |
2502 | 96 | )") |
2503 | 96 | ("functionName", functionName) |
2504 | 96 | ("stride", std::to_string(_type.calldataStride())) |
2505 | 96 | ("revertSliceStartAfterEnd", revertReasonIfDebugFunction("Slice starts after end")) |
2506 | 96 | ("revertSliceGreaterThanLength", revertReasonIfDebugFunction("Slice is greater than length")) |
2507 | 96 | .render(); |
2508 | 96 | }); |
2509 | 370 | } |
2510 | | |
2511 | | std::string YulUtilFunctions::accessCalldataTailFunction(Type const& _type) |
2512 | 14.5k | { |
2513 | 14.5k | solAssert(_type.isDynamicallyEncoded(), ""); |
2514 | 14.5k | solAssert(_type.dataStoredIn(DataLocation::CallData), ""); |
2515 | 14.5k | std::string functionName = "access_calldata_tail_" + _type.identifier(); |
2516 | 14.5k | return m_functionCollector.createFunction(functionName, [&]() { |
2517 | 803 | return Whiskers(R"( |
2518 | 803 | function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> { |
2519 | 803 | let rel_offset_of_tail := calldataload(ptr_to_tail) |
2520 | 803 | if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <invalidCalldataTailOffset>() } |
2521 | 803 | addr := add(base_ref, rel_offset_of_tail) |
2522 | 803 | <?dynamicallySized> |
2523 | 803 | length := calldataload(addr) |
2524 | 803 | if gt(length, 0xffffffffffffffff) { <invalidCalldataTailLength>() } |
2525 | 803 | addr := add(addr, 32) |
2526 | 803 | if sgt(addr, sub(calldatasize(), mul(length, <calldataStride>))) { <shortCalldataTail>() } |
2527 | 803 | </dynamicallySized> |
2528 | 803 | } |
2529 | 803 | )") |
2530 | 803 | ("functionName", functionName) |
2531 | 803 | ("dynamicallySized", _type.isDynamicallySized()) |
2532 | 803 | ("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize())) |
2533 | 803 | ("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0)) |
2534 | 803 | ("invalidCalldataTailOffset", revertReasonIfDebugFunction("Invalid calldata tail offset")) |
2535 | 803 | ("invalidCalldataTailLength", revertReasonIfDebugFunction("Invalid calldata tail length")) |
2536 | 803 | ("shortCalldataTail", revertReasonIfDebugFunction("Calldata tail too short")) |
2537 | 803 | .render(); |
2538 | 803 | }); |
2539 | 14.5k | } |
2540 | | |
2541 | | std::string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) |
2542 | 5.05k | { |
2543 | 5.05k | solAssert(!_type.isByteArrayOrString(), ""); |
2544 | 5.05k | if (_type.dataStoredIn(DataLocation::Storage)) |
2545 | 5.05k | solAssert(_type.baseType()->storageBytes() > 16, ""); |
2546 | 5.05k | std::string functionName = "array_nextElement_" + _type.identifier(); |
2547 | 5.05k | return m_functionCollector.createFunction(functionName, [&]() { |
2548 | 5.04k | Whiskers templ(R"( |
2549 | 5.04k | function <functionName>(ptr) -> next { |
2550 | 5.04k | next := add(ptr, <advance>) |
2551 | 5.04k | } |
2552 | 5.04k | )"); |
2553 | 5.04k | templ("functionName", functionName); |
2554 | 5.04k | switch (_type.location()) |
2555 | 5.04k | { |
2556 | 3.95k | case DataLocation::Memory: |
2557 | 3.95k | templ("advance", "0x20"); |
2558 | 3.95k | break; |
2559 | 435 | case DataLocation::Storage: |
2560 | 435 | { |
2561 | 435 | u256 size = _type.baseType()->storageSize(); |
2562 | 435 | solAssert(size >= 1, ""); |
2563 | 435 | templ("advance", toCompactHexWithPrefix(size)); |
2564 | 435 | break; |
2565 | 435 | } |
2566 | 0 | case DataLocation::Transient: |
2567 | 0 | solUnimplemented("Transient data location is only supported for value types."); |
2568 | 0 | break; |
2569 | 654 | case DataLocation::CallData: |
2570 | 654 | { |
2571 | 654 | u256 size = _type.calldataStride(); |
2572 | 654 | solAssert(size >= 32 && size % 32 == 0, ""); |
2573 | 654 | templ("advance", toCompactHexWithPrefix(size)); |
2574 | 654 | break; |
2575 | 654 | } |
2576 | 5.04k | } |
2577 | 5.04k | return templ.render(); |
2578 | 5.04k | }); |
2579 | 5.05k | } |
2580 | | |
2581 | | std::string YulUtilFunctions::copyArrayFromStorageToMemoryFunction(ArrayType const& _from, ArrayType const& _to) |
2582 | 182 | { |
2583 | 182 | solAssert(_from.dataStoredIn(DataLocation::Storage), ""); |
2584 | 182 | solAssert(_to.dataStoredIn(DataLocation::Memory), ""); |
2585 | 182 | solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); |
2586 | 182 | if (!_from.isDynamicallySized()) |
2587 | 182 | solAssert(_from.length() == _to.length(), ""); |
2588 | | |
2589 | 182 | std::string functionName = "copy_array_from_storage_to_memory_" + _from.identifier(); |
2590 | | |
2591 | 182 | return m_functionCollector.createFunction(functionName, [&]() { |
2592 | 182 | if (_from.baseType()->isValueType()) |
2593 | 165 | { |
2594 | 165 | solAssert(*_from.baseType() == *_to.baseType(), ""); |
2595 | 165 | ABIFunctions abi(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector); |
2596 | 165 | return Whiskers(R"( |
2597 | 165 | function <functionName>(slot) -> memPtr { |
2598 | 165 | memPtr := <allocateUnbounded>() |
2599 | 165 | let end := <encode>(slot, memPtr) |
2600 | 165 | <finalizeAllocation>(memPtr, sub(end, memPtr)) |
2601 | 165 | } |
2602 | 165 | )") |
2603 | 165 | ("functionName", functionName) |
2604 | 165 | ("allocateUnbounded", allocateUnboundedFunction()) |
2605 | 165 | ( |
2606 | 165 | "encode", |
2607 | 165 | abi.abiEncodeAndReturnUpdatedPosFunction(_from, _to, ABIFunctions::EncodingOptions{}) |
2608 | 165 | ) |
2609 | 165 | ("finalizeAllocation", finalizeAllocationFunction()) |
2610 | 165 | .render(); |
2611 | 165 | } |
2612 | 17 | else |
2613 | 17 | { |
2614 | 17 | solAssert(_to.memoryStride() == 32, ""); |
2615 | 17 | solAssert(_to.baseType()->dataStoredIn(DataLocation::Memory), ""); |
2616 | 17 | solAssert(_from.baseType()->dataStoredIn(DataLocation::Storage), ""); |
2617 | 17 | solAssert(!_from.isByteArrayOrString(), ""); |
2618 | 17 | solAssert(*_to.withLocation(DataLocation::Storage, _from.isPointer()) == _from, ""); |
2619 | 17 | return Whiskers(R"( |
2620 | 17 | function <functionName>(slot) -> memPtr { |
2621 | 17 | let length := <lengthFunction>(slot) |
2622 | 17 | memPtr := <allocateArray>(length) |
2623 | 17 | let mpos := memPtr |
2624 | 17 | <?dynamic>mpos := add(mpos, 0x20)</dynamic> |
2625 | 17 | let spos := <arrayDataArea>(slot) |
2626 | 17 | for { let i := 0 } lt(i, length) { i := add(i, 1) } { |
2627 | 17 | mstore(mpos, <convert>(spos)) |
2628 | 17 | mpos := add(mpos, 0x20) |
2629 | 17 | spos := add(spos, <baseStorageSize>) |
2630 | 17 | } |
2631 | 17 | } |
2632 | 17 | )") |
2633 | 17 | ("functionName", functionName) |
2634 | 17 | ("lengthFunction", arrayLengthFunction(_from)) |
2635 | 17 | ("allocateArray", allocateMemoryArrayFunction(_to)) |
2636 | 17 | ("arrayDataArea", arrayDataAreaFunction(_from)) |
2637 | 17 | ("dynamic", _to.isDynamicallySized()) |
2638 | 17 | ("convert", conversionFunction(*_from.baseType(), *_to.baseType())) |
2639 | 17 | ("baseStorageSize", _from.baseType()->storageSize().str()) |
2640 | 17 | .render(); |
2641 | 17 | } |
2642 | 182 | }); |
2643 | 182 | } |
2644 | | |
2645 | | std::string YulUtilFunctions::bytesOrStringConcatFunction( |
2646 | | std::vector<Type const*> const& _argumentTypes, |
2647 | | FunctionType::Kind _functionTypeKind |
2648 | | ) |
2649 | 43 | { |
2650 | 43 | solAssert(_functionTypeKind == FunctionType::Kind::BytesConcat || _functionTypeKind == FunctionType::Kind::StringConcat); |
2651 | 43 | std::string functionName = (_functionTypeKind == FunctionType::Kind::StringConcat) ? "string_concat" : "bytes_concat"; |
2652 | 43 | size_t totalParams = 0; |
2653 | 43 | std::vector<Type const*> targetTypes; |
2654 | | |
2655 | 43 | for (Type const* argumentType: _argumentTypes) |
2656 | 86 | { |
2657 | 86 | if (_functionTypeKind == FunctionType::Kind::StringConcat) |
2658 | 86 | solAssert(argumentType->isImplicitlyConvertibleTo(*TypeProvider::stringMemory())); |
2659 | 86 | else if (_functionTypeKind == FunctionType::Kind::BytesConcat) |
2660 | 86 | solAssert( |
2661 | 86 | argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) || |
2662 | 86 | argumentType->isImplicitlyConvertibleTo(*TypeProvider::fixedBytes(32)) |
2663 | 86 | ); |
2664 | | |
2665 | 86 | if (argumentType->category() == Type::Category::FixedBytes) |
2666 | 8 | targetTypes.emplace_back(argumentType); |
2667 | 78 | else if ( |
2668 | 78 | auto const* literalType = dynamic_cast<StringLiteralType const*>(argumentType); |
2669 | 78 | literalType && !literalType->value().empty() && literalType->value().size() <= 32 |
2670 | 78 | ) |
2671 | 11 | targetTypes.emplace_back(TypeProvider::fixedBytes(static_cast<unsigned>(literalType->value().size()))); |
2672 | 67 | else |
2673 | 67 | { |
2674 | 67 | solAssert(!dynamic_cast<RationalNumberType const*>(argumentType)); |
2675 | 67 | targetTypes.emplace_back( |
2676 | 67 | _functionTypeKind == FunctionType::Kind::StringConcat ? |
2677 | 0 | TypeProvider::stringMemory() : |
2678 | 67 | TypeProvider::bytesMemory() |
2679 | 67 | ); |
2680 | 67 | } |
2681 | 86 | totalParams += argumentType->sizeOnStack(); |
2682 | 86 | functionName += "_" + argumentType->identifier(); |
2683 | 86 | } |
2684 | 43 | return m_functionCollector.createFunction(functionName, [&]() { |
2685 | 40 | Whiskers templ(R"( |
2686 | 40 | function <functionName>(<parameters>) -> outPtr { |
2687 | 40 | outPtr := <allocateUnbounded>() |
2688 | 40 | let dataStart := add(outPtr, 0x20) |
2689 | 40 | let dataEnd := <encodePacked>(dataStart<?+parameters>, <parameters></+parameters>) |
2690 | 40 | mstore(outPtr, sub(dataEnd, dataStart)) |
2691 | 40 | <finalizeAllocation>(outPtr, sub(dataEnd, outPtr)) |
2692 | 40 | } |
2693 | 40 | )"); |
2694 | 40 | templ("functionName", functionName); |
2695 | 40 | templ("parameters", suffixedVariableNameList("param_", 0, totalParams)); |
2696 | 40 | templ("allocateUnbounded", allocateUnboundedFunction()); |
2697 | 40 | templ("finalizeAllocation", finalizeAllocationFunction()); |
2698 | 40 | templ( |
2699 | 40 | "encodePacked", |
2700 | 40 | ABIFunctions{m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector}.tupleEncoderPacked( |
2701 | 40 | _argumentTypes, |
2702 | 40 | targetTypes |
2703 | 40 | ) |
2704 | 40 | ); |
2705 | 40 | return templ.render(); |
2706 | 40 | }); |
2707 | 43 | } |
2708 | | |
2709 | | std::string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType) |
2710 | 407 | { |
2711 | 407 | std::string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier(); |
2712 | 407 | return m_functionCollector.createFunction(functionName, [&]() { |
2713 | 235 | if (_mappingType.keyType()->isDynamicallySized()) |
2714 | 198 | return Whiskers(R"( |
2715 | 198 | function <functionName>(slot <?+key>,</+key> <key>) -> dataSlot { |
2716 | 198 | dataSlot := <hash>(<key> <?+key>,</+key> slot) |
2717 | 198 | } |
2718 | 198 | )") |
2719 | 198 | ("functionName", functionName) |
2720 | 198 | ("key", suffixedVariableNameList("key_", 0, _keyType.sizeOnStack())) |
2721 | 198 | ("hash", packedHashFunction( |
2722 | 198 | {&_keyType, TypeProvider::uint256()}, |
2723 | 198 | {_mappingType.keyType(), TypeProvider::uint256()} |
2724 | 198 | )) |
2725 | 198 | .render(); |
2726 | 37 | else |
2727 | 37 | { |
2728 | 37 | solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); |
2729 | 37 | solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), ""); |
2730 | 37 | solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, ""); |
2731 | 37 | Whiskers templ(R"( |
2732 | 37 | function <functionName>(slot <key>) -> dataSlot { |
2733 | 37 | mstore(0, <convertedKey>) |
2734 | 37 | mstore(0x20, slot) |
2735 | 37 | dataSlot := keccak256(0, 0x40) |
2736 | 37 | } |
2737 | 37 | )"); |
2738 | 37 | templ("functionName", functionName); |
2739 | 37 | templ("key", _keyType.sizeOnStack() == 1 ? ", key" : ""); |
2740 | 37 | if (_keyType.sizeOnStack() == 0) |
2741 | 0 | templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()"); |
2742 | 37 | else |
2743 | 37 | templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)"); |
2744 | 37 | return templ.render(); |
2745 | 37 | } |
2746 | 235 | }); |
2747 | 407 | } |
2748 | | |
2749 | | std::string YulUtilFunctions::readFromStorage( |
2750 | | Type const& _type, |
2751 | | size_t _offset, |
2752 | | bool _splitFunctionTypes, |
2753 | | VariableDeclaration::Location _location |
2754 | | ) |
2755 | 599 | { |
2756 | 599 | if (_type.isValueType()) |
2757 | 592 | return readFromStorageValueType(_type, _offset, _splitFunctionTypes, _location); |
2758 | 7 | else |
2759 | 7 | { |
2760 | 7 | solAssert(_location != VariableDeclaration::Location::Transient); |
2761 | 7 | solAssert(_offset == 0, ""); |
2762 | 7 | return readFromStorageReferenceType(_type); |
2763 | 7 | } |
2764 | 599 | } |
2765 | | |
2766 | | std::string YulUtilFunctions::readFromStorageDynamic( |
2767 | | Type const& _type, |
2768 | | bool _splitFunctionTypes, |
2769 | | VariableDeclaration::Location _location |
2770 | | ) |
2771 | 1.18k | { |
2772 | 1.18k | if (_type.isValueType()) |
2773 | 1.08k | return readFromStorageValueType(_type, {}, _splitFunctionTypes, _location); |
2774 | | |
2775 | 105 | solAssert(_location != VariableDeclaration::Location::Transient); |
2776 | 105 | std::string functionName = |
2777 | 105 | "read_from_storage__dynamic_" + |
2778 | 105 | std::string(_splitFunctionTypes ? "split_" : "") + |
2779 | 105 | _type.identifier(); |
2780 | | |
2781 | 105 | return m_functionCollector.createFunction(functionName, [&] { |
2782 | 91 | return Whiskers(R"( |
2783 | 91 | function <functionName>(slot, offset) -> value { |
2784 | 91 | if gt(offset, 0) { <panic>() } |
2785 | 91 | value := <readFromStorage>(slot) |
2786 | 91 | } |
2787 | 91 | )") |
2788 | 91 | ("functionName", functionName) |
2789 | 91 | ("panic", panicFunction(util::PanicCode::Generic)) |
2790 | 91 | ("readFromStorage", readFromStorageReferenceType(_type)) |
2791 | 91 | .render(); |
2792 | 91 | }); |
2793 | 105 | } |
2794 | | |
2795 | | std::string YulUtilFunctions::readFromStorageValueType( |
2796 | | Type const& _type, |
2797 | | std::optional<size_t> _offset, |
2798 | | bool _splitFunctionTypes, |
2799 | | VariableDeclaration::Location _location |
2800 | | ) |
2801 | 1.68k | { |
2802 | 1.68k | solAssert(_type.isValueType(), ""); |
2803 | 1.68k | solAssert( |
2804 | 1.68k | _location == VariableDeclaration::Location::Transient || |
2805 | 1.68k | _location == VariableDeclaration::Location::Unspecified, |
2806 | 1.68k | "Variable location can only be transient or plain storage" |
2807 | 1.68k | ); |
2808 | | |
2809 | 1.68k | std::string functionName = |
2810 | 1.68k | "read_from_" + |
2811 | 1.68k | (_location == VariableDeclaration::Location::Transient ? "transient_"s : "") + |
2812 | 1.68k | "storage_" + |
2813 | 1.68k | std::string(_splitFunctionTypes ? "split_" : "") + ( |
2814 | 1.68k | _offset.has_value() ? |
2815 | 604 | "offset_" + std::to_string(*_offset) : |
2816 | 1.68k | "dynamic" |
2817 | 1.68k | ) + |
2818 | 1.68k | "_" + |
2819 | 1.68k | _type.identifier(); |
2820 | | |
2821 | 1.68k | return m_functionCollector.createFunction(functionName, [&] { |
2822 | 1.24k | Whiskers templ(R"( |
2823 | 1.24k | function <functionName>(slot<?dynamic>, offset</dynamic>) -> <?split>addr, selector<!split>value</split> { |
2824 | 1.24k | <?split>let</split> value := <extract>(<loadOpcode>(slot)<?dynamic>, offset</dynamic>) |
2825 | 1.24k | <?split> |
2826 | 1.24k | addr, selector := <splitFunction>(value) |
2827 | 1.24k | </split> |
2828 | 1.24k | } |
2829 | 1.24k | )"); |
2830 | 1.24k | templ("functionName", functionName); |
2831 | 1.24k | templ("dynamic", !_offset.has_value()); |
2832 | 1.24k | templ("loadOpcode", _location == VariableDeclaration::Location::Transient ? "tload" : "sload"); |
2833 | 1.24k | if (_offset.has_value()) |
2834 | 441 | templ("extract", extractFromStorageValue(_type, *_offset)); |
2835 | 802 | else |
2836 | 802 | templ("extract", extractFromStorageValueDynamic(_type)); |
2837 | 1.24k | auto const* funType = dynamic_cast<FunctionType const*>(&_type); |
2838 | 1.24k | bool split = _splitFunctionTypes && funType && funType->kind() == FunctionType::Kind::External; |
2839 | 1.24k | templ("split", split); |
2840 | 1.24k | if (split) |
2841 | 0 | templ("splitFunction", splitExternalFunctionIdFunction()); |
2842 | 1.24k | return templ.render(); |
2843 | 1.24k | }); |
2844 | 1.68k | } |
2845 | | |
2846 | | std::string YulUtilFunctions::readFromStorageReferenceType(Type const& _type) |
2847 | 98 | { |
2848 | 98 | if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type)) |
2849 | 98 | { |
2850 | 98 | solAssert(arrayType->dataStoredIn(DataLocation::Memory), ""); |
2851 | 98 | return copyArrayFromStorageToMemoryFunction( |
2852 | 98 | dynamic_cast<ArrayType const&>(*arrayType->copyForLocation(DataLocation::Storage, false)), |
2853 | 98 | *arrayType |
2854 | 98 | ); |
2855 | 98 | } |
2856 | 0 | solAssert(_type.category() == Type::Category::Struct, ""); |
2857 | | |
2858 | 0 | std::string functionName = "read_from_storage_reference_type_" + _type.identifier(); |
2859 | |
|
2860 | 0 | auto const& structType = dynamic_cast<StructType const&>(_type); |
2861 | 0 | solAssert(structType.location() == DataLocation::Memory, ""); |
2862 | 0 | MemberList::MemberMap structMembers = structType.nativeMembers(nullptr); |
2863 | 0 | std::vector<std::map<std::string, std::string>> memberSetValues(structMembers.size()); |
2864 | 0 | for (size_t i = 0; i < structMembers.size(); ++i) |
2865 | 0 | { |
2866 | 0 | auto const& [memberSlotDiff, memberStorageOffset] = structType.storageOffsetsOfMember(structMembers[i].name); |
2867 | 0 | solAssert(structMembers[i].type->isValueType() || memberStorageOffset == 0, ""); |
2868 | | |
2869 | 0 | memberSetValues[i]["setMember"] = Whiskers(R"( |
2870 | 0 | { |
2871 | 0 | let <memberValues> := <readFromStorage>(add(slot, <memberSlotDiff>)) |
2872 | 0 | <writeToMemory>(add(value, <memberMemoryOffset>), <memberValues>) |
2873 | 0 | } |
2874 | 0 | )") |
2875 | 0 | ("memberValues", suffixedVariableNameList("memberValue_", 0, structMembers[i].type->stackItems().size())) |
2876 | 0 | ("memberMemoryOffset", structType.memoryOffsetOfMember(structMembers[i].name).str()) |
2877 | 0 | ("memberSlotDiff", memberSlotDiff.str()) |
2878 | 0 | ("readFromStorage", readFromStorage(*structMembers[i].type, memberStorageOffset, true, VariableDeclaration::Location::Unspecified)) |
2879 | 0 | ("writeToMemory", writeToMemoryFunction(*structMembers[i].type)) |
2880 | 0 | .render(); |
2881 | 0 | } |
2882 | | |
2883 | 0 | return m_functionCollector.createFunction(functionName, [&] { |
2884 | 0 | return Whiskers(R"( |
2885 | 0 | function <functionName>(slot) -> value { |
2886 | 0 | value := <allocStruct>() |
2887 | 0 | <#member> |
2888 | 0 | <setMember> |
2889 | 0 | </member> |
2890 | 0 | } |
2891 | 0 | )") |
2892 | 0 | ("functionName", functionName) |
2893 | 0 | ("allocStruct", allocateMemoryStructFunction(structType)) |
2894 | 0 | ("member", memberSetValues) |
2895 | 0 | .render(); |
2896 | 0 | }); |
2897 | 0 | } |
2898 | | |
2899 | | std::string YulUtilFunctions::readFromMemory(Type const& _type) |
2900 | 5.37k | { |
2901 | 5.37k | return readFromMemoryOrCalldata(_type, false); |
2902 | 5.37k | } |
2903 | | |
2904 | | std::string YulUtilFunctions::readFromCalldata(Type const& _type) |
2905 | 60 | { |
2906 | 60 | return readFromMemoryOrCalldata(_type, true); |
2907 | 60 | } |
2908 | | |
2909 | | std::string YulUtilFunctions::updateStorageValueFunction( |
2910 | | Type const& _fromType, |
2911 | | Type const& _toType, |
2912 | | VariableDeclaration::Location _location, |
2913 | | std::optional<unsigned> const& _offset |
2914 | | ) |
2915 | 3.54k | { |
2916 | 3.54k | solAssert( |
2917 | 3.54k | _location == VariableDeclaration::Location::Transient || |
2918 | 3.54k | _location == VariableDeclaration::Location::Unspecified, |
2919 | 3.54k | "Variable location can only be transient or plain storage" |
2920 | 3.54k | ); |
2921 | | |
2922 | 3.54k | std::string const functionName = |
2923 | 3.54k | "update_" + |
2924 | 3.54k | (_location == VariableDeclaration::Location::Transient ? "transient_"s : "") + |
2925 | 3.54k | "storage_value_" + |
2926 | 3.54k | (_offset.has_value() ? ("offset_" + std::to_string(*_offset)) + "_" : "") + |
2927 | 3.54k | _fromType.identifier() + |
2928 | 3.54k | "_to_" + |
2929 | 3.54k | _toType.identifier(); |
2930 | | |
2931 | 3.54k | return m_functionCollector.createFunction(functionName, [&] { |
2932 | 2.99k | if (_toType.isValueType()) |
2933 | 2.55k | { |
2934 | 2.55k | solAssert(_fromType.isImplicitlyConvertibleTo(_toType), ""); |
2935 | 2.55k | solAssert(_toType.storageBytes() <= 32, "Invalid storage bytes size."); |
2936 | 2.55k | solAssert(_toType.storageBytes() > 0, "Invalid storage bytes size."); |
2937 | | |
2938 | 2.55k | return Whiskers(R"( |
2939 | 2.55k | function <functionName>(slot, <offset><fromValues>) { |
2940 | 2.55k | let <toValues> := <convert>(<fromValues>) |
2941 | 2.55k | <storeOpcode>(slot, <update>(<loadOpcode>(slot), <offset><prepare>(<toValues>))) |
2942 | 2.55k | } |
2943 | 2.55k | |
2944 | 2.55k | )") |
2945 | 2.55k | ("functionName", functionName) |
2946 | 2.55k | ("update", |
2947 | 2.55k | _offset.has_value() ? |
2948 | 1.05k | updateByteSliceFunction(_toType.storageBytes(), *_offset) : |
2949 | 2.55k | updateByteSliceFunctionDynamic(_toType.storageBytes()) |
2950 | 2.55k | ) |
2951 | 2.55k | ("offset", _offset.has_value() ? "" : "offset, ") |
2952 | 2.55k | ("convert", conversionFunction(_fromType, _toType)) |
2953 | 2.55k | ("fromValues", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack())) |
2954 | 2.55k | ("toValues", suffixedVariableNameList("convertedValue_", 0, _toType.sizeOnStack())) |
2955 | 2.55k | ("storeOpcode", _location == VariableDeclaration::Location::Transient ? "tstore" : "sstore") |
2956 | 2.55k | ("loadOpcode", _location == VariableDeclaration::Location::Transient ? "tload" : "sload") |
2957 | 2.55k | ("prepare", prepareStoreFunction(_toType)) |
2958 | 2.55k | .render(); |
2959 | 2.55k | } |
2960 | | |
2961 | 446 | solAssert(_location != VariableDeclaration::Location::Transient); |
2962 | 446 | auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType); |
2963 | 446 | auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType); |
2964 | 446 | solAssert(toReferenceType, ""); |
2965 | | |
2966 | 446 | if (!fromReferenceType) |
2967 | 212 | { |
2968 | 212 | solAssert(_fromType.category() == Type::Category::StringLiteral, ""); |
2969 | 212 | solAssert(toReferenceType->category() == Type::Category::Array, ""); |
2970 | 212 | auto const& toArrayType = dynamic_cast<ArrayType const&>(*toReferenceType); |
2971 | 212 | solAssert(toArrayType.isByteArrayOrString(), ""); |
2972 | | |
2973 | 212 | return Whiskers(R"( |
2974 | 212 | function <functionName>(slot<?dynamicOffset>, offset</dynamicOffset>) { |
2975 | 212 | <?dynamicOffset>if offset { <panic>() }</dynamicOffset> |
2976 | 212 | <copyToStorage>(slot) |
2977 | 212 | } |
2978 | 212 | )") |
2979 | 212 | ("functionName", functionName) |
2980 | 212 | ("dynamicOffset", !_offset.has_value()) |
2981 | 212 | ("panic", panicFunction(PanicCode::Generic)) |
2982 | 212 | ("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value())) |
2983 | 212 | .render(); |
2984 | 212 | } |
2985 | | |
2986 | 234 | solAssert((*toReferenceType->copyForLocation( |
2987 | 234 | fromReferenceType->location(), |
2988 | 234 | fromReferenceType->isPointer() |
2989 | 234 | ).get()).equals(*fromReferenceType), ""); |
2990 | | |
2991 | 234 | if (fromReferenceType->category() == Type::Category::ArraySlice) |
2992 | 234 | solAssert(toReferenceType->category() == Type::Category::Array, ""); |
2993 | 234 | else |
2994 | 234 | solAssert(toReferenceType->category() == fromReferenceType->category(), ""); |
2995 | 234 | solAssert(_offset.value_or(0) == 0, ""); |
2996 | | |
2997 | 234 | Whiskers templ(R"( |
2998 | 234 | function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) { |
2999 | 234 | <?dynamicOffset>if offset { <panic>() }</dynamicOffset> |
3000 | 234 | <copyToStorage>(slot, <value>) |
3001 | 234 | } |
3002 | 234 | )"); |
3003 | 234 | templ("functionName", functionName); |
3004 | 234 | templ("dynamicOffset", !_offset.has_value()); |
3005 | 234 | templ("panic", panicFunction(PanicCode::Generic)); |
3006 | 234 | templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack())); |
3007 | 234 | if (_fromType.category() == Type::Category::Array) |
3008 | 199 | templ("copyToStorage", copyArrayToStorageFunction( |
3009 | 199 | dynamic_cast<ArrayType const&>(_fromType), |
3010 | 199 | dynamic_cast<ArrayType const&>(_toType) |
3011 | 199 | )); |
3012 | 35 | else if (_fromType.category() == Type::Category::ArraySlice) |
3013 | 0 | { |
3014 | 0 | solAssert( |
3015 | 0 | _fromType.dataStoredIn(DataLocation::CallData), |
3016 | 0 | "Currently only calldata array slices are supported!" |
3017 | 0 | ); |
3018 | 0 | templ("copyToStorage", copyArrayToStorageFunction( |
3019 | 0 | dynamic_cast<ArraySliceType const&>(_fromType).arrayType(), |
3020 | 0 | dynamic_cast<ArrayType const&>(_toType) |
3021 | 0 | )); |
3022 | 0 | } |
3023 | 35 | else |
3024 | 35 | templ("copyToStorage", copyStructToStorageFunction( |
3025 | 35 | dynamic_cast<StructType const&>(_fromType), |
3026 | 35 | dynamic_cast<StructType const&>(_toType) |
3027 | 35 | )); |
3028 | | |
3029 | 234 | return templ.render(); |
3030 | 234 | }); |
3031 | 3.54k | } |
3032 | | |
3033 | | std::string YulUtilFunctions::writeToMemoryFunction(Type const& _type) |
3034 | 1.92k | { |
3035 | 1.92k | std::string const functionName = "write_to_memory_" + _type.identifier(); |
3036 | | |
3037 | 1.92k | return m_functionCollector.createFunction(functionName, [&] { |
3038 | 1.43k | solAssert(!dynamic_cast<StringLiteralType const*>(&_type), ""); |
3039 | 1.43k | if (auto ref = dynamic_cast<ReferenceType const*>(&_type)) |
3040 | 2 | { |
3041 | 2 | solAssert( |
3042 | 2 | ref->location() == DataLocation::Memory, |
3043 | 2 | "Can only update types with location memory." |
3044 | 2 | ); |
3045 | | |
3046 | 2 | return Whiskers(R"( |
3047 | 2 | function <functionName>(memPtr, value) { |
3048 | 2 | mstore(memPtr, value) |
3049 | 2 | } |
3050 | 2 | )") |
3051 | 2 | ("functionName", functionName) |
3052 | 2 | .render(); |
3053 | 2 | } |
3054 | 1.43k | else if ( |
3055 | 1.43k | _type.category() == Type::Category::Function && |
3056 | 1.43k | dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External |
3057 | 1.43k | ) |
3058 | 0 | { |
3059 | 0 | return Whiskers(R"( |
3060 | 0 | function <functionName>(memPtr, addr, selector) { |
3061 | 0 | mstore(memPtr, <combine>(addr, selector)) |
3062 | 0 | } |
3063 | 0 | )") |
3064 | 0 | ("functionName", functionName) |
3065 | 0 | ("combine", combineExternalFunctionIdFunction()) |
3066 | 0 | .render(); |
3067 | 0 | } |
3068 | 1.43k | else if (_type.isValueType()) |
3069 | 1.43k | { |
3070 | 1.43k | return Whiskers(R"( |
3071 | 1.43k | function <functionName>(memPtr, value) { |
3072 | 1.43k | mstore(memPtr, <cleanup>(value)) |
3073 | 1.43k | } |
3074 | 1.43k | )") |
3075 | 1.43k | ("functionName", functionName) |
3076 | 1.43k | ("cleanup", cleanupFunction(_type)) |
3077 | 1.43k | .render(); |
3078 | 1.43k | } |
3079 | 0 | else // Should never happen |
3080 | 0 | { |
3081 | 0 | solAssert( |
3082 | 0 | false, |
3083 | 0 | "Memory store of type " + _type.toString(true) + " not allowed." |
3084 | 0 | ); |
3085 | 0 | } |
3086 | 1.43k | }); |
3087 | 1.92k | } |
3088 | | |
3089 | | std::string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type) |
3090 | 876 | { |
3091 | 876 | std::string functionName = |
3092 | 876 | "extract_from_storage_value_dynamic" + |
3093 | 876 | _type.identifier(); |
3094 | 876 | return m_functionCollector.createFunction(functionName, [&] { |
3095 | 858 | return Whiskers(R"( |
3096 | 858 | function <functionName>(slot_value, offset) -> value { |
3097 | 858 | value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value)) |
3098 | 858 | } |
3099 | 858 | )") |
3100 | 858 | ("functionName", functionName) |
3101 | 858 | ("shr", shiftRightFunctionDynamic()) |
3102 | 858 | ("cleanupStorage", cleanupFromStorageFunction(_type)) |
3103 | 858 | .render(); |
3104 | 858 | }); |
3105 | 876 | } |
3106 | | |
3107 | | std::string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset) |
3108 | 9.16k | { |
3109 | 9.16k | std::string functionName = "extract_from_storage_value_offset_" + std::to_string(_offset) + "_" + _type.identifier(); |
3110 | 9.16k | return m_functionCollector.createFunction(functionName, [&] { |
3111 | 4.29k | return Whiskers(R"( |
3112 | 4.29k | function <functionName>(slot_value) -> value { |
3113 | 4.29k | value := <cleanupStorage>(<shr>(slot_value)) |
3114 | 4.29k | } |
3115 | 4.29k | )") |
3116 | 4.29k | ("functionName", functionName) |
3117 | 4.29k | ("shr", shiftRightFunction(_offset * 8)) |
3118 | 4.29k | ("cleanupStorage", cleanupFromStorageFunction(_type)) |
3119 | 4.29k | .render(); |
3120 | 4.29k | }); |
3121 | 9.16k | } |
3122 | | |
3123 | | std::string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type) |
3124 | 5.15k | { |
3125 | 5.15k | solAssert(_type.isValueType(), ""); |
3126 | | |
3127 | 5.15k | std::string functionName = std::string("cleanup_from_storage_") + _type.identifier(); |
3128 | 5.15k | return m_functionCollector.createFunction(functionName, [&] { |
3129 | 2.14k | Whiskers templ(R"( |
3130 | 2.14k | function <functionName>(value) -> cleaned { |
3131 | 2.14k | cleaned := <cleaned> |
3132 | 2.14k | } |
3133 | 2.14k | )"); |
3134 | 2.14k | templ("functionName", functionName); |
3135 | | |
3136 | 2.14k | Type const* encodingType = &_type; |
3137 | 2.14k | if (_type.category() == Type::Category::UserDefinedValueType) |
3138 | 1 | encodingType = _type.encodingType(); |
3139 | 2.14k | unsigned storageBytes = encodingType->storageBytes(); |
3140 | 2.14k | if (IntegerType const* intType = dynamic_cast<IntegerType const*>(encodingType)) |
3141 | 1.46k | if (intType->isSigned() && storageBytes != 32) |
3142 | 269 | { |
3143 | 269 | templ("cleaned", "signextend(" + std::to_string(storageBytes - 1) + ", value)"); |
3144 | 269 | return templ.render(); |
3145 | 269 | } |
3146 | | |
3147 | 1.87k | if (storageBytes == 32) |
3148 | 696 | templ("cleaned", "value"); |
3149 | 1.17k | else if (encodingType->leftAligned()) |
3150 | 277 | templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)"); |
3151 | 900 | else |
3152 | 900 | templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")"); |
3153 | | |
3154 | 1.87k | return templ.render(); |
3155 | 2.14k | }); |
3156 | 5.15k | } |
3157 | | |
3158 | | std::string YulUtilFunctions::prepareStoreFunction(Type const& _type) |
3159 | 2.62k | { |
3160 | 2.62k | std::string functionName = "prepare_store_" + _type.identifier(); |
3161 | 2.62k | return m_functionCollector.createFunction(functionName, [&]() { |
3162 | 2.43k | solAssert(_type.isValueType(), ""); |
3163 | 2.43k | auto const* funType = dynamic_cast<FunctionType const*>(&_type); |
3164 | 2.43k | if (funType && funType->kind() == FunctionType::Kind::External) |
3165 | 0 | { |
3166 | 0 | Whiskers templ(R"( |
3167 | 0 | function <functionName>(addr, selector) -> ret { |
3168 | 0 | ret := <prepareBytes>(<combine>(addr, selector)) |
3169 | 0 | } |
3170 | 0 | )"); |
3171 | 0 | templ("functionName", functionName); |
3172 | 0 | templ("prepareBytes", prepareStoreFunction(*TypeProvider::fixedBytes(24))); |
3173 | 0 | templ("combine", combineExternalFunctionIdFunction()); |
3174 | 0 | return templ.render(); |
3175 | 0 | } |
3176 | 2.43k | else |
3177 | 2.43k | { |
3178 | 2.43k | solAssert(_type.sizeOnStack() == 1, ""); |
3179 | 2.43k | Whiskers templ(R"( |
3180 | 2.43k | function <functionName>(value) -> ret { |
3181 | 2.43k | ret := <actualPrepare> |
3182 | 2.43k | } |
3183 | 2.43k | )"); |
3184 | 2.43k | templ("functionName", functionName); |
3185 | 2.43k | if (_type.leftAligned()) |
3186 | 63 | templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)"); |
3187 | 2.37k | else |
3188 | 2.37k | templ("actualPrepare", "value"); |
3189 | 2.43k | return templ.render(); |
3190 | 2.43k | } |
3191 | 2.43k | }); |
3192 | 2.62k | } |
3193 | | |
3194 | | std::string YulUtilFunctions::allocationFunction() |
3195 | 13.2k | { |
3196 | 13.2k | std::string functionName = "allocate_memory"; |
3197 | 13.2k | return m_functionCollector.createFunction(functionName, [&]() { |
3198 | 2.94k | return Whiskers(R"( |
3199 | 2.94k | function <functionName>(size) -> memPtr { |
3200 | 2.94k | memPtr := <allocateUnbounded>() |
3201 | 2.94k | <finalizeAllocation>(memPtr, size) |
3202 | 2.94k | } |
3203 | 2.94k | )") |
3204 | 2.94k | ("functionName", functionName) |
3205 | 2.94k | ("allocateUnbounded", allocateUnboundedFunction()) |
3206 | 2.94k | ("finalizeAllocation", finalizeAllocationFunction()) |
3207 | 2.94k | .render(); |
3208 | 2.94k | }); |
3209 | 13.2k | } |
3210 | | |
3211 | | std::string YulUtilFunctions::allocateUnboundedFunction() |
3212 | 48.1k | { |
3213 | 48.1k | std::string functionName = "allocate_unbounded"; |
3214 | 48.1k | return m_functionCollector.createFunction(functionName, [&]() { |
3215 | 13.2k | return Whiskers(R"( |
3216 | 13.2k | function <functionName>() -> memPtr { |
3217 | 13.2k | memPtr := mload(<freeMemoryPointer>) |
3218 | 13.2k | } |
3219 | 13.2k | )") |
3220 | 13.2k | ("freeMemoryPointer", std::to_string(CompilerUtils::freeMemoryPointer)) |
3221 | 13.2k | ("functionName", functionName) |
3222 | 13.2k | .render(); |
3223 | 13.2k | }); |
3224 | 48.1k | } |
3225 | | |
3226 | | std::string YulUtilFunctions::finalizeAllocationFunction() |
3227 | 3.38k | { |
3228 | 3.38k | std::string functionName = "finalize_allocation"; |
3229 | 3.38k | return m_functionCollector.createFunction(functionName, [&]() { |
3230 | 3.23k | return Whiskers(R"( |
3231 | 3.23k | function <functionName>(memPtr, size) { |
3232 | 3.23k | let newFreePtr := add(memPtr, <roundUp>(size)) |
3233 | 3.23k | // protect against overflow |
3234 | 3.23k | if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { <panic>() } |
3235 | 3.23k | mstore(<freeMemoryPointer>, newFreePtr) |
3236 | 3.23k | } |
3237 | 3.23k | )") |
3238 | 3.23k | ("functionName", functionName) |
3239 | 3.23k | ("freeMemoryPointer", std::to_string(CompilerUtils::freeMemoryPointer)) |
3240 | 3.23k | ("roundUp", roundUpFunction()) |
3241 | 3.23k | ("panic", panicFunction(PanicCode::ResourceError)) |
3242 | 3.23k | .render(); |
3243 | 3.23k | }); |
3244 | 3.38k | } |
3245 | | |
3246 | | std::string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type) |
3247 | 857 | { |
3248 | 857 | if (_type.baseType()->hasSimpleZeroValueInMemory()) |
3249 | 296 | return zeroMemoryFunction(*_type.baseType()); |
3250 | 561 | return zeroComplexMemoryArrayFunction(_type); |
3251 | 857 | } |
3252 | | |
3253 | | std::string YulUtilFunctions::zeroMemoryFunction(Type const& _type) |
3254 | 296 | { |
3255 | 296 | solAssert(_type.hasSimpleZeroValueInMemory(), ""); |
3256 | | |
3257 | 296 | std::string functionName = "zero_memory_chunk_" + _type.identifier(); |
3258 | 296 | return m_functionCollector.createFunction(functionName, [&]() { |
3259 | 296 | return Whiskers(R"( |
3260 | 296 | function <functionName>(dataStart, dataSizeInBytes) { |
3261 | 296 | calldatacopy(dataStart, calldatasize(), dataSizeInBytes) |
3262 | 296 | } |
3263 | 296 | )") |
3264 | 296 | ("functionName", functionName) |
3265 | 296 | .render(); |
3266 | 296 | }); |
3267 | 296 | } |
3268 | | |
3269 | | std::string YulUtilFunctions::zeroComplexMemoryArrayFunction(ArrayType const& _type) |
3270 | 561 | { |
3271 | 561 | solAssert(!_type.baseType()->hasSimpleZeroValueInMemory(), ""); |
3272 | | |
3273 | 561 | std::string functionName = "zero_complex_memory_array_" + _type.identifier(); |
3274 | 561 | return m_functionCollector.createFunction(functionName, [&]() { |
3275 | 561 | solAssert(_type.memoryStride() == 32, ""); |
3276 | 561 | return Whiskers(R"( |
3277 | 561 | function <functionName>(dataStart, dataSizeInBytes) { |
3278 | 561 | for {let i := 0} lt(i, dataSizeInBytes) { i := add(i, <stride>) } { |
3279 | 561 | mstore(add(dataStart, i), <zeroValue>()) |
3280 | 561 | } |
3281 | 561 | } |
3282 | 561 | )") |
3283 | 561 | ("functionName", functionName) |
3284 | 561 | ("stride", std::to_string(_type.memoryStride())) |
3285 | 561 | ("zeroValue", zeroValueFunction(*_type.baseType(), false)) |
3286 | 561 | .render(); |
3287 | 561 | }); |
3288 | 561 | } |
3289 | | |
3290 | | std::string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type) |
3291 | 2.25k | { |
3292 | 2.25k | std::string functionName = "allocate_memory_array_" + _type.identifier(); |
3293 | 2.25k | return m_functionCollector.createFunction(functionName, [&]() { |
3294 | 2.05k | return Whiskers(R"( |
3295 | 2.05k | function <functionName>(length) -> memPtr { |
3296 | 2.05k | let allocSize := <allocSize>(length) |
3297 | 2.05k | memPtr := <alloc>(allocSize) |
3298 | 2.05k | <?dynamic> |
3299 | 2.05k | mstore(memPtr, length) |
3300 | 2.05k | </dynamic> |
3301 | 2.05k | } |
3302 | 2.05k | )") |
3303 | 2.05k | ("functionName", functionName) |
3304 | 2.05k | ("alloc", allocationFunction()) |
3305 | 2.05k | ("allocSize", arrayAllocationSizeFunction(_type)) |
3306 | 2.05k | ("dynamic", _type.isDynamicallySized()) |
3307 | 2.05k | .render(); |
3308 | 2.05k | }); |
3309 | 2.25k | } |
3310 | | |
3311 | | std::string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType const& _type) |
3312 | 1.08k | { |
3313 | 1.08k | std::string functionName = "allocate_and_zero_memory_array_" + _type.identifier(); |
3314 | 1.08k | return m_functionCollector.createFunction(functionName, [&]() { |
3315 | 857 | return Whiskers(R"( |
3316 | 857 | function <functionName>(length) -> memPtr { |
3317 | 857 | memPtr := <allocArray>(length) |
3318 | 857 | let dataStart := memPtr |
3319 | 857 | let dataSize := <allocSize>(length) |
3320 | 857 | <?dynamic> |
3321 | 857 | dataStart := add(dataStart, 32) |
3322 | 857 | dataSize := sub(dataSize, 32) |
3323 | 857 | </dynamic> |
3324 | 857 | <zeroArrayFunction>(dataStart, dataSize) |
3325 | 857 | } |
3326 | 857 | )") |
3327 | 857 | ("functionName", functionName) |
3328 | 857 | ("allocArray", allocateMemoryArrayFunction(_type)) |
3329 | 857 | ("allocSize", arrayAllocationSizeFunction(_type)) |
3330 | 857 | ("zeroArrayFunction", zeroMemoryArrayFunction(_type)) |
3331 | 857 | ("dynamic", _type.isDynamicallySized()) |
3332 | 857 | .render(); |
3333 | 857 | }); |
3334 | 1.08k | } |
3335 | | |
3336 | | std::string YulUtilFunctions::allocateMemoryStructFunction(StructType const& _type) |
3337 | 18 | { |
3338 | 18 | std::string functionName = "allocate_memory_struct_" + _type.identifier(); |
3339 | 18 | return m_functionCollector.createFunction(functionName, [&]() { |
3340 | 18 | Whiskers templ(R"( |
3341 | 18 | function <functionName>() -> memPtr { |
3342 | 18 | memPtr := <alloc>(<allocSize>) |
3343 | 18 | } |
3344 | 18 | )"); |
3345 | 18 | templ("functionName", functionName); |
3346 | 18 | templ("alloc", allocationFunction()); |
3347 | 18 | templ("allocSize", _type.memoryDataSize().str()); |
3348 | | |
3349 | 18 | return templ.render(); |
3350 | 18 | }); |
3351 | 18 | } |
3352 | | |
3353 | | std::string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type) |
3354 | 18 | { |
3355 | 18 | std::string functionName = "allocate_and_zero_memory_struct_" + _type.identifier(); |
3356 | 18 | return m_functionCollector.createFunction(functionName, [&]() { |
3357 | 18 | Whiskers templ(R"( |
3358 | 18 | function <functionName>() -> memPtr { |
3359 | 18 | memPtr := <allocStruct>() |
3360 | 18 | let offset := memPtr |
3361 | 18 | <#member> |
3362 | 18 | mstore(offset, <zeroValue>()) |
3363 | 18 | offset := add(offset, 32) |
3364 | 18 | </member> |
3365 | 18 | } |
3366 | 18 | )"); |
3367 | 18 | templ("functionName", functionName); |
3368 | 18 | templ("allocStruct", allocateMemoryStructFunction(_type)); |
3369 | | |
3370 | 18 | TypePointers const& members = _type.memoryMemberTypes(); |
3371 | | |
3372 | 18 | std::vector<std::map<std::string, std::string>> memberParams(members.size()); |
3373 | 60 | for (size_t i = 0; i < members.size(); ++i) |
3374 | 42 | { |
3375 | 42 | solAssert(members[i]->memoryHeadSize() == 32, ""); |
3376 | 42 | memberParams[i]["zeroValue"] = zeroValueFunction( |
3377 | 42 | *TypeProvider::withLocationIfReference(DataLocation::Memory, members[i]), |
3378 | 42 | false |
3379 | 42 | ); |
3380 | 42 | } |
3381 | 18 | templ("member", memberParams); |
3382 | 18 | return templ.render(); |
3383 | 18 | }); |
3384 | 18 | } |
3385 | | |
3386 | | std::string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) |
3387 | 27.4k | { |
3388 | 27.4k | if (_from.category() == Type::Category::UserDefinedValueType) |
3389 | 58 | { |
3390 | 58 | solAssert(_from == _to || _to == dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), ""); |
3391 | 58 | return conversionFunction(dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), _to); |
3392 | 58 | } |
3393 | 27.3k | if (_to.category() == Type::Category::UserDefinedValueType) |
3394 | 12 | { |
3395 | 12 | solAssert(_from == _to || _from.isImplicitlyConvertibleTo(dynamic_cast<UserDefinedValueType const&>(_to).underlyingType()), ""); |
3396 | 12 | return conversionFunction(_from, dynamic_cast<UserDefinedValueType const&>(_to).underlyingType()); |
3397 | 12 | } |
3398 | 27.3k | if (_from.category() == Type::Category::Function) |
3399 | 103 | { |
3400 | 103 | solAssert(_to.category() == Type::Category::Function, ""); |
3401 | 103 | FunctionType const& fromType = dynamic_cast<FunctionType const&>(_from); |
3402 | 103 | FunctionType const& targetType = dynamic_cast<FunctionType const&>(_to); |
3403 | 103 | solAssert( |
3404 | 103 | fromType.isImplicitlyConvertibleTo(targetType) && |
3405 | 103 | fromType.sizeOnStack() == targetType.sizeOnStack() && |
3406 | 103 | (fromType.kind() == FunctionType::Kind::Internal || fromType.kind() == FunctionType::Kind::External) && |
3407 | 103 | fromType.kind() == targetType.kind(), |
3408 | 103 | "Invalid function type conversion requested." |
3409 | 103 | ); |
3410 | 103 | std::string const functionName = |
3411 | 103 | "convert_" + |
3412 | 103 | _from.identifier() + |
3413 | 103 | "_to_" + |
3414 | 103 | _to.identifier(); |
3415 | 103 | return m_functionCollector.createFunction(functionName, [&]() { |
3416 | 103 | return Whiskers(R"( |
3417 | 103 | function <functionName>(<?external>addr, </external>functionId) -> <?external>outAddr, </external>outFunctionId { |
3418 | 103 | <?external>outAddr := addr</external> |
3419 | 103 | outFunctionId := functionId |
3420 | 103 | } |
3421 | 103 | )") |
3422 | 103 | ("functionName", functionName) |
3423 | 103 | ("external", fromType.kind() == FunctionType::Kind::External) |
3424 | 103 | .render(); |
3425 | 103 | }); |
3426 | 103 | } |
3427 | 27.2k | else if (_from.category() == Type::Category::ArraySlice) |
3428 | 3 | { |
3429 | 3 | auto const& fromType = dynamic_cast<ArraySliceType const&>(_from); |
3430 | 3 | if (_to.category() == Type::Category::FixedBytes) |
3431 | 0 | { |
3432 | 0 | solAssert(fromType.arrayType().isByteArray(), "Array types other than bytes not convertible to bytesNN."); |
3433 | 0 | return bytesToFixedBytesConversionFunction(fromType.arrayType(), dynamic_cast<FixedBytesType const &>(_to)); |
3434 | 0 | } |
3435 | 3 | solAssert(_to.category() == Type::Category::Array); |
3436 | 3 | auto const& targetType = dynamic_cast<ArrayType const&>(_to); |
3437 | | |
3438 | 3 | solAssert( |
3439 | 3 | fromType.arrayType().isImplicitlyConvertibleTo(targetType) || |
3440 | 3 | (fromType.arrayType().isByteArrayOrString() && targetType.isByteArrayOrString()) |
3441 | 3 | ); |
3442 | 3 | solAssert( |
3443 | 3 | fromType.arrayType().dataStoredIn(DataLocation::CallData) && |
3444 | 3 | fromType.arrayType().isDynamicallySized() && |
3445 | 3 | !fromType.arrayType().baseType()->isDynamicallyEncoded() |
3446 | 3 | ); |
3447 | | |
3448 | 3 | if (!targetType.dataStoredIn(DataLocation::CallData)) |
3449 | 3 | return arrayConversionFunction(fromType.arrayType(), targetType); |
3450 | | |
3451 | 0 | std::string const functionName = |
3452 | 0 | "convert_" + |
3453 | 0 | _from.identifier() + |
3454 | 0 | "_to_" + |
3455 | 0 | _to.identifier(); |
3456 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
3457 | 0 | return Whiskers(R"( |
3458 | 0 | function <functionName>(offset, length) -> outOffset, outLength { |
3459 | 0 | outOffset := offset |
3460 | 0 | outLength := length |
3461 | 0 | } |
3462 | 0 | )") |
3463 | 0 | ("functionName", functionName) |
3464 | 0 | .render(); |
3465 | 0 | }); |
3466 | 3 | } |
3467 | 27.2k | else if (_from.category() == Type::Category::Array) |
3468 | 490 | { |
3469 | 490 | auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from); |
3470 | 490 | if (_to.category() == Type::Category::FixedBytes) |
3471 | 0 | { |
3472 | 0 | solAssert(fromArrayType.isByteArray(), "Array types other than bytes not convertible to bytesNN."); |
3473 | 0 | return bytesToFixedBytesConversionFunction(fromArrayType, dynamic_cast<FixedBytesType const &>(_to)); |
3474 | 0 | } |
3475 | 490 | solAssert(_to.category() == Type::Category::Array, ""); |
3476 | 490 | return arrayConversionFunction(fromArrayType, dynamic_cast<ArrayType const&>(_to)); |
3477 | 490 | } |
3478 | | |
3479 | 26.7k | if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) |
3480 | 1.06k | return conversionFunctionSpecial(_from, _to); |
3481 | | |
3482 | 25.6k | std::string functionName = |
3483 | 25.6k | "convert_" + |
3484 | 25.6k | _from.identifier() + |
3485 | 25.6k | "_to_" + |
3486 | 25.6k | _to.identifier(); |
3487 | 25.6k | return m_functionCollector.createFunction(functionName, [&]() { |
3488 | 17.4k | Whiskers templ(R"( |
3489 | 17.4k | function <functionName>(value) -> converted { |
3490 | 17.4k | <body> |
3491 | 17.4k | } |
3492 | 17.4k | )"); |
3493 | 17.4k | templ("functionName", functionName); |
3494 | 17.4k | std::string body; |
3495 | 17.4k | auto toCategory = _to.category(); |
3496 | 17.4k | auto fromCategory = _from.category(); |
3497 | 17.4k | switch (fromCategory) |
3498 | 17.4k | { |
3499 | 51 | case Type::Category::Address: |
3500 | 369 | case Type::Category::Contract: |
3501 | 369 | body = |
3502 | 369 | Whiskers("converted := <convert>(value)") |
3503 | 369 | ("convert", conversionFunction(IntegerType(160), _to)) |
3504 | 369 | .render(); |
3505 | 369 | break; |
3506 | 3.47k | case Type::Category::Integer: |
3507 | 16.9k | case Type::Category::RationalNumber: |
3508 | 16.9k | { |
3509 | 16.9k | solAssert(_from.mobileType(), ""); |
3510 | 16.9k | if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from)) |
3511 | 13.4k | if (rational->isFractional()) |
3512 | 13.4k | solAssert(toCategory == Type::Category::FixedPoint, ""); |
3513 | | |
3514 | 16.9k | if (toCategory == Type::Category::Address || toCategory == Type::Category::Contract) |
3515 | 342 | body = |
3516 | 342 | Whiskers("converted := <convert>(value)") |
3517 | 342 | ("convert", conversionFunction(_from, IntegerType(160))) |
3518 | 342 | .render(); |
3519 | 16.6k | else |
3520 | 16.6k | { |
3521 | 16.6k | Whiskers bodyTemplate("converted := <cleanOutput>(<convert>(<cleanInput>(value)))"); |
3522 | 16.6k | bodyTemplate("cleanInput", cleanupFunction(_from)); |
3523 | 16.6k | bodyTemplate("cleanOutput", cleanupFunction(_to)); |
3524 | 16.6k | std::string convert; |
3525 | | |
3526 | 16.6k | solAssert(_to.category() != Type::Category::UserDefinedValueType, ""); |
3527 | 16.6k | if (auto const* toFixedBytes = dynamic_cast<FixedBytesType const*>(&_to)) |
3528 | 85 | convert = shiftLeftFunction(256 - toFixedBytes->numBytes() * 8); |
3529 | 16.5k | else if (dynamic_cast<FixedPointType const*>(&_to)) |
3530 | 16.5k | solUnimplemented(""); |
3531 | 16.5k | else if (dynamic_cast<IntegerType const*>(&_to)) |
3532 | 16.5k | { |
3533 | 16.5k | solUnimplementedAssert(fromCategory != Type::Category::FixedPoint); |
3534 | 16.5k | convert = identityFunction(); |
3535 | 16.5k | } |
3536 | 3 | else if (toCategory == Type::Category::Enum) |
3537 | 0 | { |
3538 | 0 | solAssert(fromCategory != Type::Category::FixedPoint, ""); |
3539 | 0 | convert = identityFunction(); |
3540 | 0 | } |
3541 | 3 | else |
3542 | 3 | solAssert(false, ""); |
3543 | 16.6k | solAssert(!convert.empty(), ""); |
3544 | 16.6k | bodyTemplate("convert", convert); |
3545 | 16.6k | body = bodyTemplate.render(); |
3546 | 16.6k | } |
3547 | 16.9k | break; |
3548 | 16.9k | } |
3549 | 16.9k | case Type::Category::Bool: |
3550 | 16 | { |
3551 | 16 | solAssert(_from == _to, "Invalid conversion for bool."); |
3552 | 16 | body = |
3553 | 16 | Whiskers("converted := <clean>(value)") |
3554 | 16 | ("clean", cleanupFunction(_from)) |
3555 | 16 | .render(); |
3556 | 16 | break; |
3557 | 16 | } |
3558 | 0 | case Type::Category::FixedPoint: |
3559 | 0 | solUnimplemented("Fixed point types not implemented."); |
3560 | 0 | break; |
3561 | 12 | case Type::Category::Struct: |
3562 | 12 | { |
3563 | 12 | solAssert(toCategory == Type::Category::Struct, ""); |
3564 | 12 | auto const& fromStructType = dynamic_cast<StructType const &>(_from); |
3565 | 12 | auto const& toStructType = dynamic_cast<StructType const &>(_to); |
3566 | 12 | solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), ""); |
3567 | | |
3568 | 12 | if (fromStructType.location() == toStructType.location() && toStructType.isPointer()) |
3569 | 0 | body = "converted := value"; |
3570 | 12 | else |
3571 | 12 | { |
3572 | 12 | solUnimplementedAssert(toStructType.location() == DataLocation::Memory); |
3573 | 12 | solUnimplementedAssert(fromStructType.location() != DataLocation::Memory); |
3574 | | |
3575 | 12 | if (fromStructType.location() == DataLocation::CallData) |
3576 | 12 | body = Whiskers(R"( |
3577 | 12 | converted := <abiDecode>(value, calldatasize()) |
3578 | 12 | )") |
3579 | 12 | ( |
3580 | 12 | "abiDecode", |
3581 | 12 | ABIFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector).abiDecodingFunctionStruct( |
3582 | 12 | toStructType, |
3583 | 12 | false |
3584 | 12 | ) |
3585 | 12 | ).render(); |
3586 | 0 | else |
3587 | 0 | { |
3588 | 0 | solAssert(fromStructType.location() == DataLocation::Storage, ""); |
3589 | | |
3590 | 0 | body = Whiskers(R"( |
3591 | 0 | converted := <readFromStorage>(value) |
3592 | 0 | )") |
3593 | 0 | ("readFromStorage", readFromStorage(toStructType, 0, true, VariableDeclaration::Location::Unspecified)) |
3594 | 0 | .render(); |
3595 | 0 | } |
3596 | 12 | } |
3597 | | |
3598 | 12 | break; |
3599 | 12 | } |
3600 | 80 | case Type::Category::FixedBytes: |
3601 | 80 | { |
3602 | 80 | FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from); |
3603 | 80 | if (toCategory == Type::Category::Integer) |
3604 | 15 | body = |
3605 | 15 | Whiskers("converted := <convert>(<shift>(value))") |
3606 | 15 | ("shift", shiftRightFunction(256 - from.numBytes() * 8)) |
3607 | 15 | ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) |
3608 | 15 | .render(); |
3609 | 65 | else if (toCategory == Type::Category::Address) |
3610 | 2 | body = |
3611 | 2 | Whiskers("converted := <convert>(value)") |
3612 | 2 | ("convert", conversionFunction(_from, IntegerType(160))) |
3613 | 2 | .render(); |
3614 | 63 | else |
3615 | 63 | { |
3616 | 63 | solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); |
3617 | 63 | FixedBytesType const& to = dynamic_cast<FixedBytesType const&>(_to); |
3618 | 63 | body = |
3619 | 63 | Whiskers("converted := <clean>(value)") |
3620 | 63 | ("clean", cleanupFunction((to.numBytes() <= from.numBytes()) ? to : from)) |
3621 | 63 | .render(); |
3622 | 63 | } |
3623 | 80 | break; |
3624 | 80 | } |
3625 | 80 | case Type::Category::Function: |
3626 | 0 | { |
3627 | 0 | solAssert(false, "Conversion should not be called for function types."); |
3628 | 0 | break; |
3629 | 0 | } |
3630 | 25 | case Type::Category::Enum: |
3631 | 25 | { |
3632 | 25 | solAssert(toCategory == Type::Category::Integer || _from == _to, ""); |
3633 | 25 | EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from); |
3634 | 25 | body = |
3635 | 25 | Whiskers("converted := <clean>(value)") |
3636 | 25 | ("clean", cleanupFunction(enumType)) |
3637 | 25 | .render(); |
3638 | 25 | break; |
3639 | 25 | } |
3640 | 0 | case Type::Category::Tuple: |
3641 | 0 | { |
3642 | 0 | solUnimplemented("Tuple conversion not implemented."); |
3643 | 0 | break; |
3644 | 0 | } |
3645 | 5 | case Type::Category::TypeType: |
3646 | 5 | { |
3647 | 5 | TypeType const& typeType = dynamic_cast<decltype(typeType)>(_from); |
3648 | 5 | if ( |
3649 | 5 | auto const* contractType = dynamic_cast<ContractType const*>(typeType.actualType()); |
3650 | 5 | contractType->contractDefinition().isLibrary() && |
3651 | 5 | _to == *TypeProvider::address() |
3652 | 5 | ) |
3653 | 5 | body = "converted := value"; |
3654 | 0 | else |
3655 | 5 | solAssert(false, "Invalid conversion from " + _from.canonicalName() + " to " + _to.canonicalName()); |
3656 | 5 | break; |
3657 | 5 | } |
3658 | 5 | case Type::Category::Mapping: |
3659 | 0 | { |
3660 | 0 | solAssert(_from == _to, ""); |
3661 | 0 | body = "converted := value"; |
3662 | 0 | break; |
3663 | 0 | } |
3664 | 0 | default: |
3665 | 0 | solAssert(false, "Invalid conversion from " + _from.canonicalName() + " to " + _to.canonicalName()); |
3666 | 17.4k | } |
3667 | | |
3668 | 17.4k | solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); |
3669 | 17.4k | templ("body", body); |
3670 | 17.4k | return templ.render(); |
3671 | 17.4k | }); |
3672 | 26.7k | } |
3673 | | |
3674 | | std::string YulUtilFunctions::bytesToFixedBytesConversionFunction(ArrayType const& _from, FixedBytesType const& _to) |
3675 | 19 | { |
3676 | 19 | solAssert(_from.isByteArray(), ""); |
3677 | 19 | solAssert(_from.isDynamicallySized(), ""); |
3678 | 19 | std::string functionName = "convert_bytes_to_fixedbytes_from_" + _from.identifier() + "_to_" + _to.identifier(); |
3679 | 19 | return m_functionCollector.createFunction(functionName, [&](auto& _args, auto& _returnParams) { |
3680 | 19 | _args = { "array" }; |
3681 | 19 | bool fromCalldata = _from.dataStoredIn(DataLocation::CallData); |
3682 | 19 | if (fromCalldata) |
3683 | 9 | _args.emplace_back("len"); |
3684 | 19 | _returnParams = {"value"}; |
3685 | 19 | Whiskers templ(R"( |
3686 | 19 | let length := <arrayLen>(array<?fromCalldata>, len</fromCalldata>) |
3687 | 19 | let dataArea := array |
3688 | 19 | <?fromMemory> |
3689 | 19 | dataArea := <dataArea>(array) |
3690 | 19 | </fromMemory> |
3691 | 19 | <?fromStorage> |
3692 | 19 | if gt(length, 31) { dataArea := <dataArea>(array) } |
3693 | 19 | </fromStorage> |
3694 | 19 | |
3695 | 19 | <?fromCalldata> |
3696 | 19 | value := <cleanup>(calldataload(dataArea)) |
3697 | 19 | <!fromCalldata> |
3698 | 19 | value := <extractValue>(dataArea) |
3699 | 19 | </fromCalldata> |
3700 | 19 | |
3701 | 19 | if lt(length, <fixedBytesLen>) { |
3702 | 19 | value := and( |
3703 | 19 | value, |
3704 | 19 | <shl>( |
3705 | 19 | mul(8, sub(<fixedBytesLen>, length)), |
3706 | 19 | <mask> |
3707 | 19 | ) |
3708 | 19 | ) |
3709 | 19 | } |
3710 | 19 | )"); |
3711 | 19 | templ("fromCalldata", fromCalldata); |
3712 | 19 | templ("arrayLen", arrayLengthFunction(_from)); |
3713 | 19 | templ("fixedBytesLen", std::to_string(_to.numBytes())); |
3714 | 19 | templ("fromMemory", _from.dataStoredIn(DataLocation::Memory)); |
3715 | 19 | templ("fromStorage", _from.dataStoredIn(DataLocation::Storage)); |
3716 | 19 | templ("dataArea", arrayDataAreaFunction(_from)); |
3717 | 19 | if (fromCalldata) |
3718 | 9 | templ("cleanup", cleanupFunction(_to)); |
3719 | 10 | else |
3720 | 10 | templ( |
3721 | 10 | "extractValue", |
3722 | 10 | _from.dataStoredIn(DataLocation::Storage) ? |
3723 | 6 | readFromStorage(_to, 32 - _to.numBytes(), false, VariableDeclaration::Location::Unspecified) : |
3724 | 10 | readFromMemory(_to) |
3725 | 10 | ); |
3726 | 19 | templ("shl", shiftLeftFunctionDynamic()); |
3727 | 19 | templ("mask", formatNumber(~((u256(1) << (256 - _to.numBytes() * 8)) - 1))); |
3728 | 19 | return templ.render(); |
3729 | 19 | }); |
3730 | 19 | } |
3731 | | |
3732 | | std::string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, StructType const& _to) |
3733 | 35 | { |
3734 | 35 | solAssert(_to.dataStoredIn(DataLocation::Storage), ""); |
3735 | 35 | solAssert(_from.structDefinition() == _to.structDefinition(), ""); |
3736 | | |
3737 | 35 | std::string functionName = |
3738 | 35 | "copy_struct_to_storage_from_" + |
3739 | 35 | _from.identifier() + |
3740 | 35 | "_to_" + |
3741 | 35 | _to.identifier(); |
3742 | | |
3743 | 35 | return m_functionCollector.createFunction(functionName, [&](auto& _arguments, auto&) { |
3744 | 35 | _arguments = {"slot", "value"}; |
3745 | 35 | Whiskers templ(R"( |
3746 | 35 | <?fromStorage> if iszero(eq(slot, value)) { </fromStorage> |
3747 | 35 | <#member> |
3748 | 35 | { |
3749 | 35 | <updateMemberCall> |
3750 | 35 | } |
3751 | 35 | </member> |
3752 | 35 | <?fromStorage> } </fromStorage> |
3753 | 35 | )"); |
3754 | 35 | templ("fromStorage", _from.dataStoredIn(DataLocation::Storage)); |
3755 | | |
3756 | 35 | MemberList::MemberMap structMembers = _from.nativeMembers(nullptr); |
3757 | 35 | MemberList::MemberMap toStructMembers = _to.nativeMembers(nullptr); |
3758 | | |
3759 | 35 | std::vector<std::map<std::string, std::string>> memberParams(structMembers.size()); |
3760 | 138 | for (size_t i = 0; i < structMembers.size(); ++i) |
3761 | 103 | { |
3762 | 103 | Type const& memberType = *structMembers[i].type; |
3763 | 103 | solAssert(memberType.memoryHeadSize() == 32, ""); |
3764 | 103 | auto const&[slotDiff, offset] = _to.storageOffsetsOfMember(structMembers[i].name); |
3765 | | |
3766 | 103 | Whiskers t(R"( |
3767 | 103 | let memberSlot := add(slot, <memberStorageSlotDiff>) |
3768 | 103 | let memberSrcPtr := add(value, <memberOffset>) |
3769 | 103 | |
3770 | 103 | <?fromCalldata> |
3771 | 103 | let <memberValues> := |
3772 | 103 | <?dynamicallyEncodedMember> |
3773 | 103 | <accessCalldataTail>(value, memberSrcPtr) |
3774 | 103 | <!dynamicallyEncodedMember> |
3775 | 103 | memberSrcPtr |
3776 | 103 | </dynamicallyEncodedMember> |
3777 | 103 | |
3778 | 103 | <?isValueType> |
3779 | 103 | <memberValues> := <read>(<memberValues>) |
3780 | 103 | </isValueType> |
3781 | 103 | </fromCalldata> |
3782 | 103 | |
3783 | 103 | <?fromMemory> |
3784 | 103 | let <memberValues> := <read>(memberSrcPtr) |
3785 | 103 | </fromMemory> |
3786 | 103 | |
3787 | 103 | <?fromStorage> |
3788 | 103 | let <memberValues> := |
3789 | 103 | <?isValueType> |
3790 | 103 | <read>(memberSrcPtr) |
3791 | 103 | <!isValueType> |
3792 | 103 | memberSrcPtr |
3793 | 103 | </isValueType> |
3794 | 103 | </fromStorage> |
3795 | 103 | |
3796 | 103 | <updateStorageValue>(memberSlot, <memberValues>) |
3797 | 103 | )"); |
3798 | 103 | bool fromCalldata = _from.location() == DataLocation::CallData; |
3799 | 103 | t("fromCalldata", fromCalldata); |
3800 | 103 | bool fromMemory = _from.location() == DataLocation::Memory; |
3801 | 103 | t("fromMemory", fromMemory); |
3802 | 103 | bool fromStorage = _from.location() == DataLocation::Storage; |
3803 | 103 | t("fromStorage", fromStorage); |
3804 | 103 | t("isValueType", memberType.isValueType()); |
3805 | 103 | t("memberValues", suffixedVariableNameList("memberValue_", 0, memberType.stackItems().size())); |
3806 | | |
3807 | 103 | t("memberStorageSlotDiff", slotDiff.str()); |
3808 | 103 | if (fromCalldata) |
3809 | 67 | { |
3810 | 67 | t("memberOffset", std::to_string(_from.calldataOffsetOfMember(structMembers[i].name))); |
3811 | 67 | t("dynamicallyEncodedMember", memberType.isDynamicallyEncoded()); |
3812 | 67 | if (memberType.isDynamicallyEncoded()) |
3813 | 7 | t("accessCalldataTail", accessCalldataTailFunction(memberType)); |
3814 | 67 | if (memberType.isValueType()) |
3815 | 55 | t("read", readFromCalldata(memberType)); |
3816 | 67 | } |
3817 | 36 | else if (fromMemory) |
3818 | 24 | { |
3819 | 24 | t("memberOffset", _from.memoryOffsetOfMember(structMembers[i].name).str()); |
3820 | 24 | t("read", readFromMemory(memberType)); |
3821 | 24 | } |
3822 | 12 | else if (fromStorage) |
3823 | 12 | { |
3824 | 12 | auto const& [srcSlotOffset, srcOffset] = _from.storageOffsetsOfMember(structMembers[i].name); |
3825 | 12 | t("memberOffset", formatNumber(srcSlotOffset)); |
3826 | 12 | if (memberType.isValueType()) |
3827 | 12 | t("read", readFromStorageValueType(memberType, srcOffset, true, VariableDeclaration::Location::Unspecified)); |
3828 | 0 | else |
3829 | 12 | solAssert(srcOffset == 0, ""); |
3830 | | |
3831 | 12 | } |
3832 | 103 | t("updateStorageValue", updateStorageValueFunction( |
3833 | 103 | memberType, |
3834 | 103 | *toStructMembers[i].type, |
3835 | 103 | VariableDeclaration::Location::Unspecified, |
3836 | 103 | std::optional<unsigned>{offset} |
3837 | 103 | )); |
3838 | 103 | memberParams[i]["updateMemberCall"] = t.render(); |
3839 | 103 | } |
3840 | 35 | templ("member", memberParams); |
3841 | | |
3842 | 35 | return templ.render(); |
3843 | 35 | }); |
3844 | 35 | } |
3845 | | |
3846 | | std::string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayType const& _to) |
3847 | 493 | { |
3848 | 493 | if (_to.dataStoredIn(DataLocation::CallData)) |
3849 | 493 | solAssert( |
3850 | 493 | _from.dataStoredIn(DataLocation::CallData) && _from.isByteArrayOrString() && _to.isByteArrayOrString(), |
3851 | 493 | "" |
3852 | 493 | ); |
3853 | | |
3854 | | // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. |
3855 | 493 | if (_to.location() == DataLocation::Storage) |
3856 | 493 | solAssert( |
3857 | 493 | (_to.isPointer() || (_from.isByteArrayOrString() && _to.isByteArrayOrString())) && |
3858 | 493 | _from.location() == DataLocation::Storage, |
3859 | 493 | "Invalid conversion to storage type." |
3860 | 493 | ); |
3861 | | |
3862 | 493 | std::string functionName = |
3863 | 493 | "convert_array_" + |
3864 | 493 | _from.identifier() + |
3865 | 493 | "_to_" + |
3866 | 493 | _to.identifier(); |
3867 | | |
3868 | 493 | return m_functionCollector.createFunction(functionName, [&]() { |
3869 | 269 | Whiskers templ(R"( |
3870 | 269 | function <functionName>(value<?fromCalldataDynamic>, length</fromCalldataDynamic>) -> converted <?toCalldataDynamic>, outLength</toCalldataDynamic> { |
3871 | 269 | <body> |
3872 | 269 | <?toCalldataDynamic> |
3873 | 269 | outLength := <length> |
3874 | 269 | </toCalldataDynamic> |
3875 | 269 | } |
3876 | 269 | )"); |
3877 | 269 | templ("functionName", functionName); |
3878 | 269 | templ("fromCalldataDynamic", _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized()); |
3879 | 269 | templ("toCalldataDynamic", _to.dataStoredIn(DataLocation::CallData) && _to.isDynamicallySized()); |
3880 | 269 | templ("length", _from.isDynamicallySized() ? "length" : _from.length().str()); |
3881 | | |
3882 | 269 | if ( |
3883 | 269 | _from == _to || |
3884 | 269 | (_from.dataStoredIn(DataLocation::Memory) && _to.dataStoredIn(DataLocation::Memory)) || |
3885 | 269 | (_from.dataStoredIn(DataLocation::CallData) && _to.dataStoredIn(DataLocation::CallData)) || |
3886 | 269 | _to.dataStoredIn(DataLocation::Storage) |
3887 | 269 | ) |
3888 | 153 | templ("body", "converted := value"); |
3889 | 116 | else if (_to.dataStoredIn(DataLocation::Memory)) |
3890 | 116 | templ( |
3891 | 116 | "body", |
3892 | 116 | Whiskers(R"( |
3893 | 116 | // Copy the array to a free position in memory |
3894 | 116 | converted := |
3895 | 116 | <?fromStorage> |
3896 | 116 | <arrayStorageToMem>(value) |
3897 | 116 | </fromStorage> |
3898 | 116 | <?fromCalldata> |
3899 | 116 | <abiDecode>(value, <length>, calldatasize()) |
3900 | 116 | </fromCalldata> |
3901 | 116 | )") |
3902 | 116 | ("fromStorage", _from.dataStoredIn(DataLocation::Storage)) |
3903 | 116 | ("fromCalldata", _from.dataStoredIn(DataLocation::CallData)) |
3904 | 116 | ("length", _from.isDynamicallySized() ? "length" : _from.length().str()) |
3905 | 116 | ( |
3906 | 116 | "abiDecode", |
3907 | 116 | _from.dataStoredIn(DataLocation::CallData) ? |
3908 | 32 | ABIFunctions( |
3909 | 32 | m_evmVersion, |
3910 | 32 | m_eofVersion, |
3911 | 32 | m_revertStrings, |
3912 | 32 | m_functionCollector |
3913 | 32 | ).abiDecodingFunctionArrayAvailableLength(_to, false) : |
3914 | 116 | "" |
3915 | 116 | ) |
3916 | 116 | ( |
3917 | 116 | "arrayStorageToMem", |
3918 | 116 | _from.dataStoredIn(DataLocation::Storage) ? copyArrayFromStorageToMemoryFunction(_from, _to) : "" |
3919 | 116 | ) |
3920 | 116 | .render() |
3921 | 116 | ); |
3922 | 0 | else |
3923 | 116 | solAssert(false, ""); |
3924 | | |
3925 | 269 | return templ.render(); |
3926 | 269 | }); |
3927 | 493 | } |
3928 | | |
3929 | | std::string YulUtilFunctions::cleanupFunction(Type const& _type) |
3930 | 68.8k | { |
3931 | 68.8k | if (auto userDefinedValueType = dynamic_cast<UserDefinedValueType const*>(&_type)) |
3932 | 40 | return cleanupFunction(userDefinedValueType->underlyingType()); |
3933 | | |
3934 | 68.7k | std::string functionName = std::string("cleanup_") + _type.identifier(); |
3935 | 68.7k | return m_functionCollector.createFunction(functionName, [&]() { |
3936 | 31.8k | Whiskers templ(R"( |
3937 | 31.8k | function <functionName>(value) -> cleaned { |
3938 | 31.8k | <body> |
3939 | 31.8k | } |
3940 | 31.8k | )"); |
3941 | 31.8k | templ("functionName", functionName); |
3942 | 31.8k | switch (_type.category()) |
3943 | 31.8k | { |
3944 | 852 | case Type::Category::Address: |
3945 | 852 | templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); |
3946 | 852 | break; |
3947 | 15.1k | case Type::Category::Integer: |
3948 | 15.1k | { |
3949 | 15.1k | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
3950 | 15.1k | if (type.numBits() == 256) |
3951 | 9.04k | templ("body", "cleaned := value"); |
3952 | 6.11k | else if (type.isSigned()) |
3953 | 1.39k | templ("body", "cleaned := signextend(" + std::to_string(type.numBits() / 8 - 1) + ", value)"); |
3954 | 4.72k | else |
3955 | 4.72k | templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); |
3956 | 15.1k | break; |
3957 | 0 | } |
3958 | 12.4k | case Type::Category::RationalNumber: |
3959 | 12.4k | templ("body", "cleaned := value"); |
3960 | 12.4k | break; |
3961 | 1.07k | case Type::Category::Bool: |
3962 | 1.07k | templ("body", "cleaned := iszero(iszero(value))"); |
3963 | 1.07k | break; |
3964 | 4 | case Type::Category::FixedPoint: |
3965 | 4 | solUnimplemented("Fixed point types not implemented."); |
3966 | 0 | break; |
3967 | 89 | case Type::Category::Function: |
3968 | 89 | switch (dynamic_cast<FunctionType const&>(_type).kind()) |
3969 | 89 | { |
3970 | 89 | case FunctionType::Kind::External: |
3971 | 89 | templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); |
3972 | 89 | break; |
3973 | 0 | case FunctionType::Kind::Internal: |
3974 | 0 | templ("body", "cleaned := value"); |
3975 | 0 | break; |
3976 | 0 | default: |
3977 | 0 | solAssert(false, ""); |
3978 | 0 | break; |
3979 | 89 | } |
3980 | 89 | break; |
3981 | 89 | case Type::Category::Array: |
3982 | 45 | case Type::Category::Struct: |
3983 | 71 | case Type::Category::Mapping: |
3984 | 71 | solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); |
3985 | 71 | templ("body", "cleaned := value"); |
3986 | 71 | break; |
3987 | 1.92k | case Type::Category::FixedBytes: |
3988 | 1.92k | { |
3989 | 1.92k | FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type); |
3990 | 1.92k | if (type.numBytes() == 32) |
3991 | 177 | templ("body", "cleaned := value"); |
3992 | 1.74k | else if (type.numBytes() == 0) |
3993 | | // This is disallowed in the type system. |
3994 | 1.74k | solAssert(false, ""); |
3995 | 1.74k | else |
3996 | 1.74k | { |
3997 | 1.74k | size_t numBits = type.numBytes() * 8; |
3998 | 1.74k | u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); |
3999 | 1.74k | templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); |
4000 | 1.74k | } |
4001 | 1.92k | break; |
4002 | 1.92k | } |
4003 | 1.92k | case Type::Category::Contract: |
4004 | 244 | { |
4005 | 244 | AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ? |
4006 | 2 | StateMutability::Payable : |
4007 | 244 | StateMutability::NonPayable |
4008 | 244 | ); |
4009 | 244 | templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); |
4010 | 244 | break; |
4011 | 1.92k | } |
4012 | 25 | case Type::Category::Enum: |
4013 | 25 | { |
4014 | | // Out of range enums cannot be truncated unambiguously and therefore it should be an error. |
4015 | 25 | templ("body", "cleaned := value " + validatorFunction(_type, false) + "(value)"); |
4016 | 25 | break; |
4017 | 1.92k | } |
4018 | 0 | case Type::Category::InaccessibleDynamic: |
4019 | 0 | templ("body", "cleaned := 0"); |
4020 | 0 | break; |
4021 | 0 | default: |
4022 | 0 | solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); |
4023 | 31.8k | } |
4024 | | |
4025 | 31.8k | return templ.render(); |
4026 | 31.8k | }); |
4027 | 68.8k | } |
4028 | | |
4029 | | std::string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) |
4030 | 9.03k | { |
4031 | 9.03k | std::string functionName = std::string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); |
4032 | 9.03k | return m_functionCollector.createFunction(functionName, [&]() { |
4033 | 8.67k | Whiskers templ(R"( |
4034 | 8.67k | function <functionName>(value) { |
4035 | 8.67k | if iszero(<condition>) { <failure> } |
4036 | 8.67k | } |
4037 | 8.67k | )"); |
4038 | 8.67k | templ("functionName", functionName); |
4039 | 8.67k | PanicCode panicCode = PanicCode::Generic; |
4040 | | |
4041 | 8.67k | switch (_type.category()) |
4042 | 8.67k | { |
4043 | 483 | case Type::Category::Address: |
4044 | 6.39k | case Type::Category::Integer: |
4045 | 6.39k | case Type::Category::RationalNumber: |
4046 | 7.14k | case Type::Category::Bool: |
4047 | 7.14k | case Type::Category::FixedPoint: |
4048 | 7.22k | case Type::Category::Function: |
4049 | 7.24k | case Type::Category::Array: |
4050 | 7.27k | case Type::Category::Struct: |
4051 | 7.29k | case Type::Category::Mapping: |
4052 | 8.31k | case Type::Category::FixedBytes: |
4053 | 8.56k | case Type::Category::Contract: |
4054 | 8.60k | case Type::Category::UserDefinedValueType: |
4055 | 8.60k | { |
4056 | 8.60k | templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); |
4057 | 8.60k | break; |
4058 | 8.56k | } |
4059 | 59 | case Type::Category::Enum: |
4060 | 59 | { |
4061 | 59 | size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers(); |
4062 | 59 | solAssert(members > 0, "empty enum should have caused a parser error."); |
4063 | 59 | panicCode = PanicCode::EnumConversionError; |
4064 | 59 | templ("condition", "lt(value, " + std::to_string(members) + ")"); |
4065 | 59 | break; |
4066 | 59 | } |
4067 | 10 | case Type::Category::InaccessibleDynamic: |
4068 | 10 | templ("condition", "1"); |
4069 | 10 | break; |
4070 | 0 | default: |
4071 | 0 | solAssert(false, "Validation of type " + _type.identifier() + " requested."); |
4072 | 8.67k | } |
4073 | | |
4074 | 8.67k | if (_revertOnFailure) |
4075 | 8.64k | templ("failure", "revert(0, 0)"); |
4076 | 25 | else |
4077 | 25 | templ("failure", panicFunction(panicCode) + "()"); |
4078 | | |
4079 | 8.67k | return templ.render(); |
4080 | 8.67k | }); |
4081 | 9.03k | } |
4082 | | |
4083 | | std::string YulUtilFunctions::packedHashFunction( |
4084 | | std::vector<Type const*> const& _givenTypes, |
4085 | | std::vector<Type const*> const& _targetTypes |
4086 | | ) |
4087 | 198 | { |
4088 | 198 | std::string functionName = std::string("packed_hashed_"); |
4089 | 198 | for (auto const& t: _givenTypes) |
4090 | 396 | functionName += t->identifier() + "_"; |
4091 | 198 | functionName += "_to_"; |
4092 | 198 | for (auto const& t: _targetTypes) |
4093 | 396 | functionName += t->identifier() + "_"; |
4094 | 198 | size_t sizeOnStack = 0; |
4095 | 198 | for (Type const* t: _givenTypes) |
4096 | 396 | sizeOnStack += t->sizeOnStack(); |
4097 | 198 | return m_functionCollector.createFunction(functionName, [&]() { |
4098 | 198 | Whiskers templ(R"( |
4099 | 198 | function <functionName>(<variables>) -> hash { |
4100 | 198 | let pos := <allocateUnbounded>() |
4101 | 198 | let end := <packedEncode>(pos <comma> <variables>) |
4102 | 198 | hash := keccak256(pos, sub(end, pos)) |
4103 | 198 | } |
4104 | 198 | )"); |
4105 | 198 | templ("functionName", functionName); |
4106 | 198 | templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); |
4107 | 198 | templ("comma", sizeOnStack > 0 ? "," : ""); |
4108 | 198 | templ("allocateUnbounded", allocateUnboundedFunction()); |
4109 | 198 | templ( |
4110 | 198 | "packedEncode", |
4111 | 198 | ABIFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes) |
4112 | 198 | ); |
4113 | 198 | return templ.render(); |
4114 | 198 | }); |
4115 | 198 | } |
4116 | | |
4117 | | std::string YulUtilFunctions::forwardingRevertFunction() |
4118 | 346 | { |
4119 | 346 | bool forward = m_evmVersion.supportsReturndata(); |
4120 | 346 | std::string functionName = "revert_forward_" + std::to_string(forward); |
4121 | 346 | return m_functionCollector.createFunction(functionName, [&]() { |
4122 | 215 | if (forward) |
4123 | 136 | return Whiskers(R"( |
4124 | 136 | function <functionName>() { |
4125 | 136 | let pos := <allocateUnbounded>() |
4126 | 136 | returndatacopy(pos, 0, returndatasize()) |
4127 | 136 | revert(pos, returndatasize()) |
4128 | 136 | } |
4129 | 136 | )") |
4130 | 136 | ("functionName", functionName) |
4131 | 136 | ("allocateUnbounded", allocateUnboundedFunction()) |
4132 | 136 | .render(); |
4133 | 79 | else |
4134 | 79 | return Whiskers(R"( |
4135 | 79 | function <functionName>() { |
4136 | 79 | revert(0, 0) |
4137 | 79 | } |
4138 | 79 | )") |
4139 | 79 | ("functionName", functionName) |
4140 | 79 | .render(); |
4141 | 215 | }); |
4142 | 346 | } |
4143 | | |
4144 | | std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type) |
4145 | 371 | { |
4146 | 371 | solAssert(_type.category() == Type::Category::Integer, ""); |
4147 | 371 | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
4148 | | |
4149 | 371 | std::string const functionName = "decrement_" + _type.identifier(); |
4150 | | |
4151 | 371 | return m_functionCollector.createFunction(functionName, [&]() { |
4152 | 239 | return Whiskers(R"( |
4153 | 239 | function <functionName>(value) -> ret { |
4154 | 239 | value := <cleanupFunction>(value) |
4155 | 239 | if eq(value, <minval>) { <panic>() } |
4156 | 239 | ret := sub(value, 1) |
4157 | 239 | } |
4158 | 239 | )") |
4159 | 239 | ("functionName", functionName) |
4160 | 239 | ("panic", panicFunction(PanicCode::UnderOverflow)) |
4161 | 239 | ("minval", toCompactHexWithPrefix(type.min())) |
4162 | 239 | ("cleanupFunction", cleanupFunction(_type)) |
4163 | 239 | .render(); |
4164 | 239 | }); |
4165 | 371 | } |
4166 | | |
4167 | | std::string YulUtilFunctions::decrementWrappingFunction(Type const& _type) |
4168 | 0 | { |
4169 | 0 | solAssert(_type.category() == Type::Category::Integer, ""); |
4170 | 0 | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
4171 | |
|
4172 | 0 | std::string const functionName = "decrement_wrapping_" + _type.identifier(); |
4173 | |
|
4174 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
4175 | 0 | return Whiskers(R"( |
4176 | 0 | function <functionName>(value) -> ret { |
4177 | 0 | ret := <cleanupFunction>(sub(value, 1)) |
4178 | 0 | } |
4179 | 0 | )") |
4180 | 0 | ("functionName", functionName) |
4181 | 0 | ("cleanupFunction", cleanupFunction(type)) |
4182 | 0 | .render(); |
4183 | 0 | }); |
4184 | 0 | } |
4185 | | |
4186 | | std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type) |
4187 | 864 | { |
4188 | 864 | solAssert(_type.category() == Type::Category::Integer, ""); |
4189 | 864 | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
4190 | | |
4191 | 864 | std::string const functionName = "increment_" + _type.identifier(); |
4192 | | |
4193 | 864 | return m_functionCollector.createFunction(functionName, [&]() { |
4194 | 486 | return Whiskers(R"( |
4195 | 486 | function <functionName>(value) -> ret { |
4196 | 486 | value := <cleanupFunction>(value) |
4197 | 486 | if eq(value, <maxval>) { <panic>() } |
4198 | 486 | ret := add(value, 1) |
4199 | 486 | } |
4200 | 486 | )") |
4201 | 486 | ("functionName", functionName) |
4202 | 486 | ("maxval", toCompactHexWithPrefix(type.max())) |
4203 | 486 | ("panic", panicFunction(PanicCode::UnderOverflow)) |
4204 | 486 | ("cleanupFunction", cleanupFunction(_type)) |
4205 | 486 | .render(); |
4206 | 486 | }); |
4207 | 864 | } |
4208 | | |
4209 | | std::string YulUtilFunctions::incrementWrappingFunction(Type const& _type) |
4210 | 82 | { |
4211 | 82 | solAssert(_type.category() == Type::Category::Integer, ""); |
4212 | 82 | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
4213 | | |
4214 | 82 | std::string const functionName = "increment_wrapping_" + _type.identifier(); |
4215 | | |
4216 | 82 | return m_functionCollector.createFunction(functionName, [&]() { |
4217 | 41 | return Whiskers(R"( |
4218 | 41 | function <functionName>(value) -> ret { |
4219 | 41 | ret := <cleanupFunction>(add(value, 1)) |
4220 | 41 | } |
4221 | 41 | )") |
4222 | 41 | ("functionName", functionName) |
4223 | 41 | ("cleanupFunction", cleanupFunction(type)) |
4224 | 41 | .render(); |
4225 | 41 | }); |
4226 | 82 | } |
4227 | | |
4228 | | std::string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type) |
4229 | 139 | { |
4230 | 139 | solAssert(_type.category() == Type::Category::Integer, ""); |
4231 | 139 | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
4232 | 139 | solAssert(type.isSigned(), "Expected signed type!"); |
4233 | | |
4234 | 139 | std::string const functionName = "negate_" + _type.identifier(); |
4235 | 139 | return m_functionCollector.createFunction(functionName, [&]() { |
4236 | 90 | return Whiskers(R"( |
4237 | 90 | function <functionName>(value) -> ret { |
4238 | 90 | value := <cleanupFunction>(value) |
4239 | 90 | if eq(value, <minval>) { <panic>() } |
4240 | 90 | ret := sub(0, value) |
4241 | 90 | } |
4242 | 90 | )") |
4243 | 90 | ("functionName", functionName) |
4244 | 90 | ("minval", toCompactHexWithPrefix(type.min())) |
4245 | 90 | ("cleanupFunction", cleanupFunction(_type)) |
4246 | 90 | ("panic", panicFunction(PanicCode::UnderOverflow)) |
4247 | 90 | .render(); |
4248 | 90 | }); |
4249 | 139 | } |
4250 | | |
4251 | | std::string YulUtilFunctions::negateNumberWrappingFunction(Type const& _type) |
4252 | 5 | { |
4253 | 5 | solAssert(_type.category() == Type::Category::Integer, ""); |
4254 | 5 | IntegerType const& type = dynamic_cast<IntegerType const&>(_type); |
4255 | 5 | solAssert(type.isSigned(), "Expected signed type!"); |
4256 | | |
4257 | 5 | std::string const functionName = "negate_wrapping_" + _type.identifier(); |
4258 | 5 | return m_functionCollector.createFunction(functionName, [&]() { |
4259 | 5 | return Whiskers(R"( |
4260 | 5 | function <functionName>(value) -> ret { |
4261 | 5 | ret := <cleanupFunction>(sub(0, value)) |
4262 | 5 | } |
4263 | 5 | )") |
4264 | 5 | ("functionName", functionName) |
4265 | 5 | ("cleanupFunction", cleanupFunction(type)) |
4266 | 5 | .render(); |
4267 | 5 | }); |
4268 | 5 | } |
4269 | | |
4270 | | std::string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes) |
4271 | 5.50k | { |
4272 | 5.50k | solAssert(_type.category() != Type::Category::Mapping, ""); |
4273 | | |
4274 | 5.50k | std::string const functionName = "zero_value_for_" + std::string(_splitFunctionTypes ? "split_" : "") + _type.identifier(); |
4275 | | |
4276 | 5.50k | return m_functionCollector.createFunction(functionName, [&]() { |
4277 | 4.91k | FunctionType const* fType = dynamic_cast<FunctionType const*>(&_type); |
4278 | 4.91k | if (fType && fType->kind() == FunctionType::Kind::External && _splitFunctionTypes) |
4279 | 5 | return Whiskers(R"( |
4280 | 5 | function <functionName>() -> retAddress, retFunction { |
4281 | 5 | retAddress := 0 |
4282 | 5 | retFunction := 0 |
4283 | 5 | } |
4284 | 5 | )") |
4285 | 5 | ("functionName", functionName) |
4286 | 5 | .render(); |
4287 | | |
4288 | 4.90k | if (_type.dataStoredIn(DataLocation::CallData)) |
4289 | 0 | { |
4290 | 0 | solAssert( |
4291 | 0 | _type.category() == Type::Category::Struct || |
4292 | 0 | _type.category() == Type::Category::Array, |
4293 | 0 | ""); |
4294 | 0 | Whiskers templ(R"( |
4295 | 0 | function <functionName>() -> offset<?hasLength>, length</hasLength> { |
4296 | 0 | offset := calldatasize() |
4297 | 0 | <?hasLength> length := 0 </hasLength> |
4298 | 0 | } |
4299 | 0 | )"); |
4300 | 0 | templ("functionName", functionName); |
4301 | 0 | templ("hasLength", |
4302 | 0 | _type.category() == Type::Category::Array && |
4303 | 0 | dynamic_cast<ArrayType const&>(_type).isDynamicallySized() |
4304 | 0 | ); |
4305 | |
|
4306 | 0 | return templ.render(); |
4307 | 0 | } |
4308 | | |
4309 | 4.90k | Whiskers templ(R"( |
4310 | 4.90k | function <functionName>() -> ret { |
4311 | 4.90k | ret := <zeroValue> |
4312 | 4.90k | } |
4313 | 4.90k | )"); |
4314 | 4.90k | templ("functionName", functionName); |
4315 | | |
4316 | 4.90k | if (_type.isValueType()) |
4317 | 2.52k | { |
4318 | 2.52k | solAssert(( |
4319 | 2.52k | _type.hasSimpleZeroValueInMemory() || |
4320 | 2.52k | (fType && (fType->kind() == FunctionType::Kind::Internal || fType->kind() == FunctionType::Kind::External)) |
4321 | 2.52k | ), ""); |
4322 | 2.52k | templ("zeroValue", "0"); |
4323 | 2.52k | } |
4324 | 2.38k | else |
4325 | 2.38k | { |
4326 | 2.38k | solAssert(_type.dataStoredIn(DataLocation::Memory), ""); |
4327 | 2.38k | if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type)) |
4328 | 2.36k | { |
4329 | 2.36k | if (_type.isDynamicallySized()) |
4330 | 2.28k | templ("zeroValue", std::to_string(CompilerUtils::zeroPointer)); |
4331 | 80 | else |
4332 | 80 | templ("zeroValue", allocateAndInitializeMemoryArrayFunction(*arrayType) + "(" + std::to_string(unsigned(arrayType->length())) + ")"); |
4333 | | |
4334 | 2.36k | } |
4335 | 18 | else if (auto const* structType = dynamic_cast<StructType const*>(&_type)) |
4336 | 18 | templ("zeroValue", allocateAndInitializeMemoryStructFunction(*structType) + "()"); |
4337 | 0 | else |
4338 | 18 | solUnimplemented(""); |
4339 | 2.38k | } |
4340 | | |
4341 | 4.90k | return templ.render(); |
4342 | 4.90k | }); |
4343 | 5.50k | } |
4344 | | |
4345 | | std::string YulUtilFunctions::storageSetToZeroFunction(Type const& _type, VariableDeclaration::Location _location) |
4346 | 1.47k | { |
4347 | 1.47k | std::string const functionName = "storage_set_to_zero_" + _type.identifier(); |
4348 | | |
4349 | 1.47k | return m_functionCollector.createFunction(functionName, [&]() { |
4350 | 1.44k | if (_type.isValueType()) |
4351 | 1.37k | return Whiskers(R"( |
4352 | 1.37k | function <functionName>(slot, offset) { |
4353 | 1.37k | let <values> := <zeroValue>() |
4354 | 1.37k | <store>(slot, offset, <values>) |
4355 | 1.37k | } |
4356 | 1.37k | )") |
4357 | 1.37k | ("functionName", functionName) |
4358 | 1.37k | ("store", updateStorageValueFunction(_type, _type, _location)) |
4359 | 1.37k | ("values", suffixedVariableNameList("zero_", 0, _type.sizeOnStack())) |
4360 | 1.37k | ("zeroValue", zeroValueFunction(_type)) |
4361 | 1.37k | .render(); |
4362 | 67 | else if (_type.category() == Type::Category::Array) |
4363 | 36 | return Whiskers(R"( |
4364 | 36 | function <functionName>(slot, offset) { |
4365 | 36 | if iszero(eq(offset, 0)) { <panic>() } |
4366 | 36 | <clearArray>(slot) |
4367 | 36 | } |
4368 | 36 | )") |
4369 | 36 | ("functionName", functionName) |
4370 | 36 | ("clearArray", clearStorageArrayFunction(dynamic_cast<ArrayType const&>(_type))) |
4371 | 36 | ("panic", panicFunction(PanicCode::Generic)) |
4372 | 36 | .render(); |
4373 | 31 | else if (_type.category() == Type::Category::Struct) |
4374 | 31 | return Whiskers(R"( |
4375 | 31 | function <functionName>(slot, offset) { |
4376 | 31 | if iszero(eq(offset, 0)) { <panic>() } |
4377 | 31 | <clearStruct>(slot) |
4378 | 31 | } |
4379 | 31 | )") |
4380 | 31 | ("functionName", functionName) |
4381 | 31 | ("clearStruct", clearStorageStructFunction(dynamic_cast<StructType const&>(_type))) |
4382 | 31 | ("panic", panicFunction(PanicCode::Generic)) |
4383 | 31 | .render(); |
4384 | 0 | else |
4385 | 31 | solUnimplemented("setToZero for type " + _type.identifier() + " not yet implemented!"); |
4386 | 1.44k | }); |
4387 | 1.47k | } |
4388 | | |
4389 | | std::string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to) |
4390 | 1.06k | { |
4391 | 1.06k | std::string functionName = |
4392 | 1.06k | "convert_" + |
4393 | 1.06k | _from.identifier() + |
4394 | 1.06k | "_to_" + |
4395 | 1.06k | _to.identifier(); |
4396 | 1.06k | return m_functionCollector.createFunction(functionName, [&]() { |
4397 | 983 | if ( |
4398 | 983 | auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to); |
4399 | 983 | fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size() |
4400 | 983 | ) |
4401 | 340 | { |
4402 | 340 | size_t sourceStackSize = 0; |
4403 | 340 | size_t destStackSize = 0; |
4404 | 340 | std::string conversions; |
4405 | 1.02k | for (size_t i = 0; i < fromTuple->components().size(); ++i) |
4406 | 680 | { |
4407 | 680 | auto fromComponent = fromTuple->components()[i]; |
4408 | 680 | auto toComponent = toTuple->components()[i]; |
4409 | 680 | solAssert(fromComponent, ""); |
4410 | 680 | if (toComponent) |
4411 | 680 | { |
4412 | 680 | conversions += |
4413 | 680 | suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) + |
4414 | 680 | (toComponent->sizeOnStack() > 0 ? " := " : "") + |
4415 | 680 | conversionFunction(*fromComponent, *toComponent) + |
4416 | 680 | "(" + |
4417 | 680 | suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) + |
4418 | 680 | ")\n"; |
4419 | 680 | destStackSize += toComponent->sizeOnStack(); |
4420 | 680 | } |
4421 | 680 | sourceStackSize += fromComponent->sizeOnStack(); |
4422 | 680 | } |
4423 | 340 | return Whiskers(R"( |
4424 | 340 | function <functionName>(<values>) <arrow> <converted> { |
4425 | 340 | <conversions> |
4426 | 340 | } |
4427 | 340 | )") |
4428 | 340 | ("functionName", functionName) |
4429 | 340 | ("values", suffixedVariableNameList("value", 0, sourceStackSize)) |
4430 | 340 | ("arrow", destStackSize > 0 ? "->" : "") |
4431 | 340 | ("converted", suffixedVariableNameList("converted", 0, destStackSize)) |
4432 | 340 | ("conversions", conversions) |
4433 | 340 | .render(); |
4434 | 340 | } |
4435 | | |
4436 | 643 | solUnimplementedAssert( |
4437 | 643 | _from.category() == Type::Category::StringLiteral, |
4438 | 643 | "Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented." |
4439 | 643 | ); |
4440 | 643 | std::string const& data = dynamic_cast<StringLiteralType const&>(_from).value(); |
4441 | 643 | if (_to.category() == Type::Category::FixedBytes) |
4442 | 24 | { |
4443 | 24 | unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_to).numBytes(); |
4444 | 24 | solAssert(data.size() <= 32, ""); |
4445 | 24 | Whiskers templ(R"( |
4446 | 24 | function <functionName>() -> converted { |
4447 | 24 | converted := <data> |
4448 | 24 | } |
4449 | 24 | )"); |
4450 | 24 | templ("functionName", functionName); |
4451 | 24 | templ("data", formatNumber( |
4452 | 24 | h256::Arith(h256(data, h256::AlignLeft)) & |
4453 | 24 | (~(u256(-1) >> (8 * numBytes))) |
4454 | 24 | )); |
4455 | 24 | return templ.render(); |
4456 | 24 | } |
4457 | 619 | else if (_to.category() == Type::Category::Array) |
4458 | 619 | { |
4459 | 619 | solAssert(dynamic_cast<ArrayType const&>(_to).isByteArrayOrString(), ""); |
4460 | 619 | Whiskers templ(R"( |
4461 | 619 | function <functionName>() -> converted { |
4462 | 619 | converted := <copyLiteralToMemory>() |
4463 | 619 | } |
4464 | 619 | )"); |
4465 | 619 | templ("functionName", functionName); |
4466 | 619 | templ("copyLiteralToMemory", copyLiteralToMemoryFunction(data)); |
4467 | 619 | return templ.render(); |
4468 | 619 | } |
4469 | 0 | else |
4470 | 619 | solAssert( |
4471 | 643 | false, |
4472 | 643 | "Invalid conversion from std::string literal to " + _to.toString() + " requested." |
4473 | 643 | ); |
4474 | 643 | }); |
4475 | 1.06k | } |
4476 | | |
4477 | | std::string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata) |
4478 | 5.56k | { |
4479 | 5.56k | std::string functionName = |
4480 | 5.56k | std::string("read_from_") + |
4481 | 5.56k | (_fromCalldata ? "calldata" : "memory") + |
4482 | 5.56k | _type.identifier(); |
4483 | | |
4484 | | // TODO use ABI functions for handling calldata |
4485 | 5.56k | if (_fromCalldata) |
4486 | 5.56k | solAssert(!_type.isDynamicallyEncoded(), ""); |
4487 | | |
4488 | 5.56k | return m_functionCollector.createFunction(functionName, [&] { |
4489 | 1.81k | if (auto refType = dynamic_cast<ReferenceType const*>(&_type)) |
4490 | 8 | { |
4491 | 8 | solAssert(refType->sizeOnStack() == 1, ""); |
4492 | 8 | solAssert(!_fromCalldata, ""); |
4493 | | |
4494 | 8 | return Whiskers(R"( |
4495 | 8 | function <functionName>(memPtr) -> value { |
4496 | 8 | value := mload(memPtr) |
4497 | 8 | } |
4498 | 8 | )") |
4499 | 8 | ("functionName", functionName) |
4500 | 8 | .render(); |
4501 | 8 | } |
4502 | | |
4503 | 1.80k | solAssert(_type.isValueType(), ""); |
4504 | 1.80k | Whiskers templ(R"( |
4505 | 1.80k | function <functionName>(ptr) -> <returnVariables> { |
4506 | 1.80k | <?fromCalldata> |
4507 | 1.80k | let value := calldataload(ptr) |
4508 | 1.80k | <validate>(value) |
4509 | 1.80k | <!fromCalldata> |
4510 | 1.80k | let value := <cleanup>(mload(ptr)) |
4511 | 1.80k | </fromCalldata> |
4512 | 1.80k | |
4513 | 1.80k | <returnVariables> := |
4514 | 1.80k | <?externalFunction> |
4515 | 1.80k | <splitFunction>(value) |
4516 | 1.80k | <!externalFunction> |
4517 | 1.80k | value |
4518 | 1.80k | </externalFunction> |
4519 | 1.80k | } |
4520 | 1.80k | )"); |
4521 | 1.80k | templ("functionName", functionName); |
4522 | 1.80k | templ("fromCalldata", _fromCalldata); |
4523 | 1.80k | if (_fromCalldata) |
4524 | 68 | templ("validate", validatorFunction(_type, true)); |
4525 | 1.80k | auto const* funType = dynamic_cast<FunctionType const*>(&_type); |
4526 | 1.80k | if (funType && funType->kind() == FunctionType::Kind::External) |
4527 | 0 | { |
4528 | 0 | templ("externalFunction", true); |
4529 | 0 | templ("splitFunction", splitExternalFunctionIdFunction()); |
4530 | 0 | templ("returnVariables", "addr, selector"); |
4531 | 0 | } |
4532 | 1.80k | else |
4533 | 1.80k | { |
4534 | 1.80k | templ("externalFunction", false); |
4535 | 1.80k | templ("returnVariables", "returnValue"); |
4536 | 1.80k | } |
4537 | | |
4538 | | // Byte array elements generally need cleanup. |
4539 | | // Other types are cleaned as well to account for dirty memory e.g. due to inline assembly. |
4540 | 1.80k | templ("cleanup", cleanupFunction(_type)); |
4541 | 1.80k | return templ.render(); |
4542 | 1.80k | }); |
4543 | 5.56k | } |
4544 | | |
4545 | | std::string YulUtilFunctions::revertReasonIfDebugFunction(std::string const& _message) |
4546 | 78.4k | { |
4547 | 78.4k | std::string functionName = "revert_error_" + util::toHex(util::keccak256(_message).asBytes()); |
4548 | 78.4k | return m_functionCollector.createFunction(functionName, [&](auto&, auto&) -> std::string { |
4549 | 33.0k | return revertReasonIfDebugBody(m_revertStrings, allocateUnboundedFunction() + "()", _message); |
4550 | 33.0k | }); |
4551 | 78.4k | } |
4552 | | |
4553 | | std::string YulUtilFunctions::revertReasonIfDebugBody( |
4554 | | RevertStrings _revertStrings, |
4555 | | std::string const& _allocation, |
4556 | | std::string const& _message |
4557 | | ) |
4558 | 67.8k | { |
4559 | 67.8k | if (_revertStrings < RevertStrings::Debug || _message.empty()) |
4560 | 67.8k | return "revert(0, 0)"; |
4561 | | |
4562 | 0 | Whiskers templ(R"( |
4563 | 0 | let start := <allocate> |
4564 | 0 | let pos := start |
4565 | 0 | mstore(pos, <sig>) |
4566 | 0 | pos := add(pos, 4) |
4567 | 0 | mstore(pos, 0x20) |
4568 | 0 | pos := add(pos, 0x20) |
4569 | 0 | mstore(pos, <length>) |
4570 | 0 | pos := add(pos, 0x20) |
4571 | 0 | <#word> |
4572 | 0 | mstore(add(pos, <offset>), <wordValue>) |
4573 | 0 | </word> |
4574 | 0 | revert(start, <overallLength>) |
4575 | 0 | )"); |
4576 | 0 | templ("allocate", _allocation); |
4577 | 0 | templ("sig", util::selectorFromSignatureU256("Error(string)").str()); |
4578 | 0 | templ("length", std::to_string(_message.length())); |
4579 | |
|
4580 | 0 | size_t words = (_message.length() + 31) / 32; |
4581 | 0 | std::vector<std::map<std::string, std::string>> wordParams(words); |
4582 | 0 | for (size_t i = 0; i < words; ++i) |
4583 | 0 | { |
4584 | 0 | wordParams[i]["offset"] = std::to_string(i * 32); |
4585 | 0 | wordParams[i]["wordValue"] = formatAsStringOrNumber(_message.substr(32 * i, 32)); |
4586 | 0 | } |
4587 | 0 | templ("word", wordParams); |
4588 | 0 | templ("overallLength", std::to_string(4 + 0x20 + 0x20 + words * 32)); |
4589 | |
|
4590 | 0 | return templ.render(); |
4591 | 67.8k | } |
4592 | | |
4593 | | std::string YulUtilFunctions::panicFunction(util::PanicCode _code) |
4594 | 172k | { |
4595 | 172k | std::string functionName = "panic_error_" + toCompactHexWithPrefix(uint64_t(_code)); |
4596 | 172k | return m_functionCollector.createFunction(functionName, [&]() { |
4597 | 16.4k | return Whiskers(R"( |
4598 | 16.4k | function <functionName>() { |
4599 | 16.4k | mstore(0, <selector>) |
4600 | 16.4k | mstore(4, <code>) |
4601 | 16.4k | revert(0, 0x24) |
4602 | 16.4k | } |
4603 | 16.4k | )") |
4604 | 16.4k | ("functionName", functionName) |
4605 | 16.4k | ("selector", util::selectorFromSignatureU256("Panic(uint256)").str()) |
4606 | 16.4k | ("code", toCompactHexWithPrefix(static_cast<unsigned>(_code))) |
4607 | 16.4k | .render(); |
4608 | 16.4k | }); |
4609 | 172k | } |
4610 | | |
4611 | | std::string YulUtilFunctions::returnDataSelectorFunction() |
4612 | 17 | { |
4613 | 17 | std::string const functionName = "return_data_selector"; |
4614 | 17 | solAssert(m_evmVersion.supportsReturndata(), ""); |
4615 | | |
4616 | 17 | return m_functionCollector.createFunction(functionName, [&]() { |
4617 | 17 | return util::Whiskers(R"( |
4618 | 17 | function <functionName>() -> sig { |
4619 | 17 | if gt(returndatasize(), 3) { |
4620 | 17 | returndatacopy(0, 0, 4) |
4621 | 17 | sig := <shr224>(mload(0)) |
4622 | 17 | } |
4623 | 17 | } |
4624 | 17 | )") |
4625 | 17 | ("functionName", functionName) |
4626 | 17 | ("shr224", shiftRightFunction(224)) |
4627 | 17 | .render(); |
4628 | 17 | }); |
4629 | 17 | } |
4630 | | |
4631 | | std::string YulUtilFunctions::tryDecodeErrorMessageFunction() |
4632 | 14 | { |
4633 | 14 | std::string const functionName = "try_decode_error_message"; |
4634 | 14 | solAssert(m_evmVersion.supportsReturndata(), ""); |
4635 | | |
4636 | 14 | return m_functionCollector.createFunction(functionName, [&]() { |
4637 | 14 | return util::Whiskers(R"( |
4638 | 14 | function <functionName>() -> ret { |
4639 | 14 | if lt(returndatasize(), 0x44) { leave } |
4640 | 14 | |
4641 | 14 | let data := <allocateUnbounded>() |
4642 | 14 | returndatacopy(data, 4, sub(returndatasize(), 4)) |
4643 | 14 | |
4644 | 14 | let offset := mload(data) |
4645 | 14 | if or( |
4646 | 14 | gt(offset, 0xffffffffffffffff), |
4647 | 14 | gt(add(offset, 0x24), returndatasize()) |
4648 | 14 | ) { |
4649 | 14 | leave |
4650 | 14 | } |
4651 | 14 | |
4652 | 14 | let msg := add(data, offset) |
4653 | 14 | let length := mload(msg) |
4654 | 14 | if gt(length, 0xffffffffffffffff) { leave } |
4655 | 14 | |
4656 | 14 | let end := add(add(msg, 0x20), length) |
4657 | 14 | if gt(end, add(data, sub(returndatasize(), 4))) { leave } |
4658 | 14 | |
4659 | 14 | <finalizeAllocation>(data, add(offset, add(0x20, length))) |
4660 | 14 | ret := msg |
4661 | 14 | } |
4662 | 14 | )") |
4663 | 14 | ("functionName", functionName) |
4664 | 14 | ("allocateUnbounded", allocateUnboundedFunction()) |
4665 | 14 | ("finalizeAllocation", finalizeAllocationFunction()) |
4666 | 14 | .render(); |
4667 | 14 | }); |
4668 | 14 | } |
4669 | | |
4670 | | std::string YulUtilFunctions::tryDecodePanicDataFunction() |
4671 | 7 | { |
4672 | 7 | std::string const functionName = "try_decode_panic_data"; |
4673 | 7 | solAssert(m_evmVersion.supportsReturndata(), ""); |
4674 | | |
4675 | 7 | return m_functionCollector.createFunction(functionName, [&]() { |
4676 | 7 | return util::Whiskers(R"( |
4677 | 7 | function <functionName>() -> success, data { |
4678 | 7 | if gt(returndatasize(), 0x23) { |
4679 | 7 | returndatacopy(0, 4, 0x20) |
4680 | 7 | success := 1 |
4681 | 7 | data := mload(0) |
4682 | 7 | } |
4683 | 7 | } |
4684 | 7 | )") |
4685 | 7 | ("functionName", functionName) |
4686 | 7 | .render(); |
4687 | 7 | }); |
4688 | 7 | } |
4689 | | |
4690 | | std::string YulUtilFunctions::extractReturndataFunction() |
4691 | 76 | { |
4692 | 76 | std::string const functionName = "extract_returndata"; |
4693 | | |
4694 | 76 | return m_functionCollector.createFunction(functionName, [&]() { |
4695 | 76 | return util::Whiskers(R"( |
4696 | 76 | function <functionName>() -> data { |
4697 | 76 | <?supportsReturndata> |
4698 | 76 | switch returndatasize() |
4699 | 76 | case 0 { |
4700 | 76 | data := <emptyArray>() |
4701 | 76 | } |
4702 | 76 | default { |
4703 | 76 | data := <allocateArray>(returndatasize()) |
4704 | 76 | returndatacopy(add(data, 0x20), 0, returndatasize()) |
4705 | 76 | } |
4706 | 76 | <!supportsReturndata> |
4707 | 76 | data := <emptyArray>() |
4708 | 76 | </supportsReturndata> |
4709 | 76 | } |
4710 | 76 | )") |
4711 | 76 | ("functionName", functionName) |
4712 | 76 | ("supportsReturndata", m_evmVersion.supportsReturndata()) |
4713 | 76 | ("allocateArray", allocateMemoryArrayFunction(*TypeProvider::bytesMemory())) |
4714 | 76 | ("emptyArray", zeroValueFunction(*TypeProvider::bytesMemory())) |
4715 | 76 | .render(); |
4716 | 76 | }); |
4717 | 76 | } |
4718 | | |
4719 | | std::string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction( |
4720 | | ContractDefinition const& _contract, |
4721 | | std::string const& _creationObjectName |
4722 | | ) |
4723 | 241 | { |
4724 | 241 | std::string functionName = "copy_arguments_for_constructor_" + |
4725 | 241 | toString(_contract.constructor()->id()) + |
4726 | 241 | "_object_" + |
4727 | 241 | _contract.name() + |
4728 | 241 | "_" + |
4729 | 241 | toString(_contract.id()); |
4730 | | |
4731 | 241 | return m_functionCollector.createFunction(functionName, [&]() { |
4732 | 241 | std::string returnParams = suffixedVariableNameList("ret_param_",0, CompilerUtils::sizeOnStack(_contract.constructor()->parameters())); |
4733 | 241 | ABIFunctions abiFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector); |
4734 | | |
4735 | 241 | return util::Whiskers(R"( |
4736 | 241 | function <functionName>() -> <retParams> { |
4737 | 241 | <?eof> |
4738 | 241 | let argSize := calldatasize() |
4739 | 241 | let memoryDataOffset := <allocate>(argSize) |
4740 | 241 | calldatacopy(memoryDataOffset, 0, argSize) |
4741 | 241 | <!eof> |
4742 | 241 | let programSize := datasize("<object>") |
4743 | 241 | let argSize := sub(codesize(), programSize) |
4744 | 241 | |
4745 | 241 | let memoryDataOffset := <allocate>(argSize) |
4746 | 241 | codecopy(memoryDataOffset, programSize, argSize) |
4747 | 241 | </eof> |
4748 | 241 | |
4749 | 241 | <retParams> := <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize)) |
4750 | 241 | } |
4751 | 241 | )") |
4752 | 241 | ("functionName", functionName) |
4753 | 241 | ("retParams", returnParams) |
4754 | 241 | ("object", _creationObjectName) |
4755 | 241 | ("allocate", allocationFunction()) |
4756 | 241 | ("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true)) |
4757 | 241 | ("eof", m_eofVersion.has_value()) |
4758 | 241 | .render(); |
4759 | 241 | }); |
4760 | 241 | } |
4761 | | |
4762 | | std::string YulUtilFunctions::externalCodeFunction() |
4763 | 9 | { |
4764 | 9 | std::string functionName = "external_code_at"; |
4765 | | |
4766 | 9 | return m_functionCollector.createFunction(functionName, [&]() { |
4767 | 9 | return util::Whiskers(R"( |
4768 | 9 | function <functionName>(addr) -> mpos { |
4769 | 9 | let length := extcodesize(addr) |
4770 | 9 | mpos := <allocateArray>(length) |
4771 | 9 | extcodecopy(addr, add(mpos, 0x20), 0, length) |
4772 | 9 | } |
4773 | 9 | )") |
4774 | 9 | ("functionName", functionName) |
4775 | 9 | ("allocateArray", allocateMemoryArrayFunction(*TypeProvider::bytesMemory())) |
4776 | 9 | .render(); |
4777 | 9 | }); |
4778 | 9 | } |
4779 | | |
4780 | | std::string YulUtilFunctions::externalFunctionPointersEqualFunction() |
4781 | 0 | { |
4782 | 0 | std::string const functionName = "externalFunctionPointersEqualFunction"; |
4783 | 0 | return m_functionCollector.createFunction(functionName, [&]() { |
4784 | 0 | return util::Whiskers(R"( |
4785 | 0 | function <functionName>( |
4786 | 0 | leftAddress, |
4787 | 0 | leftSelector, |
4788 | 0 | rightAddress, |
4789 | 0 | rightSelector |
4790 | 0 | ) -> result { |
4791 | 0 | result := and( |
4792 | 0 | eq( |
4793 | 0 | <addressCleanUpFunction>(leftAddress), <addressCleanUpFunction>(rightAddress) |
4794 | 0 | ), |
4795 | 0 | eq( |
4796 | 0 | <selectorCleanUpFunction>(leftSelector), <selectorCleanUpFunction>(rightSelector) |
4797 | 0 | ) |
4798 | 0 | ) |
4799 | 0 | } |
4800 | 0 | )") |
4801 | 0 | ("functionName", functionName) |
4802 | 0 | ("addressCleanUpFunction", cleanupFunction(*TypeProvider::address())) |
4803 | 0 | ("selectorCleanUpFunction", cleanupFunction(*TypeProvider::uint(32))) |
4804 | 0 | .render(); |
4805 | 0 | }); |
4806 | 0 | } |