Packed encoding.

This commit is contained in:
chriseth 2019-01-22 12:24:54 +01:00
parent d1024cefb9
commit 1b9d30f05f
2 changed files with 198 additions and 53 deletions

View File

@ -574,20 +574,66 @@ string ABIFunctions::abiEncodingFunction(
// special case: convert storage reference type to value type - this is only // special case: convert storage reference type to value type - this is only
// possible for library calls where we just forward the storage reference // possible for library calls where we just forward the storage reference
solAssert(_options.encodeAsLibraryTypes, ""); solAssert(_options.encodeAsLibraryTypes, "");
solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested.");
solAssert(to == IntegerType::uint256(), ""); solAssert(to == IntegerType::uint256(), "");
templ("cleanupConvert", "value"); templ("cleanupConvert", "value");
} }
else else
{ {
string cleanupConvert;
if (_from == to) if (_from == to)
templ("cleanupConvert", cleanupFunction(_from) + "(value)"); cleanupConvert = cleanupFunction(_from) + "(value)";
else else
templ("cleanupConvert", conversionFunction(_from, to) + "(value)"); cleanupConvert = conversionFunction(_from, to) + "(value)";
if (!_options.padded)
cleanupConvert = leftAlignFunction(to) + "(" + cleanupConvert + ")";
templ("cleanupConvert", cleanupConvert);
} }
return templ.render(); 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 encoder = abiEncodingFunction(_givenType, _targetType, _options);
if (_targetType.isDynamicallyEncoded())
return Whiskers(R"(
function <functionName>(value, pos) -> updatedPos {
updatedPos := <encode>(value, pos)
}
)")
("functionName", functionName)
("encode", encoder)
.render();
else
{
unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded);
solAssert(encodedSize != 0, "Invalid encoded size.");
return Whiskers(R"(
function <functionName>(value, pos) -> updatedPos {
<encode>(value, pos)
updatedPos := add(pos, <encodedSize>)
}
)")
("functionName", functionName)
("encode", encoder)
("encodedSize", toCompactHexWithPrefix(encodedSize))
.render();
}
});
}
string ABIFunctions::abiEncodingFunctionCalldataArray( string ABIFunctions::abiEncodingFunctionCalldataArray(
Type const& _from, Type const& _from,
Type const& _to, Type const& _to,
@ -623,15 +669,15 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
function <functionName>(start, length, pos) -> end { function <functionName>(start, length, pos) -> end {
pos := <storeLength>(pos, length) pos := <storeLength>(pos, length)
<copyFun>(start, pos, length) <copyFun>(start, pos, length)
end := add(pos, <roundUpFun>(length)) end := add(pos, <lengthPadded>)
} }
)"); )");
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType)); templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
templ("functionName", functionName); templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true)); templ("readableTypeNameTo", _to.toString(true));
templ("copyFun", copyToMemoryFunction(true)); templ("copyFun", copyToMemoryFunction(true));
templ("roundUpFun", roundUpFunction()); templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
return templ.render(); return templ.render();
}); });
} }
@ -659,8 +705,9 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
bool dynamic = _to.isDynamicallyEncoded(); bool dynamic = _to.isDynamicallyEncoded();
bool dynamicBase = _to.baseType()->isDynamicallyEncoded(); bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
bool inMemory = _from.dataStoredIn(DataLocation::Memory); bool inMemory = _from.dataStoredIn(DataLocation::Memory);
bool const usesTail = dynamicBase && !_options.dynamicInplace;
Whiskers templ( Whiskers templ(
dynamicBase ? usesTail ?
R"( R"(
// <readableTypeNameFrom> -> <readableTypeNameTo> // <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(value, pos) <return> { function <functionName>(value, pos) <return> {
@ -688,9 +735,8 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
let srcPtr := <dataAreaFun>(value) let srcPtr := <dataAreaFun>(value)
for { let i := 0 } lt(i, length) { i := add(i, 1) } for { let i := 0 } lt(i, length) { i := add(i, 1) }
{ {
<encodeToMemoryFun>(<arrayElementAccess>, pos) pos := <encodeToMemoryFun>(<arrayElementAccess>, pos)
srcPtr := <nextArrayElement>(srcPtr) srcPtr := <nextArrayElement>(srcPtr)
pos := add(pos, <elementEncodedSize>)
} }
<assignEnd> <assignEnd>
} }
@ -702,17 +748,13 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
templ("return", dynamic ? " -> end " : ""); templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := pos" : ""); templ("assignEnd", dynamic ? "end := pos" : "");
templ("lengthFun", arrayLengthFunction(_from)); templ("lengthFun", arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("dataAreaFun", arrayDataAreaFunction(_from)); templ("dataAreaFun", arrayDataAreaFunction(_from));
templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()));
EncodingOptions subOptions(_options); EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false; subOptions.encodeFunctionFromStack = false;
templ("encodeToMemoryFun", abiEncodingFunction( subOptions.padded = true;
*_from.baseType(), templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
*_to.baseType(),
subOptions
));
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
templ("nextArrayElement", nextArrayElementFunction(_from)); templ("nextArrayElement", nextArrayElementFunction(_from));
return templ.render(); return templ.render();
@ -742,15 +784,16 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray(
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(value, pos) -> end { function <functionName>(value, pos) -> end {
let length := <lengthFun>(value) let length := <lengthFun>(value)
mstore(pos, length) pos := <storeLength>(pos, length)
<copyFun>(add(value, 0x20), add(pos, 0x20), length) <copyFun>(add(value, 0x20), pos, length)
end := add(add(pos, 0x20), <roundUpFun>(length)) end := add(pos, <lengthPadded>)
} }
)"); )");
templ("functionName", functionName); templ("functionName", functionName);
templ("lengthFun", arrayLengthFunction(_from)); templ("lengthFun", arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("copyFun", copyToMemoryFunction(false)); templ("copyFun", copyToMemoryFunction(false));
templ("roundUpFun", roundUpFunction()); templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
return templ.render(); return templ.render();
}); });
} }
@ -784,28 +827,30 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
case 0 { case 0 {
// short byte array // short byte array
let length := and(div(slotValue, 2), 0x7f) let length := and(div(slotValue, 2), 0x7f)
mstore(pos, length) pos := <storeLength>(pos, length)
mstore(add(pos, 0x20), and(slotValue, not(0xff))) mstore(pos, and(slotValue, not(0xff)))
ret := add(pos, 0x40) ret := add(pos, <lengthPaddedShort>)
} }
case 1 { case 1 {
// long byte array // long byte array
let length := div(slotValue, 2) let length := div(slotValue, 2)
mstore(pos, length) pos := <storeLength>(pos, length)
pos := add(pos, 0x20)
let dataPos := <arrayDataSlot>(value) let dataPos := <arrayDataSlot>(value)
let i := 0 let i := 0
for { } lt(i, length) { i := add(i, 0x20) } { for { } lt(i, length) { i := add(i, 0x20) } {
mstore(add(pos, i), sload(dataPos)) mstore(add(pos, i), sload(dataPos))
dataPos := add(dataPos, 1) dataPos := add(dataPos, 1)
} }
ret := add(pos, i) ret := add(pos, <lengthPaddedLong>)
} }
} }
)"); )");
templ("functionName", functionName); templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.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", arrayDataAreaFunction(_from)); templ("arrayDataSlot", arrayDataAreaFunction(_from));
return templ.render(); return templ.render();
} }
@ -819,7 +864,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
size_t storageBytes = _from.baseType()->storageBytes(); size_t storageBytes = _from.baseType()->storageBytes();
size_t itemsPerSlot = 32 / storageBytes; size_t itemsPerSlot = 32 / storageBytes;
// This always writes full slot contents to memory, which might be // This always writes full slot contents to memory, which might be
// more than desired, i.e. it writes beyond the end of memory. // more than desired, i.e. it always writes beyond the end of memory.
Whiskers templ( Whiskers templ(
R"( R"(
// <readableTypeNameFrom> -> <readableTypeNameTo> // <readableTypeNameFrom> -> <readableTypeNameTo>
@ -848,14 +893,16 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
templ("return", dynamic ? " -> end " : ""); templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := pos" : ""); templ("assignEnd", dynamic ? "end := pos" : "");
templ("lengthFun", arrayLengthFunction(_from)); templ("lengthFun", arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("dataArea", arrayDataAreaFunction(_from)); templ("dataArea", arrayDataAreaFunction(_from));
templ("itemsPerSlot", to_string(itemsPerSlot)); templ("itemsPerSlot", to_string(itemsPerSlot));
// We use padded size because array elements are always padded.
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
templ("elementEncodedSize", elementEncodedSize); templ("elementEncodedSize", elementEncodedSize);
EncodingOptions subOptions(_options); EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false; subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
string encodeToMemoryFun = abiEncodingFunction( string encodeToMemoryFun = abiEncodingFunction(
*_from.baseType(), *_from.baseType(),
*_to.baseType(), *_to.baseType(),
@ -910,7 +957,12 @@ string ABIFunctions::abiEncodingFunctionStruct(
templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true)); templ("readableTypeNameTo", _to.toString(true));
templ("return", dynamic ? " -> end " : ""); templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := tail" : ""); 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 // to avoid multiple loads from the same slot for subsequent members
templ("init", fromStorage ? "let slotValue := 0" : ""); templ("init", fromStorage ? "let slotValue := 0" : "");
u256 previousSlotOffset(-1); u256 previousSlotOffset(-1);
@ -960,27 +1012,38 @@ string ABIFunctions::abiEncodingFunctionStruct(
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
} }
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
// Like with arrays, struct members are always padded.
subOptions.padded = true;
string encode;
if (_options.dynamicInplace)
encode = Whiskers{"pos := <encode>(memberValue, pos)"}
("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions))
.render();
else
{
Whiskers encodeTempl( Whiskers encodeTempl(
dynamicMember ? dynamicMember ?
string(R"( string(R"(
mstore(add(pos, <encodingOffset>), sub(tail, pos)) mstore(add(pos, <encodingOffset>), sub(tail, pos))
tail := <abiEncode>(memberValue, tail) tail := <abiEncode>(memberValue, tail)
)") : )") :
string(R"( "<abiEncode>(memberValue, add(pos, <encodingOffset>))"
<abiEncode>(memberValue, add(pos, <encodingOffset>))
)")
); );
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
encode = encodeTempl.render();
}
members.back()["encode"] = encode;
members.back()["encode"] = encodeTempl.render();
members.back()["memberName"] = member.name; members.back()["memberName"] = member.name;
} }
templ("members", members); templ("members", members);
if (_options.dynamicInplace)
solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size.");
templ("headSize", toCompactHexWithPrefix(encodingOffset)); templ("headSize", toCompactHexWithPrefix(encodingOffset));
return templ.render(); return templ.render();
}); });
@ -1007,9 +1070,10 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
if (_to.isDynamicallySized()) if (_to.isDynamicallySized())
{ {
solAssert(_to.category() == Type::Category::Array, "");
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(pos) -> end { function <functionName>(pos) -> end {
mstore(pos, <length>) pos := <storeLength>(pos, <length>)
<#word> <#word>
mstore(add(pos, <offset>), <wordValue>) mstore(add(pos, <offset>), <wordValue>)
</word> </word>
@ -1020,12 +1084,17 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
// TODO this can make use of CODECOPY for large strings once we have that in Yul // TODO this can make use of CODECOPY for large strings once we have that in Yul
size_t words = (value.size() + 31) / 32; size_t words = (value.size() + 31) / 32;
templ("overallSize", to_string(32 + words * 32));
templ("length", to_string(value.size())); templ("length", to_string(value.size()));
templ("storeLength", arrayStoreLengthForEncodingFunction(dynamic_cast<ArrayType const&>(_to), _options));
if (_options.padded)
templ("overallSize", to_string(words * 32));
else
templ("overallSize", to_string(value.size()));
vector<map<string, string>> wordParams(words); vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i) for (size_t i = 0; i < words; ++i)
{ {
wordParams[i]["offset"] = to_string(32 + i * 32); wordParams[i]["offset"] = to_string(i * 32);
wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex(); wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex();
} }
templ("word", wordParams); templ("word", wordParams);
@ -1427,6 +1496,66 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
}); });
} }
string ABIFunctions::leftAlignFunction(Type const& _type)
{
string functionName = string("leftAlign_") + _type.identifier();
return 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(AddressType::address()) + "(value)");
break;
case Type::Category::Enum:
{
unsigned storageBytes = dynamic_cast<EnumType const&>(_type).storageBytes();
templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)");
break;
}
case Type::Category::InaccessibleDynamic:
solAssert(false, "Left align requested for inaccessible dynamic type.");
break;
default:
solAssert(false, "Left align of type " + _type.identifier() + " requested.");
}
return templ.render();
});
}
string ABIFunctions::shiftLeftFunction(size_t _numBits) string ABIFunctions::shiftLeftFunction(size_t _numBits)
{ {
solAssert(_numBits < 256, ""); solAssert(_numBits < 256, "");
@ -1680,11 +1809,11 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
}); });
} }
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type) string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
{ {
string functionName = "array_storeLengthForEncoding_" + _type.identifier(); string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
return createFunction(functionName, [&]() { return createFunction(functionName, [&]() {
if (_type.isDynamicallySized()) if (_type.isDynamicallySized() && !_options.dynamicInplace)
return Whiskers(R"( return Whiskers(R"(
function <functionName>(pos, length) -> updated_pos { function <functionName>(pos, length) -> updated_pos {
mstore(pos, length) mstore(pos, length)

View File

@ -90,6 +90,8 @@ private:
struct EncodingOptions struct EncodingOptions
{ {
/// Pad/signextend value types and bytes/string to multiples of 32 bytes. /// Pad/signextend value types and bytes/string to multiples of 32 bytes.
/// If false, data is always left-aligned.
/// Note that this is always re-set to true for the elements of arrays and structs.
bool padded = true; bool padded = true;
/// Store arrays and structs in place without "data pointer" and do not store the length. /// Store arrays and structs in place without "data pointer" and do not store the length.
bool dynamicInplace = false; bool dynamicInplace = false;
@ -134,6 +136,14 @@ private:
Type const& _targetType, Type const& _targetType,
EncodingOptions const& _options EncodingOptions const& _options
); );
/// @returns the name of a function that internally calls `abiEncodingFunction`
/// but always returns the updated encoding position, even if the type is
/// statically encoded.
std::string abiEncodeAndReturnUpdatedPosFunction(
Type const& _givenType,
Type const& _targetType,
EncodingOptions const& _options
);
/// Part of @a abiEncodingFunction for array target type and given calldata array. /// Part of @a abiEncodingFunction for array target type and given calldata array.
std::string abiEncodingFunctionCalldataArray( std::string abiEncodingFunctionCalldataArray(
Type const& _givenType, Type const& _givenType,
@ -212,6 +222,10 @@ private:
/// Pads with zeros and might write more than exactly length. /// Pads with zeros and might write more than exactly length.
std::string copyToMemoryFunction(bool _fromCalldata); std::string copyToMemoryFunction(bool _fromCalldata);
/// @returns the name of a function that takes a (cleaned) value of the given value type and
/// left-aligns it, usually for use in non-padded encoding.
std::string leftAlignFunction(Type const& _type);
std::string shiftLeftFunction(size_t _numBits); std::string shiftLeftFunction(size_t _numBits);
std::string shiftRightFunction(size_t _numBits); std::string shiftRightFunction(size_t _numBits);
/// @returns the name of a function that rounds its input to the next multiple /// @returns the name of a function that rounds its input to the next multiple
@ -232,9 +246,11 @@ private:
std::string nextArrayElementFunction(ArrayType const& _type); std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function used during encoding that stores the length /// @returns the name of a function used during encoding that stores the length
/// if the array is dynamically sized. It returns the new encoding position. /// if the array is dynamically sized (and the options do not request in-place encoding).
/// If the array is not dynamically sized, does nothing and just returns the position again. /// It returns the new encoding position.
std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type); /// If the array is not dynamically sized (or in-place encoding was requested),
/// does nothing and just returns the position again.
std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options);
/// @returns the name of a function that allocates memory. /// @returns the name of a function that allocates memory.
/// Modifies the "free memory pointer" /// Modifies the "free memory pointer"