Implement ABI encoding of calldata arrays and structs.

This commit is contained in:
Daniel Kirchner
2019-04-04 13:05:32 +02:00
parent d82157d46a
commit 91a2a9a9c3
19 changed files with 531 additions and 25 deletions
+124 -24
View File
@@ -831,29 +831,34 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
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, "");
if (_from.dataStoredIn(DataLocation::Storage))
solAssert(_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;
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
string elementValues = m_utils.suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions));
Whiskers templ(
usesTail ?
R"(
// <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(value, pos) <return> {
let length := <lengthFun>(value)
function <functionName>(value,<maybeLength> pos) <return> {
<declareLength>
pos := <storeLength>(pos, length)
let headStart := pos
let tail := add(pos, mul(length, 0x20))
let srcPtr := <dataAreaFun>(value)
let baseRef := <dataAreaFun>(value)
let srcPtr := baseRef
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
mstore(pos, sub(tail, headStart))
tail := <encodeToMemoryFun>(<arrayElementAccess>, tail)
let <elementValues> := <arrayElementAccess>
tail := <encodeToMemoryFun>(<elementValues>, tail)
srcPtr := <nextArrayElement>(srcPtr)
pos := add(pos, 0x20)
}
@@ -863,13 +868,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
)" :
R"(
// <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(value, pos) <return> {
let length := <lengthFun>(value)
function <functionName>(value,<maybeLength> pos) <return> {
<declareLength>
pos := <storeLength>(pos, length)
let srcPtr := <dataAreaFun>(value)
let baseRef := <dataAreaFun>(value)
let srcPtr := baseRef
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
pos := <encodeToMemoryFun>(<arrayElementAccess>, pos)
let <elementValues> := <arrayElementAccess>
pos := <encodeToMemoryFun>(<elementValues>, pos)
srcPtr := <nextArrayElement>(srcPtr)
}
<assignEnd>
@@ -877,27 +884,43 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
)"
);
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("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())
switch (_from.location())
{
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
case DataLocation::Memory:
templ("arrayElementAccess", "mload(srcPtr)");
break;
case DataLocation::Storage:
if (_from.baseType()->isValueType())
templ("arrayElementAccess", 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, "");
}
else
templ("arrayElementAccess", "srcPtr");
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
return templ.render();
});
@@ -1189,7 +1212,11 @@ string ABIFunctions::abiEncodingFunctionStruct(
break;
}
case DataLocation::CallData:
solUnimplementedAssert(false, "Encoding struct from calldata is not yet supported.");
{
string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name));
members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))";
break;
}
default:
solAssert(false, "");
}
@@ -1675,7 +1702,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
});
}
string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
@@ -1721,6 +1747,80 @@ string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset,
});
}
string ABIFunctions::calldataAccessFunction(Type const& _type)
{
solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_access_" + _type.identifier();
return createFunction(functionName, [&]() {
if (_type.isDynamicallyEncoded())
{
unsigned int baseEncodedSize = _type.calldataEncodedSize();
solAssert(baseEncodedSize > 1, "");
Whiskers w(R"(
function <functionName>(base_ref, ptr) -> <return> {
let rel_offset_of_tail := calldataload(ptr)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
value := add(rel_offset_of_tail, base_ref)
<handleLength>
}
)");
if (_type.isDynamicallySized())
{
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
solAssert(!!arrayType, "");
unsigned int calldataStride = arrayType->calldataStride();
w("handleLength", Whiskers(R"(
length := calldataload(value)
value := add(value, 0x20)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
)")("calldataStride", toCompactHexWithPrefix(calldataStride)).render());
w("return", "value, length");
}
else
{
w("handleLength", "");
w("return", "value");
}
w("neededLength", toCompactHexWithPrefix(baseEncodedSize));
w("functionName", functionName);
return w.render();
}
else if (_type.isValueType())
{
string decodingFunction;
if (auto const* functionType = dynamic_cast<FunctionType const*>(&_type))
decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false);
else
decodingFunction = abiDecodingFunctionValueType(_type, false);
// Note that the second argument to the decoding function should be discarded after inlining.
return Whiskers(R"(
function <functionName>(baseRef, ptr) -> value {
value := <decodingFunction>(ptr, add(ptr, 32))
}
)")
("functionName", functionName)
("decodingFunction", decodingFunction)
.render();
}
else
{
solAssert(
_type.category() == Type::Category::Array ||
_type.category() == Type::Category::Struct,
""
);
return Whiskers(R"(
function <functionName>(baseRef, ptr) -> value {
value := ptr
}
)")
("functionName", functionName)
.render();
}
});
}
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
{
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
+3
View File
@@ -265,6 +265,9 @@ private:
/// single variable.
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
/// @returns the name of a function that retrieves an element from calldata.
std::string calldataAccessFunction(Type const& _type);
/// @returns the name of a function used during encoding that stores the length
/// if the array is dynamically sized (and the options do not request in-place encoding).
/// It returns the new encoding position.