solidity/libsolidity/codegen/YulUtilFunctions.cpp
nishant-sachdeva dec511aad8 Corresponding code in the .cpp file has been commented instead of begin removed pending preliminary reviews
Code generators needed fixing of the cleanup process during typecasting of bytes and integers
2022-01-28 19:56:15 +05:30

4582 lines
146 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Component that can generate various useful Yul functions.
*/
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/FunctionSelector.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
#include <libsolidity/ast/TypeProvider.h>
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
string YulUtilFunctions::identityFunction()
{
string functionName = "identity";
return m_functionCollector.createFunction("identity", [&](vector<string>& _args, vector<string>& _rets) {
_args.push_back("value");
_rets.push_back("ret");
return "ret := value";
});
}
string YulUtilFunctions::combineExternalFunctionIdFunction()
{
string functionName = "combine_external_function_id";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(addr, selector) -> combined {
combined := <shl64>(or(<shl32>(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 <functionName>(combined) -> addr, selector {
combined := <shr64>(combined)
selector := and(combined, 0xffffffff)
addr := <shr32>(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 <functionName>(src, dst, length) {
calldatacopy(dst, src, length)
// clear end
mstore(add(dst, length), 0)
}
)")
("functionName", functionName)
.render();
}
else
{
return Whiskers(R"(
function <functionName>(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::copyLiteralToMemoryFunction(string const& _literal)
{
string functionName = "copy_literal_to_memory_" + util::toHex(util::keccak256(_literal).asBytes());
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> memPtr {
memPtr := <arrayAllocationFunction>(<size>)
<storeLiteralInMem>(add(memPtr, 32))
}
)")
("functionName", functionName)
("arrayAllocationFunction", allocateMemoryArrayFunction(*TypeProvider::array(DataLocation::Memory, true)))
("size", to_string(_literal.size()))
("storeLiteralInMem", storeLiteralInMemoryFunction(_literal))
.render();
});
}
string YulUtilFunctions::storeLiteralInMemoryFunction(string const& _literal)
{
string functionName = "store_literal_in_memory_" + util::toHex(util::keccak256(_literal).asBytes());
return m_functionCollector.createFunction(functionName, [&]() {
size_t words = (_literal.length() + 31) / 32;
vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = to_string(i * 32);
wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32));
}
return Whiskers(R"(
function <functionName>(memPtr) {
<#word>
mstore(add(memPtr, <offset>), <wordValue>)
</word>
}
)")
("functionName", functionName)
("word", wordParams)
.render();
});
}
string YulUtilFunctions::copyLiteralToStorageFunction(string const& _literal)
{
string functionName = "copy_literal_to_storage_" + util::toHex(util::keccak256(_literal).asBytes());
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
_args = {"slot"};
if (_literal.size() >= 32)
{
size_t words = (_literal.length() + 31) / 32;
vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = to_string(i);
wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32));
}
return Whiskers(R"(
let oldLen := <byteArrayLength>(sload(slot))
<cleanUpArrayEnd>(slot, oldLen, <length>)
sstore(slot, <encodedLen>)
let dstPtr := <dataArea>(slot)
<#word>
sstore(add(dstPtr, <offset>), <wordValue>)
</word>
)")
("byteArrayLength", extractByteArrayLengthFunction())
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
("dataArea", arrayDataAreaFunction(*TypeProvider::bytesStorage()))
("word", wordParams)
("length", to_string(_literal.size()))
("encodedLen", to_string(2 * _literal.size() + 1))
.render();
}
else
return Whiskers(R"(
let oldLen := <byteArrayLength>(sload(slot))
<cleanUpArrayEnd>(slot, oldLen, <length>)
sstore(slot, add(<wordValue>, <encodedLen>))
)")
("byteArrayLength", extractByteArrayLengthFunction())
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
("wordValue", formatAsStringOrNumber(_literal))
("length", to_string(_literal.size()))
("encodedLen", to_string(2 * _literal.size()))
.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 <functionName>(condition) {
if iszero(condition) { <error> }
}
)")
("error", _assert ? panicFunction(PanicCode::Assert) + "()" : "revert(0, 0)")
("functionName", functionName)
.render();
int const hashHeaderSize = 4;
u256 const errorHash = util::selectorFromSignature("Error(string)");
string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector)
.tupleEncoder(
{_messageType},
{TypeProvider::stringMemory()}
);
return Whiskers(R"(
function <functionName>(condition <messageVars>) {
if iszero(condition) {
let memPtr := <allocateUnbounded>()
mstore(memPtr, <errorHash>)
let end := <abiEncodeFunc>(add(memPtr, <hashHeaderSize>) <messageVars>)
revert(memPtr, sub(end, memPtr))
}
}
)")
("functionName", functionName)
("allocateUnbounded", allocateUnboundedFunction())
("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 <functionName>(value) -> aligned {
<body>
}
)");
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<IntegerType const&>(_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<EnumType const&>(_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();
});
}
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 <functionName>(value) -> newValue {
newValue :=
<?hasShifts>
shl(<numBits>, value)
<!hasShifts>
mul(value, <multiplier>)
</hasShifts>
}
)")
("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 <functionName>(bits, value) -> newValue {
newValue :=
<?hasShifts>
shl(bits, value)
<!hasShifts>
mul(value, exp(2, bits))
</hasShifts>
}
)")
("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 <functionName>(value) -> newValue {
newValue :=
<?hasShifts>
shr(<numBits>, value)
<!hasShifts>
div(value, <multiplier>)
</hasShifts>
}
)")
("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 <functionName>(bits, value) -> newValue {
newValue :=
<?hasShifts>
shr(bits, value)
<!hasShifts>
div(value, exp(2, bits))
</hasShifts>
}
)")
("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 <functionName>(bits, value) -> result {
<?hasShifts>
result := sar(bits, value)
<!hasShifts>
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)) }
</hasShifts>
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
}
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<IntegerType const&>(_amountType).isSigned(), "");
string const functionName = "shift_left_" + _type.identifier() + "_" + _amountType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, bits) -> result {
bits := <cleanAmount>(bits)
result := <cleanup>(<shift>(bits, <cleanup>(value)))
}
)")
("functionName", functionName)
("cleanAmount", cleanupFunction(_amountType))
("shift", shiftLeftFunctionDynamic())
("cleanup", cleanupFunction(_type))
.render();
});
}
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<IntegerType const&>(_amountType).isSigned(), "");
IntegerType const* integerType = dynamic_cast<IntegerType const*>(&_type);
bool valueSigned = integerType && integerType->isSigned();
string const functionName = "shift_right_" + _type.identifier() + "_" + _amountType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, bits) -> result {
bits := <cleanAmount>(bits)
result := <cleanup>(<shift>(bits, <cleanup>(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 <functionName>(value, toInsert) -> result {
let mask := <mask>
toInsert := <shl>(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 <functionName>(value, shiftBytes, toInsert) -> result {
let shiftBits := mul(shiftBytes, 8)
let mask := <shl>(shiftBits, <mask>)
toInsert := <shl>(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::maskBytesFunctionDynamic()
{
string functionName = "mask_bytes_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(data, bytes) -> result {
let mask := not(<shr>(mul(8, bytes), not(0)))
result := and(data, mask)
})")
("functionName", functionName)
("shr", shiftRightFunctionDynamic())
.render();
});
}
string YulUtilFunctions::maskLowerOrderBytesFunction(size_t _bytes)
{
string functionName = "mask_lower_order_bytes_" + to_string(_bytes);
solAssert(_bytes <= 32, "");
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(data) -> result {
result := and(data, <mask>)
})")
("functionName", functionName)
("mask", formatNumber((~u256(0)) >> (256 - 8 * _bytes)))
.render();
});
}
string YulUtilFunctions::maskLowerOrderBytesFunctionDynamic()
{
string functionName = "mask_lower_order_bytes_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(data, bytes) -> result {
let mask := not(<shl>(mul(8, bytes), not(0)))
result := and(data, mask)
})")
("functionName", functionName)
("shl", shiftLeftFunctionDynamic())
.render();
});
}
string YulUtilFunctions::roundUpFunction()
{
string functionName = "round_up_to_mul_of_32";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> result {
result := and(add(value, 31), not(31))
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::divide32CeilFunction()
{
return m_functionCollector.createFunction(
"divide_by_32_ceil",
[&](vector<string>& _args, vector<string>& _ret) {
_args = {"value"};
_ret = {"result"};
return "result := div(add(value, 31), 32)";
}
);
}
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) { <panic>() }
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
<?signed>
// overflow, if x >= 0 and y > (maxValue - x)
if and(iszero(slt(x, 0)), sgt(y, sub(<maxValue>, x))) { <panic>() }
// underflow, if x < 0 and y < (minValue - x)
if and(slt(x, 0), slt(y, sub(<minValue>, x))) { <panic>() }
<!signed>
// overflow, if x > (maxValue - y)
if gt(x, sub(<maxValue>, y)) { <panic>() }
</signed>
sum := add(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
.render();
});
}
string YulUtilFunctions::wrappingIntAddFunction(IntegerType const& _type)
{
string functionName = "wrapping_add_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
sum := <cleanupFunction>(add(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.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 <functionName>(x, y) -> product {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
<?signed>
// overflow, if x > 0, y > 0 and x > (maxValue / y)
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { <panic>() }
// underflow, if x > 0, y < 0 and y < (minValue / x)
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { <panic>() }
// underflow, if x < 0, y > 0 and x < (minValue / y)
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { <panic>() }
// overflow, if x < 0, y < 0 and x < (maxValue / y)
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { <panic>() }
<!signed>
// overflow, if x != 0 and y > (maxValue / x)
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { <panic>() }
</signed>
product := mul(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
.render();
});
}
string YulUtilFunctions::wrappingIntMulFunction(IntegerType const& _type)
{
string functionName = "wrapping_mul_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> product {
product := <cleanupFunction>(mul(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{
string functionName = "checked_div_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { <panicDivZero>() }
<?signed>
// overflow for minVal / -1
if and(
eq(x, <minVal>),
eq(y, sub(0, 1))
) { <panicOverflow>() }
</signed>
r := <?signed>s</signed>div(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("minVal", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panicDivZero", panicFunction(PanicCode::DivisionByZero))
("panicOverflow", panicFunction(PanicCode::UnderOverflow))
.render();
});
}
string YulUtilFunctions::wrappingIntDivFunction(IntegerType const& _type)
{
string functionName = "wrapping_div_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { <error>() }
r := <?signed>s</signed>div(x, y)
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
("signed", _type.isSigned())
("error", panicFunction(PanicCode::DivisionByZero))
.render();
});
}
string YulUtilFunctions::intModFunction(IntegerType const& _type)
{
string functionName = "mod_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { <panic>() }
r := <?signed>s</signed>mod(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::DivisionByZero))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
{
string functionName = "checked_sub_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
<?signed>
// underflow, if y >= 0 and x < (minValue + y)
if and(iszero(slt(y, 0)), slt(x, add(<minValue>, y))) { <panic>() }
// overflow, if y < 0 and x > (maxValue + y)
if and(slt(y, 0), sgt(x, add(<maxValue>, y))) { <panic>() }
<!signed>
if lt(x, y) { <panic>() }
</signed>
diff := sub(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
.render();
});
}
string YulUtilFunctions::wrappingIntSubFunction(IntegerType const& _type)
{
string functionName = "wrapping_sub_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
diff := <cleanupFunction>(sub(x, y))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(_type))
.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 <functionName>(base, exponent) -> power {
base := <baseCleanupFunction>(base)
exponent := <exponentCleanupFunction>(exponent)
<?signed>
power := <exp>(base, exponent, <minValue>, <maxValue>)
<!signed>
power := <exp>(base, exponent, <maxValue>)
</signed>
}
)")
("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::overflowCheckedIntLiteralExpFunction(
RationalNumberType const& _baseType,
IntegerType const& _exponentType,
IntegerType const& _commonType
)
{
solAssert(!_exponentType.isSigned(), "");
solAssert(_baseType.isNegative() == _commonType.isSigned(), "");
solAssert(_commonType.numBits() == 256, "");
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 <functionName>(exponent) -> power {
exponent := <exponentCleanupFunction>(exponent)
<?needsOverflowCheck>
if gt(exponent, <exponentUpperbound>) { <panic>() }
</needsOverflowCheck>
power := exp(<base>, exponent)
}
)")
("functionName", functionName)
("exponentCleanupFunction", cleanupFunction(_exponentType))
("needsOverflowCheck", needsOverflowCheck)
("exponentUpperbound", to_string(exponentUpperbound))
("panic", panicFunction(PanicCode::UnderOverflow))
("base", bigint2u(baseValue).str())
.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 <functionName>(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) { <panic>() }
power := exp(2, exponent)
if gt(power, max) { <panic>() }
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) { <panic>() }
leave
}
power, base := <expLoop>(1, base, exponent, max)
if gt(power, div(max, base)) { <panic>() }
power := mul(power, base)
}
)")
("functionName", functionName)
("panic", panicFunction(PanicCode::UnderOverflow))
("expLoop", overflowCheckedExpLoopFunction())
.render();
});
}
string YulUtilFunctions::overflowCheckedSignedExpFunction()
{
string functionName = "checked_exp_signed";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(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)) { <panic>() } }
case 0 { if slt(base, sdiv(max, base)) { <panic>() } }
if and(exponent, 1)
{
power := base
}
base := mul(base, base)
exponent := <shr_1>(exponent)
// Below this point, base is always positive.
power, base := <expLoop>(power, base, exponent, max)
if and(sgt(power, 0), gt(power, div(max, base))) { <panic>() }
if and(slt(power, 0), slt(power, sdiv(min, base))) { <panic>() }
power := mul(power, base)
}
)")
("functionName", functionName)
("panic", panicFunction(PanicCode::UnderOverflow))
("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 <functionName>(_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)) { <panic>() }
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 := <shr_1>(exponent)
}
}
)")
("functionName", functionName)
("panic", panicFunction(PanicCode::UnderOverflow))
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::wrappingIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
string functionName = "wrapping_exp_" + _type.identifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent) -> power {
base := <baseCleanupFunction>(base)
exponent := <exponentCleanupFunction>(exponent)
power := <baseCleanupFunction>(exp(base, exponent))
}
)")
("functionName", functionName)
("baseCleanupFunction", cleanupFunction(_type))
("exponentCleanupFunction", cleanupFunction(_exponentType))
.render();
});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(value<?dynamic><?calldata>, len</calldata></dynamic>) -> length {
<?dynamic>
<?memory>
length := mload(value)
</memory>
<?storage>
length := sload(value)
<?byteArray>
length := <extractByteArrayLength>(length)
</byteArray>
</storage>
<?calldata>
length := len
</calldata>
<!dynamic>
length := <length>
</dynamic>
}
)");
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.isByteArray());
if (_type.isByteArray())
w("extractByteArrayLength", extractByteArrayLengthFunction());
}
return w.render();
});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
string functionName = "extract_byte_array_length";
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(data) -> length {
length := div(data, 2)
let outOfPlaceEncoding := and(data, 1)
if iszero(outOfPlaceEncoding) {
length := and(length, 0x7f)
}
if eq(outOfPlaceEncoding, lt(length, 32)) {
<panic>()
}
}
)");
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.isByteArray())
return resizeDynamicByteArrayFunction(_type);
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(array, newLen) {
if gt(newLen, <maxArrayLength>) {
<panic>()
}
let oldLen := <fetchLength>(array)
<?isDynamic>
// Store new length
sstore(array, newLen)
</isDynamic>
<?needsClearing>
<cleanUpArrayEnd>(array, oldLen, newLen)
</needsClearing>
})");
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();
});
}
string YulUtilFunctions::cleanUpStorageArrayEndFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.baseType()->category() != Type::Category::Mapping, "");
solAssert(!_type.isByteArray(), "");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32);
string functionName = "cleanup_storage_array_end_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
_args = {"array", "len", "startIndex"};
return Whiskers(R"(
if lt(startIndex, len) {
// Size was reduced, clear end of array
let oldSlotCount := <convertToSize>(len)
let newSlotCount := <convertToSize>(startIndex)
let arrayDataStart := <dataPosition>(array)
let deleteStart := add(arrayDataStart, newSlotCount)
let deleteEnd := add(arrayDataStart, oldSlotCount)
<?packed>
// 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, <itemsPerSlot>), <storageBytes>)
if gt(offset, 0) { <partialClearStorageSlot>(sub(deleteStart, 1), offset) }
</packed>
<clearStorageRange>(deleteStart, deleteEnd)
}
)")
("convertToSize", arrayConvertLengthToSize(_type))
("dataPosition", arrayDataAreaFunction(_type))
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("packed", _type.baseType()->storageBytes() <= 16)
("itemsPerSlot", to_string(32 / _type.baseType()->storageBytes()))
("storageBytes", to_string(_type.baseType()->storageBytes()))
("partialClearStorageSlot", partialClearStorageSlotFunction())
.render();
});
}
string YulUtilFunctions::resizeDynamicByteArrayFunction(ArrayType const& _type)
{
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
_args = {"array", "newLen"};
return Whiskers(R"(
let data := sload(array)
let oldLen := <extractLength>(data)
if gt(newLen, oldLen) {
<increaseSize>(array, data, oldLen, newLen)
}
if lt(newLen, oldLen) {
<decreaseSize>(array, data, oldLen, newLen)
}
)")
("extractLength", extractByteArrayLengthFunction())
("decreaseSize", decreaseByteArraySizeFunction(_type))
("increaseSize", increaseByteArraySizeFunction(_type))
.render();
});
}
string YulUtilFunctions::cleanUpDynamicByteArrayEndSlotsFunction(ArrayType const& _type)
{
solAssert(_type.isByteArray(), "");
solAssert(_type.isDynamicallySized(), "");
string functionName = "clean_up_bytearray_end_slots_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
_args = {"array", "len", "startIndex"};
return Whiskers(R"(
if gt(len, 31) {
let dataArea := <dataLocation>(array)
let deleteStart := add(dataArea, <div32Ceil>(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 }
<clearStorageRange>(deleteStart, add(dataArea, <div32Ceil>(len)))
}
)")
("dataLocation", arrayDataAreaFunction(_type))
("div32Ceil", divide32CeilFunction())
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
.render();
});
}
string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _type)
{
string functionName = "byte_array_decrease_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, data, oldLen, newLen) {
switch lt(newLen, 32)
case 0 {
let arrayDataStart := <dataPosition>(array)
let deleteStart := add(arrayDataStart, <div32Ceil>(newLen))
// we have to partially clear last slot that is still used
let offset := and(newLen, 0x1f)
if offset { <partialClearStorageSlot>(sub(deleteStart, 1), offset) }
<clearStorageRange>(deleteStart, add(arrayDataStart, <div32Ceil>(oldLen)))
sstore(array, or(mul(2, newLen), 1))
}
default {
switch gt(oldLen, 31)
case 1 {
let arrayDataStart := <dataPosition>(array)
// clear whole old array, as we are transforming to short bytes array
<clearStorageRange>(add(arrayDataStart, 1), add(arrayDataStart, <div32Ceil>(oldLen)))
<transitLongToShort>(array, newLen)
}
default {
sstore(array, <encodeUsedSetLen>(data, newLen))
}
}
})")
("functionName", functionName)
("dataPosition", arrayDataAreaFunction(_type))
("partialClearStorageSlot", partialClearStorageSlotFunction())
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("transitLongToShort", byteArrayTransitLongToShortFunction(_type))
("div32Ceil", divide32CeilFunction())
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
string YulUtilFunctions::increaseByteArraySizeFunction(ArrayType const& _type)
{
string functionName = "byte_array_increase_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
_args = {"array", "data", "oldLen", "newLen"};
return Whiskers(R"(
if gt(newLen, <maxArrayLength>) { <panic>() }
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(<dataPosition>(array), data)
sstore(array, add(mul(2, newLen), 1))
}
default {
// here array stays packed, we just need to increase length
sstore(array, <encodeUsedSetLen>(data, newLen))
}
}
)")
("panic", panicFunction(PanicCode::ResourceError))
("maxArrayLength", (u256(1) << 64).str())
("dataPosition", arrayDataAreaFunction(_type))
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
string YulUtilFunctions::byteArrayTransitLongToShortFunction(ArrayType const& _type)
{
string functionName = "transit_byte_array_long_to_short_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(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 := <dataPosition>(array)
let data := <extractUsedApplyLen>(sload(dataPos), len)
sstore(array, data)
sstore(dataPos, 0)
})")
("functionName", functionName)
("dataPosition", arrayDataAreaFunction(_type))
("extractUsedApplyLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
string YulUtilFunctions::shortByteArrayEncodeUsedAreaSetLengthFunction()
{
string functionName = "extract_used_part_and_set_length_of_short_byte_array";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(data, len) -> used {
// we want to save only elements that are part of the array after resizing
// others should be set to zero
data := <maskBytes>(data, len)
used := or(data, mul(2, len))
})")
("functionName", functionName)
("maskBytes", maskBytesFunctionDynamic())
.render();
});
}
string YulUtilFunctions::longByteArrayStorageIndexAccessNoCheckFunction()
{
return m_functionCollector.createFunction(
"long_byte_array_index_access_no_checks",
[&](vector<string>& _args, vector<string>& _returnParams) {
_args = {"array", "index"};
_returnParams = {"slot", "offset"};
return Whiskers(R"(
offset := sub(31, mod(index, 0x20))
let dataArea := <dataAreaFunc>(array)
slot := add(dataArea, div(index, 0x20))
)")
("dataAreaFunc", arrayDataAreaFunction(*TypeProvider::bytesStorage()))
.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 <functionName>(array) {
let oldLen := <fetchLength>(array)
if iszero(oldLen) { <panic>() }
let newLen := sub(oldLen, 1)
let slot, offset := <indexAccess>(array, newLen)
<?+setToZero><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();
});
}
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 <functionName>(array) {
let data := sload(array)
let oldLen := <extractByteArrayLength>(data)
if iszero(oldLen) { <panic>() }
switch oldLen
case 32 {
// Here we have a special case where array transitions to shorter than 32
// So we need to copy data
<transitLongToShort>(array, 31)
}
default {
let newLen := sub(oldLen, 1)
switch lt(oldLen, 32)
case 1 {
sstore(array, <encodeUsedSetLen>(data, newLen))
}
default {
let slot, offset := <indexAccessNoChecks>(array, newLen)
<setToZero>(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();
});
}
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());
string functionName =
string{"array_push_from_"} +
_fromType->identifier() +
"_to_" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array <values>) {
<?isByteArray>
let data := sload(array)
let oldLen := <extractByteArrayLength>(data)
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
switch gt(oldLen, 31)
case 0 {
let value := byte(0 <values>)
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 := <dataAreaFunction>(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 := <shl>(shiftBits, and(0xff, value))
let mask := <shl>(shiftBits, 0xff)
data := or(and(data, not(mask)), valueShifted)
sstore(array, data)
}
}
default {
sstore(array, add(data, 2))
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset <values>)
}
<!isByteArray>
let oldLen := sload(array)
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
sstore(array, add(oldLen, 1))
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset <values>)
</isByteArray>
})")
("functionName", functionName)
("values", _fromType->sizeOnStack() == 0 ? "" : ", " + suffixedVariableNameList("value", 0, _fromType->sizeOnStack()))
("panic", panicFunction(PanicCode::ResourceError))
("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
("dataAreaFunction", arrayDataAreaFunction(_type))
("isByteArray", _type.isByteArray())
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_fromType, *_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("shl", shiftLeftFunctionDynamic())
.render();
});
}
string YulUtilFunctions::storageArrayPushZeroFunction(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_zero_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array) -> slot, offset {
<?isBytes>
let data := sload(array)
let oldLen := <extractLength>(data)
<increaseBytesSize>(array, data, oldLen, add(oldLen, 1))
<!isBytes>
let oldLen := <fetchLength>(array)
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
sstore(array, add(oldLen, 1))
</isBytes>
slot, offset := <indexAccess>(array, oldLen)
})")
("functionName", functionName)
("isBytes", _type.isByteArray())
("increaseBytesSize", _type.isByteArray() ? increaseByteArraySizeFunction(_type) : "")
("extractLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
("panic", panicFunction(PanicCode::ResourceError))
("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 <functionName>(slot, offset) {
let mask := <shr>(mul(8, sub(32, offset)), <ones>)
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 <functionName>(start, end) {
for {} lt(start, end) { start := add(start, <increment>) }
{
<setToZero>(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 <functionName>(slot) {
<?dynamic>
<resizeArray>(slot, 0)
<!dynamic>
<?+clearRange><clearRange>(slot, add(slot, <lenToSize>(<len>)))</+clearRange>
</dynamic>
}
)")
("functionName", functionName)
("dynamic", _type.isDynamicallySized())
("resizeArray", _type.isDynamicallySized() ? resizeArrayFunction(_type) : "")
(
"clearRange",
_type.baseType()->category() != Type::Category::Mapping ?
clearStorageRangeFunction((_type.baseType()->storageBytes() < 32) ? *TypeProvider::uint256() : *_type.baseType()) :
""
)
("lenToSize", arrayConvertLengthToSize(_type))
("len", _type.length().str())
.render();
});
}
string YulUtilFunctions::clearStorageStructFunction(StructType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
string functionName = "clear_struct_storage_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
MemberList::MemberMap structMembers = _type.nativeMembers(nullptr);
vector<map<string, string>> memberSetValues;
set<u256> slotsCleared;
for (auto const& member: structMembers)
{
if (member.type->category() == Type::Category::Mapping)
continue;
if (member.type->storageBytes() < 32)
{
auto const& slotDiff = _type.storageOffsetsOfMember(member.name).first;
if (!slotsCleared.count(slotDiff))
{
memberSetValues.emplace_back().emplace("clearMember", "sstore(add(slot, " + slotDiff.str() + "), 0)");
slotsCleared.emplace(slotDiff);
}
}
else
{
auto const& [memberSlotDiff, memberStorageOffset] = _type.storageOffsetsOfMember(member.name);
solAssert(memberStorageOffset == 0, "");
memberSetValues.emplace_back().emplace("clearMember", Whiskers(R"(
<setZero>(add(slot, <memberSlotDiff>), <memberStorageOffset>)
)")
("setZero", storageSetToZeroFunction(*member.type))
("memberSlotDiff", memberSlotDiff.str())
("memberStorageOffset", to_string(memberStorageOffset))
.render()
);
}
}
return Whiskers(R"(
function <functionName>(slot) {
<#member>
<clearMember>
</member>
}
)")
("functionName", functionName)
("member", memberSetValues)
.render();
});
}
string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
""
);
if (!_toType.isDynamicallySized())
solAssert(!_fromType.isDynamicallySized() && _fromType.length() <= _toType.length(), "");
if (_fromType.isByteArray())
return copyByteArrayToStorageFunction(_fromType, _toType);
if (_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType())
return copyValueArrayStorageToStorageFunction(_fromType, _toType);
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(slot, value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) {
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
let length := <arrayLength>(value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>)
<resizeArray>(slot, length)
let srcPtr := <srcDataLocation>(value)
let elementSlot := <dstDataLocation>(slot)
let elementOffset := 0
for { let i := 0 } lt(i, length) {i := add(i, 1)} {
<?fromCalldata>
let <elementValues> :=
<?dynamicallyEncodedBase>
<accessCalldataTail>(value, srcPtr)
<!dynamicallyEncodedBase>
srcPtr
</dynamicallyEncodedBase>
<?isValueType>
<elementValues> := <readFromCalldataOrMemory>(<elementValues>)
</isValueType>
</fromCalldata>
<?fromMemory>
let <elementValues> := <readFromCalldataOrMemory>(srcPtr)
</fromMemory>
<?fromStorage>
let <elementValues> := srcPtr
</fromStorage>
<updateStorageValue>(elementSlot, elementOffset, <elementValues>)
srcPtr := add(srcPtr, <srcStride>)
<?multipleItemsPerSlot>
elementOffset := add(elementOffset, <storageStride>)
if gt(elementOffset, sub(32, <storageStride>)) {
elementOffset := 0
elementSlot := add(elementSlot, 1)
}
<!multipleItemsPerSlot>
elementSlot := add(elementSlot, <storageSize>)
</multipleItemsPerSlot>
}
}
)");
if (_fromType.dataStoredIn(DataLocation::Storage))
solAssert(!_fromType.isValueType(), "");
templ("functionName", functionName);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata);
templ("fromStorage", _fromType.dataStoredIn(DataLocation::Storage));
bool fromMemory = _fromType.dataStoredIn(DataLocation::Memory);
templ("fromMemory", fromMemory);
templ("fromCalldata", fromCalldata);
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
if (fromCalldata)
{
templ("dynamicallyEncodedBase", _fromType.baseType()->isDynamicallyEncoded());
if (_fromType.baseType()->isDynamicallyEncoded())
templ("accessCalldataTail", accessCalldataTailFunction(*_fromType.baseType()));
}
templ("resizeArray", resizeArrayFunction(_toType));
templ("arrayLength",arrayLengthFunction(_fromType));
templ("isValueType", _fromType.baseType()->isValueType());
templ("dstDataLocation", arrayDataAreaFunction(_toType));
if (fromMemory || (fromCalldata && _fromType.baseType()->isValueType()))
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
templ("elementValues", suffixedVariableNameList(
"elementValue_",
0,
_fromType.baseType()->stackItems().size()
));
templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType()));
templ("srcStride",
fromCalldata ?
to_string(_fromType.calldataStride()) :
fromMemory ?
to_string(_fromType.memoryStride()) :
formatNumber(_fromType.baseType()->storageSize())
);
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
templ("storageStride", to_string(_toType.storageStride()));
templ("storageSize", _toType.baseType()->storageSize().str());
return templ.render();
});
}
string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
""
);
solAssert(_fromType.isByteArray(), "");
solAssert(_toType.isByteArray(), "");
string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(slot, src<?fromCalldata>, len</fromCalldata>) {
<?fromStorage> if eq(slot, src) { leave } </fromStorage>
let newLen := <arrayLength>(src<?fromCalldata>, len</fromCalldata>)
// Make sure array length is sane
if gt(newLen, 0xffffffffffffffff) { <panic>() }
let oldLen := <byteArrayLength>(sload(slot))
// potentially truncate data
<cleanUpEndArray>(slot, oldLen, newLen)
let srcOffset := 0
<?fromMemory>
srcOffset := 0x20
</fromMemory>
switch gt(newLen, 31)
case 1 {
let loopEnd := and(newLen, not(0x1f))
<?fromStorage> src := <srcDataLocation>(src) </fromStorage>
let dstPtr := <dstDataLocation>(slot)
let i := 0
for { } lt(i, loopEnd) { i := add(i, 0x20) } {
sstore(dstPtr, <read>(add(src, srcOffset)))
dstPtr := add(dstPtr, 1)
srcOffset := add(srcOffset, <srcIncrement>)
}
if lt(loopEnd, newLen) {
let lastValue := <read>(add(src, srcOffset))
sstore(dstPtr, <maskBytes>(lastValue, and(newLen, 0x1f)))
}
sstore(slot, add(mul(newLen, 2), 1))
}
default {
let value := 0
if newLen {
value := <read>(add(src, srcOffset))
}
sstore(slot, <byteArrayCombineShort>(value, newLen))
}
}
)");
templ("functionName", functionName);
bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage);
templ("fromStorage", fromStorage);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
templ("fromCalldata", fromCalldata);
templ("arrayLength", arrayLengthFunction(_fromType));
templ("panic", panicFunction(PanicCode::ResourceError));
templ("byteArrayLength", extractByteArrayLengthFunction());
templ("dstDataLocation", arrayDataAreaFunction(_toType));
if (fromStorage)
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
templ("cleanUpEndArray", cleanUpDynamicByteArrayEndSlotsFunction(_toType));
templ("srcIncrement", to_string(fromStorage ? 1 : 0x20));
templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload");
templ("maskBytes", maskBytesFunctionDynamic());
templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction());
return templ.render();
});
}
string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(_fromType.baseType()->isValueType(), "");
solAssert(_toType.baseType()->isValueType(), "");
solAssert(_fromType.baseType()->isImplicitlyConvertibleTo(*_toType.baseType()), "");
solAssert(!_fromType.isByteArray(), "");
solAssert(!_toType.isByteArray(), "");
solAssert(_fromType.dataStoredIn(DataLocation::Storage), "");
solAssert(_toType.dataStoredIn(DataLocation::Storage), "");
solAssert(_fromType.storageStride() <= _toType.storageStride(), "");
solAssert(_toType.storageStride() <= 32, "");
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(dst, src) {
if eq(dst, src) { leave }
let length := <arrayLength>(src)
// Make sure array length is sane
if gt(length, 0xffffffffffffffff) { <panic>() }
<resizeArray>(dst, length)
let srcSlot := <srcDataLocation>(src)
let dstSlot := <dstDataLocation>(dst)
let fullSlots := div(length, <itemsPerSlot>)
let srcSlotValue := sload(srcSlot)
let srcItemIndexInSlot := 0
for { let i := 0 } lt(i, fullSlots) { i := add(i, 1) } {
let dstSlotValue := 0
<?sameType>
dstSlotValue := <maskFull>(srcSlotValue)
<updateSrcSlotValue>
<!sameType>
<?multipleItemsPerSlotDst>for { let j := 0 } lt(j, <itemsPerSlot>) { j := add(j, 1) } </multipleItemsPerSlotDst>
{
let itemValue := <convert>(
<extractFromSlot>(srcSlotValue, mul(<srcStride>, srcItemIndexInSlot))
)
itemValue := <prepareStore>(itemValue)
dstSlotValue :=
<?multipleItemsPerSlotDst>
<updateByteSlice>(dstSlotValue, mul(<dstStride>, j), itemValue)
<!multipleItemsPerSlotDst>
itemValue
</multipleItemsPerSlotDst>
<updateSrcSlotValue>
}
</sameType>
sstore(add(dstSlot, i), dstSlotValue)
}
<?multipleItemsPerSlotDst>
let spill := sub(length, mul(fullSlots, <itemsPerSlot>))
if gt(spill, 0) {
let dstSlotValue := 0
<?sameType>
dstSlotValue := <maskBytes>(srcSlotValue, mul(spill, <srcStride>))
<updateSrcSlotValue>
<!sameType>
for { let j := 0 } lt(j, spill) { j := add(j, 1) } {
let itemValue := <convert>(
<extractFromSlot>(srcSlotValue, mul(<srcStride>, srcItemIndexInSlot))
)
itemValue := <prepareStore>(itemValue)
dstSlotValue := <updateByteSlice>(dstSlotValue, mul(<dstStride>, j), itemValue)
<updateSrcSlotValue>
}
</sameType>
sstore(add(dstSlot, fullSlots), dstSlotValue)
}
</multipleItemsPerSlotDst>
}
)");
if (_fromType.dataStoredIn(DataLocation::Storage))
solAssert(!_fromType.isValueType(), "");
templ("functionName", functionName);
templ("resizeArray", resizeArrayFunction(_toType));
templ("arrayLength", arrayLengthFunction(_fromType));
templ("panic", panicFunction(PanicCode::ResourceError));
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
templ("dstDataLocation", arrayDataAreaFunction(_toType));
templ("srcStride", to_string(_fromType.storageStride()));
unsigned itemsPerSlot = 32 / _toType.storageStride();
templ("itemsPerSlot", to_string(itemsPerSlot));
templ("multipleItemsPerSlotDst", itemsPerSlot > 1);
bool sameType = *_fromType.baseType() == *_toType.baseType();
if (auto functionType = dynamic_cast<FunctionType const*>(_fromType.baseType()))
{
solAssert(functionType->equalExcludingStateMutability(
dynamic_cast<FunctionType const&>(*_toType.baseType())
));
sameType = true;
}
templ("sameType", sameType);
if (sameType)
{
templ("maskFull", maskLowerOrderBytesFunction(itemsPerSlot * _toType.storageStride()));
templ("maskBytes", maskLowerOrderBytesFunctionDynamic());
}
else
{
templ("dstStride", to_string(_toType.storageStride()));
templ("extractFromSlot", extractFromStorageValueDynamic(*_fromType.baseType()));
templ("updateByteSlice", updateByteSliceFunctionDynamic(_toType.storageStride()));
templ("convert", conversionFunction(*_fromType.baseType(), *_toType.baseType()));
templ("prepareStore", prepareStoreFunction(*_toType.baseType()));
}
templ("updateSrcSlotValue", Whiskers(R"(
<?srcReadMultiPerSlot>
srcItemIndexInSlot := add(srcItemIndexInSlot, 1)
if eq(srcItemIndexInSlot, <srcItemsPerSlot>) {
// here we are done with this slot, we need to read next one
srcSlot := add(srcSlot, 1)
srcSlotValue := sload(srcSlot)
srcItemIndexInSlot := 0
}
<!srcReadMultiPerSlot>
srcSlot := add(srcSlot, 1)
srcSlotValue := sload(srcSlot)
</srcReadMultiPerSlot>
)")
("srcReadMultiPerSlot", !sameType && _fromType.storageStride() <= 16)
("srcItemsPerSlot", to_string(32 / _fromType.storageStride()))
.render()
);
return templ.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 <functionName>(length) -> size {
size := length
<?multiSlot>
size := <mul>(<storageSize>, length)
<!multiSlot>
// Number of slots rounded up
size := div(add(length, sub(<itemsPerSlot>, 1)), <itemsPerSlot>)
</multiSlot>
})")
("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 <functionName>(length) -> size {
<?byteArray>
size := length
<!byteArray>
size := <mul>(length, <stride>)
</byteArray>
})")
("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 <functionName>(length) -> size {
// Make sure we can allocate memory without overflow
if gt(length, 0xffffffffffffffff) { <panic>() }
<?byteArray>
size := <roundUp>(length)
<!byteArray>
size := mul(length, 0x20)
</byteArray>
<?dynamic>
// add length slot
size := add(size, 0x20)
</dynamic>
}
)");
w("functionName", functionName);
w("panic", panicFunction(PanicCode::ResourceError));
w("byteArray", _type.isByteArray());
w("roundUp", roundUpFunction());
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 <functionName>(ptr) -> data {
data := ptr
<?dynamic>
<?memory>
data := add(ptr, 0x20)
</memory>
<?storage>
mstore(0, ptr)
data := keccak256(0, 0x20)
</storage>
</dynamic>
}
)")
("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 <functionName>(array, index) -> slot, offset {
let arrayLength := <arrayLen>(array)
if iszero(lt(index, arrayLength)) { <panic>() }
<?multipleItemsPerSlot>
<?isBytesArray>
switch lt(arrayLength, 0x20)
case 0 {
slot, offset := <indexAccessNoChecks>(array, index)
}
default {
offset := sub(31, mod(index, 0x20))
slot := array
}
<!isBytesArray>
let dataArea := <dataAreaFunc>(array)
slot := add(dataArea, div(index, <itemsPerSlot>))
offset := mul(mod(index, <itemsPerSlot>), <storageBytes>)
</isBytesArray>
<!multipleItemsPerSlot>
let dataArea := <dataAreaFunc>(array)
slot := add(dataArea, mul(index, <storageSize>))
offset := 0
</multipleItemsPerSlot>
}
)")
("functionName", functionName)
("panic", panicFunction(PanicCode::ArrayOutOfBounds))
("arrayLen", arrayLengthFunction(_type))
("dataAreaFunc", arrayDataAreaFunction(_type))
("indexAccessNoChecks", longByteArrayStorageIndexAccessNoCheckFunction())
("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 <functionName>(baseRef, index) -> addr {
if iszero(lt(index, <arrayLen>(baseRef))) {
<panic>()
}
let offset := mul(index, <stride>)
<?dynamicallySized>
offset := add(offset, 32)
</dynamicallySized>
addr := add(baseRef, offset)
}
)")
("functionName", functionName)
("panic", panicFunction(PanicCode::ArrayOutOfBounds))
("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 <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { <panic>() }
addr := add(base_ref, mul(index, <stride>))
<?dynamicallyEncodedBase>
addr<?dynamicallySizedBase>, len</dynamicallySizedBase> := <accessCalldataTail>(base_ref, addr)
</dynamicallyEncodedBase>
}
)")
("functionName", functionName)
("panic", panicFunction(PanicCode::ArrayOutOfBounds))
("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 <functionName>(offset, length, startIndex, endIndex) -> offsetOut, lengthOut {
if gt(startIndex, endIndex) { <revertSliceStartAfterEnd>() }
if gt(endIndex, length) { <revertSliceGreaterThanLength>() }
offsetOut := add(offset, mul(startIndex, <stride>))
lengthOut := sub(endIndex, startIndex)
}
)")
("functionName", functionName)
("stride", to_string(_type.calldataStride()))
("revertSliceStartAfterEnd", revertReasonIfDebugFunction("Slice starts after end"))
("revertSliceGreaterThanLength", revertReasonIfDebugFunction("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 <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <invalidCalldataTailOffset>() }
addr := add(base_ref, rel_offset_of_tail)
<?dynamicallySized>
length := calldataload(addr)
if gt(length, 0xffffffffffffffff) { <invalidCalldataTailLength>() }
addr := add(addr, 32)
if sgt(addr, sub(calldatasize(), mul(length, <calldataStride>))) { <shortCalldataTail>() }
</dynamicallySized>
}
)")
("functionName", functionName)
("dynamicallySized", _type.isDynamicallySized())
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
("invalidCalldataTailOffset", revertReasonIfDebugFunction("Invalid calldata tail offset"))
("invalidCalldataTailLength", revertReasonIfDebugFunction("Invalid calldata tail length"))
("shortCalldataTail", revertReasonIfDebugFunction("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 <functionName>(ptr) -> next {
next := add(ptr, <advance>)
}
)");
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::copyArrayFromStorageToMemoryFunction(ArrayType const& _from, ArrayType const& _to)
{
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
solAssert(_to.dataStoredIn(DataLocation::Memory), "");
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
if (!_from.isDynamicallySized())
solAssert(_from.length() == _to.length(), "");
string functionName = "copy_array_from_storage_to_memory_" + _from.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
if (_from.baseType()->isValueType())
{
solAssert(*_from.baseType() == *_to.baseType(), "");
ABIFunctions abi(m_evmVersion, m_revertStrings, m_functionCollector);
return Whiskers(R"(
function <functionName>(slot) -> memPtr {
memPtr := <allocateUnbounded>()
let end := <encode>(slot, memPtr)
<finalizeAllocation>(memPtr, sub(end, memPtr))
}
)")
("functionName", functionName)
("allocateUnbounded", allocateUnboundedFunction())
(
"encode",
abi.abiEncodeAndReturnUpdatedPosFunction(_from, _to, ABIFunctions::EncodingOptions{})
)
("finalizeAllocation", finalizeAllocationFunction())
.render();
}
else
{
solAssert(_to.memoryStride() == 32, "");
solAssert(_to.baseType()->dataStoredIn(DataLocation::Memory), "");
solAssert(_from.baseType()->dataStoredIn(DataLocation::Storage), "");
solAssert(!_from.isByteArray(), "");
solAssert(*_to.withLocation(DataLocation::Storage, _from.isPointer()) == _from, "");
return Whiskers(R"(
function <functionName>(slot) -> memPtr {
let length := <lengthFunction>(slot)
memPtr := <allocateArray>(length)
let mpos := memPtr
<?dynamic>mpos := add(mpos, 0x20)</dynamic>
let spos := <arrayDataArea>(slot)
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
mstore(mpos, <convert>(spos))
mpos := add(mpos, 0x20)
spos := add(spos, <baseStorageSize>)
}
}
)")
("functionName", functionName)
("lengthFunction", arrayLengthFunction(_from))
("allocateArray", allocateMemoryArrayFunction(_to))
("arrayDataArea", arrayDataAreaFunction(_from))
("dynamic", _to.isDynamicallySized())
("convert", conversionFunction(*_from.baseType(), *_to.baseType()))
("baseStorageSize", _from.baseType()->storageSize().str())
.render();
}
});
}
string YulUtilFunctions::bytesConcatFunction(vector<Type const*> const& _argumentTypes)
{
string functionName = "bytes_concat";
size_t totalParams = 0;
vector<Type const*> targetTypes;
for (Type const* argumentType: _argumentTypes)
{
solAssert(
argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) ||
argumentType->isImplicitlyConvertibleTo(*TypeProvider::fixedBytes(32)),
""
);
if (argumentType->category() == Type::Category::FixedBytes)
targetTypes.emplace_back(argumentType);
else if (
auto const* literalType = dynamic_cast<StringLiteralType const*>(argumentType);
literalType && !literalType->value().empty() && literalType->value().size() <= 32
)
targetTypes.emplace_back(TypeProvider::fixedBytes(static_cast<unsigned>(literalType->value().size())));
else
{
solAssert(!dynamic_cast<RationalNumberType const*>(argumentType), "");
solAssert(argumentType->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()), "");
targetTypes.emplace_back(TypeProvider::bytesMemory());
}
totalParams += argumentType->sizeOnStack();
functionName += "_" + argumentType->identifier();
}
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(<parameters>) -> outPtr {
outPtr := <allocateUnbounded>()
let dataStart := add(outPtr, 0x20)
let dataEnd := <encodePacked>(dataStart<?+parameters>, <parameters></+parameters>)
mstore(outPtr, sub(dataEnd, dataStart))
<finalizeAllocation>(outPtr, sub(dataEnd, outPtr))
}
)");
templ("functionName", functionName);
templ("parameters", suffixedVariableNameList("param_", 0, totalParams));
templ("allocateUnbounded", allocateUnboundedFunction());
templ("finalizeAllocation", finalizeAllocationFunction());
templ(
"encodePacked",
ABIFunctions{m_evmVersion, m_revertStrings, m_functionCollector}.tupleEncoderPacked(
_argumentTypes,
targetTypes
)
);
return templ.render();
});
}
string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType)
{
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
if (_mappingType.keyType()->isDynamicallySized())
return Whiskers(R"(
function <functionName>(slot <?+key>,</+key> <key>) -> dataSlot {
dataSlot := <hash>(<key> <?+key>,</+key> slot)
}
)")
("functionName", functionName)
("key", suffixedVariableNameList("key_", 0, _keyType.sizeOnStack()))
("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 <functionName>(slot <key>) -> dataSlot {
mstore(0, <convertedKey>)
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)
{
if (_type.isValueType())
return readFromStorageValueType(_type, {}, _splitFunctionTypes);
string functionName =
"read_from_storage__dynamic_" +
string(_splitFunctionTypes ? "split_" : "") +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot, offset) -> value {
if gt(offset, 0) { <panic>() }
value := <readFromStorage>(slot)
}
)")
("functionName", functionName)
("panic", panicFunction(util::PanicCode::Generic))
("readFromStorage", readFromStorageReferenceType(_type))
.render();
});
}
string YulUtilFunctions::readFromStorageValueType(Type const& _type, optional<size_t> _offset, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
string functionName =
"read_from_storage_" +
string(_splitFunctionTypes ? "split_" : "") + (
_offset.has_value() ?
"offset_" + to_string(*_offset) :
"dynamic"
) +
"_" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
Whiskers templ(R"(
function <functionName>(slot<?dynamic>, offset</dynamic>) -> <?split>addr, selector<!split>value</split> {
<?split>let</split> value := <extract>(sload(slot)<?dynamic>, offset</dynamic>)
<?split>
addr, selector := <splitFunction>(value)
</split>
}
)");
templ("functionName", functionName);
templ("dynamic", !_offset.has_value());
if (_offset.has_value())
templ("extract", extractFromStorageValue(_type, *_offset));
else
templ("extract", extractFromStorageValueDynamic(_type));
auto const* funType = dynamic_cast<FunctionType const*>(&_type);
bool split = _splitFunctionTypes && funType && funType->kind() == FunctionType::Kind::External;
templ("split", split);
if (split)
templ("splitFunction", splitExternalFunctionIdFunction());
return templ.render();
});
}
string YulUtilFunctions::readFromStorageReferenceType(Type const& _type)
{
if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type))
{
solAssert(arrayType->dataStoredIn(DataLocation::Memory), "");
return copyArrayFromStorageToMemoryFunction(
dynamic_cast<ArrayType const&>(*arrayType->copyForLocation(DataLocation::Storage, false)),
*arrayType
);
}
solAssert(_type.category() == Type::Category::Struct, "");
string functionName = "read_from_storage_reference_type_" + _type.identifier();
auto const& structType = dynamic_cast<StructType const&>(_type);
solAssert(structType.location() == DataLocation::Memory, "");
MemberList::MemberMap structMembers = structType.nativeMembers(nullptr);
vector<map<string, string>> memberSetValues(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
auto const& [memberSlotDiff, memberStorageOffset] = structType.storageOffsetsOfMember(structMembers[i].name);
solAssert(structMembers[i].type->isValueType() || memberStorageOffset == 0, "");
memberSetValues[i]["setMember"] = Whiskers(R"(
{
let <memberValues> := <readFromStorage>(add(slot, <memberSlotDiff>))
<writeToMemory>(add(value, <memberMemoryOffset>), <memberValues>)
}
)")
("memberValues", suffixedVariableNameList("memberValue_", 0, structMembers[i].type->stackItems().size()))
("memberMemoryOffset", structType.memoryOffsetOfMember(structMembers[i].name).str())
("memberSlotDiff", memberSlotDiff.str())
("readFromStorage", readFromStorage(*structMembers[i].type, memberStorageOffset, true))
("writeToMemory", writeToMemoryFunction(*structMembers[i].type))
.render();
}
return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot) -> value {
value := <allocStruct>()
<#member>
<setMember>
</member>
}
)")
("functionName", functionName)
("allocStruct", allocateMemoryStructFunction(structType))
("member", memberSetValues)
.render();
});
}
string YulUtilFunctions::readFromMemory(Type const& _type)
{
return readFromMemoryOrCalldata(_type, false);
}
string YulUtilFunctions::readFromCalldata(Type const& _type)
{
return readFromMemoryOrCalldata(_type, true);
}
string YulUtilFunctions::updateStorageValueFunction(
Type const& _fromType,
Type const& _toType,
std::optional<unsigned> const& _offset
)
{
string const functionName =
"update_storage_value_" +
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
_fromType.identifier() +
"_to_" +
_toType.identifier();
return m_functionCollector.createFunction(functionName, [&] {
if (_toType.isValueType())
{
solAssert(_fromType.isImplicitlyConvertibleTo(_toType), "");
solAssert(_toType.storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(_toType.storageBytes() > 0, "Invalid storage bytes size.");
return Whiskers(R"(
function <functionName>(slot, <offset><fromValues>) {
let <toValues> := <convert>(<fromValues>)
sstore(slot, <update>(sload(slot), <offset><prepare>(<toValues>)))
}
)")
("functionName", functionName)
("update",
_offset.has_value() ?
updateByteSliceFunction(_toType.storageBytes(), *_offset) :
updateByteSliceFunctionDynamic(_toType.storageBytes())
)
("offset", _offset.has_value() ? "" : "offset, ")
("convert", conversionFunction(_fromType, _toType))
("fromValues", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()))
("toValues", suffixedVariableNameList("convertedValue_", 0, _toType.sizeOnStack()))
("prepare", prepareStoreFunction(_toType))
.render();
}
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(toReferenceType, "");
if (!fromReferenceType)
{
solAssert(_fromType.category() == Type::Category::StringLiteral, "");
solAssert(toReferenceType->category() == Type::Category::Array, "");
auto const& toArrayType = dynamic_cast<ArrayType const&>(*toReferenceType);
solAssert(toArrayType.isByteArray(), "");
return Whiskers(R"(
function <functionName>(slot<?dynamicOffset>, offset</dynamicOffset>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyToStorage>(slot)
}
)")
("functionName", functionName)
("dynamicOffset", !_offset.has_value())
("panic", panicFunction(PanicCode::Generic))
("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
.render();
}
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
if (fromReferenceType->category() == Type::Category::ArraySlice)
solAssert(toReferenceType->category() == Type::Category::Array, "");
else
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction(PanicCode::Generic));
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
if (_fromType.category() == Type::Category::Array)
templ("copyToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
else if (_fromType.category() == Type::Category::ArraySlice)
{
solAssert(
_fromType.dataStoredIn(DataLocation::CallData),
"Currently only calldata array slices are supported!"
);
templ("copyToStorage", copyArrayToStorageFunction(
dynamic_cast<ArraySliceType const&>(_fromType).arrayType(),
dynamic_cast<ArrayType const&>(_toType)
));
}
else
templ("copyToStorage", copyStructToStorageFunction(
dynamic_cast<StructType const&>(_fromType),
dynamic_cast<StructType const&>(_toType)
));
return templ.render();
});
}
string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
{
string const functionName = "write_to_memory_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
{
solAssert(
ref->location() == DataLocation::Memory,
"Can only update types with location memory."
);
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, value)
}
)")
("functionName", functionName)
.render();
}
else if (
_type.category() == Type::Category::Function &&
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
)
{
return Whiskers(R"(
function <functionName>(memPtr, addr, selector) {
mstore(memPtr, <combine>(addr, selector))
}
)")
("functionName", functionName)
("combine", combineExternalFunctionIdFunction())
.render();
}
else if (_type.isValueType())
{
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, <cleanup>(value))
}
)")
("functionName", functionName)
("cleanup", cleanupFunction(_type))
.render();
}
else // Should never happen
{
solAssert(
false,
"Memory store of type " + _type.toString(true) + " not allowed."
);
}
});
}
string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type)
{
string functionName =
"extract_from_storage_value_dynamic" +
_type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value, offset) -> value {
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
}
)")
("functionName", functionName)
("shr", shiftRightFunctionDynamic())
("cleanupStorage", cleanupFromStorageFunction(_type))
.render();
});
}
string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset)
{
string functionName = "extract_from_storage_value_offset_" + to_string(_offset) + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value) -> value {
value := <cleanupStorage>(<shr>(slot_value))
}
)")
("functionName", functionName)
("shr", shiftRightFunction(_offset * 8))
("cleanupStorage", cleanupFromStorageFunction(_type))
.render();
});
}
string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type)
{
solAssert(_type.isValueType(), "");
string functionName = string("cleanup_from_storage_") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
Whiskers templ(R"(
function <functionName>(value) -> cleaned {
cleaned := <cleaned>
}
)");
templ("functionName", functionName);
Type const* encodingType = &_type;
if (_type.category() == Type::Category::UserDefinedValueType)
encodingType = _type.encodingType();
unsigned storageBytes = encodingType->storageBytes();
if (IntegerType const* intType = dynamic_cast<IntegerType const*>(encodingType))
if (intType->isSigned() && storageBytes != 32)
{
templ("cleaned", "signextend(" + to_string(storageBytes - 1) + ", value)");
return templ.render();
}
if (storageBytes == 32)
templ("cleaned", "value");
else if (encodingType->leftAligned())
templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
else
templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
return templ.render();
});
}
string YulUtilFunctions::prepareStoreFunction(Type const& _type)
{
string functionName = "prepare_store_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
solAssert(_type.isValueType(), "");
auto const* funType = dynamic_cast<FunctionType const*>(&_type);
if (funType && funType->kind() == FunctionType::Kind::External)
{
Whiskers templ(R"(
function <functionName>(addr, selector) -> ret {
ret := <prepareBytes>(<combine>(addr, selector))
}
)");
templ("functionName", functionName);
templ("prepareBytes", prepareStoreFunction(*TypeProvider::fixedBytes(24)));
templ("combine", combineExternalFunctionIdFunction());
return templ.render();
}
else
{
solAssert(_type.sizeOnStack() == 1, "");
Whiskers templ(R"(
function <functionName>(value) -> ret {
ret := <actualPrepare>
}
)");
templ("functionName", functionName);
if (_type.leftAligned())
templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)");
else
templ("actualPrepare", "value");
return templ.render();
}
});
}
string YulUtilFunctions::allocationFunction()
{
string functionName = "allocate_memory";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(size) -> memPtr {
memPtr := <allocateUnbounded>()
<finalizeAllocation>(memPtr, size)
}
)")
("functionName", functionName)
("allocateUnbounded", allocateUnboundedFunction())
("finalizeAllocation", finalizeAllocationFunction())
.render();
});
}
string YulUtilFunctions::allocateUnboundedFunction()
{
string functionName = "allocate_unbounded";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> memPtr {
memPtr := mload(<freeMemoryPointer>)
}
)")
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::finalizeAllocationFunction()
{
string functionName = "finalize_allocation";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(memPtr, size) {
let newFreePtr := add(memPtr, <roundUp>(size))
// protect against overflow
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { <panic>() }
mstore(<freeMemoryPointer>, newFreePtr)
}
)")
("functionName", functionName)
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
("roundUp", roundUpFunction())
("panic", panicFunction(PanicCode::ResourceError))
.render();
});
}
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
if (_type.baseType()->hasSimpleZeroValueInMemory())
return zeroMemoryFunction(*_type.baseType());
return zeroComplexMemoryArrayFunction(_type);
}
string YulUtilFunctions::zeroMemoryFunction(Type const& _type)
{
solAssert(_type.hasSimpleZeroValueInMemory(), "");
string functionName = "zero_memory_chunk_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(dataStart, dataSizeInBytes) {
calldatacopy(dataStart, calldatasize(), dataSizeInBytes)
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::zeroComplexMemoryArrayFunction(ArrayType const& _type)
{
solAssert(!_type.baseType()->hasSimpleZeroValueInMemory(), "");
string functionName = "zero_complex_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
solAssert(_type.memoryStride() == 32, "");
return Whiskers(R"(
function <functionName>(dataStart, dataSizeInBytes) {
for {let i := 0} lt(i, dataSizeInBytes) { i := add(i, <stride>) } {
mstore(add(dataStart, i), <zeroValue>())
}
}
)")
("functionName", functionName)
("stride", to_string(_type.memoryStride()))
("zeroValue", zeroValueFunction(*_type.baseType(), false))
.render();
});
}
string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
{
string functionName = "allocate_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
let allocSize := <allocSize>(length)
memPtr := <alloc>(allocSize)
<?dynamic>
mstore(memPtr, length)
</dynamic>
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType const& _type)
{
string functionName = "allocate_and_zero_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
memPtr := <allocArray>(length)
let dataStart := memPtr
let dataSize := <allocSize>(length)
<?dynamic>
dataStart := add(dataStart, 32)
dataSize := sub(dataSize, 32)
</dynamic>
<zeroArrayFunction>(dataStart, dataSize)
}
)")
("functionName", functionName)
("allocArray", allocateMemoryArrayFunction(_type))
("allocSize", arrayAllocationSizeFunction(_type))
("zeroArrayFunction", zeroMemoryArrayFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::allocateMemoryStructFunction(StructType const& _type)
{
string functionName = "allocate_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
memPtr := <alloc>(<allocSize>)
}
)");
templ("functionName", functionName);
templ("alloc", allocationFunction());
templ("allocSize", _type.memoryDataSize().str());
return templ.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
{
string functionName = "allocate_and_zero_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
memPtr := <allocStruct>()
let offset := memPtr
<#member>
mstore(offset, <zeroValue>())
offset := add(offset, 32)
</member>
}
)");
templ("functionName", functionName);
templ("allocStruct", allocateMemoryStructFunction(_type));
TypePointers const& members = _type.memoryMemberTypes();
vector<map<string, string>> memberParams(members.size());
for (size_t i = 0; i < members.size(); ++i)
{
solAssert(members[i]->memoryHeadSize() == 32, "");
memberParams[i]["zeroValue"] = zeroValueFunction(
*TypeProvider::withLocationIfReference(DataLocation::Memory, members[i]),
false
);
}
templ("member", memberParams);
return templ.render();
});
}
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::UserDefinedValueType)
{
solAssert(_from == _to || _to == dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), "");
return conversionFunction(dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), _to);
}
if (_to.category() == Type::Category::UserDefinedValueType)
{
solAssert(_from == _to || _from.isImplicitlyConvertibleTo(dynamic_cast<UserDefinedValueType const&>(_to).underlyingType()), "");
return conversionFunction(_from, dynamic_cast<UserDefinedValueType const&>(_to).underlyingType());
}
if (_from.category() == Type::Category::Function)
{
solAssert(_to.category() == Type::Category::Function, "");
FunctionType const& fromType = dynamic_cast<FunctionType const&>(_from);
FunctionType const& targetType = dynamic_cast<FunctionType const&>(_to);
solAssert(
fromType.isImplicitlyConvertibleTo(targetType) &&
fromType.sizeOnStack() == targetType.sizeOnStack() &&
(fromType.kind() == FunctionType::Kind::Internal || fromType.kind() == FunctionType::Kind::External) &&
fromType.kind() == targetType.kind(),
"Invalid function type conversion requested."
);
string const functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(<?external>addr, </external>functionId) -> <?external>outAddr, </external>outFunctionId {
<?external>outAddr := addr</external>
outFunctionId := functionId
}
)")
("functionName", functionName)
("external", fromType.kind() == FunctionType::Kind::External)
.render();
});
}
else if (_from.category() == Type::Category::ArraySlice)
{
auto const& fromType = dynamic_cast<ArraySliceType const&>(_from);
if (_to.category() == Type::Category::FixedBytes)
{
solAssert(fromType.arrayType().isByteArray(), "Array types other than bytes not convertible to bytesNN.");
return bytesToFixedBytesConversionFunction(fromType.arrayType(), dynamic_cast<FixedBytesType const &>(_to));
}
solAssert(_to.category() == Type::Category::Array);
auto const& targetType = dynamic_cast<ArrayType const&>(_to);
solAssert(
fromType.arrayType().isImplicitlyConvertibleTo(targetType) ||
(fromType.arrayType().isByteArray() && targetType.isByteArray())
);
solAssert(
fromType.arrayType().dataStoredIn(DataLocation::CallData) &&
fromType.arrayType().isDynamicallySized() &&
!fromType.arrayType().baseType()->isDynamicallyEncoded()
);
if (!targetType.dataStoredIn(DataLocation::CallData))
return arrayConversionFunction(fromType.arrayType(), targetType);
string const functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(offset, length) -> outOffset, outLength {
outOffset := offset
outLength := length
}
)")
("functionName", functionName)
.render();
});
}
else if (_from.category() == Type::Category::Array)
{
auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from);
if (_to.category() == Type::Category::FixedBytes)
{
solAssert(fromArrayType.isByteArray(), "Array types other than bytes not convertible to bytesNN.");
return bytesToFixedBytesConversionFunction(fromArrayType, dynamic_cast<FixedBytesType const &>(_to));
}
solAssert(_to.category() == Type::Category::Array, "");
return arrayConversionFunction(fromArrayType, dynamic_cast<ArrayType const&>(_to));
}
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to);
string functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> converted {
<body>
}
)");
templ("functionName", functionName);
string body;
auto toCategory = _to.category();
auto fromCategory = _from.category();
switch (fromCategory)
{
case Type::Category::Address:
case Type::Category::Contract:
body =
Whiskers("converted := <convert>(value)")
("convert", conversionFunction(IntegerType(160), _to))
.render();
break;
case Type::Category::Integer:
case Type::Category::RationalNumber:
{
solAssert(_from.mobileType(), "");
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from))
if (rational->isFractional())
solAssert(toCategory == Type::Category::FixedPoint, "");
if (toCategory == Type::Category::Address || toCategory == Type::Category::Contract)
body =
Whiskers("converted := <convert>(value)")
("convert", conversionFunction(_from, IntegerType(160)))
.render();
else
{
Whiskers bodyTemplate("converted := <cleanOutput>(<convert>(<cleanInput>(value)))");
bodyTemplate("cleanInput", cleanupFunction(_from));
bodyTemplate("cleanOutput", cleanupFunction(_to));
string convert;
solAssert(_to.category() != Type::Category::UserDefinedValueType, "");
if (auto const* toFixedBytes = dynamic_cast<FixedBytesType const*>(&_to))
convert = shiftLeftFunction(256 - toFixedBytes->numBytes() * 8);
else if (dynamic_cast<FixedPointType const*>(&_to))
solUnimplemented("");
else if (dynamic_cast<IntegerType const*>(&_to))
{
solUnimplementedAssert(fromCategory != Type::Category::FixedPoint);
convert = identityFunction();
}
else if (toCategory == Type::Category::Enum)
{
solAssert(fromCategory != Type::Category::FixedPoint, "");
convert = identityFunction();
}
else
solAssert(false, "");
solAssert(!convert.empty(), "");
bodyTemplate("convert", convert);
body = bodyTemplate.render();
}
break;
}
case Type::Category::Bool:
{
solAssert(_from == _to, "Invalid conversion for bool.");
body =
Whiskers("converted := <clean>(value)")
("clean", cleanupFunction(_from))
.render();
break;
}
case Type::Category::FixedPoint:
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Struct:
{
solAssert(toCategory == Type::Category::Struct, "");
auto const& fromStructType = dynamic_cast<StructType const &>(_from);
auto const& toStructType = dynamic_cast<StructType const &>(_to);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
if (fromStructType.location() == toStructType.location() && toStructType.isPointer())
body = "converted := value";
else
{
solUnimplementedAssert(toStructType.location() == DataLocation::Memory);
solUnimplementedAssert(fromStructType.location() != DataLocation::Memory);
if (fromStructType.location() == DataLocation::CallData)
body = Whiskers(R"(
converted := <abiDecode>(value, calldatasize())
)")
(
"abiDecode",
ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).abiDecodingFunctionStruct(
toStructType,
false
)
).render();
else
{
solAssert(fromStructType.location() == DataLocation::Storage, "");
body = Whiskers(R"(
converted := <readFromStorage>(value)
)")
("readFromStorage", readFromStorage(toStructType, 0, true))
.render();
}
}
break;
}
case Type::Category::FixedBytes:
{
FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from);
if (toCategory == Type::Category::Integer)
body =
Whiskers("converted := <convert>(<shift>(value))")
("shift", shiftRightFunction(256 - from.numBytes() * 8))
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
.render();
else if (toCategory == Type::Category::Address)
body =
Whiskers("converted := <convert>(value)")
("convert", conversionFunction(_from, IntegerType(160)))
.render();
else
{
solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
FixedBytesType const& to = dynamic_cast<FixedBytesType const&>(_to);
body =
Whiskers("converted := <clean>(value)")
("clean", cleanupFunction((to.numBytes() <= from.numBytes()) ? to : from))
.render();
}
break;
}
case Type::Category::Function:
{
solAssert(false, "Conversion should not be called for function types.");
break;
}
case Type::Category::Enum:
{
solAssert(toCategory == Type::Category::Integer || _from == _to, "");
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from);
body =
Whiskers("converted := <clean>(value)")
("clean", cleanupFunction(enumType))
.render();
break;
}
case Type::Category::Tuple:
{
solUnimplemented("Tuple conversion not implemented.");
break;
}
case Type::Category::TypeType:
{
TypeType const& typeType = dynamic_cast<decltype(typeType)>(_from);
if (
auto const* contractType = dynamic_cast<ContractType const*>(typeType.actualType());
contractType->contractDefinition().isLibrary() &&
_to == *TypeProvider::address()
)
body = "converted := value";
else
solAssert(false, "Invalid conversion from " + _from.canonicalName() + " to " + _to.canonicalName());
break;
}
case Type::Category::Mapping:
{
solAssert(_from == _to, "");
body = "converted := value";
break;
}
default:
solAssert(false, "Invalid conversion from " + _from.canonicalName() + " to " + _to.canonicalName());
}
solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName());
templ("body", body);
return templ.render();
});
}
string YulUtilFunctions::bytesToFixedBytesConversionFunction(ArrayType const& _from, FixedBytesType const& _to)
{
solAssert(_from.isByteArray() && !_from.isString(), "");
solAssert(_from.isDynamicallySized(), "");
string functionName = "convert_bytes_to_fixedbytes_from_" + _from.identifier() + "_to_" + _to.identifier();
return m_functionCollector.createFunction(functionName, [&](auto& _args, auto& _returnParams) {
_args = { "array" };
bool fromCalldata = _from.dataStoredIn(DataLocation::CallData);
if (fromCalldata)
_args.emplace_back("len");
_returnParams = {"value"};
Whiskers templ(R"(
let length := <arrayLen>(array<?fromCalldata>, len</fromCalldata>)
let dataArea := array
<?fromMemory>
dataArea := <dataArea>(array)
</fromMemory>
<?fromStorage>
if gt(length, 31) { dataArea := <dataArea>(array) }
</fromStorage>
<?fromCalldata>
value := <cleanup>(calldataload(dataArea))
<!fromCalldata>
value := <extractValue>(dataArea)
</fromCalldata>
if lt(length, <fixedBytesLen>) {
value := and(
value,
<shl>(
mul(8, sub(<fixedBytesLen>, length)),
<mask>
)
)
}
)");
templ("fromCalldata", fromCalldata);
templ("arrayLen", arrayLengthFunction(_from));
templ("fixedBytesLen", to_string(_to.numBytes()));
templ("fromMemory", _from.dataStoredIn(DataLocation::Memory));
templ("fromStorage", _from.dataStoredIn(DataLocation::Storage));
templ("dataArea", arrayDataAreaFunction(_from));
if (fromCalldata)
templ("cleanup", cleanupFunction(_to));
else
templ(
"extractValue",
_from.dataStoredIn(DataLocation::Storage) ?
readFromStorage(_to, 32 - _to.numBytes(), false) :
readFromMemory(_to)
);
templ("shl", shiftLeftFunctionDynamic());
templ("mask", formatNumber(~((u256(1) << (256 - _to.numBytes() * 8)) - 1)));
return templ.render();
});
}
string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, StructType const& _to)
{
solAssert(_to.dataStoredIn(DataLocation::Storage), "");
solAssert(_from.structDefinition() == _to.structDefinition(), "");
string functionName =
"copy_struct_to_storage_from_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&](auto& _arguments, auto&) {
_arguments = {"slot", "value"};
Whiskers templ(R"(
<?fromStorage> if iszero(eq(slot, value)) { </fromStorage>
<#member>
{
<updateMemberCall>
}
</member>
<?fromStorage> } </fromStorage>
)");
templ("fromStorage", _from.dataStoredIn(DataLocation::Storage));
MemberList::MemberMap structMembers = _from.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = _to.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
Type const& memberType = *structMembers[i].type;
solAssert(memberType.memoryHeadSize() == 32, "");
auto const&[slotDiff, offset] = _to.storageOffsetsOfMember(structMembers[i].name);
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
let memberSrcPtr := add(value, <memberOffset>)
<?fromCalldata>
let <memberValues> :=
<?dynamicallyEncodedMember>
<accessCalldataTail>(value, memberSrcPtr)
<!dynamicallyEncodedMember>
memberSrcPtr
</dynamicallyEncodedMember>
<?isValueType>
<memberValues> := <read>(<memberValues>)
</isValueType>
</fromCalldata>
<?fromMemory>
let <memberValues> := <read>(memberSrcPtr)
</fromMemory>
<?fromStorage>
let <memberValues> :=
<?isValueType>
<read>(memberSrcPtr)
<!isValueType>
memberSrcPtr
</isValueType>
</fromStorage>
<updateStorageValue>(memberSlot, <memberValues>)
)");
bool fromCalldata = _from.location() == DataLocation::CallData;
t("fromCalldata", fromCalldata);
bool fromMemory = _from.location() == DataLocation::Memory;
t("fromMemory", fromMemory);
bool fromStorage = _from.location() == DataLocation::Storage;
t("fromStorage", fromStorage);
t("isValueType", memberType.isValueType());
t("memberValues", suffixedVariableNameList("memberValue_", 0, memberType.stackItems().size()));
t("memberStorageSlotDiff", slotDiff.str());
if (fromCalldata)
{
t("memberOffset", to_string(_from.calldataOffsetOfMember(structMembers[i].name)));
t("dynamicallyEncodedMember", memberType.isDynamicallyEncoded());
if (memberType.isDynamicallyEncoded())
t("accessCalldataTail", accessCalldataTailFunction(memberType));
if (memberType.isValueType())
t("read", readFromCalldata(memberType));
}
else if (fromMemory)
{
t("memberOffset", _from.memoryOffsetOfMember(structMembers[i].name).str());
t("read", readFromMemory(memberType));
}
else if (fromStorage)
{
auto const& [srcSlotOffset, srcOffset] = _from.storageOffsetsOfMember(structMembers[i].name);
t("memberOffset", formatNumber(srcSlotOffset));
if (memberType.isValueType())
t("read", readFromStorageValueType(memberType, srcOffset, true));
else
solAssert(srcOffset == 0, "");
}
t("updateStorageValue", updateStorageValueFunction(
memberType,
*toStructMembers[i].type,
optional<unsigned>{offset}
));
memberParams[i]["updateMemberCall"] = t.render();
}
templ("member", memberParams);
return templ.render();
});
}
string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayType const& _to)
{
if (_to.dataStoredIn(DataLocation::CallData))
solAssert(
_from.dataStoredIn(DataLocation::CallData) && _from.isByteArray() && _to.isByteArray(),
""
);
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
if (_to.location() == DataLocation::Storage)
solAssert(
(_to.isPointer() || (_from.isByteArray() && _to.isByteArray())) &&
_from.location() == DataLocation::Storage,
"Invalid conversion to storage type."
);
string functionName =
"convert_array_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value<?fromCalldataDynamic>, length</fromCalldataDynamic>) -> converted <?toCalldataDynamic>, outLength</toCalldataDynamic> {
<body>
<?toCalldataDynamic>
outLength := <length>
</toCalldataDynamic>
}
)");
templ("functionName", functionName);
templ("fromCalldataDynamic", _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized());
templ("toCalldataDynamic", _to.dataStoredIn(DataLocation::CallData) && _to.isDynamicallySized());
templ("length", _from.isDynamicallySized() ? "length" : _from.length().str());
if (
_from == _to ||
(_from.dataStoredIn(DataLocation::Memory) && _to.dataStoredIn(DataLocation::Memory)) ||
(_from.dataStoredIn(DataLocation::CallData) && _to.dataStoredIn(DataLocation::CallData)) ||
_to.dataStoredIn(DataLocation::Storage)
)
templ("body", "converted := value");
else if (_to.dataStoredIn(DataLocation::Memory))
templ(
"body",
Whiskers(R"(
// Copy the array to a free position in memory
converted :=
<?fromStorage>
<arrayStorageToMem>(value)
</fromStorage>
<?fromCalldata>
<abiDecode>(value, <length>, calldatasize())
</fromCalldata>
)")
("fromStorage", _from.dataStoredIn(DataLocation::Storage))
("fromCalldata", _from.dataStoredIn(DataLocation::CallData))
("length", _from.isDynamicallySized() ? "length" : _from.length().str())
(
"abiDecode",
_from.dataStoredIn(DataLocation::CallData) ?
ABIFunctions(
m_evmVersion,
m_revertStrings,
m_functionCollector
).abiDecodingFunctionArrayAvailableLength(_to, false) :
""
)
(
"arrayStorageToMem",
_from.dataStoredIn(DataLocation::Storage) ? copyArrayFromStorageToMemoryFunction(_from, _to) : ""
)
.render()
);
else
solAssert(false, "");
return templ.render();
});
}
string YulUtilFunctions::cleanupFunction(Type const& _type)
{
if (auto userDefinedValueType = dynamic_cast<UserDefinedValueType const*>(&_type))
return cleanupFunction(userDefinedValueType->underlyingType());
string functionName = string("cleanup_") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> cleaned {
<body>
}
)");
templ("functionName", functionName);
switch (_type.category())
{
case Type::Category::Address:
templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)");
break;
case Type::Category::Integer:
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
if (type.numBits() == 256)
templ("body", "cleaned := value");
else if (type.isSigned())
templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)");
else
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")");
break;
}
case Type::Category::RationalNumber:
templ("body", "cleaned := value");
break;
case Type::Category::Bool:
templ("body", "cleaned := iszero(iszero(value))");
break;
case Type::Category::FixedPoint:
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Function:
switch (dynamic_cast<FunctionType const&>(_type).kind())
{
case FunctionType::Kind::External:
templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)");
break;
case FunctionType::Kind::Internal:
templ("body", "cleaned := value");
break;
default:
solAssert(false, "");
break;
}
break;
case Type::Category::Array:
case Type::Category::Struct:
case Type::Category::Mapping:
solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type.");
templ("body", "cleaned := value");
break;
case Type::Category::FixedBytes:
{
FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type);
if (type.numBytes() == 32)
templ("body", "cleaned := value");
else if (type.numBytes() == 0)
// This is disallowed in the type system.
solAssert(false, "");
else
{
size_t numBits = type.numBytes() * 8;
u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits);
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")");
}
break;
}
case Type::Category::Contract:
{
AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ?
StateMutability::Payable :
StateMutability::NonPayable
);
templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)");
break;
}
case Type::Category::Enum:
{
// Out of range enums cannot be truncated unambigiously and therefore it should be an error.
templ("body", "cleaned := value " + validatorFunction(_type, false) + "(value)");
break;
}
case Type::Category::InaccessibleDynamic:
templ("body", "cleaned := 0");
break;
default:
solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
}
return templ.render();
});
}
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
{
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) {
if iszero(<condition>) { <failure> }
}
)");
templ("functionName", functionName);
PanicCode panicCode = PanicCode::Generic;
switch (_type.category())
{
case Type::Category::Address:
case Type::Category::Integer:
case Type::Category::RationalNumber:
case Type::Category::Bool:
case Type::Category::FixedPoint:
case Type::Category::Function:
case Type::Category::Array:
case Type::Category::Struct:
case Type::Category::Mapping:
case Type::Category::FixedBytes:
case Type::Category::Contract:
case Type::Category::UserDefinedValueType:
{
templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))");
break;
}
case Type::Category::Enum:
{
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
solAssert(members > 0, "empty enum should have caused a parser error.");
panicCode = PanicCode::EnumConversionError;
templ("condition", "lt(value, " + to_string(members) + ")");
break;
}
case Type::Category::InaccessibleDynamic:
templ("condition", "1");
break;
default:
solAssert(false, "Validation of type " + _type.identifier() + " requested.");
}
if (_revertOnFailure)
templ("failure", "revert(0, 0)");
else
templ("failure", panicFunction(panicCode) + "()");
return templ.render();
});
}
string YulUtilFunctions::packedHashFunction(
vector<Type const*> const& _givenTypes,
vector<Type const*> const& _targetTypes
)
{
string functionName = string("packed_hashed_");
for (auto const& t: _givenTypes)
functionName += t->identifier() + "_";
functionName += "_to_";
for (auto const& t: _targetTypes)
functionName += t->identifier() + "_";
size_t sizeOnStack = 0;
for (Type const* t: _givenTypes)
sizeOnStack += t->sizeOnStack();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(<variables>) -> hash {
let pos := <allocateUnbounded>()
let end := <packedEncode>(pos <comma> <variables>)
hash := keccak256(pos, sub(end, pos))
}
)");
templ("functionName", functionName);
templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack));
templ("comma", sizeOnStack > 0 ? "," : "");
templ("allocateUnbounded", allocateUnboundedFunction());
templ("packedEncode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes));
return templ.render();
});
}
string YulUtilFunctions::forwardingRevertFunction()
{
bool forward = m_evmVersion.supportsReturndata();
string functionName = "revert_forward_" + to_string(forward);
return m_functionCollector.createFunction(functionName, [&]() {
if (forward)
return Whiskers(R"(
function <functionName>() {
let pos := <allocateUnbounded>()
returndatacopy(pos, 0, returndatasize())
revert(pos, returndatasize())
}
)")
("functionName", functionName)
("allocateUnbounded", allocateUnboundedFunction())
.render();
else
return Whiskers(R"(
function <functionName>() {
revert(0, 0)
}
)")
("functionName", functionName)
.render();
});
}
std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
{
solAssert(_type.category() == Type::Category::Integer, "");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "decrement_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
value := <cleanupFunction>(value)
if eq(value, <minval>) { <panic>() }
ret := sub(value, 1)
}
)")
("functionName", functionName)
("panic", panicFunction(PanicCode::UnderOverflow))
("minval", toCompactHexWithPrefix(type.min()))
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
std::string YulUtilFunctions::decrementWrappingFunction(Type const& _type)
{
solAssert(_type.category() == Type::Category::Integer, "");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "decrement_wrapping_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
ret := <cleanupFunction>(sub(value, 1))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
{
solAssert(_type.category() == Type::Category::Integer, "");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "increment_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
value := <cleanupFunction>(value)
if eq(value, <maxval>) { <panic>() }
ret := add(value, 1)
}
)")
("functionName", functionName)
("maxval", toCompactHexWithPrefix(type.max()))
("panic", panicFunction(PanicCode::UnderOverflow))
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
std::string YulUtilFunctions::incrementWrappingFunction(Type const& _type)
{
solAssert(_type.category() == Type::Category::Integer, "");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "increment_wrapping_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
ret := <cleanupFunction>(add(value, 1))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
{
solAssert(_type.category() == Type::Category::Integer, "");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
solAssert(type.isSigned(), "Expected signed type!");
string const functionName = "negate_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
value := <cleanupFunction>(value)
if eq(value, <minval>) { <panic>() }
ret := sub(0, value)
}
)")
("functionName", functionName)
("minval", toCompactHexWithPrefix(type.min()))
("cleanupFunction", cleanupFunction(_type))
("panic", panicFunction(PanicCode::UnderOverflow))
.render();
});
}
string YulUtilFunctions::negateNumberWrappingFunction(Type const& _type)
{
solAssert(_type.category() == Type::Category::Integer, "");
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
solAssert(type.isSigned(), "Expected signed type!");
string const functionName = "negate_wrapping_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(value) -> ret {
ret := <cleanupFunction>(sub(0, value))
}
)")
("functionName", functionName)
("cleanupFunction", cleanupFunction(type))
.render();
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.category() != Type::Category::Mapping, "");
string const functionName = "zero_value_for_" + string(_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
FunctionType const* fType = dynamic_cast<FunctionType const*>(&_type);
if (fType && fType->kind() == FunctionType::Kind::External && _splitFunctionTypes)
return Whiskers(R"(
function <functionName>() -> retAddress, retFunction {
retAddress := 0
retFunction := 0
}
)")
("functionName", functionName)
.render();
if (_type.dataStoredIn(DataLocation::CallData))
{
solAssert(
_type.category() == Type::Category::Struct ||
_type.category() == Type::Category::Array,
"");
Whiskers templ(R"(
function <functionName>() -> offset<?hasLength>, length</hasLength> {
offset := calldatasize()
<?hasLength> length := 0 </hasLength>
}
)");
templ("functionName", functionName);
templ("hasLength",
_type.category() == Type::Category::Array &&
dynamic_cast<ArrayType const&>(_type).isDynamicallySized()
);
return templ.render();
}
Whiskers templ(R"(
function <functionName>() -> ret {
ret := <zeroValue>
}
)");
templ("functionName", functionName);
if (_type.isValueType())
{
solAssert((
_type.hasSimpleZeroValueInMemory() ||
(fType && (fType->kind() == FunctionType::Kind::Internal || fType->kind() == FunctionType::Kind::External))
), "");
templ("zeroValue", "0");
}
else
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type))
{
if (_type.isDynamicallySized())
templ("zeroValue", to_string(CompilerUtils::zeroPointer));
else
templ("zeroValue", allocateAndInitializeMemoryArrayFunction(*arrayType) + "(" + to_string(unsigned(arrayType->length())) + ")");
}
else if (auto const* structType = dynamic_cast<StructType const*>(&_type))
templ("zeroValue", allocateAndInitializeMemoryStructFunction(*structType) + "()");
else
solUnimplemented("");
}
return templ.render();
});
}
string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
{
string const functionName = "storage_set_to_zero_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
if (_type.isValueType())
return Whiskers(R"(
function <functionName>(slot, offset) {
let <values> := <zeroValue>()
<store>(slot, offset, <values>)
}
)")
("functionName", functionName)
("store", updateStorageValueFunction(_type, _type))
("values", suffixedVariableNameList("zero_", 0, _type.sizeOnStack()))
("zeroValue", zeroValueFunction(_type))
.render();
else if (_type.category() == Type::Category::Array)
return Whiskers(R"(
function <functionName>(slot, offset) {
if iszero(eq(offset, 0)) { <panic>() }
<clearArray>(slot)
}
)")
("functionName", functionName)
("clearArray", clearStorageArrayFunction(dynamic_cast<ArrayType const&>(_type)))
("panic", panicFunction(PanicCode::Generic))
.render();
else if (_type.category() == Type::Category::Struct)
return Whiskers(R"(
function <functionName>(slot, offset) {
if iszero(eq(offset, 0)) { <panic>() }
<clearStruct>(slot)
}
)")
("functionName", functionName)
("clearStruct", clearStorageStructFunction(dynamic_cast<StructType const&>(_type)))
("panic", panicFunction(PanicCode::Generic))
.render();
else
solUnimplemented("setToZero for type " + _type.identifier() + " not yet implemented!");
});
}
string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to)
{
string functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
if (
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
)
{
size_t sourceStackSize = 0;
size_t destStackSize = 0;
std::string conversions;
for (size_t i = 0; i < fromTuple->components().size(); ++i)
{
auto fromComponent = fromTuple->components()[i];
auto toComponent = toTuple->components()[i];
solAssert(fromComponent, "");
if (toComponent)
{
conversions +=
suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) +
(toComponent->sizeOnStack() > 0 ? " := " : "") +
conversionFunction(*fromComponent, *toComponent) +
"(" +
suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) +
")\n";
destStackSize += toComponent->sizeOnStack();
}
sourceStackSize += fromComponent->sizeOnStack();
}
return Whiskers(R"(
function <functionName>(<values>) <arrow> <converted> {
<conversions>
}
)")
("functionName", functionName)
("values", suffixedVariableNameList("value", 0, sourceStackSize))
("arrow", destStackSize > 0 ? "->" : "")
("converted", suffixedVariableNameList("converted", 0, destStackSize))
("conversions", conversions)
.render();
}
solUnimplementedAssert(
_from.category() == Type::Category::StringLiteral,
"Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented."
);
string const& data = dynamic_cast<StringLiteralType const&>(_from).value();
if (_to.category() == Type::Category::FixedBytes)
{
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_to).numBytes();
solAssert(data.size() <= 32, "");
Whiskers templ(R"(
function <functionName>() -> converted {
converted := <data>
}
)");
templ("functionName", functionName);
templ("data", formatNumber(
h256::Arith(h256(data, h256::AlignLeft)) &
(~(u256(-1) >> (8 * numBytes)))
));
return templ.render();
}
else if (_to.category() == Type::Category::Array)
{
solAssert(dynamic_cast<ArrayType const&>(_to).isByteArray(), "");
Whiskers templ(R"(
function <functionName>() -> converted {
converted := <copyLiteralToMemory>()
}
)");
templ("functionName", functionName);
templ("copyLiteralToMemory", copyLiteralToMemoryFunction(data));
return templ.render();
}
else
solAssert(
false,
"Invalid conversion from string literal to " + _to.toString() + " requested."
);
});
}
string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata)
{
string functionName =
string("read_from_") +
(_fromCalldata ? "calldata" : "memory") +
_type.identifier();
// TODO use ABI functions for handling calldata
if (_fromCalldata)
solAssert(!_type.isDynamicallyEncoded(), "");
return m_functionCollector.createFunction(functionName, [&] {
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
{
solAssert(refType->sizeOnStack() == 1, "");
solAssert(!_fromCalldata, "");
return Whiskers(R"(
function <functionName>(memPtr) -> value {
value := mload(memPtr)
}
)")
("functionName", functionName)
.render();
}
solAssert(_type.isValueType(), "");
Whiskers templ(R"(
function <functionName>(ptr) -> <returnVariables> {
<?fromCalldata>
let value := calldataload(ptr)
<validate>(value)
<!fromCalldata>
let value := <cleanup>(mload(ptr))
</fromCalldata>
<returnVariables> :=
<?externalFunction>
<splitFunction>(value)
<!externalFunction>
value
</externalFunction>
}
)");
templ("functionName", functionName);
templ("fromCalldata", _fromCalldata);
if (_fromCalldata)
templ("validate", validatorFunction(_type, true));
auto const* funType = dynamic_cast<FunctionType const*>(&_type);
if (funType && funType->kind() == FunctionType::Kind::External)
{
templ("externalFunction", true);
templ("splitFunction", splitExternalFunctionIdFunction());
templ("returnVariables", "addr, selector");
}
else
{
templ("externalFunction", false);
templ("returnVariables", "returnValue");
}
// Byte array elements generally need cleanup.
// Other types are cleaned as well to account for dirty memory e.g. due to inline assembly.
templ("cleanup", cleanupFunction(_type));
return templ.render();
});
}
string YulUtilFunctions::revertReasonIfDebugFunction(string const& _message)
{
string functionName = "revert_error_" + util::toHex(util::keccak256(_message).asBytes());
return m_functionCollector.createFunction(functionName, [&](auto&, auto&) -> string {
return revertReasonIfDebugBody(m_revertStrings, allocateUnboundedFunction() + "()", _message);
});
}
string YulUtilFunctions::revertReasonIfDebugBody(
RevertStrings _revertStrings,
string const& _allocation,
string const& _message
)
{
if (_revertStrings < RevertStrings::Debug || _message.empty())
return "revert(0, 0)";
Whiskers templ(R"(
let start := <allocate>
let pos := start
mstore(pos, <sig>)
pos := add(pos, 4)
mstore(pos, 0x20)
pos := add(pos, 0x20)
mstore(pos, <length>)
pos := add(pos, 0x20)
<#word>
mstore(add(pos, <offset>), <wordValue>)
</word>
revert(start, <overallLength>)
)");
templ("allocate", _allocation);
templ("sig", util::selectorFromSignature("Error(string)").str());
templ("length", to_string(_message.length()));
size_t words = (_message.length() + 31) / 32;
vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = to_string(i * 32);
wordParams[i]["wordValue"] = formatAsStringOrNumber(_message.substr(32 * i, 32));
}
templ("word", wordParams);
templ("overallLength", to_string(4 + 0x20 + 0x20 + words * 32));
return templ.render();
}
string YulUtilFunctions::panicFunction(util::PanicCode _code)
{
string functionName = "panic_error_" + toCompactHexWithPrefix(uint64_t(_code));
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() {
mstore(0, <selector>)
mstore(4, <code>)
revert(0, 0x24)
}
)")
("functionName", functionName)
("selector", util::selectorFromSignature("Panic(uint256)").str())
("code", toCompactHexWithPrefix(static_cast<unsigned>(_code)))
.render();
});
}
string YulUtilFunctions::returnDataSelectorFunction()
{
string const functionName = "return_data_selector";
solAssert(m_evmVersion.supportsReturndata(), "");
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> sig {
if gt(returndatasize(), 3) {
returndatacopy(0, 0, 4)
sig := <shr224>(mload(0))
}
}
)")
("functionName", functionName)
("shr224", shiftRightFunction(224))
.render();
});
}
string YulUtilFunctions::tryDecodeErrorMessageFunction()
{
string const functionName = "try_decode_error_message";
solAssert(m_evmVersion.supportsReturndata(), "");
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> ret {
if lt(returndatasize(), 0x44) { leave }
let data := <allocateUnbounded>()
returndatacopy(data, 4, sub(returndatasize(), 4))
let offset := mload(data)
if or(
gt(offset, 0xffffffffffffffff),
gt(add(offset, 0x24), returndatasize())
) {
leave
}
let msg := add(data, offset)
let length := mload(msg)
if gt(length, 0xffffffffffffffff) { leave }
let end := add(add(msg, 0x20), length)
if gt(end, add(data, sub(returndatasize(), 4))) { leave }
<finalizeAllocation>(data, add(offset, add(0x20, length)))
ret := msg
}
)")
("functionName", functionName)
("allocateUnbounded", allocateUnboundedFunction())
("finalizeAllocation", finalizeAllocationFunction())
.render();
});
}
string YulUtilFunctions::tryDecodePanicDataFunction()
{
string const functionName = "try_decode_panic_data";
solAssert(m_evmVersion.supportsReturndata(), "");
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> success, data {
if gt(returndatasize(), 0x23) {
returndatacopy(0, 4, 0x20)
success := 1
data := mload(0)
}
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::extractReturndataFunction()
{
string const functionName = "extract_returndata";
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> data {
<?supportsReturndata>
switch returndatasize()
case 0 {
data := <emptyArray>()
}
default {
data := <allocateArray>(returndatasize())
returndatacopy(add(data, 0x20), 0, returndatasize())
}
<!supportsReturndata>
data := <emptyArray>()
</supportsReturndata>
}
)")
("functionName", functionName)
("supportsReturndata", m_evmVersion.supportsReturndata())
("allocateArray", allocateMemoryArrayFunction(*TypeProvider::bytesMemory()))
("emptyArray", zeroValueFunction(*TypeProvider::bytesMemory()))
.render();
});
}
string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction(
ContractDefinition const& _contract,
string const& _creationObjectName
)
{
string functionName = "copy_arguments_for_constructor_" +
toString(_contract.constructor()->id()) +
"_object_" +
_contract.name() +
"_" +
toString(_contract.id());
return m_functionCollector.createFunction(functionName, [&]() {
string returnParams = suffixedVariableNameList("ret_param_",0, CompilerUtils::sizeOnStack(_contract.constructor()->parameters()));
ABIFunctions abiFunctions(m_evmVersion, m_revertStrings, m_functionCollector);
return util::Whiskers(R"(
function <functionName>() -> <retParams> {
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
<retParams> := <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
}
)")
("functionName", functionName)
("retParams", returnParams)
("object", _creationObjectName)
("allocate", allocationFunction())
("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true))
.render();
});
}
string YulUtilFunctions::externalCodeFunction()
{
string functionName = "external_code_at";
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>(addr) -> mpos {
let length := extcodesize(addr)
mpos := <allocateArray>(length)
extcodecopy(addr, add(mpos, 0x20), 0, length)
}
)")
("functionName", functionName)
("allocateArray", allocateMemoryArrayFunction(*TypeProvider::bytesMemory()))
.render();
});
}
std::string YulUtilFunctions::externalFunctionPointersEqualFunction()
{
std::string const functionName = "externalFunctionPointersEqualFunction";
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>(
leftAddress,
leftSelector,
rightAddress,
rightSelector
) -> result {
result := and(
eq(
<addressCleanUpFunction>(leftAddress), <addressCleanUpFunction>(rightAddress)
),
eq(
<selectorCleanUpFunction>(leftSelector), <selectorCleanUpFunction>(rightSelector)
)
)
}
)")
("functionName", functionName)
("addressCleanUpFunction", cleanupFunction(*TypeProvider::address()))
("selectorCleanUpFunction", cleanupFunction(*TypeProvider::uint(32)))
.render();
});
}