Merge pull request #1673 from ethereum/structs

Returning structs
This commit is contained in:
Alex Beregszaszi 2017-09-18 15:20:56 +01:00 committed by GitHub
commit f676325d60
15 changed files with 894 additions and 79 deletions

View File

@ -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.

View File

@ -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": []
}
]

View File

@ -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.");

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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))

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
};
}

View File

@ -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()

View File

@ -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()
}

View File

@ -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"(

View File

@ -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"(