/*
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
#include
#include
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace std::string_literals;
std::string YulUtilFunctions::identityFunction()
{
std::string functionName = "identity";
return m_functionCollector.createFunction("identity", [&](std::vector& _args, std::vector& _rets) {
_args.push_back("value");
_rets.push_back("ret");
return "ret := value";
});
}
std::string YulUtilFunctions::combineExternalFunctionIdFunction()
{
std::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();
});
}
std::string YulUtilFunctions::splitExternalFunctionIdFunction()
{
std::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();
});
}
std::string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata, bool _cleanup)
{
std::string functionName =
"copy_"s +
(_fromCalldata ? "calldata"s : "memory"s) +
"_to_memory"s +
(_cleanup ? "_with_cleanup"s : ""s);
return m_functionCollector.createFunction(functionName, [&]() {
if (_fromCalldata)
{
return Whiskers(R"(
function (src, dst, length) {
calldatacopy(dst, src, length)
mstore(add(dst, length), 0)
}
)")
("functionName", functionName)
("cleanup", _cleanup)
.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)))
}
mstore(add(dst, length), 0)
}
)")
("functionName", functionName)
("cleanup", _cleanup)
.render();
}
});
}
std::string YulUtilFunctions::copyLiteralToMemoryFunction(std::string const& _literal)
{
std::string functionName = "copy_literal_to_memory_" + util::toHex(util::keccak256(_literal).asBytes());
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function () -> memPtr {
memPtr := ()
(add(memPtr, 32))
}
)")
("functionName", functionName)
("arrayAllocationFunction", allocateMemoryArrayFunction(*TypeProvider::array(DataLocation::Memory, true)))
("size", std::to_string(_literal.size()))
("storeLiteralInMem", storeLiteralInMemoryFunction(_literal))
.render();
});
}
std::string YulUtilFunctions::storeLiteralInMemoryFunction(std::string const& _literal)
{
std::string functionName = "store_literal_in_memory_" + util::toHex(util::keccak256(_literal).asBytes());
return m_functionCollector.createFunction(functionName, [&]() {
size_t words = (_literal.length() + 31) / 32;
std::vector> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = std::to_string(i * 32);
wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32));
}
return Whiskers(R"(
function (memPtr) {
<#word>
mstore(add(memPtr, ), )
}
)")
("functionName", functionName)
("word", wordParams)
.render();
});
}
std::string YulUtilFunctions::copyLiteralToStorageFunction(std::string const& _literal)
{
std::string functionName = "copy_literal_to_storage_" + util::toHex(util::keccak256(_literal).asBytes());
return m_functionCollector.createFunction(functionName, [&](std::vector& _args, std::vector&) {
_args = {"slot"};
if (_literal.size() >= 32)
{
size_t words = (_literal.length() + 31) / 32;
std::vector> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = std::to_string(i);
wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32));
}
return Whiskers(R"(
let oldLen := (sload(slot))
(slot, oldLen, )
sstore(slot, )
let dstPtr := (slot)
<#word>
sstore(add(dstPtr, ), )
)")
("byteArrayLength", extractByteArrayLengthFunction())
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
("dataArea", arrayDataAreaFunction(*TypeProvider::bytesStorage()))
("word", wordParams)
("length", std::to_string(_literal.size()))
("encodedLen", std::to_string(2 * _literal.size() + 1))
.render();
}
else
return Whiskers(R"(
let oldLen := (sload(slot))
(slot, oldLen, )
sstore(slot, add(, ))
)")
("byteArrayLength", extractByteArrayLengthFunction())
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
("wordValue", formatAsStringOrNumber(_literal))
("length", std::to_string(_literal.size()))
("encodedLen", std::to_string(2 * _literal.size()))
.render();
});
}
std::string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType)
{
std::string functionName =
std::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(PanicCode::Assert) + "()" : "revert(0, 0)")
("functionName", functionName)
.render();
int const hashHeaderSize = 4;
u256 const errorHash = util::selectorFromSignatureU256("Error(string)");
std::string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector)
.tupleEncoder(
{_messageType},
{TypeProvider::stringMemory()}
);
return Whiskers(R"(
function (condition ) {
if iszero(condition) {
let memPtr := ()
mstore(memPtr, )
let end := (add(memPtr, ) )
revert(memPtr, sub(end, memPtr))
}
}
)")
("functionName", functionName)
("allocateUnbounded", allocateUnboundedFunction())
("errorHash", formatNumber(errorHash))
("abiEncodeFunc", encodeFunc)
("hashHeaderSize", std::to_string(hashHeaderSize))
("messageVars",
(_messageType->sizeOnStack() > 0 ? ", " : "") +
suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack())
)
.render();
});
}
std::string YulUtilFunctions::leftAlignFunction(Type const& _type)
{
std::string functionName = std::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:
{
solAssert(dynamic_cast(_type).storageBytes() == 1, "");
templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(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();
});
}
std::string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
{
solAssert(_numBits < 256, "");
std::string functionName = "shift_left_" + std::to_string(_numBits);
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (value) -> newValue {
newValue :=
shl(, value)
mul(value, )
}
)")
("functionName", functionName)
("numBits", std::to_string(_numBits))
("hasShifts", m_evmVersion.hasBitwiseShifting())
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
std::string YulUtilFunctions::shiftLeftFunctionDynamic()
{
std::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();
});
}
std::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!
std::string functionName = "shift_right_" + std::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", std::to_string(_numBits))
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
std::string YulUtilFunctions::shiftRightFunctionDynamic()
{
std::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();
});
}
std::string YulUtilFunctions::shiftRightSignedFunctionDynamic()
{
std::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();
});
}
std::string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _amountType)
{
solUnimplementedAssert(_type.category() != Type::Category::FixedPoint, "Not yet implemented - FixedPointType.");
solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
solAssert(_amountType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast(_amountType).isSigned(), "");
std::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();
});
}
std::string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const& _amountType)
{
solUnimplementedAssert(_type.category() != Type::Category::FixedPoint, "Not yet implemented - FixedPointType.");
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();
std::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();
});
}
std::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;
std::string functionName = "update_byte_slice_" + std::to_string(_numBytes) + "_shift_" + std::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();
});
}
std::string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
{
solAssert(_numBytes <= 32, "");
size_t numBits = _numBytes * 8;
std::string functionName = "update_byte_slice_dynamic" + std::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();
});
}
std::string YulUtilFunctions::maskBytesFunctionDynamic()
{
std::string functionName = "mask_bytes_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (data, bytes) -> result {
let mask := not((mul(8, bytes), not(0)))
result := and(data, mask)
})")
("functionName", functionName)
("shr", shiftRightFunctionDynamic())
.render();
});
}
std::string YulUtilFunctions::maskLowerOrderBytesFunction(size_t _bytes)
{
std::string functionName = "mask_lower_order_bytes_" + std::to_string(_bytes);
solAssert(_bytes <= 32, "");
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (data) -> result {
result := and(data, )
})")
("functionName", functionName)
("mask", formatNumber((~u256(0)) >> (256 - 8 * _bytes)))
.render();
});
}
std::string YulUtilFunctions::maskLowerOrderBytesFunctionDynamic()
{
std::string functionName = "mask_lower_order_bytes_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (data, bytes) -> result {
let mask := not((mul(8, bytes), not(0)))
result := and(data, mask)
})")
("functionName", functionName)
("shl", shiftLeftFunctionDynamic())
.render();
});
}
std::string YulUtilFunctions::roundUpFunction()
{
std::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();
});
}
std::string YulUtilFunctions::divide32CeilFunction()
{
return m_functionCollector.createFunction(
"divide_by_32_ceil",
[&](std::vector& _args, std::vector& _ret) {
_args = {"value"};
_ret = {"result"};
return "result := div(add(value, 31), 32)";
}
);
}
std::string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
{
std::string functionName = "checked_add_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (x, y) -> sum {
x := (x)
y := (y)
sum := add(x, y)
256bit>
// overflow, if x >= 0 and sum < y
// underflow, if x < 0 and sum >= y
if or(
and(iszero(slt(x, 0)), slt(sum, y)),
and(slt(x, 0), iszero(slt(sum, y)))
) { () }
if or(
sgt(sum, ),
slt(sum, )
) { () }
256bit>
256bit>
if gt(x, sum) { () }
if gt(sum, ) { () }
256bit>
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
("256bit", _type.numBits() == 256)
.render();
});
}
std::string YulUtilFunctions::wrappingIntAddFunction(IntegerType const& _type)
{
std::string functionName = "wrapping_add_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (x, y) -> sum {
sum := (add(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
std::string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{
std::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)
let product_raw := mul(x, y)
product := (product_raw)
256bit>
// special case
if and(slt(x, 0), eq(y, )) { () }
256bit>
// overflow, if x != 0 and y != product/x
if iszero(
or(
iszero(x),
eq(y, sdiv(product, x))
)
) { () }
if iszero(eq(product, product_raw)) { () }
// overflow, if x != 0 and y != product/x
if iszero(
or(
iszero(x),
eq(y, div(product, x))
)
) { () }
if iszero(eq(product, product_raw)) { () }
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("256bit", _type.numBits() == 256)
("gt128bit", _type.numBits() > 128)
.render();
});
}
std::string YulUtilFunctions::wrappingIntMulFunction(IntegerType const& _type)
{
std::string functionName = "wrapping_mul_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (x, y) -> product {
product := (mul(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
std::string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{
std::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))
("panicDivZero", panicFunction(PanicCode::DivisionByZero))
("panicOverflow", panicFunction(PanicCode::UnderOverflow))
.render();
});
}
std::string YulUtilFunctions::wrappingIntDivFunction(IntegerType const& _type)
{
std::string functionName = "wrapping_div_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (x, y) -> r {
x := (x)
y := (y)
if iszero(y) { () }
r := sdiv(x, y)
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
("signed", _type.isSigned())
("error", panicFunction(PanicCode::DivisionByZero))
.render();
});
}
std::string YulUtilFunctions::intModFunction(IntegerType const& _type)
{
std::string functionName = "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(PanicCode::DivisionByZero))
.render();
});
}
std::string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
{
std::string functionName = "checked_sub_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function (x, y) -> diff {
x := (x)
y := (y)
diff := sub(x, y)
256bit>
// underflow, if y >= 0 and diff > x
// overflow, if y < 0 and diff < x
if or(
and(iszero(slt(y, 0)), sgt(diff, x)),
and(slt(y, 0), slt(diff, x))
) { () }
if or(
slt(diff, ),
sgt(diff, )
) { () }
256bit>
256bit>
if gt(diff, x) { () }
if gt(diff, ) { () }
256bit>
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
("256bit", _type.numBits() == 256)
.render();
});
}
std::string YulUtilFunctions::wrappingIntSubFunction(IntegerType const& _type)
{
std::string functionName = "wrapping_sub_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function (x, y) -> diff {
diff := (sub(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
std::string YulUtilFunctions::overflowCheckedIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
std::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();
});
}
std::string YulUtilFunctions::overflowCheckedIntLiteralExpFunction(
RationalNumberType const& _baseType,
IntegerType const& _exponentType,
IntegerType const& _commonType
)
{
solAssert(!_exponentType.isSigned(), "");
solAssert(_baseType.isNegative() == _commonType.isSigned(), "");
solAssert(_commonType.numBits() == 256, "");
std::string functionName = "checked_exp_" + _baseType.richIdentifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]()
{
// Converts a bigint number into u256 (negative numbers represented in two's complement form.)
// We assume that `_v` fits in 256 bits.
auto bigint2u = [&](bigint const& _v) -> u256
{
if (_v < 0)
return s2u(s256(_v));
return u256(_v);
};
// Calculates the upperbound for exponentiation, that is, calculate `b`, such that
// _base**b <= _maxValue and _base**(b + 1) > _maxValue
auto findExponentUpperbound = [](bigint const _base, bigint const _maxValue) -> unsigned
{
// There is no overflow for these cases
if (_base == 0 || _base == -1 || _base == 1)
return 0;
unsigned first = 0;
unsigned last = 255;
unsigned middle;
while (first < last)
{
middle = (first + last) / 2;
if (
// The condition on msb is a shortcut that avoids computing large powers in
// arbitrary precision.
boost::multiprecision::msb(_base) * middle <= boost::multiprecision::msb(_maxValue) &&
boost::multiprecision::pow(_base, middle) <= _maxValue
)
{
if (boost::multiprecision::pow(_base, middle + 1) > _maxValue)
return middle;
else
first = middle + 1;
}
else
last = middle;
}
return last;
};
bigint baseValue = _baseType.isNegative() ?
u2s(_baseType.literalValue(nullptr)) :
_baseType.literalValue(nullptr);
bool needsOverflowCheck = !((baseValue == 0) || (baseValue == -1) || (baseValue == 1));
unsigned exponentUpperbound;
if (_baseType.isNegative())
{
// Only checks for underflow. The only case where this can be a problem is when, for a
// negative base, say `b`, and an even exponent, say `e`, `b**e = 2**255` (which is an
// overflow.) But this never happens because, `255 = 3*5*17`, and therefore there is no even
// number `e` such that `b**e = 2**255`.
exponentUpperbound = findExponentUpperbound(abs(baseValue), abs(_commonType.minValue()));
bigint power = boost::multiprecision::pow(baseValue, exponentUpperbound);
bigint overflowedPower = boost::multiprecision::pow(baseValue, exponentUpperbound + 1);
if (needsOverflowCheck)
solAssert(
(power <= _commonType.maxValue()) && (power >= _commonType.minValue()) &&
!((overflowedPower <= _commonType.maxValue()) && (overflowedPower >= _commonType.minValue())),
"Incorrect exponent upper bound calculated."
);
}
else
{
exponentUpperbound = findExponentUpperbound(baseValue, _commonType.maxValue());
if (needsOverflowCheck)
solAssert(
boost::multiprecision::pow(baseValue, exponentUpperbound) <= _commonType.maxValue() &&
boost::multiprecision::pow(baseValue, exponentUpperbound + 1) > _commonType.maxValue(),
"Incorrect exponent upper bound calculated."
);
}
return Whiskers(R"(
function (exponent) -> power {
exponent := (exponent)
if gt(exponent, ) { () }
power := exp(, exponent)
}
)")
("functionName", functionName)
("exponentCleanupFunction", cleanupFunction(_exponentType))
("needsOverflowCheck", needsOverflowCheck)
("exponentUpperbound", std::to_string(exponentUpperbound))
("panic", panicFunction(PanicCode::UnderOverflow))
("base", bigint2u(baseValue).str())
.render();
});
}
std::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), "");
std::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(PanicCode::UnderOverflow))
("expLoop", overflowCheckedExpLoopFunction())
.render();
});
}
std::string YulUtilFunctions::overflowCheckedSignedExpFunction()
{
std::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(PanicCode::UnderOverflow))
("expLoop", overflowCheckedExpLoopFunction())
("shr_1", shiftRightFunction(1))
.render();
});
}
std::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.
std::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(PanicCode::UnderOverflow))
("shr_1", shiftRightFunction(1))
.render();
});
}
std::string YulUtilFunctions::wrappingIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
std::string functionName = "wrapping_exp_" + _type.identifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function (base, exponent) -> power {
base := (base)
exponent := (exponent)
power := (exp(base, exponent))
}
)")
("functionName", functionName)
("baseCleanupFunction", cleanupFunction(_type))
("exponentCleanupFunction", cleanupFunction(_exponentType))
.render();
});
}
std::string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
std::string functionName = "array_length_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function (value, len) -> length {
length := mload(value)
length := sload(value)
length := (length)
length := len
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);
w("calldata", _type.location() == DataLocation::CallData);
if (_type.location() == DataLocation::Storage)
{
w("byteArray", _type.isByteArrayOrString());
if (_type.isByteArrayOrString())
w("extractByteArrayLength", extractByteArrayLengthFunction());
}
return w.render();
});
}
std::string YulUtilFunctions::extractByteArrayLengthFunction()
{
std::string functionName = "extract_byte_array_length";
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function (data) -> length {
length := div(data, 2)
let outOfPlaceEncoding := and(data, 1)
if iszero(outOfPlaceEncoding) {
length := and(length, 0x7f)
}
if eq(outOfPlaceEncoding, lt(length, 32)) {
()
}
}
)");
w("functionName", functionName);
w("panic", panicFunction(PanicCode::StorageEncodingError));
return w.render();
});
}
std::string YulUtilFunctions::resizeArrayFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32);
if (_type.isByteArrayOrString())
return resizeDynamicByteArrayFunction(_type);
std::string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function (array, newLen) {
if gt(newLen, ) {
()
}
let oldLen := (array)
// Store new length
sstore(array, newLen)
(array, oldLen, newLen)
})");
templ("functionName", functionName);
templ("maxArrayLength", (u256(1) << 64).str());
templ("panic", panicFunction(util::PanicCode::ResourceError));
templ("fetchLength", arrayLengthFunction(_type));
templ("isDynamic", _type.isDynamicallySized());
bool isMappingBase = _type.baseType()->category() == Type::Category::Mapping;
templ("needsClearing", !isMappingBase);
if (!isMappingBase)
templ("cleanUpArrayEnd", cleanUpStorageArrayEndFunction(_type));
return templ.render();
});
}
std::string YulUtilFunctions::cleanUpStorageArrayEndFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.baseType()->category() != Type::Category::Mapping, "");
solAssert(!_type.isByteArrayOrString(), "");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32);
std::string functionName = "cleanup_storage_array_end_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](std::vector& _args, std::vector&) {
_args = {"array", "len", "startIndex"};
return Whiskers(R"(
if lt(startIndex, len) {
// Size was reduced, clear end of array
let oldSlotCount := (len)
let newSlotCount := (startIndex)
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(startIndex, ), )
if gt(offset, 0) { (sub(deleteStart, 1), offset) }
(deleteStart, deleteEnd)
}
)")
("convertToSize", arrayConvertLengthToSize(_type))
("dataPosition", arrayDataAreaFunction(_type))
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("packed", _type.baseType()->storageBytes() <= 16)
("itemsPerSlot", std::to_string(32 / _type.baseType()->storageBytes()))
("storageBytes", std::to_string(_type.baseType()->storageBytes()))
("partialClearStorageSlot", partialClearStorageSlotFunction())
.render();
});
}
std::string YulUtilFunctions::resizeDynamicByteArrayFunction(ArrayType const& _type)
{
std::string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](std::vector& _args, std::vector&) {
_args = {"array", "newLen"};
return Whiskers(R"(
let data := sload(array)
let oldLen := (data)
if gt(newLen, oldLen) {
(array, data, oldLen, newLen)
}
if lt(newLen, oldLen) {
(array, data, oldLen, newLen)
}
)")
("extractLength", extractByteArrayLengthFunction())
("decreaseSize", decreaseByteArraySizeFunction(_type))
("increaseSize", increaseByteArraySizeFunction(_type))
.render();
});
}
std::string YulUtilFunctions::cleanUpDynamicByteArrayEndSlotsFunction(ArrayType const& _type)
{
solAssert(_type.isByteArrayOrString(), "");
solAssert(_type.isDynamicallySized(), "");
std::string functionName = "clean_up_bytearray_end_slots_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](std::vector& _args, std::vector&) {
_args = {"array", "len", "startIndex"};
return Whiskers(R"(
if gt(len, 31) {
let dataArea := (array)
let deleteStart := add(dataArea, (startIndex))
// If we are clearing array to be short byte array, we want to clear only data starting from array data area.
if lt(startIndex, 32) { deleteStart := dataArea }
(deleteStart, add(dataArea, (len)))
}
)")
("dataLocation", arrayDataAreaFunction(_type))
("div32Ceil", divide32CeilFunction())
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
.render();
});
}
std::string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _type)
{
std::string functionName = "byte_array_decrease_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array, data, oldLen, newLen) {
switch lt(newLen, 32)
case 0 {
let arrayDataStart := (array)
let deleteStart := add(arrayDataStart, (newLen))
// we have to partially clear last slot that is still used
let offset := and(newLen, 0x1f)
if offset { (sub(deleteStart, 1), offset) }
(deleteStart, add(arrayDataStart, (oldLen)))
sstore(array, or(mul(2, newLen), 1))
}
default {
switch gt(oldLen, 31)
case 1 {
let arrayDataStart := (array)
// clear whole old array, as we are transforming to short bytes array
(add(arrayDataStart, 1), add(arrayDataStart, (oldLen)))
(array, newLen)
}
default {
sstore(array, (data, newLen))
}
}
})")
("functionName", functionName)
("dataPosition", arrayDataAreaFunction(_type))
("partialClearStorageSlot", partialClearStorageSlotFunction())
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("transitLongToShort", byteArrayTransitLongToShortFunction(_type))
("div32Ceil", divide32CeilFunction())
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
std::string YulUtilFunctions::increaseByteArraySizeFunction(ArrayType const& _type)
{
std::string functionName = "byte_array_increase_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](std::vector& _args, std::vector&) {
_args = {"array", "data", "oldLen", "newLen"};
return Whiskers(R"(
if gt(newLen, ) { () }
switch lt(oldLen, 32)
case 0 {
// in this case array stays unpacked, so we just set new length
sstore(array, add(mul(2, newLen), 1))
}
default {
switch lt(newLen, 32)
case 0 {
// we need to copy elements to data area as we changed array from packed to unpacked
data := and(not(0xff), data)
sstore((array), data)
sstore(array, add(mul(2, newLen), 1))
}
default {
// here array stays packed, we just need to increase length
sstore(array, (data, newLen))
}
}
)")
("panic", panicFunction(PanicCode::ResourceError))
("maxArrayLength", (u256(1) << 64).str())
("dataPosition", arrayDataAreaFunction(_type))
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
std::string YulUtilFunctions::byteArrayTransitLongToShortFunction(ArrayType const& _type)
{
std::string functionName = "transit_byte_array_long_to_short_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array, len) {
// we need to copy elements from old array to new
// we want to copy only elements that are part of the array after resizing
let dataPos := (array)
let data := (sload(dataPos), len)
sstore(array, data)
sstore(dataPos, 0)
})")
("functionName", functionName)
("dataPosition", arrayDataAreaFunction(_type))
("extractUsedApplyLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
std::string YulUtilFunctions::shortByteArrayEncodeUsedAreaSetLengthFunction()
{
std::string functionName = "extract_used_part_and_set_length_of_short_byte_array";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (data, len) -> used {
// we want to save only elements that are part of the array after resizing
// others should be set to zero
data := (data, len)
used := or(data, mul(2, len))
})")
("functionName", functionName)
("maskBytes", maskBytesFunctionDynamic())
.render();
});
}
std::string YulUtilFunctions::longByteArrayStorageIndexAccessNoCheckFunction()
{
return m_functionCollector.createFunction(
"long_byte_array_index_access_no_checks",
[&](std::vector& _args, std::vector& _returnParams) {
_args = {"array", "index"};
_returnParams = {"slot", "offset"};
return Whiskers(R"(
offset := sub(31, mod(index, 0x20))
let dataArea := (array)
slot := add(dataArea, div(index, 0x20))
)")
("dataAreaFunc", arrayDataAreaFunction(*TypeProvider::bytesStorage()))
.render();
}
);
}
std::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.isByteArrayOrString())
return storageByteArrayPopFunction(_type);
std::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)
+setToZero>(slot, offset)+setToZero>
sstore(array, newLen)
})")
("functionName", functionName)
("panic", panicFunction(PanicCode::EmptyArrayPop))
("fetchLength", arrayLengthFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
(
"setToZero",
_type.baseType()->category() != Type::Category::Mapping ? storageSetToZeroFunction(*_type.baseType()) : ""
)
.render();
});
}
std::string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solAssert(_type.isByteArrayOrString(), "");
std::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 oldLen
case 32 {
// Here we have a special case where array transitions to shorter than 32
// So we need to copy data
(array, 31)
}
default {
let newLen := sub(oldLen, 1)
switch lt(oldLen, 32)
case 1 {
sstore(array, (data, newLen))
}
default {
let slot, offset := (array, newLen)
(slot, offset)
sstore(array, sub(data, 2))
}
}
})")
("functionName", functionName)
("panic", panicFunction(PanicCode::EmptyArrayPop))
("extractByteArrayLength", extractByteArrayLengthFunction())
("transitLongToShort", byteArrayTransitLongToShortFunction(_type))
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
("indexAccessNoChecks", longByteArrayStorageIndexAccessNoCheckFunction())
("setToZero", storageSetToZeroFunction(*_type.baseType()))
.render();
});
}
std::string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type, Type const* _fromType)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
if (!_fromType)
_fromType = _type.baseType();
else if (_fromType->isValueType())
solUnimplementedAssert(*_fromType == *_type.baseType());
std::string functionName =
std::string{"array_push_from_"} +
_fromType->identifier() +
"_to_" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function (array ) {
let data := sload(array)
let oldLen := (data)
if iszero(lt(oldLen, )) { () }
switch gt(oldLen, 31)
case 0 {
let value := byte(0 )
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 )
}
let oldLen := sload(array)
if iszero(lt(oldLen, )) {