From 0e4912a2038720fcce46d2ee369b4783041d7223 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Tue, 5 Feb 2019 20:29:57 +0100 Subject: [PATCH] ABIEncoderV2: Implement calldata structs without dynamically encoded members. --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 10 +- libsolidity/ast/Types.cpp | 18 ++ libsolidity/ast/Types.h | 1 + libsolidity/codegen/ABIFunctions.cpp | 37 ++- libsolidity/codegen/ABIFunctions.h | 2 + libsolidity/codegen/CompilerUtils.cpp | 20 +- libsolidity/codegen/ExpressionCompiler.cpp | 18 ++ test/libsolidity/SolidityEndToEndTest.cpp | 242 ++++++++++++++++++ .../override/calldata_memory_struct.sol | 4 - .../syntaxTests/structs/array_calldata.sol | 2 - .../syntaxTests/structs/calldata.sol | 1 - .../structs/calldata_array_assign.sol | 10 + .../syntaxTests/structs/calldata_assign.sol | 8 + .../structs/calldata_struct_function_type.sol | 9 + .../structs/memory_to_calldata.sol | 12 + 16 files changed, 377 insertions(+), 18 deletions(-) create mode 100644 test/libsolidity/syntaxTests/structs/calldata_array_assign.sol create mode 100644 test/libsolidity/syntaxTests/structs/calldata_assign.sol create mode 100644 test/libsolidity/syntaxTests/structs/calldata_struct_function_type.sol create mode 100644 test/libsolidity/syntaxTests/structs/memory_to_calldata.sol diff --git a/Changelog.md b/Changelog.md index 0f8a2bbc9..2bf09226d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ Bugfixes: Language Features: + * Allow calldata structs without dynamically encoded members with ABIEncoderV2. Compiler Features: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index e0ac3f9d5..0f63d8048 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -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(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(exprType.get())) + annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); else if (exprType->category() == Type::Category::Array) { auto const& arrayType(dynamic_cast(*exprType)); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index cca632446..aeb59dfca 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -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(), ""); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index bd249c633..ea6792398 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -852,6 +852,7 @@ public: std::pair 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; } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 1d532f5da..0c7a43c37 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -1246,7 +1246,16 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo return abiDecodingFunctionArray(*arrayType, _fromMemory); } else if (auto const* structType = dynamic_cast(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(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"( + // + function (offset, end) -> value { + if slt(sub(end, offset), ) { 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"( // diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index a8a3c64e5..8d8be9d71 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -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. diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 676dd5b60..f8c8b3a88 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -918,8 +918,7 @@ void CompilerUtils::convertType( auto& targetType = dynamic_cast(_targetType); auto& typeOnStack = dynamic_cast(_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: 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: diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 942363461..1e7a84e65 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1380,6 +1380,24 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) setLValue(_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."); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index fb480459d..31d9be7a1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -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"( diff --git a/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol b/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol index b81e3859b..42aebf304 100644 --- a/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol +++ b/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/structs/array_calldata.sol b/test/libsolidity/syntaxTests/structs/array_calldata.sol index 3aac5606b..9e1071a0e 100644 --- a/test/libsolidity/syntaxTests/structs/array_calldata.sol +++ b/test/libsolidity/syntaxTests/structs/array_calldata.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/structs/calldata.sol b/test/libsolidity/syntaxTests/structs/calldata.sol index dadf6e4fc..8d584e577 100644 --- a/test/libsolidity/syntaxTests/structs/calldata.sol +++ b/test/libsolidity/syntaxTests/structs/calldata.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/structs/calldata_array_assign.sol b/test/libsolidity/syntaxTests/structs/calldata_array_assign.sol new file mode 100644 index 000000000..70e537ad6 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/calldata_array_assign.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/structs/calldata_assign.sol b/test/libsolidity/syntaxTests/structs/calldata_assign.sol new file mode 100644 index 000000000..f44730b4f --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/calldata_assign.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/structs/calldata_struct_function_type.sol b/test/libsolidity/syntaxTests/structs/calldata_struct_function_type.sol new file mode 100644 index 000000000..6a3c2d844 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/calldata_struct_function_type.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/structs/memory_to_calldata.sol b/test/libsolidity/syntaxTests/structs/memory_to_calldata.sol new file mode 100644 index 000000000..09b3ef294 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/memory_to_calldata.sol @@ -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.