aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Beregszaszi <alex@rtfs.hu>2017-09-18 22:20:56 +0800
committerGitHub <noreply@github.com>2017-09-18 22:20:56 +0800
commitf676325d60dbc6c1120ce5a4478f363622c1b1cf (patch)
tree83eabcc9ff5b5481da4f98ab8b4d2dde34e0299b
parentf473a43a25b562ee91aa8ddddd35f4174e0d4456 (diff)
parentc001903cdc6db97f437150375a9b5343c70c3656 (diff)
downloaddexon-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.md1
-rw-r--r--docs/abi-spec.rst105
-rw-r--r--libsolidity/analysis/TypeChecker.cpp6
-rw-r--r--libsolidity/ast/Types.cpp154
-rw-r--r--libsolidity/ast/Types.h38
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp128
-rw-r--r--libsolidity/codegen/ABIFunctions.h7
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp2
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp2
-rw-r--r--libsolidity/interface/ABI.cpp61
-rw-r--r--libsolidity/interface/ABI.h4
-rw-r--r--test/libsolidity/ABIEncoderTests.cpp36
-rw-r--r--test/libsolidity/SolidityABIJSON.cpp213
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp1
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp215
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"(