ABIEncoderV2: Implement calldata structs without dynamically encoded members.

This commit is contained in:
Daniel Kirchner 2019-02-05 20:29:57 +01:00
parent 94607011dc
commit 0e4912a203
16 changed files with 377 additions and 18 deletions

View File

@ -9,6 +9,7 @@ Bugfixes:
Language Features:
* Allow calldata structs without dynamically encoded members with ABIEncoderV2.
Compiler Features:

View File

@ -371,10 +371,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
if (
!m_scope->isInterface() &&
baseType->category() == Type::Category::Struct &&
baseType->dataStoredIn(DataLocation::CallData)
)
m_errorReporter.typeError(var->location(), "Calldata structs are not yet supported.");
if (auto const* structType = dynamic_cast<StructType const*>(baseType.get()))
if (structType->isDynamicallyEncoded())
m_errorReporter.typeError(var->location(), "Dynamically encoded calldata structs are not yet supported.");
checkArgumentAndReturnParameter(*var);
var->accept(*this);
}
@ -2071,8 +2073,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
);
if (exprType->category() == Type::Category::Struct)
annotation.isLValue = true;
if (auto const* structType = dynamic_cast<StructType const*>(exprType.get()))
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
else if (exprType->category() == Type::Category::Array)
{
auto const& arrayType(dynamic_cast<ArrayType const&>(*exprType));

View File

@ -2053,6 +2053,24 @@ unsigned StructType::calldataEncodedSize(bool) const
return size;
}
unsigned StructType::calldataOffsetOfMember(std::string const& _member) const
{
unsigned offset = 0;
for (auto const& member: members(nullptr))
{
solAssert(member.type->canLiveOutsideStorage(), "");
if (member.name == _member)
return offset;
{
// Struct members are always padded.
unsigned memberSize = member.type->calldataEncodedSize(true);
solAssert(memberSize != 0, "");
offset += memberSize;
}
}
solAssert(false, "Struct member not found.");
}
bool StructType::isDynamicallyEncoded() const
{
solAssert(!recursive(), "");

View File

@ -852,6 +852,7 @@ public:
std::pair<u256, unsigned> const& storageOffsetsOfMember(std::string const& _name) const;
u256 memoryOffsetOfMember(std::string const& _name) const;
unsigned calldataOffsetOfMember(std::string const& _name) const;
StructDefinition const& structDefinition() const { return m_struct; }

View File

@ -1246,7 +1246,16 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
return abiDecodingFunctionArray(*arrayType, _fromMemory);
}
else if (auto const* structType = dynamic_cast<StructType const*>(decodingType.get()))
return abiDecodingFunctionStruct(*structType, _fromMemory);
{
if (structType->dataStoredIn(DataLocation::CallData))
{
solAssert(!_fromMemory, "");
solUnimplementedAssert(!structType->isDynamicallyEncoded(), "Dynamically encoded calldata structs are not yet implemented.");
return abiDecodingFunctionCalldataStruct(*structType);
}
else
return abiDecodingFunctionStruct(*structType, _fromMemory);
}
else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType.get()))
return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack);
else
@ -1423,15 +1432,37 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
});
}
string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
solAssert(_type.calldataEncodedSize(true) != 0, "");
string functionName =
"abi_decode_" +
_type.identifier();
return createFunction(functionName, [&]() {
Whiskers w{R"(
// <readableTypeName>
function <functionName>(offset, end) -> value {
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) }
value := offset
}
)"};
w("functionName", functionName);
w("readableTypeName", _type.toString(true));
w("minimumSize", to_string(_type.calldataEncodedSize(true)));
return w.render();
});
}
string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory)
{
solAssert(!_type.dataStoredIn(DataLocation::CallData), "");
string functionName =
"abi_decode_" +
_type.identifier() +
(_fromMemory ? "_fromMemory" : "");
solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), "");
return createFunction(functionName, [&]() {
Whiskers templ(R"(
// <readableTypeName>

View File

@ -222,6 +222,8 @@ private:
std::string abiDecodingFunctionCalldataArray(ArrayType const& _type);
/// Part of @a abiDecodingFunction for byte array types.
std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory);
/// Part of @a abiDecodingFunction for calldata struct types.
std::string abiDecodingFunctionCalldataStruct(StructType const& _type);
/// Part of @a abiDecodingFunction for struct types.
std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory);
/// Part of @a abiDecodingFunction for array types.

View File

@ -918,8 +918,7 @@ void CompilerUtils::convertType(
auto& targetType = dynamic_cast<StructType const&>(_targetType);
auto& typeOnStack = dynamic_cast<StructType const&>(_typeOnStack);
solAssert(
targetType.location() != DataLocation::CallData &&
typeOnStack.location() != DataLocation::CallData
targetType.location() != DataLocation::CallData
, "");
switch (targetType.location())
{
@ -933,9 +932,9 @@ void CompilerUtils::convertType(
break;
case DataLocation::Memory:
// Copy the array to a free position in memory, unless it is already in memory.
if (typeOnStack.location() != DataLocation::Memory)
switch (typeOnStack.location())
{
solAssert(typeOnStack.location() == DataLocation::Storage, "");
case DataLocation::Storage:
// stack: <source ref>
m_context << typeOnStack.memorySize();
allocateMemory();
@ -955,6 +954,19 @@ void CompilerUtils::convertType(
storeInMemoryDynamic(*targetMemberType, true);
}
m_context << Instruction::POP << Instruction::POP;
break;
case DataLocation::CallData:
{
solUnimplementedAssert(!typeOnStack.isDynamicallyEncoded(), "");
m_context << Instruction::DUP1;
m_context << Instruction::CALLDATASIZE;
m_context << Instruction::SUB;
abiDecode({targetType.shared_from_this()}, false);
break;
}
case DataLocation::Memory:
// nothing to do
break;
}
break;
case DataLocation::CallData:

View File

@ -1380,6 +1380,24 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
setLValue<MemoryItem>(_memberAccess, *_memberAccess.annotation().type);
break;
}
case DataLocation::CallData:
{
solUnimplementedAssert(!type.isDynamicallyEncoded(), "");
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
// For non-value types the calldata offset is returned directly.
if (_memberAccess.annotation().type->isValueType())
{
solAssert(_memberAccess.annotation().type->calldataEncodedSize(false) > 0, "");
CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false);
}
else
solAssert(
_memberAccess.annotation().type->category() == Type::Category::Array ||
_memberAccess.annotation().type->category() == Type::Category::Struct,
""
);
break;
}
default:
solAssert(false, "Illegal data location for struct.");
}

View File

@ -8013,6 +8013,248 @@ BOOST_AUTO_TEST_CASE(struct_named_constructor)
ABI_CHECK(callContractFunction("s()"), encodeArgs(u256(1), true));
}
BOOST_AUTO_TEST_CASE(calldata_struct)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; uint256 b; }
function f(S calldata s) external pure returns (uint256 a, uint256 b) {
a = s.a;
b = s.b;
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256))", encodeArgs(u256(42), u256(23))), encodeArgs(u256(42), u256(23)));
}
BOOST_AUTO_TEST_CASE(calldata_struct_and_ints)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; uint256 b; }
function f(uint256 a, S calldata s, uint256 b) external pure returns (uint256, uint256, uint256, uint256) {
return (a, s.a, s.b, b);
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f(uint256,(uint256,uint256),uint256)", encodeArgs(u256(1), u256(2), u256(3), u256(4))), encodeArgs(u256(1), u256(2), u256(3), u256(4)));
}
BOOST_AUTO_TEST_CASE(calldata_structs)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S1 { uint256 a; uint256 b; }
struct S2 { uint256 a; }
function f(S1 calldata s1, S2 calldata s2, S1 calldata s3)
external pure returns (uint256 a, uint256 b, uint256 c, uint256 d, uint256 e) {
a = s1.a;
b = s1.b;
c = s2.a;
d = s3.a;
e = s3.b;
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256),(uint256),(uint256,uint256))", encodeArgs(u256(1), u256(2), u256(3), u256(4), u256(5))), encodeArgs(u256(1), u256(2), u256(3), u256(4), u256(5)));
}
BOOST_AUTO_TEST_CASE(calldata_struct_array_member)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; uint256[2] b; uint256 c; }
function f(S calldata s) external pure returns (uint256 a, uint256 b0, uint256 b1, uint256 c) {
a = s.a;
b0 = s.b[0];
b1 = s.b[1];
c = s.c;
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256[2],uint256))", encodeArgs(u256(42), u256(1), u256(2), u256(23))), encodeArgs(u256(42), u256(1), u256(2), u256(23)));
}
BOOST_AUTO_TEST_CASE(calldata_array_of_struct)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; uint256 b; }
function f(S[] calldata s) external pure returns (uint256 l, uint256 a, uint256 b, uint256 c, uint256 d) {
l = s.length;
a = s[0].a;
b = s[0].b;
c = s[1].a;
d = s[1].b;
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256)[])", encodeArgs(u256(0x20), u256(2), u256(1), u256(2), u256(3), u256(4))), encodeArgs(u256(2), u256(1), u256(2), u256(3), u256(4)));
}
BOOST_AUTO_TEST_CASE(calldata_array_of_struct_to_memory)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; uint256 b; }
function f(S[] calldata s) external pure returns (uint256 l, uint256 a, uint256 b, uint256 c, uint256 d) {
S[] memory m = s;
l = m.length;
a = m[0].a;
b = m[0].b;
c = m[1].a;
d = m[1].b;
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256)[])", encodeArgs(u256(0x20), u256(2), u256(1), u256(2), u256(3), u256(4))), encodeArgs(u256(2), u256(1), u256(2), u256(3), u256(4)));
}
BOOST_AUTO_TEST_CASE(calldata_struct_to_memory)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; uint256 b; }
function f(S calldata s) external pure returns (uint256, uint256) {
S memory m = s;
return (m.a, m.b);
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256))", encodeArgs(u256(42), u256(23))), encodeArgs(u256(42), u256(23)));
}
BOOST_AUTO_TEST_CASE(nested_calldata_struct)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S1 { uint256 a; uint256 b; }
struct S2 { uint256 a; uint256 b; S1 s; uint256 c; }
function f(S2 calldata s) external pure returns (uint256 a, uint256 b, uint256 sa, uint256 sb, uint256 c) {
return (s.a, s.b, s.s.a, s.s.b, s.c);
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256,(uint256,uint256),uint256))", encodeArgs(u256(1), u256(2), u256(3), u256(4), u256(5))), encodeArgs(u256(1), u256(2), u256(3), u256(4), u256(5)));
}
BOOST_AUTO_TEST_CASE(nested_calldata_struct_to_memory)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S1 { uint256 a; uint256 b; }
struct S2 { uint256 a; uint256 b; S1 s; uint256 c; }
function f(S2 calldata s) external pure returns (uint256 a, uint256 b, uint256 sa, uint256 sb, uint256 c) {
S2 memory m = s;
return (m.a, m.b, m.s.a, m.s.b, m.c);
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f((uint256,uint256,(uint256,uint256),uint256))", encodeArgs(u256(1), u256(2), u256(3), u256(4), u256(5))), encodeArgs(u256(1), u256(2), u256(3), u256(4), u256(5)));
}
BOOST_AUTO_TEST_CASE(calldata_struct_short)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; uint256 b; }
function f(S calldata) external pure returns (uint256) {
return msg.data.length;
}
}
)";
compileAndRun(sourceCode, 0, "C");
// double check that the valid case goes through
ABI_CHECK(callContractFunction("f((uint256,uint256))", u256(1), u256(2)), encodeArgs(0x44));
ABI_CHECK(callContractFunctionNoEncoding("f((uint256,uint256))", bytes(63,0)), encodeArgs());
ABI_CHECK(callContractFunctionNoEncoding("f((uint256,uint256))", bytes(33,0)), encodeArgs());
ABI_CHECK(callContractFunctionNoEncoding("f((uint256,uint256))", bytes(32,0)), encodeArgs());
ABI_CHECK(callContractFunctionNoEncoding("f((uint256,uint256))", bytes(31,0)), encodeArgs());
ABI_CHECK(callContractFunctionNoEncoding("f((uint256,uint256))", bytes()), encodeArgs());
}
BOOST_AUTO_TEST_CASE(calldata_struct_cleaning)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { uint8 a; bytes1 b; }
function f(S calldata s) external pure returns (uint256 a, bytes32 b) {
uint8 tmp1 = s.a;
bytes1 tmp2 = s.b;
assembly {
a := tmp1
b := tmp2
}
}
}
)";
compileAndRun(sourceCode, 0, "C");
// double check that the valid case goes through
ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(0x12), bytes{0x34} + bytes(31,0)), encodeArgs(0x12, bytes{0x34} + bytes(31,0)));
ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(0x1234), bytes{0x56, 0x78} + bytes(30,0)), encodeArgs(0x34, bytes{0x56} + bytes(31,0)));
ABI_CHECK(callContractFunction("f((uint8,bytes1))", u256(-1), u256(-1)), encodeArgs(0xFF, bytes{0xFF} + bytes(31,0)));
}
BOOST_AUTO_TEST_CASE(calldata_struct_function_type)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S { function (uint) external returns (uint) fn; }
function f(S calldata s) external returns (uint256) {
return s.fn(42);
}
function g(uint256 a) external returns (uint256) {
return a * 3;
}
function h(uint256 a) external returns (uint256) {
return 23;
}
}
)";
compileAndRun(sourceCode, 0, "C");
bytes fn_C_g = m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("g(uint256)")).asBytes() + bytes(8,0);
bytes fn_C_h = m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("h(uint256)")).asBytes() + bytes(8,0);
ABI_CHECK(callContractFunctionNoEncoding("f((function))", fn_C_g), encodeArgs(42 * 3));
ABI_CHECK(callContractFunctionNoEncoding("f((function))", fn_C_h), encodeArgs(23));
}
BOOST_AUTO_TEST_CASE(literal_strings)
{
char const* sourceCode = R"(

View File

@ -15,7 +15,3 @@ contract B is A {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (102-112): Calldata structs are not yet supported.
// TypeError: (146-156): Calldata structs are not yet supported.
// TypeError: (198-208): Calldata structs are not yet supported.
// TypeError: (250-260): Calldata structs are not yet supported.

View File

@ -6,5 +6,3 @@ contract Test {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (89-101): Calldata structs are not yet supported.
// TypeError: (131-145): Calldata structs are not yet supported.

View File

@ -5,4 +5,3 @@ contract Test {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (89-99): Calldata structs are not yet supported.

View File

@ -0,0 +1,10 @@
pragma experimental ABIEncoderV2;
contract Test {
struct S { int[3] a; }
function f(S calldata s, int[3] calldata a) external {
s.a = a;
}
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (144-147): Expression has to be an lvalue.

View File

@ -0,0 +1,8 @@
pragma experimental ABIEncoderV2;
contract Test {
struct S { int a; }
function f(S calldata s) external { s.a = 4; }
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (114-117): Expression has to be an lvalue.

View File

@ -0,0 +1,9 @@
pragma experimental ABIEncoderV2;
contract C {
struct S { function (uint) external returns (uint) fn; }
function f(S calldata s) external returns (uint256 a) {
return s.fn(42);
}
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.

View File

@ -0,0 +1,12 @@
pragma experimental ABIEncoderV2;
contract Test {
struct S { int a; }
function f(S calldata s) external { s = S(2); }
function g(S calldata s) external { S memory m; s = m; }
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (114-115): Expression has to be an lvalue.
// TypeError: (118-122): Type struct Test.S memory is not implicitly convertible to expected type struct Test.S calldata.
// TypeError: (178-179): Expression has to be an lvalue.
// TypeError: (182-183): Type struct Test.S memory is not implicitly convertible to expected type struct Test.S calldata.