/*
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 .
*/
/**
* @author Christian
* @date 2017
* Routines that generate Yul code related to ABI encoding, decoding and type conversions.
*/
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
string ABIFunctions::tupleEncoder(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _encodeAsLibraryTypes
)
{
EncodingOptions options;
options.encodeAsLibraryTypes = _encodeAsLibraryTypes;
options.encodeFunctionFromStack = true;
options.padded = true;
options.dynamicInplace = false;
string functionName = string("abi_encode_tuple_");
for (auto const& t: _givenTypes)
functionName += t->identifier() + "_";
functionName += "_to_";
for (auto const& t: _targetTypes)
functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
// Note that the values are in reverse due to the difference in calling semantics.
Whiskers templ(R"(
function (headStart ) -> tail {
tail := add(headStart, )
}
)");
templ("functionName", functionName);
size_t const headSize_ = headSize(_targetTypes);
templ("headSize", to_string(headSize_));
string encodeElements;
size_t headPos = 0;
size_t stackPos = 0;
for (size_t i = 0; i < _givenTypes.size(); ++i)
{
solAssert(_givenTypes[i], "");
solAssert(_targetTypes[i], "");
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
Whiskers elementTempl(
dynamic ?
string(R"(
mstore(add(headStart, ), sub(tail, headStart))
tail := ( tail)
)") :
string(R"(
( add(headStart, ))
)")
);
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
elementTempl("values", values.empty() ? "" : values + ", ");
elementTempl("pos", to_string(headPos));
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
encodeElements += elementTempl.render();
headPos += _targetTypes[i]->calldataHeadSize();
stackPos += sizeOnStack;
}
solAssert(headPos == headSize_, "");
string valueParams = suffixedVariableNameList("value", stackPos, 0);
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
templ("encodeElements", encodeElements);
return templ.render();
});
}
string ABIFunctions::tupleEncoderPacked(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes
)
{
EncodingOptions options;
options.encodeAsLibraryTypes = false;
options.encodeFunctionFromStack = true;
options.padded = false;
options.dynamicInplace = true;
string functionName = string("abi_encode_tuple_packed_");
for (auto const& t: _givenTypes)
functionName += t->identifier() + "_";
functionName += "_to_";
for (auto const& t: _targetTypes)
functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
solAssert(!_givenTypes.empty(), "");
// Note that the values are in reverse due to the difference in calling semantics.
Whiskers templ(R"(
function (pos ) -> end {
end := pos
}
)");
templ("functionName", functionName);
string encodeElements;
size_t stackPos = 0;
for (size_t i = 0; i < _givenTypes.size(); ++i)
{
solAssert(_givenTypes[i], "");
solAssert(_targetTypes[i], "");
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
Whiskers elementTempl(
dynamic ?
string(R"(
pos := ( pos)
)") :
string(R"(
( pos)
pos := add(pos, )
)")
);
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
elementTempl("values", values.empty() ? "" : values + ", ");
if (!dynamic)
elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
encodeElements += elementTempl.render();
stackPos += sizeOnStack;
}
string valueParams = suffixedVariableNameList("value", stackPos, 0);
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
templ("encodeElements", encodeElements);
return templ.render();
});
}
string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
{
string functionName = string("abi_decode_tuple_");
for (auto const& t: _types)
functionName += t->identifier();
if (_fromMemory)
functionName += "_fromMemory";
return createFunction(functionName, [&]() {
TypePointers decodingTypes;
for (auto const& t: _types)
decodingTypes.emplace_back(t->decodingType());
Whiskers templ(R"(
function (headStart, dataEnd) {
if slt(sub(dataEnd, headStart), ) { }
}
)");
templ("functionName", functionName);
templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short"));
templ("minimumSize", to_string(headSize(decodingTypes)));
string decodeElements;
vector valueReturnParams;
size_t headPos = 0;
size_t stackPos = 0;
for (size_t i = 0; i < _types.size(); ++i)
{
solAssert(_types[i], "");
solAssert(decodingTypes[i], "");
size_t sizeOnStack = _types[i]->sizeOnStack();
solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), "");
solAssert(sizeOnStack > 0, "");
vector valueNamesLocal;
for (size_t j = 0; j < sizeOnStack; j++)
{
valueNamesLocal.emplace_back("value" + to_string(stackPos));
valueReturnParams.emplace_back("value" + to_string(stackPos));
stackPos++;
}
bool dynamic = decodingTypes[i]->isDynamicallyEncoded();
Whiskers elementTempl(
dynamic ?
R"(
{
let offset := (add(headStart, ))
if gt(offset, 0xffffffffffffffff) { }
:= (add(headStart, offset), dataEnd)
}
)" :
R"(
{
let offset :=
:= (add(headStart, offset), dataEnd)
}
)"
);
// TODO add test
elementTempl("revertString", revertReasonIfDebug("ABI decoding: invalid tuple offset"));
elementTempl("load", _fromMemory ? "mload" : "calldataload");
elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
elementTempl("pos", to_string(headPos));
elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
decodeElements += elementTempl.render();
headPos += decodingTypes[i]->calldataHeadSize();
}
templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
templ("arrow", valueReturnParams.empty() ? "" : "->");
templ("decodeElements", decodeElements);
return templ.render();
});
}
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
{
string suffix;
if (!padded)
suffix += "_nonPadded";
if (dynamicInplace)
suffix += "_inplace";
if (encodeFunctionFromStack)
suffix += "_fromStack";
if (encodeAsLibraryTypes)
suffix += "_library";
return suffix;
}
string ABIFunctions::abiEncodingFunction(
Type const& _from,
Type const& _to,
EncodingOptions const& _options
)
{
TypePointer toInterface = _to.fullEncodingType(_options.encodeAsLibraryTypes, true, false);
solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented.");
Type const& to = *toInterface;
if (_from.category() == Type::Category::StringLiteral)
return abiEncodingFunctionStringLiteral(_from, to, _options);
else if (auto toArray = dynamic_cast(&to))
{
solAssert(_from.category() == Type::Category::Array, "");
solAssert(to.dataStoredIn(DataLocation::Memory), "");
ArrayType const& fromArray = dynamic_cast(_from);
switch (fromArray.location())
{
case DataLocation::CallData:
if (
fromArray.isByteArray() ||
*fromArray.baseType() == *TypeProvider::uint256() ||
*fromArray.baseType() == FixedBytesType(32)
)
return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
case DataLocation::Memory:
if (fromArray.isByteArray())
return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
case DataLocation::Storage:
if (fromArray.baseType()->storageBytes() <= 16)
return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
default:
solAssert(false, "");
}
}
else if (auto const* toStruct = dynamic_cast(&to))
{
StructType const* fromStruct = dynamic_cast(&_from);
solAssert(fromStruct, "");
return abiEncodingFunctionStruct(*fromStruct, *toStruct, _options);
}
else if (_from.category() == Type::Category::Function)
return abiEncodingFunctionFunctionType(
dynamic_cast(_from),
to,
_options
);
solAssert(_from.sizeOnStack() == 1, "");
solAssert(to.isValueType(), "");
solAssert(to.calldataEncodedSize() == 32, "");
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
to.identifier() +
_options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
solAssert(!to.isDynamicallyEncoded(), "");
Whiskers templ(R"(
function (value, pos) {
mstore(pos, )
}
)");
templ("functionName", functionName);
if (_from.dataStoredIn(DataLocation::Storage))
{
// special case: convert storage reference type to value type - this is only
// possible for library calls where we just forward the storage reference
solAssert(_options.encodeAsLibraryTypes, "");
solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested.");
solAssert(to == *TypeProvider::uint256(), "");
templ("cleanupConvert", "value");
}
else
{
string cleanupConvert;
if (_from == to)
cleanupConvert = m_utils.cleanupFunction(_from) + "(value)";
else
cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)";
if (!_options.padded)
cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")";
templ("cleanupConvert", cleanupConvert);
}
return templ.render();
});
}
string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
Type const& _givenType,
Type const& _targetType,
ABIFunctions::EncodingOptions const& _options
)
{
string functionName =
"abi_encodeUpdatedPos_" +
_givenType.identifier() +
"_to_" +
_targetType.identifier() +
_options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options));
string encoder = abiEncodingFunction(_givenType, _targetType, _options);
if (_targetType.isDynamicallyEncoded())
return Whiskers(R"(
function (, pos) -> updatedPos {
updatedPos := (, pos)
}
)")
("functionName", functionName)
("encode", encoder)
("values", values)
.render();
else
{
unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded);
solAssert(encodedSize != 0, "Invalid encoded size.");
return Whiskers(R"(
function (, pos) -> updatedPos {
(, pos)
updatedPos := add(pos, )
}
)")
("functionName", functionName)
("encode", encoder)
("encodedSize", toCompactHexWithPrefix(encodedSize))
("values", values)
.render();
}
});
}
string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
Type const& _from,
Type const& _to,
EncodingOptions const& _options
)
{
solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type.");
solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type.");
auto const& fromArrayType = dynamic_cast(_from);
auto const& toArrayType = dynamic_cast(_to);
solAssert(fromArrayType.location() == DataLocation::CallData, "");
solAssert(
fromArrayType.isByteArray() ||
*fromArrayType.baseType() == *TypeProvider::uint256() ||
*fromArrayType.baseType() == FixedBytesType(32),
""
);
solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), "");
solAssert(
*fromArrayType.copyForLocation(DataLocation::Memory, true) ==
*toArrayType.copyForLocation(DataLocation::Memory, true),
""
);
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
_to.identifier() +
_options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
bool needsPadding = _options.padded && fromArrayType.isByteArray();
if (fromArrayType.isDynamicallySized())
{
Whiskers templ(R"(
// ->
function (start, length, pos) -> end {
pos := (pos, length)
(start, pos, length)
end := add(pos, )
}
)");
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
templ("functionName", functionName);
if (fromArrayType.isByteArray() || fromArrayType.calldataStride() == 1)
templ("scaleLengthByStride", "");
else
templ("scaleLengthByStride",
Whiskers(R"(
if gt(length, ) { }
length := mul(length, )
)")
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
("revertString", revertReasonIfDebug("ABI encoding: array data too long"))
.render()
// TODO add revert test
);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("copyFun", m_utils.copyToMemoryFunction(true));
templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length");
return templ.render();
}
else
{
solAssert(fromArrayType.calldataStride() == 32, "");
Whiskers templ(R"(
// ->
function (start, pos) {
(start, pos, )
}
)");
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("copyFun", m_utils.copyToMemoryFunction(true));
templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride()));
return templ.render();
}
});
}
string ABIFunctions::abiEncodingFunctionSimpleArray(
ArrayType const& _from,
ArrayType const& _to,
EncodingOptions const& _options
)
{
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
_to.identifier() +
_options.toFunctionNameSuffix();
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
solAssert(_from.length() == _to.length(), "");
solAssert(!_from.isByteArray(), "");
if (_from.dataStoredIn(DataLocation::Storage))
solAssert(_from.baseType()->storageBytes() > 16, "");
return createFunction(functionName, [&]() {
bool dynamic = _to.isDynamicallyEncoded();
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
bool const usesTail = dynamicBase && !_options.dynamicInplace;
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
string elementValues = suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions));
Whiskers templ(
usesTail ?
R"(
// ->
function (value, pos) {
pos := (pos, length)
let headStart := pos
let tail := add(pos, mul(length, 0x20))
let baseRef := (value)
let srcPtr := baseRef
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
mstore(pos, sub(tail, headStart))
let :=
tail := (, tail)
srcPtr := (srcPtr)
pos := add(pos, 0x20)
}
pos := tail
}
)" :
R"(
// ->
function (value, pos) {
pos := (pos, length)
let baseRef := (value)
let srcPtr := baseRef
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
let :=
pos := (, pos)
srcPtr := (srcPtr)
}
}
)"
);
templ("functionName", functionName);
templ("elementValues", elementValues);
bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized();
if (lengthAsArgument)
{
templ("maybeLength", " length,");
templ("declareLength", "");
}
else
{
templ("maybeLength", "");
templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)");
}
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := pos" : "");
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
switch (_from.location())
{
case DataLocation::Memory:
templ("arrayElementAccess", "mload(srcPtr)");
break;
case DataLocation::Storage:
if (_from.baseType()->isValueType())
templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
else
templ("arrayElementAccess", "srcPtr");
break;
case DataLocation::CallData:
templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)");
break;
default:
solAssert(false, "");
}
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
return templ.render();
});
}
string ABIFunctions::abiEncodingFunctionMemoryByteArray(
ArrayType const& _from,
ArrayType const& _to,
EncodingOptions const& _options
)
{
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
_to.identifier() +
_options.toFunctionNameSuffix();
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
solAssert(_from.length() == _to.length(), "");
solAssert(_from.dataStoredIn(DataLocation::Memory), "");
solAssert(_from.isByteArray(), "");
return createFunction(functionName, [&]() {
solAssert(_to.isByteArray(), "");
Whiskers templ(R"(
function (value, pos) -> end {
let length := (value)
pos := (pos, length)
(add(value, 0x20), pos, length)
end := add(pos, )
}
)");
templ("functionName", functionName);
templ("lengthFun", m_utils.arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("copyFun", m_utils.copyToMemoryFunction(false));
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
return templ.render();
});
}
string ABIFunctions::abiEncodingFunctionCompactStorageArray(
ArrayType const& _from,
ArrayType const& _to,
EncodingOptions const& _options
)
{
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
_to.identifier() +
_options.toFunctionNameSuffix();
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
solAssert(_from.length() == _to.length(), "");
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
return createFunction(functionName, [&]() {
if (_from.isByteArray())
{
solAssert(_to.isByteArray(), "");
Whiskers templ(R"(
// ->
function (value, pos) -> ret {
let slotValue := sload(value)
switch and(slotValue, 1)
case 0 {
// short byte array
let length := and(div(slotValue, 2), 0x7f)
pos := (pos, length)
mstore(pos, and(slotValue, not(0xff)))
ret := add(pos, )
}
case 1 {
// long byte array
let length := div(slotValue, 2)
pos := (pos, length)
let dataPos := (value)
let i := 0
for { } lt(i, length) { i := add(i, 0x20) } {
mstore(add(pos, i), sload(dataPos))
dataPos := add(dataPos, 1)
}
ret := add(pos, )
}
}
)");
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("lengthPaddedShort", _options.padded ? "0x20" : "length");
templ("lengthPaddedLong", _options.padded ? "i" : "length");
templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from));
return templ.render();
}
else
{
// Multiple items per slot
solAssert(_from.baseType()->storageBytes() <= 16, "");
solAssert(!_from.baseType()->isDynamicallyEncoded(), "");
solAssert(!_to.baseType()->isDynamicallyEncoded(), "");
solAssert(_from.baseType()->isValueType(), "");
bool dynamic = _to.isDynamicallyEncoded();
size_t storageBytes = _from.baseType()->storageBytes();
size_t itemsPerSlot = 32 / storageBytes;
solAssert(itemsPerSlot > 0, "");
// The number of elements we need to handle manually after the loop.
size_t spill = size_t(_from.length() % itemsPerSlot);
Whiskers templ(
R"(
// ->
function (value, pos) {
let length := (value)
pos := (pos, length)
let originalPos := pos
let srcPtr := (value)
let itemCounter := 0
if {
// Run the loop over all full slots
for { } lt(add(itemCounter, sub(, 1)), length)
{ itemCounter := add(itemCounter, ) }
{
let data := sload(srcPtr)
<#items>
((data), pos)
pos := add(pos, )
srcPtr := add(srcPtr, 1)
}
}
// Handle the last (not necessarily full) slot specially
if {
let data := sload(srcPtr)
<#items>
if {
((data), pos)
pos := add(pos, )
itemCounter := add(itemCounter, 1)
}
}
}
)"
);
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := pos" : "");
templ("lengthFun", m_utils.arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("dataArea", m_utils.arrayDataAreaFunction(_from));
// We skip the loop for arrays that fit a single slot.
if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot)
templ("useLoop", "1");
else
templ("useLoop", "0");
if (_from.isDynamicallySized() || spill != 0)
templ("useSpill", "1");
else
templ("useSpill", "0");
templ("itemsPerSlot", to_string(itemsPerSlot));
templ("stride", toCompactHexWithPrefix(_to.calldataStride()));
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
string encodeToMemoryFun = abiEncodingFunction(
*_from.baseType(),
*_to.baseType(),
subOptions
);
templ("encodeToMemoryFun", encodeToMemoryFun);
std::vector> items(itemsPerSlot);
for (size_t i = 0; i < itemsPerSlot; ++i)
{
if (_from.isDynamicallySized())
items[i]["inRange"] = "lt(itemCounter, length)";
else if (i < spill)
items[i]["inRange"] = "1";
else
items[i]["inRange"] = "0";
items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
}
templ("items", items);
return templ.render();
}
});
}
string ABIFunctions::abiEncodingFunctionStruct(
StructType const& _from,
StructType const& _to,
EncodingOptions const& _options
)
{
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
_to.identifier() +
_options.toFunctionNameSuffix();
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
return createFunction(functionName, [&]() {
bool dynamic = _to.isDynamicallyEncoded();
Whiskers templ(R"(
// ->
function (value, pos) {
let tail := add(pos, )
<#members>
{
//
let :=
}
}
)");
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("return", dynamic ? " -> end " : "");
if (dynamic && _options.dynamicInplace)
templ("assignEnd", "end := pos");
else if (dynamic && !_options.dynamicInplace)
templ("assignEnd", "end := tail");
else
templ("assignEnd", "");
// to avoid multiple loads from the same slot for subsequent members
templ("init", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : "");
u256 previousSlotOffset(-1);
u256 encodingOffset = 0;
vector