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