/*
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
using namespace std;
using namespace dev;
using namespace dev::solidity;
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 createExternallyUsedFunction(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 = m_utils.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 += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
stackPos += sizeOnStack;
}
solAssert(headPos == headSize_, "");
string valueParams = m_utils.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 createExternallyUsedFunction(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 = m_utils.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 = m_utils.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 createExternallyUsedFunction(functionName, [&]() {
TypePointers decodingTypes;
for (auto const& t: _types)
decodingTypes.emplace_back(t->decodingType());
Whiskers templ(R"(
function (headStart, dataEnd) {
if slt(sub(dataEnd, headStart), ) { revert(0, 0) }
}
)");
templ("functionName", functionName);
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) { revert(0, 0) }
:= (add(headStart, offset), dataEnd)
}
)" :
R"(
{
let offset :=
:= (add(headStart, offset), dataEnd)
}
)"
);
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 += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
}
templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
templ("arrow", valueReturnParams.empty() ? "" : "->");
templ("decodeElements", decodeElements);
return templ.render();
});
}
pair> ABIFunctions::requestedFunctions()
{
std::set empty;
swap(empty, m_externallyUsedFunctions);
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
}
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::cleanupFunction(Type const& _type, bool _revertOnFailure)
{
string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
return createFunction(functionName, [&]() {
Whiskers templ(R"(
function (value) -> cleaned {
}
)");
templ("functionName", functionName);
switch (_type.category())
{
case Type::Category::Address:
templ("body", "cleaned := " + cleanupFunction(IntegerType(160), _revertOnFailure) + "(value)");
break;
case Type::Category::Integer:
{
IntegerType const& type = dynamic_cast(_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::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(_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(_type).isPayable() ?
StateMutability::Payable :
StateMutability::NonPayable
);
templ("body", "cleaned := " + cleanupFunction(addressType, _revertOnFailure) + "(value)");
break;
}
case Type::Category::Enum:
{
size_t members = dynamic_cast(_type).numberOfMembers();
solAssert(members > 0, "empty enum should have caused a parser error.");
Whiskers w("if iszero(lt(value, )) { } cleaned := value");
w("members", to_string(members));
if (_revertOnFailure)
w("failure", "revert(0, 0)");
else
w("failure", "invalid()");
templ("body", w.render());
break;
}
case Type::Category::InaccessibleDynamic:
templ("body", "cleaned := 0");
break;
default:
solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
}
return templ.render();
});
}
string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
return createFunction(functionName, [&] {
Whiskers templ(R"(
function (value) -> cleaned {
}
)");
templ("functionName", functionName);
unsigned storageBytes = _type.storageBytes();
if (IntegerType const* type = dynamic_cast(&_type))
if (type->isSigned() && storageBytes != 32)
{
templ("body", "cleaned := signextend(" + to_string(storageBytes - 1) + ", value)");
return templ.render();
}
if (storageBytes == 32)
templ("body", "cleaned := value");
else if (_type.leftAligned())
templ("body", "cleaned := " + m_utils.shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
else
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
return templ.render();
});
}
string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
{
string functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return createFunction(functionName, [&]() {
Whiskers templ(R"(
function (value) -> converted {
}
)");
templ("functionName", functionName);
string body;
auto toCategory = _to.category();
auto fromCategory = _from.category();
switch (fromCategory)
{
case Type::Category::Address:
body =
Whiskers("converted := (value)")
("convert", conversionFunction(IntegerType(160), _to))
.render();
break;
case Type::Category::Integer:
case Type::Category::RationalNumber:
case Type::Category::Contract:
{
if (RationalNumberType const* rational = dynamic_cast(&_from))
solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType.");
if (toCategory == Type::Category::FixedBytes)
{
solAssert(
fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber,
"Invalid conversion to FixedBytesType requested."
);
FixedBytesType const& toBytesType = dynamic_cast(_to);
body =
Whiskers("converted := ((value))")
("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8))
("clean", cleanupFunction(_from))
.render();
}
else if (toCategory == Type::Category::Enum)
{
solAssert(_from.mobileType(), "");
body =
Whiskers("converted := ((value))")
("cleanEnum", cleanupFunction(_to))
// "mobileType()" returns integer type for rational
("cleanInt", cleanupFunction(*_from.mobileType()))
.render();
}
else if (toCategory == Type::Category::FixedPoint)
solUnimplemented("Not yet implemented - FixedPointType.");
else if (toCategory == Type::Category::Address)
body =
Whiskers("converted := (value)")
("convert", conversionFunction(_from, IntegerType(160)))
.render();
else
{
solAssert(
toCategory == Type::Category::Integer ||
toCategory == Type::Category::Contract,
"");
IntegerType const addressType(160);
IntegerType const& to =
toCategory == Type::Category::Integer ?
dynamic_cast(_to) :
addressType;
// Clean according to the "to" type, except if this is
// a widening conversion.
IntegerType const* cleanupType = &to;
if (fromCategory != Type::Category::RationalNumber)
{
IntegerType const& from =
fromCategory == Type::Category::Integer ?
dynamic_cast(_from) :
addressType;
if (to.numBits() > from.numBits())
cleanupType = &from;
}
body =
Whiskers("converted := (value)")
("cleanInt", cleanupFunction(*cleanupType))
.render();
}
break;
}
case Type::Category::Bool:
{
solAssert(_from == _to, "Invalid conversion for bool.");
body =
Whiskers("converted := (value)")
("clean", cleanupFunction(_from))
.render();
break;
}
case Type::Category::FixedPoint:
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Array:
solUnimplementedAssert(false, "Array conversion not implemented.");
break;
case Type::Category::Struct:
solUnimplementedAssert(false, "Struct conversion not implemented.");
break;
case Type::Category::FixedBytes:
{
FixedBytesType const& from = dynamic_cast(_from);
if (toCategory == Type::Category::Integer)
body =
Whiskers("converted := ((value))")
("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8))
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
.render();
else if (toCategory == Type::Category::Address)
body =
Whiskers("converted := (value)")
("convert", conversionFunction(_from, IntegerType(160)))
.render();
else
{
// clear for conversion to longer bytes
solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
body =
Whiskers("converted := (value)")
("clean", cleanupFunction(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(_from);
body =
Whiskers("converted := (value)")
("clean", cleanupFunction(enumType))
.render();
break;
}
case Type::Category::Tuple:
{
solUnimplementedAssert(false, "Tuple conversion not implemented.");
break;
}
default:
solAssert(false, "");
}
solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName());
templ("body", body);
return templ.render();
});
}
string ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
{
string functionName = "cleanup_combined_external_function_id";
return createFunction(functionName, [&]() {
return Whiskers(R"(
function (addr_and_selector) -> cleaned {
cleaned := (addr_and_selector)
}
)")
("functionName", functionName)
("clean", cleanupFunction(FixedBytesType(24)))
.render();
});
}
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);
if (fromArray.location() == DataLocation::CallData)
return abiEncodingFunctionCalldataArray(fromArray, *toArray, _options);
else if (!fromArray.isByteArray() && (
fromArray.location() == DataLocation::Memory ||
fromArray.baseType()->storageBytes() > 16
))
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
else if (fromArray.location() == DataLocation::Memory)
return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
else if (fromArray.location() == DataLocation::Storage)
return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
else
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 == IntegerType::uint256(), "");
templ("cleanupConvert", "value");
}
else
{
string cleanupConvert;
if (_from == to)
cleanupConvert = cleanupFunction(_from) + "(value)";
else
cleanupConvert = 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 = m_utils.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::abiEncodingFunctionCalldataArray(
Type const& _from,
Type const& _to,
EncodingOptions const& _options
)
{
solAssert(_to.isDynamicallySized(), "");
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.copyForLocation(DataLocation::Memory, true) ==
*toArrayType.copyForLocation(DataLocation::Memory, true),
""
);
string functionName =
"abi_encode_" +
_from.identifier() +
"_to_" +
_to.identifier() +
_options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently.");
// TODO if this is not a byte array, we might just copy byte-by-byte anyway,
// because the encoding is position-independent, but we have to check that.
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);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("copyFun", m_utils.copyToMemoryFunction(true));
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
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.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), "");
solAssert(!_from.isByteArray(), "");
solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, "");
return createFunction(functionName, [&]() {
bool dynamic = _to.isDynamicallyEncoded();
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
bool inMemory = _from.dataStoredIn(DataLocation::Memory);
bool const usesTail = dynamicBase && !_options.dynamicInplace;
Whiskers templ(
usesTail ?
R"(
// ->
function (value, pos) {
let length := (value)
pos := (pos, length)
let headStart := pos
let tail := add(pos, mul(length, 0x20))
let srcPtr := (value)
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
mstore(pos, sub(tail, headStart))
tail := (, tail)
srcPtr := (srcPtr)
pos := add(pos, 0x20)
}
pos := tail
}
)" :
R"(
// ->
function (value, pos) {
let length := (value)
pos := (pos, length)
let srcPtr := (value)
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
pos := (, pos)
srcPtr := (srcPtr)
}
}
)"
);
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("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
if (inMemory)
templ("arrayElementAccess", "mload(srcPtr)");
else if (_from.baseType()->isValueType())
{
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
}
else
templ("arrayElementAccess", "srcPtr");
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(_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));
// We use padded size because array elements are always padded.
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
templ("elementEncodedSize", elementEncodedSize);
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"] = 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();
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
return createFunction(functionName, [&]() {
bool fromStorage = _from.location() == DataLocation::Storage;
bool dynamic = _to.isDynamicallyEncoded();
Whiskers templ(R"(
// ->
function (value, pos) {
let tail := add(pos, )
<#members>
{
//
let memberValue :=
}
}
)");
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", fromStorage ? "let slotValue := 0" : "");
u256 previousSlotOffset(-1);
u256 encodingOffset = 0;
vector