mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Implement struct encoder.
This commit is contained in:
parent
6385641f6e
commit
70d70e7816
@ -1735,7 +1735,15 @@ unsigned StructType::calldataEncodedSize(bool _padded) const
|
||||
|
||||
bool StructType::isDynamicallyEncoded() const
|
||||
{
|
||||
solAssert(false, "Structs are not yet supported in the ABI.");
|
||||
solAssert(!recursive(), "");
|
||||
for (auto t: memoryMemberTypes())
|
||||
{
|
||||
solAssert(t, "Parameter should have external type.");
|
||||
t = t->interfaceType(false);
|
||||
if (t->isDynamicallyEncoded())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
u256 StructType::memorySize() const
|
||||
|
@ -745,7 +745,7 @@ public:
|
||||
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
virtual TypePointer encodingType() const override
|
||||
{
|
||||
return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : TypePointer();
|
||||
return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : shared_from_this();
|
||||
}
|
||||
virtual TypePointer interfaceType(bool _inLibrary) const override;
|
||||
|
||||
|
@ -404,9 +404,11 @@ string ABIFunctions::abiEncodingFunction(
|
||||
else
|
||||
solAssert(false, "");
|
||||
}
|
||||
else if (dynamic_cast<StructType const*>(&to))
|
||||
else if (auto const* toStruct = dynamic_cast<StructType const*>(&to))
|
||||
{
|
||||
solUnimplementedAssert(false, "Structs not yet implemented.");
|
||||
StructType const* fromStruct = dynamic_cast<StructType const*>(&_from);
|
||||
solAssert(fromStruct, "");
|
||||
return abiEncodingFunctionStruct(*fromStruct, *toStruct, _encodeAsLibraryTypes);
|
||||
}
|
||||
else if (_from.category() == Type::Category::Function)
|
||||
return abiEncodingFunctionFunctionType(
|
||||
@ -534,7 +536,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||
{
|
||||
mstore(pos, sub(tail, headStart))
|
||||
tail := <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), tail)
|
||||
tail := <encodeToMemoryFun>(<arrayElementAccess>, tail)
|
||||
srcPtr := <nextArrayElement>(srcPtr)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
}
|
||||
@ -549,7 +551,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
let srcPtr := <dataAreaFun>(value)
|
||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||
{
|
||||
<encodeToMemoryFun>(<arrayElementAccess>(srcPtr), pos)
|
||||
<encodeToMemoryFun>(<arrayElementAccess>, pos)
|
||||
srcPtr := <nextArrayElement>(srcPtr)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
}
|
||||
@ -573,7 +575,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
_encodeAsLibraryTypes,
|
||||
true
|
||||
));
|
||||
templ("arrayElementAccess", inMemory ? "mload" : "sload");
|
||||
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||
templ("nextArrayElement", nextArrayElementFunction(_from));
|
||||
return templ.render();
|
||||
});
|
||||
@ -726,6 +728,122 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunctionStruct(
|
||||
StructType const& _from,
|
||||
StructType const& _to,
|
||||
bool _encodeAsLibraryTypes
|
||||
)
|
||||
{
|
||||
string functionName =
|
||||
"abi_encode_" +
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier() +
|
||||
(_encodeAsLibraryTypes ? "_library" : "");
|
||||
|
||||
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "");
|
||||
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
|
||||
|
||||
return createFunction(functionName, [&]() {
|
||||
bool fromStorage = _from.location() == DataLocation::Storage;
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value, pos) <return> {
|
||||
let tail := add(pos, <headSize>)
|
||||
<init>
|
||||
<#members>
|
||||
{
|
||||
// <memberName>
|
||||
<encode>
|
||||
}
|
||||
</members>
|
||||
<assignEnd>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("return", dynamic ? " -> end " : "");
|
||||
templ("assignEnd", dynamic ? "end := tail" : "");
|
||||
// to avoid multiple loads from the same slot for subsequent members
|
||||
templ("init", fromStorage ? "let slotValue := 0" : "");
|
||||
u256 previousSlotOffset(-1);
|
||||
u256 encodingOffset = 0;
|
||||
vector<map<string, string>> members;
|
||||
for (auto const& member: _to.members(nullptr))
|
||||
{
|
||||
solAssert(member.type, "");
|
||||
if (!member.type->canLiveOutsideStorage())
|
||||
continue;
|
||||
solUnimplementedAssert(
|
||||
member.type->mobileType() &&
|
||||
member.type->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
|
||||
member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
|
||||
"Encoding type \"" + member.type->toString() + "\" not yet implemented."
|
||||
);
|
||||
auto memberTypeTo = member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
|
||||
auto memberTypeFrom = _from.memberType(member.name);
|
||||
solAssert(memberTypeFrom, "");
|
||||
bool dynamicMember = memberTypeTo->isDynamicallyEncoded();
|
||||
if (dynamicMember)
|
||||
solAssert(dynamic, "");
|
||||
Whiskers memberTempl(R"(
|
||||
<preprocess>
|
||||
let memberValue := <retrieveValue>
|
||||
)" + (
|
||||
dynamicMember ?
|
||||
string(R"(
|
||||
mstore(add(pos, <encodingOffset>), sub(tail, pos))
|
||||
tail := <abiEncode>(memberValue, tail)
|
||||
)") :
|
||||
string(R"(
|
||||
<abiEncode>(memberValue, add(pos, <encodingOffset>))
|
||||
)")
|
||||
)
|
||||
);
|
||||
if (fromStorage)
|
||||
{
|
||||
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
|
||||
u256 storageSlotOffset;
|
||||
size_t intraSlotOffset;
|
||||
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
|
||||
if (memberTypeFrom->isValueType())
|
||||
{
|
||||
if (storageSlotOffset != previousSlotOffset)
|
||||
{
|
||||
memberTempl("preprocess", "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))");
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
else
|
||||
memberTempl("preprocess", "");
|
||||
memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8, false) + "(slotValue)");
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
|
||||
solAssert(intraSlotOffset == 0, "");
|
||||
memberTempl("preprocess", "");
|
||||
memberTempl("retrieveValue", "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memberTempl("preprocess", "");
|
||||
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
|
||||
memberTempl("retrieveValue", "mload(add(value, " + sourceOffset + "))");
|
||||
}
|
||||
memberTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
|
||||
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
|
||||
memberTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, _encodeAsLibraryTypes, false));
|
||||
|
||||
members.push_back({});
|
||||
members.back()["encode"] = memberTempl.render();
|
||||
members.back()["memberName"] = member.name;
|
||||
}
|
||||
templ("members", members);
|
||||
templ("headSize", toCompactHexWithPrefix(encodingOffset));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunctionStringLiteral(
|
||||
Type const& _from,
|
||||
Type const& _to,
|
||||
|
@ -123,6 +123,13 @@ private:
|
||||
bool _encodeAsLibraryTypes
|
||||
);
|
||||
|
||||
/// Part of @a abiEncodingFunction for struct types.
|
||||
std::string abiEncodingFunctionStruct(
|
||||
StructType const& _givenType,
|
||||
StructType const& _targetType,
|
||||
bool _encodeAsLibraryTypes
|
||||
);
|
||||
|
||||
// @returns the name of the ABI encoding function with the given type
|
||||
// and queues the generation of the function to the requested functions.
|
||||
// Case for _givenType being a string literal
|
||||
|
@ -121,7 +121,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
|
||||
{
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
||||
{
|
||||
solAssert(ref->location() == DataLocation::Memory, "");
|
||||
solUnimplementedAssert(ref->location() == DataLocation::Memory, "");
|
||||
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
|
||||
}
|
||||
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
|
||||
|
@ -424,7 +424,43 @@ BOOST_AUTO_TEST_CASE(function_name_collision)
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(structs)
|
||||
{
|
||||
string sourceCode = R"(
|
||||
contract C {
|
||||
struct S { uint16 a; uint16 b; T[] sub; uint16 c; }
|
||||
struct T { uint64[2] x; }
|
||||
S s;
|
||||
event e(uint16, S);
|
||||
function f() returns (uint, S) {
|
||||
uint16 x = 7;
|
||||
s.a = 8;
|
||||
s.b = 9;
|
||||
s.c = 10;
|
||||
s.sub.length = 3;
|
||||
s.sub[0].x[0] = 11;
|
||||
s.sub[1].x[0] = 12;
|
||||
s.sub[2].x[1] = 13;
|
||||
e(x, s);
|
||||
return (x, s);
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
NEW_ENCODER(
|
||||
compileAndRun(sourceCode, 0, "C");
|
||||
bytes encoded = encodeArgs(
|
||||
u256(7), 0x40,
|
||||
8, 9, 0x80, 10,
|
||||
3,
|
||||
11, 0,
|
||||
12, 0,
|
||||
0, 13
|
||||
);
|
||||
BOOST_CHECK(callContractFunction("f()") == encoded);
|
||||
REQUIRE_LOG_DATA(encoded);
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
|
@ -9682,50 +9682,6 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment)
|
||||
compileAndRun(sourceCode, 0, "C2");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(return_structs)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
pragma experimental ABIEncoderV2;
|
||||
contract C {
|
||||
struct S { uint a; T[] sub; }
|
||||
struct T { uint[2] x; }
|
||||
function f() returns (uint x, S s) {
|
||||
x = 7;
|
||||
s.a = 8;
|
||||
s.sub = new T[](3);
|
||||
s.sub[0].x[0] = 9;
|
||||
s.sub[1].x[0] = 10;
|
||||
s.sub[2].x[1] = 11;
|
||||
}
|
||||
}
|
||||
)";
|
||||
// This will throw "unimplemented" until it is implemented.
|
||||
BOOST_CHECK_THROW(
|
||||
compileAndRun(sourceCode, 0, "C"),
|
||||
Exception);
|
||||
|
||||
// Will calculate the exact encoding later.
|
||||
// BOOST_CHECK(callContractFunction("f()") == encodeArgs(
|
||||
// u256(7), u256(0x40),
|
||||
// u256(8), u256(0x40),
|
||||
// u256(3),
|
||||
// // s.sub[0]
|
||||
// u256(9), u256(0xc0),
|
||||
// // s.sub[1]
|
||||
// u256(10), u256(0xe0),
|
||||
// // s.sub[2]
|
||||
// u256(11), u256(0x100),
|
||||
// // s.sub[0].sub
|
||||
// u256(2)
|
||||
// // s.sub[0].sub[0].a
|
||||
// u256(8),
|
||||
// u256()
|
||||
// // s.sub[1].sub
|
||||
// u256(0)
|
||||
// // s.sub[2].sub
|
||||
// u256(2)
|
||||
// ));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user