diff options
author | Alex Beregszaszi <alex@rtfs.hu> | 2017-09-18 22:20:56 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-18 22:20:56 +0800 |
commit | f676325d60dbc6c1120ce5a4478f363622c1b1cf (patch) | |
tree | 83eabcc9ff5b5481da4f98ab8b4d2dde34e0299b | |
parent | f473a43a25b562ee91aa8ddddd35f4174e0d4456 (diff) | |
parent | c001903cdc6db97f437150375a9b5343c70c3656 (diff) | |
download | dexon-solidity-f676325d60dbc6c1120ce5a4478f363622c1b1cf.tar.gz dexon-solidity-f676325d60dbc6c1120ce5a4478f363622c1b1cf.tar.zst dexon-solidity-f676325d60dbc6c1120ce5a4478f363622c1b1cf.zip |
Merge pull request #1673 from ethereum/structs
Returning structs
-rw-r--r-- | Changelog.md | 1 | ||||
-rw-r--r-- | docs/abi-spec.rst | 105 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 6 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 154 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 38 | ||||
-rw-r--r-- | libsolidity/codegen/ABIFunctions.cpp | 128 | ||||
-rw-r--r-- | libsolidity/codegen/ABIFunctions.h | 7 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 2 | ||||
-rw-r--r-- | libsolidity/codegen/ContractCompiler.cpp | 2 | ||||
-rw-r--r-- | libsolidity/interface/ABI.cpp | 61 | ||||
-rw-r--r-- | libsolidity/interface/ABI.h | 4 | ||||
-rw-r--r-- | test/libsolidity/ABIEncoderTests.cpp | 36 | ||||
-rw-r--r-- | test/libsolidity/SolidityABIJSON.cpp | 213 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 1 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 215 |
15 files changed, 894 insertions, 79 deletions
diff --git a/Changelog.md b/Changelog.md index 0b9295d0..376494e0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Features: * Code Generator: Added ``.selector`` member on external function types to retrieve their signature. * Code Generator: Keep a single copy of encoding functions when using the experimental "ABIEncoderV2". * Optimizer: Add new optimization step to remove unused ``JUMPDEST``s. + * Code Generator: Support passing ``structs`` as arguments and return parameters (requires ``pragma experimental ABIEncoderV2`` for now). * Syntax Checker: Warn if no visibility is specified on contract functions. * Type Checker: Display helpful warning for unused function arguments/return parameters. * Type Checker: Do not show the same error multiple times for events. diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index f75a6885..97320c7f 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -68,13 +68,15 @@ The following non-fixed-size types exist: - ``<type>[]``: a variable-length array of the given fixed-length type. -Types can be combined to anonymous structs by enclosing a finite non-negative number +Types can be combined to a tuple by enclosing a finite non-negative number of them inside parentheses, separated by commas: -- ``(T1,T2,...,Tn)``: anonymous struct (ordered tuple) consisting of the types ``T1``, ..., ``Tn``, ``n >= 0`` +- ``(T1,T2,...,Tn)``: tuple consisting of the types ``T1``, ..., ``Tn``, ``n >= 0`` -It is possible to form structs of structs, arrays of structs and so on. +It is possible to form tuples of tuples, arrays of tuples and so on. +.. note:: + Solidity supports all the types presented above with the same names with the exception of tuples. The ABI tuple type is utilised for encoding Solidity ``structs``. Formal Specification of the Encoding ==================================== @@ -133,7 +135,7 @@ on the type of ``X`` being ``enc(X) = enc((X[0], ..., X[k-1]))`` - i.e. it is encoded as if it were an anonymous struct with ``k`` elements + i.e. it is encoded as if it were a tuple with ``k`` elements of the same type. - ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``): @@ -176,7 +178,7 @@ and the return values ``v_1, ..., v_k`` of ``f`` are encoded as ``enc((v_1, ..., v_k))`` -i.e. the values are combined into an anonymous struct and encoded. +i.e. the values are combined into a tuple and encoded. Examples ======== @@ -289,14 +291,16 @@ In effect, a log entry using this ABI is described as: JSON ==== -The JSON format for a contract's interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields: +The JSON format for a contract's interface is given by an array of function and/or event descriptions. +A function description is a JSON object with the fields: - ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-function>`); - ``name``: the name of the function; - ``inputs``: an array of objects, each of which contains: * ``name``: the name of the parameter; - * ``type``: the canonical type of the parameter. + * ``type``: the canonical type of the parameter (more below). + * ``components``: used for tuple types (more below). - ``outputs``: an array of objects similar to ``inputs``, can be omitted if function doesn't return anything; - ``payable``: ``true`` if function accepts ether, defaults to ``false``; @@ -316,7 +320,8 @@ An event description is a JSON object with fairly similar fields: - ``inputs``: an array of objects, each of which contains: * ``name``: the name of the parameter; - * ``type``: the canonical type of the parameter. + * ``type``: the canonical type of the parameter (more below). + * ``components``: used for tuple types (more below). * ``indexed``: ``true`` if the field is part of the log's topics, ``false`` if it one of the log's data segment. - ``anonymous``: ``true`` if the event was declared as ``anonymous``. @@ -353,3 +358,87 @@ would result in the JSON: "name":"foo", "outputs": [] }] + +Handling tuple types +-------------------- + +Despite that names are intentionally not part of the ABI encoding they do make a lot of sense to be included +in the JSON to enable displaying it to the end user. The structure is nested in the following way: + +An object with members ``name``, ``type`` and potentially ``components`` describes a typed variable. +The canonical type is determined until a tuple type is reached and the string description up +to that point is stored in ``type`` prefix with the word ``tuple``, i.e. it will be ``tuple`` followed by +a sequence of ``[]`` and ``[k]`` with +integers ``k``. The components of the tuple are then stored in the member ``components``, +which is of array type and has the same structure as the top-level object except that +``indexed`` is not allowed there. + +As an example, the code + +:: + + contract Test { + struct S { uint a; uint[] b; T[] c; } + struct T { uint x; uint y; } + function f(S s, T t, uint a) { } + } + +would result in the JSON: + +.. code:: json + + [ + { + "name": "f", + "type": "function", + "inputs": [ + { + "name": "s", + "type": "tuple", + "components": [ + { + "name": "a", + "type": "uint256" + }, + { + "name": "b", + "type": "uint256[]" + }, + { + "name": "c", + "type": "tuple[]", + "components": [ + { + "name": "x", + "type": "uint256" + }, + { + "name": "y", + "type": "uint256" + } + ] + } + ] + }, + { + "name": "t", + "type": "tuple", + "components": [ + { + "name": "x", + "type": "uint256" + }, + { + "name": "y", + "type": "uint256" + } + ] + }, + { + "name": "a", + "type": "uint256" + } + ], + "outputs": [] + } + ] diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 26529c22..fe4207a3 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -546,7 +546,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) if (!type(*var)->canLiveOutsideStorage()) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) - m_errorReporter.fatalTypeError(var->location(), "Internal type is not allowed for public or external functions."); + m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); var->accept(*this); } @@ -641,7 +641,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) _variable.visibility() >= VariableDeclaration::Visibility::Public && !FunctionType(_variable).interfaceFunctionType() ) - m_errorReporter.typeError(_variable.location(), "Internal type is not allowed for public state variables."); + m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables."); if (varType->category() == Type::Category::Array) if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get())) @@ -728,7 +728,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) if (!type(*var)->canLiveOutsideStorage()) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (!type(*var)->interfaceType(false)) - m_errorReporter.typeError(var->location(), "Internal type is not allowed as event parameter type."); + m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type."); } if (_eventDef.isAnonymous() && numIndexed > 4) m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event."); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 705d0f7f..83a5b465 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -33,6 +33,7 @@ #include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/range/adaptor/reversed.hpp> +#include <boost/range/algorithm/copy.hpp> #include <boost/range/adaptor/sliced.hpp> #include <boost/range/adaptor/transformed.hpp> @@ -1470,7 +1471,7 @@ string ArrayType::toString(bool _short) const return ret; } -string ArrayType::canonicalName(bool _addDataLocation) const +string ArrayType::canonicalName() const { string ret; if (isString()) @@ -1479,16 +1480,29 @@ string ArrayType::canonicalName(bool _addDataLocation) const ret = "bytes"; else { - ret = baseType()->canonicalName(false) + "["; + ret = baseType()->canonicalName() + "["; if (!isDynamicallySized()) ret += length().str(); ret += "]"; } - if (_addDataLocation && location() == DataLocation::Storage) - ret += " storage"; return ret; } +string ArrayType::signatureInExternalFunction(bool _structsByName) const +{ + if (isByteArray()) + return canonicalName(); + else + { + solAssert(baseType(), ""); + return + baseType()->signatureInExternalFunction(_structsByName) + + "[" + + (isDynamicallySized() ? "" : length().str()) + + "]"; + } +} + MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const { MemberList::MemberMap members; @@ -1597,7 +1611,7 @@ string ContractType::toString(bool) const m_contract.name(); } -string ContractType::canonicalName(bool) const +string ContractType::canonicalName() const { return m_contract.annotation().canonicalName; } @@ -1721,15 +1735,22 @@ 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 { u256 size; - for (auto const& member: members(nullptr)) - if (member.type->canLiveOutsideStorage()) - size += member.type->memoryHeadSize(); + for (auto const& t: memoryMemberTypes()) + size += t->memoryHeadSize(); return size; } @@ -1767,10 +1788,33 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const TypePointer StructType::interfaceType(bool _inLibrary) const { + if (!canBeUsedExternally(_inLibrary)) + return TypePointer(); + + // Has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) if (_inLibrary && location() == DataLocation::Storage) return shared_from_this(); else - return TypePointer(); + return copyForLocation(DataLocation::Memory, true); +} + +bool StructType::canBeUsedExternally(bool _inLibrary) const +{ + if (_inLibrary && location() == DataLocation::Storage) + return true; + else if (recursive()) + return false; + else + { + // Check that all members have interface types. + // We pass "false" to canBeUsedExternally (_inLibrary), because this struct will be + // passed by value and thus the encoding does not differ, but it will disallow + // mappings. + for (auto const& var: m_struct.members()) + if (!var->annotation().type->canBeUsedExternally(false)) + return false; + } + return true; } TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const @@ -1780,12 +1824,27 @@ TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) return copy; } -string StructType::canonicalName(bool _addDataLocation) const +string StructType::signatureInExternalFunction(bool _structsByName) const { - string ret = m_struct.annotation().canonicalName; - if (_addDataLocation && location() == DataLocation::Storage) - ret += " storage"; - return ret; + if (_structsByName) + return canonicalName(); + else + { + TypePointers memberTypes = memoryMemberTypes(); + auto memberTypeStrings = memberTypes | boost::adaptors::transformed([&](TypePointer _t) -> string + { + solAssert(_t, "Parameter should have external type."); + auto t = _t->interfaceType(_structsByName); + solAssert(t, ""); + return t->signatureInExternalFunction(_structsByName); + }); + return "(" + boost::algorithm::join(memberTypeStrings, ",") + ")"; + } +} + +string StructType::canonicalName() const +{ + return m_struct.annotation().canonicalName; } FunctionTypePointer StructType::constructorType() const @@ -1827,6 +1886,15 @@ u256 StructType::memoryOffsetOfMember(string const& _name) const return 0; } +TypePointers StructType::memoryMemberTypes() const +{ + TypePointers types; + for (ASTPointer<VariableDeclaration> const& variable: m_struct.members()) + if (variable->annotation().type->canLiveOutsideStorage()) + types.push_back(variable->annotation().type); + return types; +} + set<string> StructType::membersMissingInMemory() const { set<string> missing; @@ -1836,6 +1904,33 @@ set<string> StructType::membersMissingInMemory() const return missing; } +bool StructType::recursive() const +{ + if (!m_recursive.is_initialized()) + { + set<StructDefinition const*> structsSeen; + function<bool(StructType const*)> check = [&](StructType const* t) -> bool + { + StructDefinition const* str = &t->structDefinition(); + if (structsSeen.count(str)) + return true; + structsSeen.insert(str); + for (ASTPointer<VariableDeclaration> const& variable: str->members()) + { + Type const* memberType = variable->annotation().type.get(); + while (dynamic_cast<ArrayType const*>(memberType)) + memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get(); + if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType)) + if (check(innerStruct)) + return true; + } + return false; + }; + m_recursive = check(this); + } + return *m_recursive; +} + TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const { return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); @@ -1868,7 +1963,7 @@ string EnumType::toString(bool) const return string("enum ") + m_enum.annotation().canonicalName; } -string EnumType::canonicalName(bool) const +string EnumType::canonicalName() const { return m_enum.annotation().canonicalName; } @@ -2295,7 +2390,7 @@ TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePoint return TypePointer(); } -string FunctionType::canonicalName(bool) const +string FunctionType::canonicalName() const { solAssert(m_kind == Kind::External, ""); return "function"; @@ -2555,20 +2650,19 @@ string FunctionType::externalSignature() const solAssert(m_declaration != nullptr, "External signature of function needs declaration"); solAssert(!m_declaration->name().empty(), "Fallback function has no signature."); - bool _inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary(); - - string ret = m_declaration->name() + "("; - + bool const inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary(); FunctionTypePointer external = interfaceFunctionType(); solAssert(!!external, "External function type requested."); - TypePointers externalParameterTypes = external->parameterTypes(); - for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it) + auto parameterTypes = external->parameterTypes(); + auto typeStrings = parameterTypes | boost::adaptors::transformed([&](TypePointer _t) -> string { - solAssert(!!(*it), "Parameter should have external type"); - ret += (*it)->canonicalName(_inLibrary) + (it + 1 == externalParameterTypes.cend() ? "" : ","); - } - - return ret + ")"; + solAssert(_t, "Parameter should have external type."); + string typeName = _t->signatureInExternalFunction(inLibrary); + if (inLibrary && _t->dataStoredIn(DataLocation::Storage)) + typeName += " storage"; + return typeName; + }); + return m_declaration->name() + "(" + boost::algorithm::join(typeStrings, ",") + ")"; } u256 FunctionType::externalIdentifier() const @@ -2699,9 +2793,9 @@ string MappingType::toString(bool _short) const return "mapping(" + keyType()->toString(_short) + " => " + valueType()->toString(_short) + ")"; } -string MappingType::canonicalName(bool) const +string MappingType::canonicalName() const { - return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")"; + return "mapping(" + keyType()->canonicalName() + " => " + valueType()->canonicalName() + ")"; } string TypeType::identifier() const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index d4d6da69..8ba55521 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -32,6 +32,7 @@ #include <boost/noncopyable.hpp> #include <boost/rational.hpp> +#include <boost/optional.hpp> #include <memory> #include <string> @@ -245,9 +246,15 @@ public: virtual std::string toString(bool _short) const = 0; std::string toString() const { return toString(false); } - /// @returns the canonical name of this type for use in function signatures. - /// @param _addDataLocation if true, includes data location for reference types if it is "storage". - virtual std::string canonicalName(bool /*_addDataLocation*/) const { return toString(true); } + /// @returns the canonical name of this type for use in library function signatures. + virtual std::string canonicalName() const { return toString(true); } + /// @returns the signature of this type in external functions, i.e. `uint256` for integers + /// or `(uint256,bytes8)[2]` for an array of structs. If @a _structsByName, + /// structs are given by canonical name like `ContractName.StructName[2]`. + virtual std::string signatureInExternalFunction(bool /*_structsByName*/) const + { + return canonicalName(); + } virtual u256 literalValue(Literal const*) const { solAssert(false, "Literal value requested for type without literals."); @@ -619,7 +626,8 @@ public: virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } virtual unsigned sizeOnStack() const override; virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; + virtual std::string signatureInExternalFunction(bool _structsByName) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override; virtual TypePointer decodingType() const override; @@ -677,7 +685,7 @@ public: virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; } virtual bool isValueType() const override { return true; } virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override @@ -738,13 +746,15 @@ 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; + virtual bool canBeUsedExternally(bool _inLibrary) const override; TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; + virtual std::string signatureInExternalFunction(bool _structsByName) const override; /// @returns a function that peforms the type conversion between a list of struct members /// and a memory struct of this type. @@ -755,11 +765,19 @@ public: StructDefinition const& structDefinition() const { return m_struct; } + /// @returns the vector of types of members available in memory. + TypePointers memoryMemberTypes() const; /// @returns the set of all members that are removed in the memory version (typically mappings). std::set<std::string> membersMissingInMemory() const; + /// @returns true if the same struct is used recursively in one of its members. Only + /// analyses the "memory" representation, i.e. mappings are ignored in all structs. + bool recursive() const; + private: StructDefinition const& m_struct; + /// Cache for the recursive() function. + mutable boost::optional<bool> m_recursive; }; /** @@ -780,7 +798,7 @@ public: virtual unsigned storageBytes() const override; virtual bool canLiveOutsideStorage() const override { return true; } virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; virtual bool isValueType() const override { return true; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; @@ -951,7 +969,7 @@ public: virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override; - virtual std::string canonicalName(bool /*_addDataLocation*/) const override; + virtual std::string canonicalName() const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } @@ -1053,7 +1071,7 @@ public: virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer encodingType() const override diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 3a9f1a48..9f6c55ba 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -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, diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 5bbd842f..de2a140a 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -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 diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 1e623357..37aa1aea 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -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)) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 51496368..92782b8d 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -333,7 +333,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // stack: v1 v2 ... v(k-1) base_offset current_offset TypePointer type = parameterType->decodingType(); - solAssert(type, "No decoding type found."); + solUnimplementedAssert(type, "No decoding type found."); if (type->category() == Type::Category::Array) { auto const& arrayType = dynamic_cast<ArrayType const&>(*type); diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 49df843d..aefb34af 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -86,12 +86,12 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) Json::Value params(Json::arrayValue); for (auto const& p: it->parameters()) { - solAssert(!!p->annotation().type->interfaceType(false), ""); + auto type = p->annotation().type->interfaceType(false); + solAssert(type, ""); Json::Value input; - input["name"] = p->name(); - input["type"] = p->annotation().type->interfaceType(false)->canonicalName(false); - input["indexed"] = p->isIndexed(); - params.append(input); + auto param = formatType(p->name(), *type, false); + param["indexed"] = p->isIndexed(); + params.append(param); } event["inputs"] = params; abi.append(event); @@ -111,10 +111,53 @@ Json::Value ABI::formatTypeList( for (unsigned i = 0; i < _names.size(); ++i) { solAssert(_types[i], ""); - Json::Value param; - param["name"] = _names[i]; - param["type"] = _types[i]->canonicalName(_forLibrary); - params.append(param); + params.append(formatType(_names[i], *_types[i], _forLibrary)); } return params; } + +Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLibrary) +{ + Json::Value ret; + ret["name"] = _name; + string suffix = (_forLibrary && _type.dataStoredIn(DataLocation::Storage)) ? " storage" : ""; + if (_type.isValueType() || (_forLibrary && _type.dataStoredIn(DataLocation::Storage))) + ret["type"] = _type.canonicalName() + suffix; + else if (ArrayType const* arrayType = dynamic_cast<ArrayType const*>(&_type)) + { + if (arrayType->isByteArray()) + ret["type"] = _type.canonicalName() + suffix; + else + { + string suffix; + if (arrayType->isDynamicallySized()) + suffix = "[]"; + else + suffix = string("[") + arrayType->length().str() + "]"; + solAssert(arrayType->baseType(), ""); + Json::Value subtype = formatType("", *arrayType->baseType(), _forLibrary); + if (subtype.isMember("components")) + { + ret["type"] = subtype["type"].asString() + suffix; + ret["components"] = subtype["components"]; + } + else + ret["type"] = subtype["type"].asString() + suffix; + } + } + else if (StructType const* structType = dynamic_cast<StructType const*>(&_type)) + { + ret["type"] = "tuple"; + ret["components"] = Json::arrayValue; + for (auto const& member: structType->members(nullptr)) + { + solAssert(member.type, ""); + auto t = member.type->interfaceType(_forLibrary); + solAssert(t, ""); + ret["components"].append(formatType(member.name, *t, _forLibrary)); + } + } + else + solAssert(false, "Invalid type."); + return ret; +} diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h index 95b162a9..db70729d 100644 --- a/libsolidity/interface/ABI.h +++ b/libsolidity/interface/ABI.h @@ -50,6 +50,10 @@ private: std::vector<TypePointer> const& _types, bool _forLibrary ); + /// @returns a Json object with "name", "type" and potentially "components" keys, according + /// to the ABI specification. + /// If it is possible to express the type as a single string, it is allowed to return a single string. + static Json::Value formatType(std::string const& _name, Type const& _type, bool _forLibrary); }; } diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index 4ddf17ce..05158601 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -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() diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 4b9223de..e5d9e99c 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -48,7 +48,7 @@ public: Json::Value generatedInterface = m_compilerStack.contractABI(""); Json::Value expectedInterface; - m_reader.parse(_expectedInterfaceString, expectedInterface); + BOOST_REQUIRE(m_reader.parse(_expectedInterfaceString, expectedInterface)); BOOST_CHECK_MESSAGE( expectedInterface == generatedInterface, "Expected:\n" << expectedInterface.toStyledString() << @@ -939,6 +939,217 @@ BOOST_AUTO_TEST_CASE(function_type) checkInterface(sourceCode, interface); } +BOOST_AUTO_TEST_CASE(return_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; T[] sub; } + struct T { uint[2] x; } + function f() returns (uint x, S s) { + } + } + )"; + char const* interface = R"( + [{ + "constant" : false, + "inputs" : [], + "name" : "f", + "outputs" : [ + { + "name" : "x", + "type" : "uint256" + }, + { + "components" : [ + { + "name" : "a", + "type" : "uint256" + }, + { + "components" : [ + { + "name" : "x", + "type" : "uint256[2]" + } + ], + "name" : "sub", + "type" : "tuple[]" + } + ], + "name" : "s", + "type" : "tuple" + } + ], + "payable" : false, + "stateMutability" : "nonpayable", + "type" : "function" + }] + )"; + checkInterface(text, interface); +} + +BOOST_AUTO_TEST_CASE(return_structs_with_contracts) +{ + char const* text = R"( + contract C { + struct S { C[] x; C y; } + function f() returns (S s, C c) { + } + } + )"; + char const* interface = R"( + [{ + "constant": false, + "inputs": [], + "name": "f", + "outputs": [ + { + "components": [ + { + "name": "x", + "type": "address[]" + }, + { + "name": "y", + "type": "address" + } + ], + "name": "s", + "type": "tuple" + }, + { + "name": "c", + "type": "address" + } + ], + "payable": false, + "stateMutability" : "nonpayable", + "type": "function" + }] + )"; + checkInterface(text, interface); +} + +BOOST_AUTO_TEST_CASE(event_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; T[] sub; bytes b; } + struct T { uint[2] x; } + event E(T t, S s); + } + )"; + char const *interface = R"( + [{ + "anonymous": false, + "inputs": [ + { + "components": [ + { + "name": "x", + "type": "uint256[2]" + } + ], + "indexed": false, + "name": "t", + "type": "tuple" + }, + { + "components": [ + { + "name": "a", + "type": "uint256" + }, + { + "components": [ + { + "name": "x", + "type": "uint256[2]" + } + ], + "name": "sub", + "type": "tuple[]" + }, + { + "name": "b", + "type": "bytes" + } + ], + "indexed": false, + "name": "s", + "type": "tuple" + } + ], + "name": "E", + "type": "event" + }] + )"; + checkInterface(text, interface); +} + +BOOST_AUTO_TEST_CASE(structs_in_libraries) +{ + char const* text = R"( + library L { + struct S { uint a; T[] sub; bytes b; } + struct T { uint[2] x; } + function f(L.S storage s) {} + function g(L.S s) {} + } + )"; + char const* interface = R"( + [{ + "constant": false, + "inputs": [ + { + "components": [ + { + "name": "a", + "type": "uint256" + }, + { + "components": [ + { + "name": "x", + "type": "uint256[2]" + } + ], + "name": "sub", + "type": "tuple[]" + }, + { + "name": "b", + "type": "bytes" + } + ], + "name": "s", + "type": "tuple" + } + ], + "name": "g", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "s", + "type": "L.S storage" + } + ], + "name": "f", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }] + )"; + checkInterface(text, interface); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index ea924fcb..f885b0d3 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9682,6 +9682,7 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment) compileAndRun(sourceCode, 0, "C2"); } + BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 8c271fe1..d9e6a63d 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -599,6 +599,149 @@ BOOST_AUTO_TEST_CASE(enum_external_type) } } +BOOST_AUTO_TEST_CASE(external_structs) +{ + char const* text = R"( + contract Test { + enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } + struct Empty {} + struct Nested { X[2][] a; uint y; } + struct X { bytes32 x; Test t; Empty[] e; } + function f(ActionChoices, uint, Empty) external {} + function g(Test, Nested) external {} + function h(function(Nested) external returns (uint)[]) external {} + function i(Nested[]) external {} + } + )"; + SourceUnit const* sourceUnit = parseAndAnalyse(text); + for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) + { + auto functions = contract->definedFunctions(); + BOOST_REQUIRE(!functions.empty()); + BOOST_CHECK_EQUAL("f(uint8,uint256,())", functions[0]->externalSignature()); + BOOST_CHECK_EQUAL("g(address,((bytes32,address,()[])[2][],uint256))", functions[1]->externalSignature()); + BOOST_CHECK_EQUAL("h(function[])", functions[2]->externalSignature()); + BOOST_CHECK_EQUAL("i(((bytes32,address,()[])[2][],uint256)[])", functions[3]->externalSignature()); + } +} + +BOOST_AUTO_TEST_CASE(external_structs_in_libraries) +{ + char const* text = R"( + library Test { + enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } + struct Empty {} + struct Nested { X[2][] a; uint y; } + struct X { bytes32 x; Test t; Empty[] e; } + function f(ActionChoices, uint, Empty) external {} + function g(Test, Nested) external {} + function h(function(Nested) external returns (uint)[]) external {} + function i(Nested[]) external {} + } + )"; + SourceUnit const* sourceUnit = parseAndAnalyse(text); + for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) + { + auto functions = contract->definedFunctions(); + BOOST_REQUIRE(!functions.empty()); + BOOST_CHECK_EQUAL("f(Test.ActionChoices,uint256,Test.Empty)", functions[0]->externalSignature()); + BOOST_CHECK_EQUAL("g(Test,Test.Nested)", functions[1]->externalSignature()); + BOOST_CHECK_EQUAL("h(function[])", functions[2]->externalSignature()); + BOOST_CHECK_EQUAL("i(Test.Nested[])", functions[3]->externalSignature()); + } +} + +BOOST_AUTO_TEST_CASE(struct_with_mapping_in_library) +{ + char const* text = R"( + library Test { + struct Nested { mapping(uint => uint)[2][] a; uint y; } + struct X { Nested n; } + function f(X storage x) external {} + } + )"; + SourceUnit const* sourceUnit = parseAndAnalyse(text); + for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) + { + auto functions = contract->definedFunctions(); + BOOST_REQUIRE(!functions.empty()); + BOOST_CHECK_EQUAL("f(Test.X storage)", functions[0]->externalSignature()); + } +} + +BOOST_AUTO_TEST_CASE(functions_with_identical_structs_in_interface) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S1 { } + struct S2 { } + function f(S1) pure {} + function f(S2) pure {} + } + )"; + CHECK_ERROR(text, TypeError, "Function overload clash during conversion to external types for arguments"); +} + +BOOST_AUTO_TEST_CASE(functions_with_different_structs_in_interface) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S1 { function() external a; } + struct S2 { bytes24 a; } + function f(S1) pure {} + function f(S2) pure {} + } + )"; + CHECK_SUCCESS(text); +} + +BOOST_AUTO_TEST_CASE(functions_with_stucts_of_non_external_types_in_interface) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S { function() internal a; } + function f(S) {} + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(functions_with_stucts_of_non_external_types_in_interface_2) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S { mapping(uint => uint) a; } + function f(S) {} + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(functions_with_stucts_of_non_external_types_in_interface_nested) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct T { mapping(uint => uint) a; } + struct S { T[][2] b; } + function f(S) {} + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + BOOST_AUTO_TEST_CASE(function_external_call_allowed_conversion) { char const* text = R"( @@ -980,24 +1123,24 @@ BOOST_AUTO_TEST_CASE(state_variable_accessors) FunctionTypePointer function = retrieveFunctionBySignature(*contract, "foo()"); BOOST_REQUIRE(function && function->hasDeclaration()); auto returnParams = function->returnParameterTypes(); - BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "uint256"); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(), "uint256"); BOOST_CHECK(function->stateMutability() == StateMutability::View); function = retrieveFunctionBySignature(*contract, "map(uint256)"); BOOST_REQUIRE(function && function->hasDeclaration()); auto params = function->parameterTypes(); - BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256"); + BOOST_CHECK_EQUAL(params.at(0)->canonicalName(), "uint256"); returnParams = function->returnParameterTypes(); - BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4"); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(), "bytes4"); BOOST_CHECK(function->stateMutability() == StateMutability::View); function = retrieveFunctionBySignature(*contract, "multiple_map(uint256,uint256)"); BOOST_REQUIRE(function && function->hasDeclaration()); params = function->parameterTypes(); - BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256"); - BOOST_CHECK_EQUAL(params.at(1)->canonicalName(false), "uint256"); + BOOST_CHECK_EQUAL(params.at(0)->canonicalName(), "uint256"); + BOOST_CHECK_EQUAL(params.at(1)->canonicalName(), "uint256"); returnParams = function->returnParameterTypes(); - BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4"); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(), "bytes4"); BOOST_CHECK(function->stateMutability() == StateMutability::View); } @@ -1072,7 +1215,7 @@ BOOST_AUTO_TEST_CASE(struct_accessor_one_array_only) Data public data; } )"; - CHECK_ERROR(sourceCode, TypeError, "Internal type is not allowed for public state variables."); + CHECK_ERROR(sourceCode, TypeError, "Internal or recursive type is not allowed for public state variables."); } BOOST_AUTO_TEST_CASE(base_class_state_variable_internal_member) @@ -3283,7 +3426,7 @@ BOOST_AUTO_TEST_CASE(library_memory_struct) function f() public returns (S ) {} } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(using_for_arbitrary_mismatch) @@ -4874,7 +5017,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) } } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); } BOOST_AUTO_TEST_CASE(internal_function_returned_from_public_function) @@ -4886,7 +5029,7 @@ BOOST_AUTO_TEST_CASE(internal_function_returned_from_public_function) } } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); } BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal) @@ -4908,7 +5051,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external } } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); } BOOST_AUTO_TEST_CASE(function_type_arrays) @@ -5396,6 +5539,56 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor) success(text); } +BOOST_AUTO_TEST_CASE(return_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; T[] sub; } + struct T { uint[] x; } + function f() returns (uint, S) { + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(return_recursive_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; S[] sub; } + function f() returns (uint, S) { + } + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(return_recursive_structs2) +{ + char const* text = R"( + contract C { + struct S { uint a; S[2][] sub; } + function f() returns (uint, S) { + } + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(return_recursive_structs3) +{ + char const* text = R"( + contract C { + struct S { uint a; S[][][] sub; } + struct T { S s; } + function f() returns (uint x, T t) { + } + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + BOOST_AUTO_TEST_CASE(address_checksum_type_deduction) { char const* text = R"( |