mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
commit
f676325d60
@ -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.
|
||||
|
@ -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": []
|
||||
}
|
||||
]
|
||||
|
@ -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.");
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -404,9 +404,11 @@ string ABIFunctions::abiEncodingFunction(
|
||||
else
|
||||
solAssert(false, "");
|
||||
}
|
||||
else if (dynamic_cast<StructType const*>(&to))
|
||||
else if (auto const* toStruct = dynamic_cast<StructType const*>(&to))
|
||||
{
|
||||
solUnimplementedAssert(false, "Structs not yet implemented.");
|
||||
StructType const* fromStruct = dynamic_cast<StructType const*>(&_from);
|
||||
solAssert(fromStruct, "");
|
||||
return abiEncodingFunctionStruct(*fromStruct, *toStruct, _encodeAsLibraryTypes);
|
||||
}
|
||||
else if (_from.category() == Type::Category::Function)
|
||||
return abiEncodingFunctionFunctionType(
|
||||
@ -534,7 +536,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||
{
|
||||
mstore(pos, sub(tail, headStart))
|
||||
tail := <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), tail)
|
||||
tail := <encodeToMemoryFun>(<arrayElementAccess>, tail)
|
||||
srcPtr := <nextArrayElement>(srcPtr)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
}
|
||||
@ -549,7 +551,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
let srcPtr := <dataAreaFun>(value)
|
||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||
{
|
||||
<encodeToMemoryFun>(<arrayElementAccess>(srcPtr), pos)
|
||||
<encodeToMemoryFun>(<arrayElementAccess>, pos)
|
||||
srcPtr := <nextArrayElement>(srcPtr)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
}
|
||||
@ -573,7 +575,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
_encodeAsLibraryTypes,
|
||||
true
|
||||
));
|
||||
templ("arrayElementAccess", inMemory ? "mload" : "sload");
|
||||
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||
templ("nextArrayElement", nextArrayElementFunction(_from));
|
||||
return templ.render();
|
||||
});
|
||||
@ -726,6 +728,122 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunctionStruct(
|
||||
StructType const& _from,
|
||||
StructType const& _to,
|
||||
bool _encodeAsLibraryTypes
|
||||
)
|
||||
{
|
||||
string functionName =
|
||||
"abi_encode_" +
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier() +
|
||||
(_encodeAsLibraryTypes ? "_library" : "");
|
||||
|
||||
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "");
|
||||
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
|
||||
|
||||
return createFunction(functionName, [&]() {
|
||||
bool fromStorage = _from.location() == DataLocation::Storage;
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value, pos) <return> {
|
||||
let tail := add(pos, <headSize>)
|
||||
<init>
|
||||
<#members>
|
||||
{
|
||||
// <memberName>
|
||||
<encode>
|
||||
}
|
||||
</members>
|
||||
<assignEnd>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("return", dynamic ? " -> end " : "");
|
||||
templ("assignEnd", dynamic ? "end := tail" : "");
|
||||
// to avoid multiple loads from the same slot for subsequent members
|
||||
templ("init", fromStorage ? "let slotValue := 0" : "");
|
||||
u256 previousSlotOffset(-1);
|
||||
u256 encodingOffset = 0;
|
||||
vector<map<string, string>> members;
|
||||
for (auto const& member: _to.members(nullptr))
|
||||
{
|
||||
solAssert(member.type, "");
|
||||
if (!member.type->canLiveOutsideStorage())
|
||||
continue;
|
||||
solUnimplementedAssert(
|
||||
member.type->mobileType() &&
|
||||
member.type->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
|
||||
member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
|
||||
"Encoding type \"" + member.type->toString() + "\" not yet implemented."
|
||||
);
|
||||
auto memberTypeTo = member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
|
||||
auto memberTypeFrom = _from.memberType(member.name);
|
||||
solAssert(memberTypeFrom, "");
|
||||
bool dynamicMember = memberTypeTo->isDynamicallyEncoded();
|
||||
if (dynamicMember)
|
||||
solAssert(dynamic, "");
|
||||
Whiskers memberTempl(R"(
|
||||
<preprocess>
|
||||
let memberValue := <retrieveValue>
|
||||
)" + (
|
||||
dynamicMember ?
|
||||
string(R"(
|
||||
mstore(add(pos, <encodingOffset>), sub(tail, pos))
|
||||
tail := <abiEncode>(memberValue, tail)
|
||||
)") :
|
||||
string(R"(
|
||||
<abiEncode>(memberValue, add(pos, <encodingOffset>))
|
||||
)")
|
||||
)
|
||||
);
|
||||
if (fromStorage)
|
||||
{
|
||||
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
|
||||
u256 storageSlotOffset;
|
||||
size_t intraSlotOffset;
|
||||
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
|
||||
if (memberTypeFrom->isValueType())
|
||||
{
|
||||
if (storageSlotOffset != previousSlotOffset)
|
||||
{
|
||||
memberTempl("preprocess", "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))");
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
else
|
||||
memberTempl("preprocess", "");
|
||||
memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8, false) + "(slotValue)");
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
|
||||
solAssert(intraSlotOffset == 0, "");
|
||||
memberTempl("preprocess", "");
|
||||
memberTempl("retrieveValue", "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memberTempl("preprocess", "");
|
||||
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
|
||||
memberTempl("retrieveValue", "mload(add(value, " + sourceOffset + "))");
|
||||
}
|
||||
memberTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
|
||||
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
|
||||
memberTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, _encodeAsLibraryTypes, false));
|
||||
|
||||
members.push_back({});
|
||||
members.back()["encode"] = memberTempl.render();
|
||||
members.back()["memberName"] = member.name;
|
||||
}
|
||||
templ("members", members);
|
||||
templ("headSize", toCompactHexWithPrefix(encodingOffset));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunctionStringLiteral(
|
||||
Type const& _from,
|
||||
Type const& _to,
|
||||
|
@ -123,6 +123,13 @@ private:
|
||||
bool _encodeAsLibraryTypes
|
||||
);
|
||||
|
||||
/// Part of @a abiEncodingFunction for struct types.
|
||||
std::string abiEncodingFunctionStruct(
|
||||
StructType const& _givenType,
|
||||
StructType const& _targetType,
|
||||
bool _encodeAsLibraryTypes
|
||||
);
|
||||
|
||||
// @returns the name of the ABI encoding function with the given type
|
||||
// and queues the generation of the function to the requested functions.
|
||||
// Case for _givenType being a string literal
|
||||
|
@ -121,7 +121,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
|
||||
{
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
||||
{
|
||||
solAssert(ref->location() == DataLocation::Memory, "");
|
||||
solUnimplementedAssert(ref->location() == DataLocation::Memory, "");
|
||||
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
|
||||
}
|
||||
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
@ -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"(
|
||||
|
@ -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"(
|
||||
|
Loading…
Reference in New Issue
Block a user