Merge pull request #11806 from ethereum/user-defined-types

User defined value types
This commit is contained in:
Harikrishnan Mulackal 2021-09-09 10:28:35 +02:00 committed by GitHub
commit 8a37f56e98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1874 additions and 1 deletions

View File

@ -3,6 +3,7 @@
Language Features:
* Inheritance: A function that overrides only a single interface function does not require the ``override`` specifier.
* Type System: Support ``type().min`` and ``type().max`` for enums.
* User Defined Value Type: allows creating a zero cost abstraction over a value type with stricter type requirements.
Compiler Features:

View File

@ -113,6 +113,9 @@ them.
+-------------------------------+-----------------------------------------------------------------------------+
|:ref:`enum<enums>` |``uint8`` |
+-------------------------------+-----------------------------------------------------------------------------+
|:ref:`user defined value types |its underlying value type |
|<user-defined-value-types>` | |
+-------------------------------+-----------------------------------------------------------------------------+
|:ref:`struct<structs>` |``tuple`` |
+-------------------------------+-----------------------------------------------------------------------------+

View File

@ -19,6 +19,7 @@ sourceUnit: (
| constantVariableDeclaration
| structDefinition
| enumDefinition
| userDefinedValueTypeDefinition
| errorDefinition
)* EOF;
@ -89,6 +90,7 @@ contractBodyElement:
| receiveFunctionDefinition
| structDefinition
| enumDefinition
| userDefinedValueTypeDefinition
| stateVariableDeclaration
| eventDefinition
| errorDefinition
@ -247,6 +249,11 @@ structMember: type=typeName name=identifier Semicolon;
* Definition of an enum. Can occur at top-level within a source unit or within a contract, library or interface.
*/
enumDefinition: Enum name=identifier LBrace enumValues+=identifier (Comma enumValues+=identifier)* RBrace;
/**
* Definition of a user defined value type. Can occur at top-level within a source unit or within a contract, library or interface.
*/
userDefinedValueTypeDefinition:
Type name=identifier Is elementaryTypeName[true] Semicolon;
/**
* The declaration of a state variable.

View File

@ -628,6 +628,69 @@ smallest and respectively largest value of the given enum.
.. note::
Enums can also be declared on the file level, outside of contract or library definitions.
.. index:: ! user defined value type, custom type
.. _user-defined-value-types:
User Defined Value Types
------------------------
A user defined value type allows creating a zero cost abstraction over an elementary value type.
This is similar to an alias, but with stricter type requirements.
A user defined value type is defined using ``type C is V``, where ``C`` is the name of the newly
introduced type and ``V`` has to be a built-in value type (the "underlying type"). The function
``C.wrap`` is used to convert from the underlying type to the custom type. Similarly, the
function ``C.unwrap`` is used to convert from the custom type to the underlying type.
The type ``C`` does not have any operators or bound member functions. In particular, even the
operator ``==`` is not defined. Explicit and implicit conversions to and from other types are
disallowed.
The data-representation of values of such types are inherited from the underlying type
and the underlying type is also used in the ABI.
The following example illustrates a custom type ``UFixed256x18`` representing a decimal fixed point
type with 18 decimals and a minimal library to do arithmetic operations on the type.
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type.
type UFixed256x18 is uint256;
/// A minimal library to do fixed point operations on UFixed256x18.
library FixedMath {
uint constant multiplier = 10**18;
/// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
}
/// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
}
/// Truncates UFixed256x18 to the nearest uint256 number.
function truncate(UFixed256x18 a) internal pure returns (uint256) {
return UFixed256x18.unwrap(a) / multiplier;
}
/// Turns a uint256 into a UFixed256x18 of the same value.
/// Reverts if the integer is too large.
function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(a * multiplier);
}
}
Notice how ``UFixed256x18.wrap`` and ``FixedMath.toUFixed256x18`` have the same signature but
perform two very different operations: The ``UFixed256x18.wrap`` function returns a ``UFixed256x18``
that has the same data representation as the input, whereas ``toUFixed256x18`` returns a
``UFixed256x18`` that has the same numerical value.
.. index:: ! function type, ! type; function

View File

@ -140,6 +140,30 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
return false;
}
void DeclarationTypeChecker::endVisit(UserDefinedValueTypeDefinition const& _userDefined)
{
TypeName const* typeName = _userDefined.underlyingType();
solAssert(typeName, "");
if (!dynamic_cast<ElementaryTypeName const*>(typeName))
m_errorReporter.fatalTypeError(
8657_error,
typeName->location(),
"The underlying type for a user defined value type has to be an elementary value type."
);
Type const* type = typeName->annotation().type;
solAssert(type, "");
solAssert(!dynamic_cast<UserDefinedValueType const*>(type), "");
if (!type->isValueType())
m_errorReporter.typeError(
8129_error,
_userDefined.location(),
"The underlying type of the user defined value type \"" +
_userDefined.name() +
"\" is not a value type."
);
}
void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
{
if (_typeName.annotation().type)
@ -158,6 +182,8 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::contract(*contract);
else if (auto userDefinedValueType = dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::userDefinedValueType(*userDefinedValueType);
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();

View File

@ -60,6 +60,7 @@ private:
void endVisit(VariableDeclaration const& _variable) override;
bool visit(EnumDefinition const& _enum) override;
bool visit(StructDefinition const& _struct) override;
void endVisit(UserDefinedValueTypeDefinition const& _userDefined) override;
bool visit(UsingForDirective const& _usingForDirective) override;
bool visit(InheritanceSpecifier const& _inheritanceSpecifier) override;

View File

@ -2481,6 +2481,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
returnTypes = functionType->returnParameterTypes();
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
typeCheckFunctionGeneralChecks(_functionCall, functionType);
returnTypes = functionType->returnParameterTypes();
break;
}
default:
{
typeCheckFunctionCall(_functionCall, functionType);

View File

@ -335,6 +335,12 @@ TypeNameAnnotation& TypeName::annotation() const
return initAnnotation<TypeNameAnnotation>();
}
Type const* UserDefinedValueTypeDefinition::type() const
{
solAssert(m_underlyingType->annotation().type, "");
return TypeProvider::typeType(TypeProvider::userDefinedValueType(*this));
}
Type const* StructDefinition::type() const
{
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");

View File

@ -726,6 +726,37 @@ public:
Type const* type() const override;
};
/**
* User defined value types, i.e., custom types, for example, `type MyInt is int`. Allows creating a
* zero cost abstraction over value type with stricter type requirements.
*/
class UserDefinedValueTypeDefinition: public Declaration
{
public:
UserDefinedValueTypeDefinition(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> _name,
SourceLocation _nameLocation,
ASTPointer<TypeName> _underlyingType
):
Declaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default),
m_underlyingType(std::move(_underlyingType))
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Type const* type() const override;
TypeName const* underlyingType() const { return m_underlyingType.get(); }
private:
/// The name of the underlying type
ASTPointer<TypeName> m_underlyingType;
};
/**
* Parameter list, used as function parameter list, return list and for try and catch.
* None of the parameters is allowed to contain mappings (not even recursively

View File

@ -51,6 +51,7 @@ class UsingForDirective;
class StructDefinition;
class EnumDefinition;
class EnumValue;
class UserDefinedValueTypeDefinition;
class ParameterList;
class FunctionDefinition;
class VariableDeclaration;

View File

@ -354,6 +354,20 @@ bool ASTJsonConverter::visit(EnumValue const& _node)
return false;
}
bool ASTJsonConverter::visit(UserDefinedValueTypeDefinition const& _node)
{
solAssert(_node.underlyingType(), "");
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("nameLocation", sourceLocationToString(_node.nameLocation())),
make_pair("underlyingType", toJson(*_node.underlyingType()))
};
setJsonNode(_node, "UserDefinedValueTypeDefinition", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(ParameterList const& _node)
{
setJsonNode(_node, "ParameterList", {

View File

@ -81,6 +81,7 @@ public:
bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
bool visit(UserDefinedValueTypeDefinition const& _node) override;
bool visit(ParameterList const& _node) override;
bool visit(OverrideSpecifier const& _node) override;
bool visit(FunctionDefinition const& _node) override;

View File

@ -133,6 +133,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
return createEnumDefinition(_json);
if (nodeType == "EnumValue")
return createEnumValue(_json);
if (nodeType == "UserDefinedValueTypeDefinition")
return createUserDefinedValueTypeDefinition(_json);
if (nodeType == "ParameterList")
return createParameterList(_json);
if (nodeType == "OverrideSpecifier")
@ -387,6 +389,16 @@ ASTPointer<EnumValue> ASTJsonImporter::createEnumValue(Json::Value const& _node)
);
}
ASTPointer<UserDefinedValueTypeDefinition> ASTJsonImporter::createUserDefinedValueTypeDefinition(Json::Value const& _node)
{
return createASTNode<UserDefinedValueTypeDefinition>(
_node,
memberAsASTString(_node, "name"),
createNameSourceLocation(_node),
convertJsonToASTNode<TypeName>(member(_node, "underlyingType"))
);
}
ASTPointer<ParameterList> ASTJsonImporter::createParameterList(Json::Value const& _node)
{
std::vector<ASTPointer<VariableDeclaration>> parameters;

View File

@ -81,6 +81,7 @@ private:
ASTPointer<ASTNode> createStructDefinition(Json::Value const& _node);
ASTPointer<EnumDefinition> createEnumDefinition(Json::Value const& _node);
ASTPointer<EnumValue> createEnumValue(Json::Value const& _node);
ASTPointer<UserDefinedValueTypeDefinition> createUserDefinedValueTypeDefinition(Json::Value const& _node);
ASTPointer<ParameterList> createParameterList(Json::Value const& _node);
ASTPointer<OverrideSpecifier> createOverrideSpecifier(Json::Value const& _node);
ASTPointer<FunctionDefinition> createFunctionDefinition(Json::Value const& _node);

View File

@ -61,6 +61,7 @@ public:
virtual bool visit(IdentifierPath& _node) { return visitNode(_node); }
virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); }
virtual bool visit(UsingForDirective& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedValueTypeDefinition& _node) { return visitNode(_node); }
virtual bool visit(StructDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumValue& _node) { return visitNode(_node); }
@ -116,6 +117,7 @@ public:
virtual void endVisit(IdentifierPath& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedValueTypeDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(StructDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); }
@ -194,6 +196,7 @@ public:
virtual bool visit(InheritanceSpecifier const& _node) { return visitNode(_node); }
virtual bool visit(StructDefinition const& _node) { return visitNode(_node); }
virtual bool visit(UsingForDirective const& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedValueTypeDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumValue const& _node) { return visitNode(_node); }
virtual bool visit(ParameterList const& _node) { return visitNode(_node); }
@ -248,6 +251,7 @@ public:
virtual void endVisit(IdentifierPath const& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective const& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedValueTypeDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(StructDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }

View File

@ -164,6 +164,26 @@ void EnumValue::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void UserDefinedValueTypeDefinition::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_underlyingType)
m_underlyingType->accept(_visitor);
}
_visitor.endVisit(*this);
}
void UserDefinedValueTypeDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_underlyingType)
m_underlyingType->accept(_visitor);
}
_visitor.endVisit(*this);
}
void UsingForDirective::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))

View File

@ -578,3 +578,8 @@ MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valu
{
return createAndGet<MappingType>(_keyType, _valueType);
}
UserDefinedValueType const* TypeProvider::userDefinedValueType(UserDefinedValueTypeDefinition const& _definition)
{
return createAndGet<UserDefinedValueType>(_definition);
}

View File

@ -201,6 +201,8 @@ public:
static MappingType const* mapping(Type const* _keyType, Type const* _valueType);
static UserDefinedValueType const* userDefinedValueType(UserDefinedValueTypeDefinition const& _definition);
private:
/// Global TypeProvider instance.
static TypeProvider& instance()

View File

@ -2533,6 +2533,36 @@ unsigned EnumType::memberValue(ASTString const& _member) const
solAssert(false, "Requested unknown enum value " + _member);
}
Type const& UserDefinedValueType::underlyingType() const
{
Type const* type = m_definition.underlyingType()->annotation().type;
solAssert(type, "");
return *type;
}
string UserDefinedValueType::richIdentifier() const
{
return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + to_string(m_definition.id());
}
bool UserDefinedValueType::operator==(Type const& _other) const
{
if (_other.category() != category())
return false;
UserDefinedValueType const& other = dynamic_cast<UserDefinedValueType const&>(_other);
return other.definition() == definition();
}
string UserDefinedValueType::toString(bool /* _short */) const
{
return "user defined type " + definition().name();
}
vector<tuple<string, Type const*>> UserDefinedValueType::makeStackItems() const
{
return underlyingType().stackItems();
}
BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const
{
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
@ -2884,6 +2914,8 @@ string FunctionType::richIdentifier() const
case Kind::GasLeft: id += "gasleft"; break;
case Kind::Event: id += "event"; break;
case Kind::Error: id += "error"; break;
case Kind::Wrap: id += "wrap"; break;
case Kind::Unwrap: id += "unwrap"; break;
case Kind::SetGas: id += "setgas"; break;
case Kind::SetValue: id += "setvalue"; break;
case Kind::BlockHash: id += "blockhash"; break;
@ -3754,6 +3786,34 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
for (ASTPointer<EnumValue> const& enumValue: enumDef.members())
members.emplace_back(enumValue.get(), enumType);
}
else if (m_actualType->category() == Category::UserDefinedValueType)
{
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(*m_actualType);
members.emplace_back(
"wrap",
TypeProvider::function(
TypePointers{&userDefined.underlyingType()},
TypePointers{&userDefined},
strings{string{}},
strings{string{}},
FunctionType::Kind::Wrap,
false, /*_arbitraryParameters */
StateMutability::Pure
)
);
members.emplace_back(
"unwrap",
TypeProvider::function(
TypePointers{&userDefined},
TypePointers{&userDefined.underlyingType()},
strings{string{}},
strings{string{}},
FunctionType::Kind::Unwrap,
false, /* _arbitraryParameters */
StateMutability::Pure
)
);
}
else if (
auto const* arrayType = dynamic_cast<ArrayType const*>(m_actualType);
arrayType && arrayType->isByteArray()

View File

@ -174,7 +174,7 @@ public:
enum class Category
{
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
FixedBytes, Contract, Struct, Function, Enum, Tuple,
FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple,
Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic
};
@ -1082,6 +1082,53 @@ private:
EnumDefinition const& m_enum;
};
/**
* The type of a UserDefinedValueType.
*/
class UserDefinedValueType: public Type
{
public:
explicit UserDefinedValueType(UserDefinedValueTypeDefinition const& _definition):
m_definition(_definition)
{}
Category category() const override { return Category::UserDefinedValueType; }
Type const& underlyingType() const;
UserDefinedValueTypeDefinition const& definition() const { return m_definition; }
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
Type const* encodingType() const override { return &underlyingType(); }
TypeResult interfaceType(bool /* _inLibrary */) const override {return &underlyingType(); }
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
unsigned calldataEncodedSize(bool _padded) const override { return underlyingType().calldataEncodedSize(_padded); }
bool leftAligned() const override { return underlyingType().leftAligned(); }
bool canBeStored() const override { return underlyingType().canBeStored(); }
u256 storageSize() const override { return underlyingType().storageSize(); }
bool isValueType() const override
{
solAssert(underlyingType().isValueType(), "");
return true;
}
bool nameable() const override
{
solAssert(underlyingType().nameable(), "");
return true;
}
std::string toString(bool _short) const override;
std::string canonicalName() const override { solAssert(false, ""); }
std::string signatureInExternalFunction(bool) const override { solAssert(false, ""); }
protected:
std::vector<std::tuple<std::string, Type const*>> makeStackItems() const override;
private:
UserDefinedValueTypeDefinition const& m_definition;
};
/**
* Type that can hold a finite sequence of values of different types.
* In some cases, the components are empty pointers (when used as placeholders).
@ -1150,6 +1197,8 @@ public:
RIPEMD160, ///< CALL to special contract for ripemd160
Event, ///< syntactic sugar for LOG*
Error, ///< creating an error instance in revert or require
Wrap, ///< customType.wrap(...) for user defined value types
Unwrap, ///< customType.unwrap(...) for user defined value types
SetGas, ///< modify the default gas value for the function call
SetValue, ///< modify the default value transfer for the function call
BlockHash, ///< BLOCKHASH

View File

@ -772,6 +772,33 @@ void CompilerUtils::convertType(
Type::Category stackTypeCategory = _typeOnStack.category();
Type::Category targetTypeCategory = _targetType.category();
if (stackTypeCategory == Type::Category::UserDefinedValueType)
{
solAssert(_cleanupNeeded, "");
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_typeOnStack);
solAssert(_typeOnStack == _targetType || _targetType == userDefined.underlyingType(), "");
return convertType(
userDefined.underlyingType(),
_targetType,
_cleanupNeeded,
_chopSignBits,
_asPartOfArgumentDecoding
);
}
if (targetTypeCategory == Type::Category::UserDefinedValueType)
{
solAssert(_cleanupNeeded, "");
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_targetType);
solAssert(_typeOnStack.isImplicitlyConvertibleTo(userDefined.underlyingType()), "");
return convertType(
_typeOnStack,
userDefined.underlyingType(),
_cleanupNeeded,
_chopSignBits,
_asPartOfArgumentDecoding
);
}
if (auto contrType = dynamic_cast<ContractType const*>(&_typeOnStack))
solAssert(!contrType->isSuper(), "Cannot convert magic variable \"super\"");

View File

@ -957,6 +957,35 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
);
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
solAssert(arguments.size() == 1, "");
Type const* argumentType = arguments.at(0)->annotation().type;
Type const* functionCallType = _functionCall.annotation().type;
solAssert(argumentType, "");
solAssert(functionCallType, "");
FunctionType::Kind kind = functionType->kind();
if (kind == FunctionType::Kind::Wrap)
{
solAssert(
argumentType->isImplicitlyConvertibleTo(
dynamic_cast<UserDefinedValueType const&>(*functionCallType).underlyingType()
),
""
);
solAssert(argumentType->isImplicitlyConvertibleTo(*function.parameterTypes()[0]), "");
}
else
solAssert(
dynamic_cast<UserDefinedValueType const&>(*argumentType) ==
dynamic_cast<UserDefinedValueType const&>(*function.parameterTypes()[0]),
""
);
acceptAndConvert(*arguments[0], *function.parameterTypes()[0]);
break;
}
case FunctionType::Kind::BlockHash:
{
acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true);
@ -2157,6 +2186,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
{
// no-op
}
else if (dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
{
// no-op
}
else if (dynamic_cast<StructDefinition const*>(declaration))
{
// no-op

View File

@ -3168,6 +3168,16 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::UserDefinedValueType)
{
solAssert(_from == _to || _to == dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), "");
return conversionFunction(dynamic_cast<UserDefinedValueType const&>(_from).underlyingType(), _to);
}
if (_to.category() == Type::Category::UserDefinedValueType)
{
solAssert(_from == _to || _from.isImplicitlyConvertibleTo(dynamic_cast<UserDefinedValueType const&>(_to).underlyingType()), "");
return conversionFunction(_from, dynamic_cast<UserDefinedValueType const&>(_to).underlyingType());
}
if (_from.category() == Type::Category::Function)
{
solAssert(_to.category() == Type::Category::Function, "");
@ -3696,6 +3706,9 @@ string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayTy
string YulUtilFunctions::cleanupFunction(Type const& _type)
{
if (auto userDefinedValueType = dynamic_cast<UserDefinedValueType const*>(&_type))
return cleanupFunction(userDefinedValueType->underlyingType());
string functionName = string("cleanup_") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
@ -3816,6 +3829,7 @@ string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFail
case Type::Category::Mapping:
case Type::Category::FixedBytes:
case Type::Category::Contract:
case Type::Category::UserDefinedValueType:
{
templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))");
break;

View File

@ -1049,6 +1049,24 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
);
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
solAssert(arguments.size() == 1, "");
FunctionType::Kind kind = functionType->kind();
if (kind == FunctionType::Kind::Wrap)
solAssert(
type(*arguments.at(0)).isImplicitlyConvertibleTo(
dynamic_cast<UserDefinedValueType const&>(type(_functionCall)).underlyingType()
),
""
);
else
solAssert(type(*arguments.at(0)).category() == Type::Category::UserDefinedValueType, "");
define(_functionCall, *arguments.at(0));
break;
}
case FunctionType::Kind::Assert:
case FunctionType::Kind::Require:
{
@ -2001,6 +2019,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
}
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(&actualType))
define(_memberAccess) << to_string(enumType->memberValue(_memberAccess.memberName())) << "\n";
else if (dynamic_cast<UserDefinedValueType const*>(&actualType))
solAssert(member == "wrap" || member == "unwrap", "");
else if (auto const* arrayType = dynamic_cast<ArrayType const*>(&actualType))
solAssert(arrayType->isByteArray() && member == "concat", "");
else
@ -2312,6 +2332,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{
// no-op
}
else if (dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
{
// no-op
}
else
{
solAssert(false, "Identifier type not expected in expression context.");

View File

@ -113,6 +113,9 @@ ASTPointer<SourceUnit> Parser::parse(CharStream& _charStream)
case Token::Enum:
nodes.push_back(parseEnumDefinition());
break;
case Token::Type:
nodes.push_back(parseUserDefinedValueTypeDefinition());
break;
case Token::Function:
nodes.push_back(parseFunctionDefinition(true));
break;
@ -364,6 +367,8 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
subNodes.push_back(parseStructDefinition());
else if (currentTokenValue == Token::Enum)
subNodes.push_back(parseEnumDefinition());
else if (currentTokenValue == Token::Type)
subNodes.push_back(parseUserDefinedValueTypeDefinition());
else if (
// Workaround because `error` is not a keyword.
currentTokenValue == Token::Identifier &&
@ -1010,6 +1015,22 @@ ASTPointer<UserDefinedTypeName> Parser::parseUserDefinedTypeName()
return nodeFactory.createNode<UserDefinedTypeName>(identifierPath);
}
ASTPointer<UserDefinedValueTypeDefinition> Parser::parseUserDefinedValueTypeDefinition()
{
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Type);
auto&& [name, nameLocation] = expectIdentifierWithLocation();
expectToken(Token::Is);
ASTPointer<TypeName> typeName = parseTypeName();
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<UserDefinedValueTypeDefinition>(
name,
move(nameLocation),
typeName
);
}
ASTPointer<IdentifierPath> Parser::parseIdentifierPath()
{
RecursionGuard recursionGuard(*this);

View File

@ -95,6 +95,7 @@ private:
ASTPointer<ASTNode> parseFunctionDefinition(bool _freeFunction = false);
ASTPointer<StructDefinition> parseStructDefinition();
ASTPointer<EnumDefinition> parseEnumDefinition();
ASTPointer<UserDefinedValueTypeDefinition> parseUserDefinedValueTypeDefinition();
ASTPointer<EnumValue> parseEnumValue();
ASTPointer<VariableDeclaration> parseVariableDeclaration(
VarDeclParserOptions const& _options = {},

View File

@ -0,0 +1,173 @@
type MyInt is int;
type MyByte1 is bytes1;
contract C {
type MyAddress is address;
type MyUInt8 is uint8;
type MyBytes32 is bytes32;
MyInt public myInt;
MyByte1 public myByte1;
MyAddress public myAddress;
MyUInt8 public myUInt8;
MyBytes32 public myBytes32;
function setMyInt(MyInt a) external {
myInt = a;
}
function setMyByte1(MyByte1 a) external {
myByte1 = a;
}
function setMyAddress(MyAddress a) external {
myAddress = a;
}
function setMyUInt8(MyUInt8 a) external {
myUInt8 = a;
}
function setMyBytes32(MyBytes32 a) external {
myBytes32 = a;
}
}
// ----
// :C
// [
// {
// "inputs": [],
// "name": "myAddress",
// "outputs":
// [
// {
// "internalType": "user defined type MyAddress",
// "name": "",
// "type": "address"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// },
// {
// "inputs": [],
// "name": "myByte1",
// "outputs":
// [
// {
// "internalType": "user defined type MyByte1",
// "name": "",
// "type": "bytes1"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// },
// {
// "inputs": [],
// "name": "myBytes32",
// "outputs":
// [
// {
// "internalType": "user defined type MyBytes32",
// "name": "",
// "type": "bytes32"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// },
// {
// "inputs": [],
// "name": "myInt",
// "outputs":
// [
// {
// "internalType": "user defined type MyInt",
// "name": "",
// "type": "int256"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// },
// {
// "inputs": [],
// "name": "myUInt8",
// "outputs":
// [
// {
// "internalType": "user defined type MyUInt8",
// "name": "",
// "type": "uint8"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// },
// {
// "inputs":
// [
// {
// "internalType": "user defined type MyAddress",
// "name": "a",
// "type": "address"
// }
// ],
// "name": "setMyAddress",
// "outputs": [],
// "stateMutability": "nonpayable",
// "type": "function"
// },
// {
// "inputs":
// [
// {
// "internalType": "user defined type MyByte1",
// "name": "a",
// "type": "bytes1"
// }
// ],
// "name": "setMyByte1",
// "outputs": [],
// "stateMutability": "nonpayable",
// "type": "function"
// },
// {
// "inputs":
// [
// {
// "internalType": "user defined type MyBytes32",
// "name": "a",
// "type": "bytes32"
// }
// ],
// "name": "setMyBytes32",
// "outputs": [],
// "stateMutability": "nonpayable",
// "type": "function"
// },
// {
// "inputs":
// [
// {
// "internalType": "user defined type MyInt",
// "name": "a",
// "type": "int256"
// }
// ],
// "name": "setMyInt",
// "outputs": [],
// "stateMutability": "nonpayable",
// "type": "function"
// },
// {
// "inputs":
// [
// {
// "internalType": "user defined type MyUInt8",
// "name": "a",
// "type": "uint8"
// }
// ],
// "name": "setMyUInt8",
// "outputs": [],
// "stateMutability": "nonpayable",
// "type": "function"
// }
// ]

View File

@ -0,0 +1,264 @@
{
"absolutePath": "a",
"exportedSymbols":
{
"C":
[
21
],
"MyAddress":
[
2
],
"MyUInt":
[
4
],
"f":
[
16
]
},
"id": 22,
"nodeType": "SourceUnit",
"nodes":
[
{
"id": 2,
"name": "MyAddress",
"nameLocation": "5:9:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "0:26:1",
"underlyingType":
{
"id": 1,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "18:7:1",
"stateMutability": "nonpayable",
"typeDescriptions":
{
"typeIdentifier": "t_address",
"typeString": "address"
}
}
},
{
"id": 4,
"name": "MyUInt",
"nameLocation": "32:6:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "27:20:1",
"underlyingType":
{
"id": 3,
"name": "uint",
"nodeType": "ElementaryTypeName",
"src": "42:4:1",
"typeDescriptions":
{
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
}
},
{
"body":
{
"id": 15,
"nodeType": "Block",
"src": "61:34:1",
"statements":
[
{
"assignments":
[
9
],
"declarations":
[
{
"constant": false,
"id": 9,
"mutability": "mutable",
"name": "a",
"nameLocation": "77:1:1",
"nodeType": "VariableDeclaration",
"scope": 15,
"src": "67:11:1",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions":
{
"typeIdentifier": "t_userDefinedValueType$_MyAddress_$2",
"typeString": "user defined type MyAddress"
},
"typeName":
{
"id": 8,
"nodeType": "UserDefinedTypeName",
"pathNode":
{
"id": 7,
"name": "MyAddress",
"nodeType": "IdentifierPath",
"referencedDeclaration": 2,
"src": "67:9:1"
},
"referencedDeclaration": 2,
"src": "67:9:1",
"typeDescriptions":
{
"typeIdentifier": "t_userDefinedValueType$_MyAddress_$2",
"typeString": "user defined type MyAddress"
}
},
"visibility": "internal"
}
],
"id": 10,
"nodeType": "VariableDeclarationStatement",
"src": "67:11:1"
},
{
"assignments":
[
13
],
"declarations":
[
{
"constant": false,
"id": 13,
"mutability": "mutable",
"name": "b",
"nameLocation": "91:1:1",
"nodeType": "VariableDeclaration",
"scope": 15,
"src": "84:8:1",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions":
{
"typeIdentifier": "t_userDefinedValueType$_MyUInt_$4",
"typeString": "user defined type MyUInt"
},
"typeName":
{
"id": 12,
"nodeType": "UserDefinedTypeName",
"pathNode":
{
"id": 11,
"name": "MyUInt",
"nodeType": "IdentifierPath",
"referencedDeclaration": 4,
"src": "84:6:1"
},
"referencedDeclaration": 4,
"src": "84:6:1",
"typeDescriptions":
{
"typeIdentifier": "t_userDefinedValueType$_MyUInt_$4",
"typeString": "user defined type MyUInt"
}
},
"visibility": "internal"
}
],
"id": 14,
"nodeType": "VariableDeclarationStatement",
"src": "84:8:1"
}
]
},
"id": 16,
"implemented": true,
"kind": "freeFunction",
"modifiers": [],
"name": "f",
"nameLocation": "57:1:1",
"nodeType": "FunctionDefinition",
"parameters":
{
"id": 5,
"nodeType": "ParameterList",
"parameters": [],
"src": "58:2:1"
},
"returnParameters":
{
"id": 6,
"nodeType": "ParameterList",
"parameters": [],
"src": "61:0:1"
},
"scope": 22,
"src": "48:47:1",
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "internal"
},
{
"abstract": false,
"baseContracts": [],
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"id": 21,
"linearizedBaseContracts":
[
21
],
"name": "C",
"nameLocation": "105:1:1",
"nodeType": "ContractDefinition",
"nodes":
[
{
"id": 18,
"name": "MyAddress",
"nameLocation": "118:9:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "113:26:1",
"underlyingType":
{
"id": 17,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "131:7:1",
"stateMutability": "nonpayable",
"typeDescriptions":
{
"typeIdentifier": "t_address",
"typeString": "address"
}
}
},
{
"id": 20,
"name": "MyUInt",
"nameLocation": "149:6:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "144:20:1",
"underlyingType":
{
"id": 19,
"name": "uint",
"nodeType": "ElementaryTypeName",
"src": "159:4:1",
"typeDescriptions":
{
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
}
}
],
"scope": 22,
"src": "96:70:1",
"usedErrors": []
}
],
"src": "0:167:1"
}

View File

@ -0,0 +1,12 @@
type MyAddress is address;
type MyUInt is uint;
function f() {
MyAddress a;
MyUInt b;
}
contract C {
type MyAddress is address;
type MyUInt is uint;
}
// ----

View File

@ -0,0 +1,200 @@
{
"absolutePath": "a",
"id": 22,
"nodeType": "SourceUnit",
"nodes":
[
{
"id": 2,
"name": "MyAddress",
"nameLocation": "5:9:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "0:26:1",
"underlyingType":
{
"id": 1,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "18:7:1",
"stateMutability": "nonpayable",
"typeDescriptions": {}
}
},
{
"id": 4,
"name": "MyUInt",
"nameLocation": "32:6:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "27:20:1",
"underlyingType":
{
"id": 3,
"name": "uint",
"nodeType": "ElementaryTypeName",
"src": "42:4:1",
"typeDescriptions": {}
}
},
{
"body":
{
"id": 15,
"nodeType": "Block",
"src": "61:34:1",
"statements":
[
{
"assignments":
[
9
],
"declarations":
[
{
"constant": false,
"id": 9,
"mutability": "mutable",
"name": "a",
"nameLocation": "77:1:1",
"nodeType": "VariableDeclaration",
"src": "67:11:1",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {},
"typeName":
{
"id": 8,
"nodeType": "UserDefinedTypeName",
"pathNode":
{
"id": 7,
"name": "MyAddress",
"nodeType": "IdentifierPath",
"src": "67:9:1"
},
"src": "67:9:1",
"typeDescriptions": {}
},
"visibility": "internal"
}
],
"id": 10,
"nodeType": "VariableDeclarationStatement",
"src": "67:11:1"
},
{
"assignments":
[
13
],
"declarations":
[
{
"constant": false,
"id": 13,
"mutability": "mutable",
"name": "b",
"nameLocation": "91:1:1",
"nodeType": "VariableDeclaration",
"src": "84:8:1",
"stateVariable": false,
"storageLocation": "default",
"typeDescriptions": {},
"typeName":
{
"id": 12,
"nodeType": "UserDefinedTypeName",
"pathNode":
{
"id": 11,
"name": "MyUInt",
"nodeType": "IdentifierPath",
"src": "84:6:1"
},
"src": "84:6:1",
"typeDescriptions": {}
},
"visibility": "internal"
}
],
"id": 14,
"nodeType": "VariableDeclarationStatement",
"src": "84:8:1"
}
]
},
"id": 16,
"implemented": true,
"kind": "freeFunction",
"modifiers": [],
"name": "f",
"nameLocation": "57:1:1",
"nodeType": "FunctionDefinition",
"parameters":
{
"id": 5,
"nodeType": "ParameterList",
"parameters": [],
"src": "58:2:1"
},
"returnParameters":
{
"id": 6,
"nodeType": "ParameterList",
"parameters": [],
"src": "61:0:1"
},
"src": "48:47:1",
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "internal"
},
{
"abstract": false,
"baseContracts": [],
"contractDependencies": [],
"contractKind": "contract",
"id": 21,
"name": "C",
"nameLocation": "105:1:1",
"nodeType": "ContractDefinition",
"nodes":
[
{
"id": 18,
"name": "MyAddress",
"nameLocation": "118:9:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "113:26:1",
"underlyingType":
{
"id": 17,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "131:7:1",
"stateMutability": "nonpayable",
"typeDescriptions": {}
}
},
{
"id": 20,
"name": "MyUInt",
"nameLocation": "149:6:1",
"nodeType": "UserDefinedValueTypeDefinition",
"src": "144:20:1",
"underlyingType":
{
"id": 19,
"name": "uint",
"nodeType": "ElementaryTypeName",
"src": "159:4:1",
"typeDescriptions": {}
}
}
],
"src": "96:70:1",
"usedErrors": []
}
],
"src": "0:167:1"
}

View File

@ -0,0 +1,65 @@
pragma abicoder v2;
type MyAddress is address;
contract C {
MyAddress[] public addresses;
function f(MyAddress[] calldata _addresses) external {
for (uint i = 0; i < _addresses.length; i++) {
MyAddress.unwrap(_addresses[i]).call("");
}
addresses = _addresses;
}
function g(MyAddress[] memory _addresses) external {
for (uint i = 0; i < _addresses.length; i++) {
MyAddress.unwrap(_addresses[i]).call("");
}
addresses = _addresses;
}
function test_f() external returns (bool) {
clean();
MyAddress[] memory test = new MyAddress[](3);
test[0] = MyAddress.wrap(address(21));
test[1] = MyAddress.wrap(address(22));
test[2] = MyAddress.wrap(address(23));
this.f(test);
test_equality(test);
return true;
}
function test_g() external returns (bool) {
clean();
MyAddress[] memory test = new MyAddress[](5);
test[0] = MyAddress.wrap(address(24));
test[1] = MyAddress.wrap(address(25));
test[2] = MyAddress.wrap(address(26));
test[3] = MyAddress.wrap(address(27));
test[4] = MyAddress.wrap(address(28));
this.g(test);
test_equality(test);
return true;
}
function clean() internal {
delete addresses;
}
function test_equality(MyAddress[] memory _addresses) internal view {
require (_addresses.length == addresses.length);
for (uint i = 0; i < _addresses.length; i++) {
require(MyAddress.unwrap(_addresses[i]) == MyAddress.unwrap(addresses[i]));
}
}
}
// ====
// compileViaYul: also
// ----
// test_f() -> true
// gas irOptimized: 122655
// gas legacy: 125037
// gas legacyOptimized: 122605
// test_g() -> true
// gas irOptimized: 95940
// gas legacy: 100656
// gas legacyOptimized: 96057
// addresses(uint256): 0 -> 0x18
// addresses(uint256): 1 -> 0x19
// addresses(uint256): 3 -> 0x1b
// addresses(uint256): 4 -> 0x1c
// addresses(uint256): 5 -> FAILURE

View File

@ -0,0 +1,44 @@
pragma abicoder v2;
type MyUInt8 is uint8;
// Note that this wraps from a uint256
function wrap(uint x) pure returns (MyUInt8 y) { assembly { y := x } }
function unwrap(MyUInt8 x) pure returns (uint8 y) { assembly { y := x } }
contract C {
uint8 a;
MyUInt8 b;
uint8 c;
function ret() external returns(MyUInt8) {
return wrap(0x1ff);
}
function f(MyUInt8 x) external returns(MyUInt8) {
return x;
}
function mem() external returns (MyUInt8[] memory) {
MyUInt8[] memory x = new MyUInt8[](2);
x[0] = wrap(0x1ff);
x[1] = wrap(0xff);
require(unwrap(x[0]) == unwrap(x[1]));
assembly {
mstore(add(x, 0x20), 0x1ff)
}
require(unwrap(x[0]) == unwrap(x[1]));
return x;
}
function stor() external returns (uint8, MyUInt8, uint8) {
a = 1;
c = 2;
b = wrap(0x1ff);
return (a, b, c);
}
}
// ====
// compileViaYul: also
// ----
// ret() -> 0xff
// f(uint8): 0x1ff -> FAILURE
// f(uint8): 0xff -> 0xff
// mem() -> 0x20, 2, 0xff, 0xff
// stor() -> 1, 0xff, 2

View File

@ -0,0 +1,44 @@
pragma abicoder v1;
type MyUInt8 is uint8;
// Note that this wraps from a uint256
function wrap(uint x) pure returns (MyUInt8 y) { assembly { y := x } }
function unwrap(MyUInt8 x) pure returns (uint8 y) { assembly { y := x } }
contract C {
uint8 a;
MyUInt8 b;
uint8 c;
function ret() external returns(MyUInt8) {
return wrap(0x1ff);
}
function f(MyUInt8 x) external returns(MyUInt8) {
return x;
}
function mem() external returns (MyUInt8[] memory) {
MyUInt8[] memory x = new MyUInt8[](2);
x[0] = wrap(0x1ff);
x[1] = wrap(0xff);
require(unwrap(x[0]) == unwrap(x[1]));
assembly {
mstore(add(x, 0x20), 0x1ff)
}
require(unwrap(x[0]) == unwrap(x[1]));
return x;
}
function stor() external returns (uint8, MyUInt8, uint8) {
a = 1;
c = 2;
b = wrap(0x1ff);
return (a, b, c);
}
}
// ====
// compileViaYul: false
// ----
// ret() -> 0xff
// f(uint8): 0x1ff -> 0xff
// f(uint8): 0xff -> 0xff
// mem() -> 0x20, 2, 0x01ff, 0xff
// stor() -> 1, 0xff, 2

View File

@ -0,0 +1,57 @@
pragma abicoder v2;
type MyUInt8 is uint8;
type MyInt8 is int8;
type MyUInt16 is uint16;
contract C {
function f(uint a) external returns(MyUInt8) {
return MyUInt8.wrap(uint8(a));
}
function g(uint a) external returns(MyInt8) {
return MyInt8.wrap(int8(int(a)));
}
function h(MyUInt8 a) external returns (MyInt8) {
return MyInt8.wrap(int8(MyUInt8.unwrap(a)));
}
function i(MyUInt8 a) external returns(MyUInt16) {
return MyUInt16.wrap(MyUInt8.unwrap(a));
}
function j(MyUInt8 a) external returns (uint) {
return MyUInt8.unwrap(a);
}
function k(MyUInt8 a) external returns (MyUInt16) {
return MyUInt16.wrap(MyUInt8.unwrap(a));
}
function m(MyUInt16 a) external returns (MyUInt8) {
return MyUInt8.wrap(uint8(MyUInt16.unwrap(a)));
}
}
// ====
// compileViaYul: also
// ----
// f(uint256): 1 -> 1
// f(uint256): 2 -> 2
// f(uint256): 257 -> 1
// g(uint256): 1 -> 1
// g(uint256): 2 -> 2
// g(uint256): 255 -> -1
// g(uint256): 257 -> 1
// h(uint8): 1 -> 1
// h(uint8): 2 -> 2
// h(uint8): 255 -> -1
// h(uint8): 257 -> FAILURE
// i(uint8): 250 -> 250
// j(uint8): 1 -> 1
// j(uint8): 2 -> 2
// j(uint8): 255 -> 0xff
// j(uint8): 257 -> FAILURE
// k(uint8): 1 -> 1
// k(uint8): 2 -> 2
// k(uint8): 255 -> 0xff
// k(uint8): 257 -> FAILURE
// m(uint16): 1 -> 1
// m(uint16): 2 -> 2
// m(uint16): 255 -> 0xff
// m(uint16): 257 -> 1

View File

@ -0,0 +1,57 @@
pragma abicoder v1;
type MyUInt8 is uint8;
type MyInt8 is int8;
type MyUInt16 is uint16;
contract C {
function f(uint a) external returns(MyUInt8) {
return MyUInt8.wrap(uint8(a));
}
function g(uint a) external returns(MyInt8) {
return MyInt8.wrap(int8(int(a)));
}
function h(MyUInt8 a) external returns (MyInt8) {
return MyInt8.wrap(int8(MyUInt8.unwrap(a)));
}
function i(MyUInt8 a) external returns(MyUInt16) {
return MyUInt16.wrap(MyUInt8.unwrap(a));
}
function j(MyUInt8 a) external returns (uint) {
return MyUInt8.unwrap(a);
}
function k(MyUInt8 a) external returns (MyUInt16) {
return MyUInt16.wrap(MyUInt8.unwrap(a));
}
function m(MyUInt16 a) external returns (MyUInt8) {
return MyUInt8.wrap(uint8(MyUInt16.unwrap(a)));
}
}
// ====
// compileViaYul: false
// ----
// f(uint256): 1 -> 1
// f(uint256): 2 -> 2
// f(uint256): 257 -> 1
// g(uint256): 1 -> 1
// g(uint256): 2 -> 2
// g(uint256): 255 -> -1
// g(uint256): 257 -> 1
// h(uint8): 1 -> 1
// h(uint8): 2 -> 2
// h(uint8): 255 -> -1
// h(uint8): 257 -> 1
// i(uint8): 250 -> 250
// j(uint8): 1 -> 1
// j(uint8): 2 -> 2
// j(uint8): 255 -> 0xff
// j(uint8): 257 -> 1
// k(uint8): 1 -> 1
// k(uint8): 2 -> 2
// k(uint8): 255 -> 0xff
// k(uint8): 257 -> 1
// m(uint16): 1 -> 1
// m(uint16): 2 -> 2
// m(uint16): 255 -> 0xff
// m(uint16): 257 -> 1

View File

@ -0,0 +1,147 @@
pragma abicoder v2;
// A rewrite of the test/libsolidity/semanticTests/various/erc20.sol, but using user defined value
// types.
// User defined type name. Indicating a type with 18 decimals.
type UFixed18 is uint256;
library FixedMath
{
function add(UFixed18 a, UFixed18 b) internal pure returns (UFixed18 c) {
return UFixed18.wrap(UFixed18.unwrap(a) + UFixed18.unwrap(b));
}
function sub(UFixed18 a, UFixed18 b) internal pure returns (UFixed18 c) {
return UFixed18.wrap(UFixed18.unwrap(a) - UFixed18.unwrap(b));
}
}
contract ERC20 {
using FixedMath for UFixed18;
event Transfer(address indexed from, address indexed to, UFixed18 value);
event Approval(address indexed owner, address indexed spender, UFixed18 value);
mapping (address => UFixed18) private _balances;
mapping (address => mapping (address => UFixed18)) private _allowances;
UFixed18 private _totalSupply;
constructor() {
_mint(msg.sender, UFixed18.wrap(20));
}
function totalSupply() public view returns (UFixed18) {
return _totalSupply;
}
function balanceOf(address owner) public view returns (UFixed18) {
return _balances[owner];
}
function allowance(address owner, address spender) public view returns (UFixed18) {
return _allowances[owner][spender];
}
function transfer(address to, UFixed18 value) public returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function approve(address spender, UFixed18 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, UFixed18 value) public returns (bool) {
_transfer(from, to, value);
// The subtraction here will revert on overflow.
_approve(from, msg.sender, _allowances[from][msg.sender].sub(value));
return true;
}
function increaseAllowance(address spender, UFixed18 addedValue) public returns (bool) {
// The addition here will revert on overflow.
_approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
return true;
}
function decreaseAllowance(address spender, UFixed18 subtractedValue) public returns (bool) {
// The subtraction here will revert on overflow.
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
return true;
}
function _transfer(address from, address to, UFixed18 value) internal {
require(to != address(0), "ERC20: transfer to the zero address");
// The subtraction and addition here will revert on overflow.
_balances[from] = _balances[from].sub(value);
_balances[to] = _balances[to].add(value);
emit Transfer(from, to, value);
}
function _mint(address account, UFixed18 value) internal {
require(account != address(0), "ERC20: mint to the zero address");
// The additions here will revert on overflow.
_totalSupply = _totalSupply.add(value);
_balances[account] = _balances[account].add(value);
emit Transfer(address(0), account, value);
}
function _burn(address account, UFixed18 value) internal {
require(account != address(0), "ERC20: burn from the zero address");
// The subtractions here will revert on overflow.
_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
emit Transfer(account, address(0), value);
}
function _approve(address owner, address spender, UFixed18 value) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _burnFrom(address account, UFixed18 value) internal {
_burn(account, value);
_approve(account, msg.sender, _allowances[account][msg.sender].sub(value));
}
}
// ====
// compileViaYul: also
// ----
// constructor()
// ~ emit Transfer(address,address,uint256): #0x00, #0x1212121212121212121212121212120000000012, 0x14
// gas irOptimized: 460447
// gas legacy: 861547
// gas legacyOptimized: 420959
// totalSupply() -> 20
// gas irOptimized: 23378
// gas legacy: 23653
// gas legacyOptimized: 23368
// transfer(address,uint256): 2, 5 -> true
// ~ emit Transfer(address,address,uint256): #0x1212121212121212121212121212120000000012, #0x02, 0x05
// gas irOptimized: 48514
// gas legacy: 49572
// gas legacyOptimized: 48575
// decreaseAllowance(address,uint256): 2, 0 -> true
// ~ emit Approval(address,address,uint256): #0x1212121212121212121212121212120000000012, #0x02, 0x00
// gas irOptimized: 26316
// gas legacy: 27204
// gas legacyOptimized: 26317
// decreaseAllowance(address,uint256): 2, 1 -> FAILURE, hex"4e487b71", 0x11
// gas irOptimized: 24040
// gas legacy: 24506
// gas legacyOptimized: 24077
// transfer(address,uint256): 2, 14 -> true
// ~ emit Transfer(address,address,uint256): #0x1212121212121212121212121212120000000012, #0x02, 0x0e
// gas irOptimized: 28614
// gas legacy: 29672
// gas legacyOptimized: 28675
// transfer(address,uint256): 2, 2 -> FAILURE, hex"4e487b71", 0x11
// gas irOptimized: 24052
// gas legacy: 24492
// gas legacyOptimized: 24074

View File

@ -0,0 +1,42 @@
// Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type.
type UFixed256x18 is uint256;
/// A minimal library to do fixed point operations on UFixed256x18.
library FixedMath {
/// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked arithmetic on
/// uint256.
function add(UFixed256x18 a, UFixed256x18 b) internal returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
}
/// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked arithmetic on
/// uint256.
function mul(UFixed256x18 a, uint256 b) internal returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
}
/// Truncates UFixed256x18 to the nearest uint256 number.
function truncate(UFixed256x18 a) internal returns (uint256) {
return UFixed256x18.unwrap(a) / 10**18;
}
}
contract TestFixedMath {
function add(UFixed256x18 a, UFixed256x18 b) external returns (UFixed256x18) {
return FixedMath.add(a, b);
}
function mul(UFixed256x18 a, uint256 b) external returns (UFixed256x18) {
return FixedMath.mul(a, b);
}
function truncate(UFixed256x18 a) external returns (uint256) {
return FixedMath.truncate(a);
}
}
// ====
// compileViaYul: also
// ----
// add(uint256,uint256): 0, 0 -> 0
// add(uint256,uint256): 25, 45 -> 0x46
// add(uint256,uint256): 115792089237316195423570985008687907853269984665640564039457584007913129639935, 10 -> FAILURE, hex"4e487b71", 0x11
// mul(uint256,uint256): 340282366920938463463374607431768211456, 45671926166590716193865151022383844364247891968 -> FAILURE, hex"4e487b71", 0x11
// mul(uint256,uint256): 340282366920938463463374607431768211456, 20 -> 6805647338418769269267492148635364229120
// truncate(uint256): 11579208923731619542357098500868790785326998665640564039457584007913129639930 -> 11579208923731619542357098500868790785326998665640564039457
// truncate(uint256): 115792089237316195423570985008687907853269984665640564039457584007913129639935 -> 115792089237316195423570985008687907853269984665640564039457

View File

@ -0,0 +1,32 @@
// Implementation of OpenZepplin's
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
// using user defined value types.
contract Ownable {
type Owner is address;
Owner public owner = Owner.wrap(msg.sender);
error OnlyOwner();
modifier onlyOwner() {
if (Owner.unwrap(owner) != msg.sender)
revert OnlyOwner();
_;
}
event OwnershipTransferred(Owner indexed previousOwner, Owner indexed newOwner);
function setOwner(Owner newOwner) onlyOwner external {
emit OwnershipTransferred({previousOwner: owner, newOwner: newOwner});
owner = newOwner;
}
function renounceOwnership() onlyOwner external {
owner = Owner.wrap(address(0));
}
}
// ====
// compileViaYul: also
// ----
// owner() -> 0x1212121212121212121212121212120000000012
// setOwner(address): 0x1212121212121212121212121212120000000012 ->
// ~ emit OwnershipTransferred(address,address): #0x1212121212121212121212121212120000000012, #0x1212121212121212121212121212120000000012
// renounceOwnership() ->
// owner() -> 0
// setOwner(address): 0x1212121212121212121212121212120000000012 -> FAILURE, hex"5fc483c5"

View File

@ -0,0 +1,42 @@
pragma abicoder v2;
type MyAddress is address;
contract C {
function id(MyAddress a) external returns (MyAddress b) {
b = a;
}
function unwrap_assembly(MyAddress a) external returns (address b) {
assembly { b := a }
}
function wrap_assembly(address a) external returns (MyAddress b) {
assembly { b := a }
}
function unwrap(MyAddress a) external returns (address b) {
b = MyAddress.unwrap(a);
}
function wrap(address a) external returns (MyAddress b) {
b = MyAddress.wrap(a);
}
}
// ====
// compileViaYul: also
// ----
// id(address): 5 -> 5
// id(address): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff
// id(address): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> FAILURE
// unwrap(address): 5 -> 5
// unwrap(address): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff
// unwrap(address): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> FAILURE
// wrap(address): 5 -> 5
// wrap(address): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff
// wrap(address): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> FAILURE
// unwrap_assembly(address): 5 -> 5
// unwrap_assembly(address): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff
// unwrap_assembly(address): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> FAILURE
// wrap_assembly(address): 5 -> 5
// wrap_assembly(address): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff
// wrap_assembly(address): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> FAILURE

View File

@ -0,0 +1,14 @@
type MyInt is int;
contract C {
function f() external pure returns (MyInt a) {
}
function g() external pure returns (MyInt b, MyInt c) {
b = MyInt.wrap(int(1));
c = MyInt.wrap(1);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0
// g() -> 1, 1

View File

@ -0,0 +1,11 @@
type MyAddress is address;
contract C {
function f() pure public {
MyAddress.wrap;
MyAddress.unwrap;
}
}
// ====
// compileViaYul: also
// ----
// f() ->

View File

@ -0,0 +1,5 @@
type MyInt is uint;
contract C {
type MyAddress is address;
}

View File

@ -0,0 +1,3 @@
type(MyInt) is uint256;
// ----
// ParserError 2314: (4-5): Expected identifier but got '('

View File

@ -0,0 +1,6 @@
function f() {
type(uint).max;
type MyInt is int;
}
// ----
// ParserError 2314: (44-49): Expected ';' but got identifier

View File

@ -0,0 +1,12 @@
type MyAddress is address;
type MyAddressPayable is address payable;
type MyInt is int;
type MyUInt is uint;
type MyInt128 is int128;
type MyUInt128 is uint128;
// TODO add fixed point type, when it's implemented
type MyFixedBytes32 is bytes32;
type MyFixedBytes1 is bytes1;
type MyBool is bool;
/// test to see if having NatSpec causes issues
type redundantNatSpec is bytes2;

View File

@ -0,0 +1,6 @@
type MyAddress is address;
function f() {
MyAddress a = MyAddress(5, 2);
}
// ----
// TypeError 2558: (60-75): Exactly one argument expected for explicit type conversion.

View File

@ -0,0 +1,4 @@
type MyInt is int;
function f(MyInt a) pure returns (MyInt b) {
b = MyInt(a);
}

View File

@ -0,0 +1,24 @@
type MyUInt is uint;
type MyAddress is address;
type AnotherUInt is uint;
function f() pure {
MyUInt(-1);
MyAddress(-1);
MyUInt(5);
MyAddress(address(5));
AnotherUInt(MyUInt.wrap(5));
MyUInt(AnotherUInt.wrap(10));
AnotherUInt.unwrap(MyUInt.wrap(5));
MyUInt.unwrap(AnotherUInt.wrap(10));
}
// ----
// TypeError 9640: (99-109): Explicit type conversion not allowed from "int_const -1" to "user defined type MyUInt".
// TypeError 9640: (115-128): Explicit type conversion not allowed from "int_const -1" to "user defined type MyAddress".
// TypeError 9640: (134-143): Explicit type conversion not allowed from "int_const 5" to "user defined type MyUInt".
// TypeError 9640: (149-170): Explicit type conversion not allowed from "address" to "user defined type MyAddress".
// TypeError 9640: (177-204): Explicit type conversion not allowed from "user defined type MyUInt" to "user defined type AnotherUInt".
// TypeError 9640: (210-238): Explicit type conversion not allowed from "user defined type AnotherUInt" to "user defined type MyUInt".
// TypeError 9553: (263-277): Invalid type for argument in function call. Invalid implicit conversion from user defined type MyUInt to user defined type AnotherUInt requested.
// TypeError 9553: (298-318): Invalid type for argument in function call. Invalid implicit conversion from user defined type AnotherUInt to user defined type MyUInt requested.

View File

@ -0,0 +1,8 @@
type MyUint is uint;
type MyAddress is address;
function f() pure {
MyUint.wrap(5);
MyAddress.wrap(address(5));
}
// ----

View File

@ -0,0 +1,9 @@
type MyAddress is address;
type MyUInt8 is uint8;
function f() pure {
MyAddress.wrap(address(5));
MyUInt8.wrap(5);
MyUInt8.wrap(50);
}
// ----

View File

@ -0,0 +1,4 @@
function f(MyIntB x) pure {}
type MyIntB is MyIntB;
// ----
// TypeError 8657: (44-50): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,14 @@
type MyInt is uint;
type MyAddress is address;
function f() pure {
MyInt a;
MyInt b = a;
MyAddress c;
MyAddress d = c;
b;
d;
}
function g(MyInt a) pure returns (MyInt) {
return a;
}

View File

@ -0,0 +1,22 @@
type MyInt is int;
function f(int a) pure returns (int) {
MyInt b = a;
int c = b;
address d = b;
MyInt e = d;
uint x = 0;
MyInt y = MyInt(x);
return e;
}
// ----
// TypeError 9574: (62-73): Type int256 is not implicitly convertible to expected type user defined type MyInt.
// TypeError 9574: (80-89): Type user defined type MyInt is not implicitly convertible to expected type int256.
// TypeError 9574: (96-109): Type user defined type MyInt is not implicitly convertible to expected type address.
// TypeError 9574: (116-127): Type address is not implicitly convertible to expected type user defined type MyInt.
// TypeError 9640: (160-168): Explicit type conversion not allowed from "uint256" to "user defined type MyInt".
// TypeError 6359: (182-183): Return argument type user defined type MyInt is not implicitly convertible to expected type (type of first return variable) int256.

View File

@ -0,0 +1,7 @@
type MyInt is int;
function f() pure {
(MyInt).wrap;
(MyInt).wrap(5);
(MyInt).unwrap;
(MyInt).unwrap(MyInt.wrap(5));
}

View File

@ -0,0 +1,6 @@
type MyAddress is address;
contract C {
type MyAddress is address;
}
// ----
// Warning 2519: (44-70): This declaration shadows an existing declaration.

View File

@ -0,0 +1,9 @@
type MyInt is int;
type MyInt is address;
contract C {
type MyAddress is address;
type MyAddress is address;
}
// ----
// DeclarationError 2333: (19-41): Identifier already declared.
// DeclarationError 2333: (90-116): Identifier already declared.

View File

@ -0,0 +1,3 @@
type MyBytes is bytes;
// ----
// TypeError 8129: (0-22): The underlying type of the user defined value type "MyBytes" is not a value type.

View File

@ -0,0 +1,4 @@
contract C {}
type MyContract is C;
// ----
// TypeError 8657: (33-34): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,3 @@
type MyFunction is function(uint) returns (uint);
// ----
// TypeError 8657: (19-49): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,3 @@
type MyInt is mapping(uint => uint);
// ----
// TypeError 8657: (14-35): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,3 @@
type MyString is string;
// ----
// TypeError 8129: (0-24): The underlying type of the user defined value type "MyString" is not a value type.

View File

@ -0,0 +1,7 @@
struct S {uint x;}
contract C {
type MyType is S;
}
// ----
// TypeError 8657: (52-53): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,19 @@
type MyAddress is address;
interface I {}
contract C {
function f(MyAddress a) external {
}
function f(address a) external {
}
}
contract D {
function g(MyAddress a) external {
}
}
contract E is D {
function g(I a) external {
}
}
// ----
// TypeError 9914: (104-142): Function overload clash during conversion to external types for arguments.
// TypeError 9914: (162-202): Function overload clash during conversion to external types for arguments.

View File

@ -0,0 +1,4 @@
type MyInt1 is MyInt2;
type MyInt2 is MyInt1;
// ----
// TypeError 8657: (15-21): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,3 @@
type MyFunction is function(MyFunction) external returns(MyFunction);
// ----
// TypeError 8657: (19-69): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,13 @@
library L {
type MyInt is int;
}
contract C {
L.MyInt a;
type MyInt is int8;
}
contract D is C {
C.MyInt b;
L.MyInt c;
}

View File

@ -0,0 +1,7 @@
type MyInt is int;
type MyInt is int;
type MyAddress is address;
type MyAddress is uint;
// ----
// DeclarationError 2333: (19-37): Identifier already declared.
// DeclarationError 2333: (65-88): Identifier already declared.

View File

@ -0,0 +1,3 @@
type MyInt is MyInt;
// ----
// TypeError 8657: (14-19): The underlying type for a user defined value type has to be an elementary value type.

View File

@ -0,0 +1,8 @@
type MyInt is int;
function test() pure {
function (MyInt) returns (int) f = MyInt.unwrap;
function (int) returns (MyInt) g = MyInt.wrap;
}
// ----
// TypeError 9574: (46-93): Type function (user defined type MyInt) pure returns (int256) is not implicitly convertible to expected type function (user defined type MyInt) returns (int256). Special functions can not be converted to function types.
// TypeError 9574: (99-144): Type function (int256) pure returns (user defined type MyInt) is not implicitly convertible to expected type function (int256) returns (user defined type MyInt). Special functions can not be converted to function types.

View File

@ -0,0 +1,16 @@
type MyInt is int;
function f() {
MyInt.wrap(5, 6, 7);
MyInt.wrap({test: 5});
MyInt.wrap();
MyInt.unwrap(5);
MyInt.unwrap({test: 5});
MyInt.unwrap(MyInt.wrap(1), MyInt.wrap(2));
}
// ----
// TypeError 6160: (38-57): Wrong argument count for function call: 3 arguments given but expected 1.
// TypeError 4974: (63-84): Named argument "test" does not match function declaration.
// TypeError 6160: (90-102): Wrong argument count for function call: 0 arguments given but expected 1.
// TypeError 9553: (121-122): Invalid type for argument in function call. Invalid implicit conversion from int_const 5 to user defined type MyInt requested.
// TypeError 4974: (129-152): Named argument "test" does not match function declaration.
// TypeError 6160: (158-200): Wrong argument count for function call: 2 arguments given but expected 1.