Refactor abi encoding functions to prepare implementing calldata arrays and structs.

This commit is contained in:
Daniel Kirchner 2019-03-15 18:26:17 +01:00
parent b0cb330397
commit d82157d46a
3 changed files with 120 additions and 94 deletions

View File

@ -1106,11 +1106,9 @@ string ABIFunctions::abiEncodingFunctionStruct(
_to.identifier() + _to.identifier() +
_options.toFunctionNameSuffix(); _options.toFunctionNameSuffix();
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
return createFunction(functionName, [&]() { return createFunction(functionName, [&]() {
bool fromStorage = _from.location() == DataLocation::Storage;
bool dynamic = _to.isDynamicallyEncoded(); bool dynamic = _to.isDynamicallyEncoded();
Whiskers templ(R"( Whiskers templ(R"(
// <readableTypeNameFrom> -> <readableTypeNameTo> // <readableTypeNameFrom> -> <readableTypeNameTo>
@ -1121,7 +1119,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
{ {
// <memberName> // <memberName>
<preprocess> <preprocess>
let memberValue := <retrieveValue> let <memberValues> := <retrieveValue>
<encode> <encode>
} }
</members> </members>
@ -1139,7 +1137,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
else else
templ("assignEnd", ""); 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", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : "");
u256 previousSlotOffset(-1); u256 previousSlotOffset(-1);
u256 encodingOffset = 0; u256 encodingOffset = 0;
vector<map<string, string>> members; vector<map<string, string>> members;
@ -1159,32 +1157,41 @@ string ABIFunctions::abiEncodingFunctionStruct(
members.push_back({}); members.push_back({});
members.back()["preprocess"] = ""; members.back()["preprocess"] = "";
if (fromStorage) switch (_from.location())
{ {
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); case DataLocation::Storage:
u256 storageSlotOffset;
size_t intraSlotOffset;
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
if (memberTypeFrom->isValueType())
{ {
if (storageSlotOffset != previousSlotOffset) solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
u256 storageSlotOffset;
size_t intraSlotOffset;
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
if (memberTypeFrom->isValueType())
{ {
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; if (storageSlotOffset != previousSlotOffset)
previousSlotOffset = storageSlotOffset; {
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
previousSlotOffset = storageSlotOffset;
}
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
} }
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; else
{
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
solAssert(intraSlotOffset == 0, "");
members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")";
}
break;
} }
else case DataLocation::Memory:
{ {
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
solAssert(intraSlotOffset == 0, ""); members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; break;
} }
} case DataLocation::CallData:
else solUnimplementedAssert(false, "Encoding struct from calldata is not yet supported.");
{ default:
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); solAssert(false, "");
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
} }
EncodingOptions subOptions(_options); EncodingOptions subOptions(_options);
@ -1192,10 +1199,14 @@ string ABIFunctions::abiEncodingFunctionStruct(
// Like with arrays, struct members are always padded. // Like with arrays, struct members are always padded.
subOptions.padded = true; subOptions.padded = true;
string memberValues = m_utils.suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions));
members.back()["memberValues"] = memberValues;
string encode; string encode;
if (_options.dynamicInplace) if (_options.dynamicInplace)
encode = Whiskers{"pos := <encode>(memberValue, pos)"} encode = Whiskers{"pos := <encode>(<memberValues>, pos)"}
("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions)) ("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions))
("memberValues", memberValues)
.render(); .render();
else else
{ {
@ -1203,10 +1214,11 @@ string ABIFunctions::abiEncodingFunctionStruct(
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>(<memberValues>, tail)
)") : )") :
"<abiEncode>(memberValue, add(pos, <encodingOffset>))" "<abiEncode>(<memberValues>, add(pos, <encodingOffset>))"
); );
encodeTempl("memberValues", memberValues);
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));

View File

@ -330,40 +330,53 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
{ {
string functionName = "array_dataslot_" + _type.identifier(); string functionName = "array_dataslot_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
if (_type.dataStoredIn(DataLocation::Memory)) switch (_type.location())
{ {
if (_type.isDynamicallySized()) case DataLocation::Memory:
return Whiskers(R"( if (_type.isDynamicallySized())
function <functionName>(memPtr) -> dataPtr { return Whiskers(R"(
dataPtr := add(memPtr, 0x20) function <functionName>(memPtr) -> dataPtr {
} dataPtr := add(memPtr, 0x20)
)") }
("functionName", functionName) )")
.render(); ("functionName", functionName)
else .render();
return Whiskers(R"( else
function <functionName>(memPtr) -> dataPtr { return Whiskers(R"(
dataPtr := memPtr function <functionName>(memPtr) -> dataPtr {
} dataPtr := memPtr
)") }
("functionName", functionName) )")
.render(); ("functionName", functionName)
} .render();
else if (_type.dataStoredIn(DataLocation::Storage)) case DataLocation::Storage:
{ if (_type.isDynamicallySized())
if (_type.isDynamicallySized()) {
{ Whiskers w(R"(
Whiskers w(R"( function <functionName>(slot) -> dataSlot {
function <functionName>(slot) -> dataSlot { mstore(0, slot)
mstore(0, slot) dataSlot := keccak256(0, 0x20)
dataSlot := keccak256(0, 0x20) }
} )");
)"); w("functionName", functionName);
w("functionName", functionName); return w.render();
return w.render(); }
} else
else {
Whiskers w(R"(
function <functionName>(slot) -> dataSlot {
dataSlot := slot
}
)");
w("functionName", functionName);
return w.render();
}
case DataLocation::CallData:
{ {
// Calldata arrays 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.
Whiskers w(R"( Whiskers w(R"(
function <functionName>(slot) -> dataSlot { function <functionName>(slot) -> dataSlot {
dataSlot := slot dataSlot := slot
@ -372,11 +385,8 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
w("functionName", functionName); w("functionName", functionName);
return w.render(); return w.render();
} }
} default:
else solAssert(false, "");
{
// Not used for calldata
solAssert(false, "");
} }
}); });
} }
@ -384,36 +394,40 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{ {
solAssert(!_type.isByteArray(), ""); solAssert(!_type.isByteArray(), "");
solAssert( if (_type.dataStoredIn(DataLocation::Storage))
_type.location() == DataLocation::Memory || solAssert(_type.baseType()->storageBytes() > 16, "");
_type.location() == DataLocation::Storage,
""
);
solAssert(
_type.location() == DataLocation::Memory ||
_type.baseType()->storageBytes() > 16,
""
);
string functionName = "array_nextElement_" + _type.identifier(); string functionName = "array_nextElement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
if (_type.location() == DataLocation::Memory) switch (_type.location())
return Whiskers(R"( {
function <functionName>(memPtr) -> nextPtr { case DataLocation::Memory:
nextPtr := add(memPtr, 0x20) return Whiskers(R"(
} function <functionName>(memPtr) -> nextPtr {
)") nextPtr := add(memPtr, 0x20)
("functionName", functionName) }
.render(); )")
else if (_type.location() == DataLocation::Storage) ("functionName", functionName)
return Whiskers(R"( .render();
function <functionName>(slot) -> nextSlot { case DataLocation::Storage:
nextSlot := add(slot, 1) return Whiskers(R"(
} function <functionName>(slot) -> nextSlot {
)") nextSlot := add(slot, 1)
("functionName", functionName) }
.render(); )")
else ("functionName", functionName)
solAssert(false, ""); .render();
case DataLocation::CallData:
return Whiskers(R"(
function <functionName>(calldataPtr) -> nextPtr {
nextPtr := add(calldataPtr, <stride>)
}
)")
("stride", toCompactHexWithPrefix(_type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize()))
("functionName", functionName)
.render();
default:
solAssert(false, "");
}
}); });
} }

View File

@ -79,11 +79,11 @@ public:
/// The function reverts for too large lengths. /// The function reverts for too large lengths.
std::string arrayAllocationSizeFunction(ArrayType const& _type); std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number /// @returns the name of a function that converts a storage slot number
/// or a memory pointer to the slot number / memory pointer for the data position of an array /// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
/// which is stored in that slot / memory area. /// for the data position of an array which is stored in that slot / memory area / calldata area.
std::string arrayDataAreaFunction(ArrayType const& _type); std::string arrayDataAreaFunction(ArrayType const& _type);
/// @returns the name of a function that advances an array data pointer to the next element. /// @returns the name of a function that advances an array data pointer to the next element.
/// Only works for memory arrays and storage arrays that store one item per slot. /// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type); std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function that allocates memory. /// @returns the name of a function that allocates memory.