/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see .
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Component that can generate various useful Yul functions.
*/
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
string YulUtilFunctions::combineExternalFunctionIdFunction()
{
string functionName = "combine_external_function_id";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (addr, selector) -> combined {
combined := (or((addr), and(selector, 0xffffffff)))
}
)")
("functionName", functionName)
("shl32", shiftLeftFunction(32))
("shl64", shiftLeftFunction(64))
.render();
});
}
string YulUtilFunctions::splitExternalFunctionIdFunction()
{
string functionName = "split_external_function_id";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (combined) -> addr, selector {
combined := (combined)
selector := and(combined, 0xffffffff)
addr := (combined)
}
)")
("functionName", functionName)
("shr32", shiftRightFunction(32))
("shr64", shiftRightFunction(64))
.render();
});
}
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
{
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
return m_functionCollector.createFunction(functionName, [&]() {
if (_fromCalldata)
{
return Whiskers(R"(
function (src, dst, length) {
calldatacopy(dst, src, length)
// clear end
mstore(add(dst, length), 0)
}
)")
("functionName", functionName)
.render();
}
else
{
return Whiskers(R"(
function (src, dst, length) {
let i := 0
for { } lt(i, length) { i := add(i, 32) }
{
mstore(add(dst, i), mload(add(src, i)))
}
if gt(i, length)
{
// clear end
mstore(add(dst, length), 0)
}
}
)")
("functionName", functionName)
.render();
}
});
}
string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType)
{
string functionName =
string(_assert ? "assert_helper" : "require_helper") +
(_messageType ? ("_" + _messageType->identifier()) : "");
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
return m_functionCollector.createFunction(functionName, [&]() {
if (!_messageType)
return Whiskers(R"(
function (condition) {
if iszero(condition) { }
}
)")
("error", _assert ? panicFunction() + "()" : "revert(0, 0)")
("functionName", functionName)
.render();
int const hashHeaderSize = 4;
int const byteSize = 8;
u256 const errorHash =
u256(FixedHash::Arith(
FixedHash(keccak256("Error(string)"))
)) << (256 - hashHeaderSize * byteSize);
string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector)
.tupleEncoder(
{_messageType},
{TypeProvider::stringMemory()}
);
return Whiskers(R"(
function (condition ) {
if iszero(condition) {
let fmp := mload()
mstore(fmp, )
let end := (add(fmp, ) )
revert(fmp, sub(end, fmp))
}
}
)")
("functionName", functionName)
("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
("errorHash", formatNumber(errorHash))
("abiEncodeFunc", encodeFunc)
("hashHeaderSize", to_string(hashHeaderSize))
("messageVars",
(_messageType->sizeOnStack() > 0 ? ", " : "") +
suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack())
)
.render();
});
}
string YulUtilFunctions::leftAlignFunction(Type const& _type)
{
string functionName = string("leftAlign_") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function (value) -> aligned {
}
)");
templ("functionName", functionName);
switch (_type.category())
{
case Type::Category::Address:
templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)");
break;
case Type::Category::Integer:
{
IntegerType const& type = dynamic_cast(_type);
if (type.numBits() == 256)
templ("body", "aligned := value");
else
templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)");
break;
}
case Type::Category::RationalNumber:
solAssert(false, "Left align requested for rational number.");
break;
case Type::Category::Bool:
templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)");
break;
case Type::Category::FixedPoint:
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Array:
case Type::Category::Struct:
solAssert(false, "Left align requested for non-value type.");
break;
case Type::Category::FixedBytes:
templ("body", "aligned := value");
break;
case Type::Category::Contract:
templ("body", "aligned := " + leftAlignFunction(*TypeProvider::address()) + "(value)");
break;
case Type::Category::Enum:
{
unsigned storageBytes = dynamic_cast(_type).storageBytes();
templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)");
break;
}
case Type::Category::InaccessibleDynamic:
solAssert(false, "Left align requested for inaccessible dynamic type.");
break;
default:
solAssert(false, "Left align of type " + _type.identifier() + " requested.");
}
return templ.render();
});
}
string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
{
solAssert(_numBits < 256, "");
string functionName = "shift_left_" + to_string(_numBits);
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value) -> newValue {
newValue :=
shl(, value)
mul(value, )
}
)")
("functionName", functionName)
("numBits", to_string(_numBits))
("hasShifts", m_evmVersion.hasBitwiseShifting())
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
string YulUtilFunctions::shiftLeftFunctionDynamic()
{
string functionName = "shift_left_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (bits, value) -> newValue {
newValue :=
shl(bits, value)
mul(value, exp(2, bits))
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
}
string YulUtilFunctions::shiftRightFunction(size_t _numBits)
{
solAssert(_numBits < 256, "");
// Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value) -> newValue {
newValue :=
shr(, value)
div(value, )
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
("numBits", to_string(_numBits))
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
string YulUtilFunctions::shiftRightFunctionDynamic()
{
string const functionName = "shift_right_unsigned_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (bits, value) -> newValue {
newValue :=
shr(bits, value)
div(value, exp(2, bits))
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
}
string YulUtilFunctions::shiftRightSignedFunctionDynamic()
{
string const functionName = "shift_right_signed_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (bits, value) -> result {
result := sar(bits, value)
let divisor := exp(2, bits)
let xor_mask := sub(0, slt(value, 0))
result := xor(div(xor(value, xor_mask), divisor), xor_mask)
// combined version of
// switch slt(value, 0)
// case 0 { result := div(value, divisor) }
// default { result := not(div(not(value), divisor)) }
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
}
string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _amountType)
{
solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
solAssert(_amountType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast(_amountType).isSigned(), "");
string const functionName = "shift_left_" + _type.identifier() + "_" + _amountType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value, bits) -> result {
bits := (bits)
result := ((bits, value))
}
)")
("functionName", functionName)
("cleanAmount", cleanupFunction(_amountType))
("shift", shiftLeftFunctionDynamic())
("cleanup", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const& _amountType)
{
solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
solAssert(_amountType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast(_amountType).isSigned(), "");
IntegerType const* integerType = dynamic_cast(&_type);
bool valueSigned = integerType && integerType->isSigned();
string const functionName = "shift_right_" + _type.identifier() + "_" + _amountType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value, bits) -> result {
bits := (bits)
result := ((bits, (value)))
}
)")
("functionName", functionName)
("cleanAmount", cleanupFunction(_amountType))
("shift", valueSigned ? shiftRightSignedFunctionDynamic() : shiftRightFunctionDynamic())
("cleanup", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
{
solAssert(_numBytes <= 32, "");
solAssert(_shiftBytes <= 32, "");
size_t numBits = _numBytes * 8;
size_t shiftBits = _shiftBytes * 8;
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value, toInsert) -> result {
let mask :=
toInsert := (toInsert)
value := and(value, not(mask))
result := or(value, and(toInsert, mask))
}
)")
("functionName", functionName)
("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits))
("shl", shiftLeftFunction(shiftBits))
.render();
});
}
string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
{
solAssert(_numBytes <= 32, "");
size_t numBits = _numBytes * 8;
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value, shiftBytes, toInsert) -> result {
let shiftBits := mul(shiftBytes, 8)
let mask := (shiftBits, )
toInsert := (shiftBits, toInsert)
value := and(value, not(mask))
result := or(value, and(toInsert, mask))
}
)")
("functionName", functionName)
("mask", formatNumber((bigint(1) << numBits) - 1))
("shl", shiftLeftFunctionDynamic())
.render();
});
}
string YulUtilFunctions::roundUpFunction()
{
string functionName = "round_up_to_mul_of_32";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value) -> result {
result := and(add(value, 31), not(31))
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
{
string functionName = "checked_add_" + _type.identifier();
// TODO: Consider to add a special case for unsigned 256-bit integers
// and use the following instead:
// sum := add(x, y) if lt(sum, x) { () }
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (x, y) -> sum {
x := (x)
y := (y)
// overflow, if x >= 0 and y > (maxValue - x)
if and(iszero(slt(x, 0)), sgt(y, sub(, x))) { () }
// underflow, if x < 0 and y < (minValue - x)
if and(slt(x, 0), slt(y, sub(, x))) { () }
// overflow, if x > (maxValue - y)
if gt(x, sub(, y)) { () }
sum := add(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction())
.render();
});
}
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{
string functionName = "checked_mul_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
// Multiplication by zero could be treated separately and directly return zero.
Whiskers(R"(
function (x, y) -> product {
x := (x)
y := (y)
// overflow, if x > 0, y > 0 and x > (maxValue / y)
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(, y))) { () }
// underflow, if x > 0, y < 0 and y < (minValue / x)
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(, x))) { () }
// underflow, if x < 0, y > 0 and x < (minValue / y)
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(, y))) { () }
// overflow, if x < 0, y < 0 and x < (maxValue / y)
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(, y))) { () }
// overflow, if x != 0 and y > (maxValue / x)
if and(iszero(iszero(x)), gt(y, div(, x))) { () }
product := mul(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction())
.render();
});
}
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{
string functionName = "checked_div_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (x, y) -> r {
x := (x)
y := (y)
if iszero(y) { () }
// overflow for minVal / -1
if and(
eq(x, ),
eq(y, sub(0, 1))
) { () }
r := sdiv(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("minVal", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction())
.render();
});
}
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
{
string functionName = "checked_mod_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (x, y) -> r {
x := (x)
y := (y)
if iszero(y) { () }
r := smod(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction())
.render();
});
}
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
{
string functionName = "checked_sub_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function (x, y) -> diff {
x := (x)
y := (y)
// underflow, if y >= 0 and x < (minValue + y)
if and(iszero(slt(y, 0)), slt(x, add(, y))) { () }
// overflow, if y < 0 and x > (maxValue + y)
if and(slt(y, 0), sgt(x, add(, y))) { () }
if lt(x, y) { () }
diff := sub(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction())
.render();
});
}
string YulUtilFunctions::overflowCheckedIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
string functionName = "checked_exp_" + _type.identifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (base, exponent) -> power {
base := (base)
exponent := (exponent)
power := (base, exponent, , )
power := (base, exponent, )
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("exp", _type.isSigned() ? overflowCheckedSignedExpFunction() : overflowCheckedUnsignedExpFunction())
("maxValue", toCompactHexWithPrefix(_type.max()))
("minValue", toCompactHexWithPrefix(_type.min()))
("baseCleanupFunction", cleanupFunction(_type))
("exponentCleanupFunction", cleanupFunction(_exponentType))
.render();
});
}
string YulUtilFunctions::overflowCheckedUnsignedExpFunction()
{
// Checks for the "small number specialization" below.
using namespace boost::multiprecision;
solAssert(pow(bigint(10), 77) < pow(bigint(2), 256), "");
solAssert(pow(bigint(11), 77) >= pow(bigint(2), 256), "");
solAssert(pow(bigint(10), 78) >= pow(bigint(2), 256), "");
solAssert(pow(bigint(306), 31) < pow(bigint(2), 256), "");
solAssert(pow(bigint(307), 31) >= pow(bigint(2), 256), "");
solAssert(pow(bigint(306), 32) >= pow(bigint(2), 256), "");
string functionName = "checked_exp_unsigned";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (base, exponent, max) -> power {
// This function currently cannot be inlined because of the
// "leave" statements. We have to improve the optimizer.
// Note that 0**0 == 1
if iszero(exponent) { power := 1 leave }
if iszero(base) { power := 0 leave }
// Specializations for small bases
switch base
// 0 is handled above
case 1 { power := 1 leave }
case 2
{
if gt(exponent, 255) { () }
power := exp(2, exponent)
if gt(power, max) { () }
leave
}
if or(
and(lt(base, 11), lt(exponent, 78)),
and(lt(base, 307), lt(exponent, 32))
)
{
power := exp(base, exponent)
if gt(power, max) { () }
leave
}
power, base := (1, base, exponent, max)
if gt(power, div(max, base)) { () }
power := mul(power, base)
}
)")
("functionName", functionName)
("panic", panicFunction())
("expLoop", overflowCheckedExpLoopFunction())
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::overflowCheckedSignedExpFunction()
{
string functionName = "checked_exp_signed";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (base, exponent, min, max) -> power {
// Currently, `leave` avoids this function being inlined.
// We have to improve the optimizer.
// Note that 0**0 == 1
switch exponent
case 0 { power := 1 leave }
case 1 { power := base leave }
if iszero(base) { power := 0 leave }
power := 1
// We pull out the first iteration because it is the only one in which
// base can be negative.
// Exponent is at least 2 here.
// overflow check for base * base
switch sgt(base, 0)
case 1 { if gt(base, div(max, base)) { () } }
case 0 { if slt(base, sdiv(max, base)) { () } }
if and(exponent, 1)
{
power := base
}
base := mul(base, base)
exponent := (exponent)
// Below this point, base is always positive.
power, base := (power, base, exponent, max)
if and(sgt(power, 0), gt(power, div(max, base))) { () }
if and(slt(power, 0), slt(power, sdiv(min, base))) { () }
power := mul(power, base)
}
)")
("functionName", functionName)
("panic", panicFunction())
("expLoop", overflowCheckedExpLoopFunction())
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::overflowCheckedExpLoopFunction()
{
// We use this loop for both signed and unsigned exponentiation
// because we pull out the first iteration in the signed case which
// results in the base always being positive.
// This function does not include the final multiplication.
string functionName = "checked_exp_helper";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (_power, _base, exponent, max) -> power, base {
power := _power
base := _base
for { } gt(exponent, 1) {}
{
// overflow check for base * base
if gt(base, div(max, base)) { () }
if and(exponent, 1)
{
// No checks for power := mul(power, base) needed, because the check
// for base * base above is sufficient, since:
// |power| <= base (proof by induction) and thus:
// |power * base| <= base * base <= max <= |min| (for signed)
// (this is equally true for signed and unsigned exp)
power := mul(power, base)
}
base := mul(base, base)
exponent := (exponent)
}
}
)")
("functionName", functionName)
("panic", panicFunction())
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
string functionName = "extract_byte_array_length";
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function (data) -> length {
// Retrieve length both for in-place strings and off-place strings:
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
let mask := sub(mul(0x100, iszero(and(data, 1))), 1)
length := div(and(data, mask), 2)
}
)");
w("functionName", functionName);
return w.render();
});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function (value) -> length {
length := mload(value)
length := sload(value)
length := (length)
length :=
}
)");
w("functionName", functionName);
w("dynamic", _type.isDynamicallySized());
if (!_type.isDynamicallySized())
w("length", toCompactHexWithPrefix(_type.length()));
w("memory", _type.location() == DataLocation::Memory);
w("storage", _type.location() == DataLocation::Storage);
if (_type.location() == DataLocation::Storage)
{
w("byteArray", _type.isByteArray());
if (_type.isByteArray())
w("extractByteArrayLength", extractByteArrayLengthFunction());
}
if (_type.isDynamicallySized())
solAssert(
_type.location() != DataLocation::CallData,
"called regular array length function on calldata array"
);
return w.render();
});
}
std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "...");
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array, newLen) {
if gt(newLen, ) {
()
}
let oldLen := (array)
// Store new length
sstore(array, newLen)
// Size was reduced, clear end of array
if lt(newLen, oldLen) {
let oldSlotCount := (oldLen)
let newSlotCount := (newLen)
let arrayDataStart := (array)
let deleteStart := add(arrayDataStart, newSlotCount)
let deleteEnd := add(arrayDataStart, oldSlotCount)
// if we are dealing with packed array and offset is greater than zero
// we have to partially clear last slot that is still used, so decreasing start by one
let offset := mul(mod(newLen, ), )
if gt(offset, 0) { (sub(deleteStart, 1), offset) }
(deleteStart, deleteEnd)
}
})")
("functionName", functionName)
("panic", panicFunction())
("fetchLength", arrayLengthFunction(_type))
("convertToSize", arrayConvertLengthToSize(_type))
("dataPosition", arrayDataAreaFunction(_type))
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("packed", _type.baseType()->storageBytes() <= 16)
("itemsPerSlot", to_string(32 / _type.baseType()->storageBytes()))
("storageBytes", to_string(_type.baseType()->storageBytes()))
("partialClearStorageSlot", partialClearStorageSlotFunction())
.render();
});
}
string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
if (_type.isByteArray())
return storageByteArrayPopFunction(_type);
string functionName = "array_pop_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array) {
let oldLen := (array)
if iszero(oldLen) { () }
let newLen := sub(oldLen, 1)
let slot, offset := (array, newLen)
(slot, offset)
sstore(array, newLen)
})")
("functionName", functionName)
("panic", panicFunction())
("fetchLength", arrayLengthFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("setToZero", storageSetToZeroFunction(*_type.baseType()))
.render();
});
}
string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solAssert(_type.isByteArray(), "");
string functionName = "byte_array_pop_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array) {
let data := sload(array)
let oldLen := (data)
if iszero(oldLen) { () }
switch eq(oldLen, 32)
case 1 {
// Here we have a special case where array transitions to shorter than 32
// So we need to copy data
let copyFromSlot := (array)
data := sload(copyFromSlot)
sstore(copyFromSlot, 0)
// New length is 31, encoded to 31 * 2 = 62
data := or(and(data, not(0xff)), 62)
}
default {
data := sub(data, 2)
let newLen := sub(oldLen, 1)
switch lt(oldLen, 32)
case 1 {
// set last element to zero
let mask := not((mul(8, sub(31, newLen)), 0xff))
data := and(data, mask)
}
default {
let slot, offset := (array, newLen)
(slot, offset)
}
}
sstore(array, data)
})")
("functionName", functionName)
("panic", panicFunction())
("extractByteArrayLength", extractByteArrayLengthFunction())
("dataAreaFunction", arrayDataAreaFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("setToZero", storageSetToZeroFunction(*_type.baseType()))
("shl", shiftLeftFunctionDynamic())
.render();
});
}
string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array, value) {
let data := sload(array)
let oldLen := (data)
if iszero(lt(oldLen, )) { () }
switch gt(oldLen, 31)
case 0 {
value := byte(0, value)
switch oldLen
case 31 {
// Here we have special case when array switches from short array to long array
// We need to copy data
let dataArea := (array)
data := and(data, not(0xff))
sstore(dataArea, or(and(0xff, value), data))
// New length is 32, encoded as (32 * 2 + 1)
sstore(array, 65)
}
default {
data := add(data, 2)
let shiftBits := mul(8, sub(31, oldLen))
let valueShifted := (shiftBits, and(0xff, value))
let mask := (shiftBits, 0xff)
data := or(and(data, not(mask)), valueShifted)
sstore(array, data)
}
}
default {
sstore(array, add(data, 2))
let slot, offset := (array, oldLen)
(slot, offset, value)
}
let oldLen := sload(array)
if iszero(lt(oldLen, )) { () }
sstore(array, add(oldLen, 1))
let slot, offset := (array, oldLen)
(slot, offset, value)
})")
("functionName", functionName)
("panic", panicFunction())
("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
("dataAreaFunction", arrayDataAreaFunction(_type))
("isByteArray", _type.isByteArray())
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("shl", shiftLeftFunctionDynamic())
("shr", shiftRightFunction(248))
.render();
});
}
string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array) -> slot, offset {
let oldLen := (array)
if iszero(lt(oldLen, )) { () }
sstore(array, add(oldLen, 1))
slot, offset := (array, oldLen)
})")
("functionName", functionName)
("panic", panicFunction())
("fetchLength", arrayLengthFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("maxArrayLength", (u256(1) << 64).str())
.render();
});
}
string YulUtilFunctions::partialClearStorageSlotFunction()
{
string functionName = "partial_clear_storage_slot";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (slot, offset) {
let mask := (mul(8, sub(32, offset)), )
sstore(slot, and(mask, sload(slot)))
}
)")
("functionName", functionName)
("ones", formatNumber((bigint(1) << 256) - 1))
("shr", shiftRightFunctionDynamic())
.render();
});
}
string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
{
if (_type.storageBytes() < 32)
solAssert(_type.isValueType(), "");
string functionName = "clear_storage_range_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (start, end) {
for {} lt(start, end) { start := add(start, ) }
{
(start, 0)
}
}
)")
("functionName", functionName)
("setToZero", storageSetToZeroFunction(_type.storageBytes() < 32 ? *TypeProvider::uint256() : _type))
("increment", _type.storageSize().str())
.render();
});
}
string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
if (_type.baseType()->storageBytes() < 32)
{
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
}
if (_type.baseType()->isValueType())
solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
string functionName = "clear_storage_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (slot) {
(slot, 0)
(slot, add(slot, ()))
}
)")
("functionName", functionName)
("dynamic", _type.isDynamicallySized())
("resizeArray", _type.isDynamicallySized() ? resizeDynamicArrayFunction(_type) : "")
(
"clearRange",
clearStorageRangeFunction(
(_type.baseType()->storageBytes() < 32) ?
*TypeProvider::uint256() :
*_type.baseType()
)
)
("lenToSize", arrayConvertLengthToSize(_type))
("len", _type.length().str())
.render();
});
}
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Type const& baseType = *_type.baseType();
switch (_type.location())
{
case DataLocation::Storage:
{
unsigned const baseStorageBytes = baseType.storageBytes();
solAssert(baseStorageBytes > 0, "");
solAssert(32 / baseStorageBytes > 0, "");
return Whiskers(R"(
function (length) -> size {
size := length
size := (, length)
// Number of slots rounded up
size := div(add(length, sub(, 1)), )
})")
("functionName", functionName)
("multiSlot", baseType.storageSize() > 1)
("itemsPerSlot", to_string(32 / baseStorageBytes))
("storageSize", baseType.storageSize().str())
("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256()))
.render();
}
case DataLocation::CallData: // fallthrough
case DataLocation::Memory:
return Whiskers(R"(
function (length) -> size {
size := length
size := (length, )
})")
("functionName", functionName)
("stride", to_string(_type.location() == DataLocation::Memory ? _type.memoryStride() : _type.calldataStride()))
("byteArray", _type.isByteArray())
("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256()))
.render();
default:
solAssert(false, "");
}
});
}
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
string functionName = "array_allocation_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function (length) -> size {
// Make sure we can allocate memory without overflow
if gt(length, 0xffffffffffffffff) { () }
// round up
size := and(add(length, 0x1f), not(0x1f))
size := mul(length, 0x20)
// add length slot
size := add(size, 0x20)
}
)");
w("functionName", functionName);
w("panic", panicFunction());
w("byteArray", _type.isByteArray());
w("dynamic", _type.isDynamicallySized());
return w.render();
});
}
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
{
string functionName = "array_dataslot_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
// No special processing for calldata arrays, because they are stored as
// offset of the data area and length on the stack, so the offset already
// points to the data area.
// This might change, if calldata arrays are stored in a single
// stack slot at some point.
return Whiskers(R"(
function (ptr) -> data {
data := ptr
data := add(ptr, 0x20)
mstore(0, ptr)
data := keccak256(0, 0x20)
}
)")
("functionName", functionName)
("dynamic", _type.isDynamicallySized())
("memory", _type.location() == DataLocation::Memory)
("storage", _type.location() == DataLocation::Storage)
.render();
});
}
string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
{
string functionName = "storage_array_index_access_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array, index) -> slot, offset {
let arrayLength := (array)
if iszero(lt(index, arrayLength)) { () }
offset := sub(31, mod(index, 0x20))
switch lt(arrayLength, 0x20)
case 0 {
let dataArea := (array)
slot := add(dataArea, div(index, 0x20))
}
default {
slot := array
}
let dataArea := (array)
slot := add(dataArea, div(index, ))
offset := mul(mod(index, ), )
let dataArea := (array)
slot := add(dataArea, mul(index, ))
offset := 0
}
)")
("functionName", functionName)
("panic", panicFunction())
("arrayLen", arrayLengthFunction(_type))
("dataAreaFunc", arrayDataAreaFunction(_type))
("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16)
("isBytesArray", _type.isByteArray())
("storageSize", _type.baseType()->storageSize().str())
("storageBytes", toString(_type.baseType()->storageBytes()))
("itemsPerSlot", to_string(32 / _type.baseType()->storageBytes()))
.render();
});
}
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
{
string functionName = "memory_array_index_access_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (baseRef, index) -> addr {
if iszero(lt(index, (baseRef))) {
()
}
let offset := mul(index, )
offset := add(offset, 32)
addr := add(baseRef, offset)
}
)")
("functionName", functionName)
("panic", panicFunction())
("arrayLen", arrayLengthFunction(_type))
("stride", to_string(_type.memoryStride()))
("dynamicallySized", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_array_index_access_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (base_ref, length, index) -> addr, len {
if iszero(lt(index, length)) { () }
addr := add(base_ref, mul(index, ))
addr, len := (base_ref, addr)
}
)")
("functionName", functionName)
("panic", panicFunction())
("stride", to_string(_type.calldataStride()))
("dynamicallySized", _type.isDynamicallySized())
("dynamicallyEncodedBase", _type.baseType()->isDynamicallyEncoded())
("dynamicallySizedBase", _type.baseType()->isDynamicallySized())
("arrayLen", toCompactHexWithPrefix(_type.length()))
("accessCalldataTail", _type.baseType()->isDynamicallyEncoded() ? accessCalldataTailFunction(*_type.baseType()): "")
.render();
});
}
string YulUtilFunctions::calldataArrayIndexRangeAccess(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
solAssert(_type.isDynamicallySized(), "");
string functionName = "calldata_array_index_range_access_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (offset, length, startIndex, endIndex) -> offsetOut, lengthOut {
if gt(startIndex, endIndex) { }
if gt(endIndex, length) { }
offsetOut := add(offset, mul(startIndex, ))
lengthOut := sub(endIndex, startIndex)
}
)")
("functionName", functionName)
("stride", to_string(_type.calldataStride()))
("revertSliceStartAfterEnd", revertReasonIfDebug("Slice starts after end"))
("revertSliceGreaterThanLength", revertReasonIfDebug("Slice is greater than length"))
.render();
});
}
string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
{
solAssert(_type.isDynamicallyEncoded(), "");
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "access_calldata_tail_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (base_ref, ptr_to_tail) -> addr, length {
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { }
addr := add(base_ref, rel_offset_of_tail)
length := calldataload(addr)
if gt(length, 0xffffffffffffffff) { }
addr := add(addr, 32)
if sgt(addr, sub(calldatasize(), mul(length, ))) { }
}
)")
("functionName", functionName)
("dynamicallySized", _type.isDynamicallySized())
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast(_type).calldataStride() : 0))
("invalidCalldataTailOffset", revertReasonIfDebug("Invalid calldata tail offset"))
("invalidCalldataTailLength", revertReasonIfDebug("Invalid calldata tail length"))
("shortCalldataTail", revertReasonIfDebug("Calldata tail too short"))
.render();
});
}
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{
solAssert(!_type.isByteArray(), "");
if (_type.dataStoredIn(DataLocation::Storage))
solAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "array_nextElement_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function (ptr) -> next {
next := add(ptr, )
}
)");
templ("functionName", functionName);
switch (_type.location())
{
case DataLocation::Memory:
templ("advance", "0x20");
break;
case DataLocation::Storage:
{
u256 size = _type.baseType()->storageSize();
solAssert(size >= 1, "");
templ("advance", toCompactHexWithPrefix(size));
break;
}
case DataLocation::CallData:
{
u256 size = _type.calldataStride();
solAssert(size >= 32 && size % 32 == 0, "");
templ("advance", toCompactHexWithPrefix(size));
break;
}
}
return templ.render();
});
}
string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType)
{
solAssert(_keyType.sizeOnStack() <= 1, "");
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
if (_mappingType.keyType()->isDynamicallySized())
return Whiskers(R"(
function (slot +key>,+key> ) -> dataSlot {
dataSlot := ( +key>,+key> slot)
}
)")
("functionName", functionName)
("key", _keyType.sizeOnStack() > 0 ? "key" : "")
("hash", packedHashFunction(
{&_keyType, TypeProvider::uint256()},
{_mappingType.keyType(), TypeProvider::uint256()}
))
.render();
else
{
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), "");
solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, "");
Whiskers templ(R"(
function (slot ) -> dataSlot {
mstore(0, )
mstore(0x20, slot)
dataSlot := keccak256(0, 0x40)
}
)");
templ("functionName", functionName);
templ("key", _keyType.sizeOnStack() == 1 ? ", key" : "");
if (_keyType.sizeOnStack() == 0)
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()");
else
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)");
return templ.render();
}
});
}
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
if (_type.isValueType())
return readFromStorageValueType(_type, _offset, _splitFunctionTypes);
else
{
solAssert(_offset == 0, "");
return readFromStorageReferenceType(_type);
}
}
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
return readFromStorageValueTypeDynamic(_type, _splitFunctionTypes);
}
string YulUtilFunctions::readFromStorageValueType(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_" +
string(_splitFunctionTypes ? "split_" : "") +
"offset_" +
to_string(_offset) +
"_" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function (slot) -> value {
value := (sload(slot))
}
)")
("functionName", functionName)
("extract", extractFromStorageValue(_type, _offset, false))
.render();
});
}
string YulUtilFunctions::readFromStorageValueTypeDynamic(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_value_type_dynamic" +
string(_splitFunctionTypes ? "split_" : "") +
"_" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function (slot, offset) -> value {
value := (sload(slot), offset)
}
)")
("functionName", functionName)
("extract", extractFromStorageValueDynamic(_type, _splitFunctionTypes))
.render();
});
}
string YulUtilFunctions::readFromStorageReferenceType(Type const& _type)
{
solUnimplementedAssert(_type.category() == Type::Category::Struct, "");
string functionName = "read_from_storage_reference_type_" + _type.identifier();
auto const& structType = dynamic_cast(_type);
solAssert(structType.location() == DataLocation::Memory, "");
MemberList::MemberMap structMembers = structType.nativeMembers(nullptr);
vector