diff --git a/Changelog.md b/Changelog.md index 47df79890..21e0e91f5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.6.2 (unreleased) Language Features: + * Allow accessing external functions via contract and interface names to obtain their selector. Compiler Features: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index eb1b6ddcc..5d8c7b85f 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1691,6 +1691,16 @@ void TypeChecker::typeCheckFunctionCall( solAssert(!!_functionType, ""); solAssert(_functionType->kind() != FunctionType::Kind::ABIDecode, ""); + if (_functionType->kind() == FunctionType::Kind::Declaration) + { + m_errorReporter.typeError( + _functionCall.location(), + "Cannot call function via contract name." + ); + return; + } + + // Check for unsupported use of bare static call if ( _functionType->kind() == FunctionType::Kind::BareStaticCall && diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index f11725469..9f8e1471d 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -199,7 +199,7 @@ vector, FunctionTypePointer>> const& ContractDefinition: vector functions; for (FunctionDefinition const* f: contract->definedFunctions()) if (f->isPartOfExternalInterface()) - functions.push_back(TypeProvider::function(*f, false)); + functions.push_back(TypeProvider::function(*f, FunctionType::Kind::External)); for (VariableDeclaration const* v: contract->stateVariables()) if (v->isPartOfExternalInterface()) functions.push_back(TypeProvider::function(*v)); @@ -311,7 +311,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const case Visibility::Private: case Visibility::Internal: case Visibility::Public: - return TypeProvider::function(*this, _internal); + return TypeProvider::function(*this, FunctionType::Kind::Internal); case Visibility::External: return {}; } @@ -327,7 +327,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const return {}; case Visibility::Public: case Visibility::External: - return TypeProvider::function(*this, _internal); + return TypeProvider::function(*this, FunctionType::Kind::External); } } @@ -338,7 +338,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const TypePointer FunctionDefinition::type() const { solAssert(visibility() != Visibility::External, ""); - return TypeProvider::function(*this); + return TypeProvider::function(*this, FunctionType::Kind::Internal); } string FunctionDefinition::externalSignature() const diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 749a77655..4cb3e781b 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -414,9 +414,9 @@ ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, Data return static_cast(instance().m_generalTypes.back().get()); } -FunctionType const* TypeProvider::function(FunctionDefinition const& _function, bool _isInternal) +FunctionType const* TypeProvider::function(FunctionDefinition const& _function, FunctionType::Kind _kind) { - return createAndGet(_function, _isInternal); + return createAndGet(_function, _kind); } FunctionType const* TypeProvider::function(VariableDeclaration const& _varDecl) diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index 1932ae919..cef182aed 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -120,8 +120,8 @@ public: return _type; } - /// @returns the internally-facing or externally-facing type of a function. - static FunctionType const* function(FunctionDefinition const& _function, bool _isInternal = true); + /// @returns the internally-facing or externally-facing type of a function or the type of a function declaration. + static FunctionType const* function(FunctionDefinition const& _function, FunctionType::Kind _kind = FunctionType::Kind::Declaration); /// @returns the accessor function type of a state variable. static FunctionType const* function(VariableDeclaration const& _varDecl); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 4f596468b..f3f575a18 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -362,7 +362,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition seenFunctions.insert(function); if (function->parameters().empty()) continue; - FunctionTypePointer fun = FunctionType(*function, false).asCallableFunction(true, true); + FunctionTypePointer fun = FunctionType(*function, FunctionType::Kind::External).asCallableFunction(true, true); if (_type.isImplicitlyConvertibleTo(*fun->selfType())) members.emplace_back(function->name(), fun, function); } @@ -1933,7 +1933,7 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con if (!function->isVisibleInDerivedContracts() || !function->isImplemented()) continue; - auto functionType = TypeProvider::function(*function, true); + auto functionType = TypeProvider::function(*function, FunctionType::Kind::Internal); bool functionWithEqualArgumentsFound = false; for (auto const& member: members) { @@ -2465,12 +2465,16 @@ TypePointer TupleType::closestTemporaryType(Type const* _targetType) const return TypeProvider::tuple(move(tempComponents)); } -FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): - m_kind(_isInternal ? Kind::Internal : Kind::External), +FunctionType::FunctionType(FunctionDefinition const& _function, Kind _kind): + m_kind(_kind), m_stateMutability(_function.stateMutability()), m_declaration(&_function) { - if (_isInternal && m_stateMutability == StateMutability::Payable) + solAssert( + _kind == Kind::Internal || _kind == Kind::External || _kind == Kind::Declaration, + "Only internal or external function types or function declaration types can be created from function definitions." + ); + if (_kind == Kind::Internal && m_stateMutability == StateMutability::Payable) m_stateMutability = StateMutability::NonPayable; for (ASTPointer const& var: _function.parameters()) @@ -2689,6 +2693,7 @@ string FunctionType::richIdentifier() const string id = "t_function_"; switch (m_kind) { + case Kind::Declaration: id += "declaration"; break; case Kind::Internal: id += "internal"; break; case Kind::External: id += "external"; break; case Kind::DelegateCall: id += "delegatecall"; break; @@ -2755,7 +2760,12 @@ bool FunctionType::operator==(Type const& _other) const BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return _convertTo.category() == category(); + if (_convertTo.category() == category()) + { + auto const& convertToType = dynamic_cast(_convertTo); + return (m_kind == FunctionType::Kind::Declaration) == (convertToType.kind() == FunctionType::Kind::Declaration); + } + return false; } BoolResult FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -2808,7 +2818,18 @@ string FunctionType::canonicalName() const string FunctionType::toString(bool _short) const { - string name = "function ("; + string name = "function "; + if (m_kind == Kind::Declaration) + { + auto const* functionDefinition = dynamic_cast(m_declaration); + solAssert(functionDefinition, ""); + auto const* contract = dynamic_cast(functionDefinition->scope()); + solAssert(contract, ""); + name += contract->annotation().canonicalName; + name += '.'; + name += functionDefinition->name(); + } + name += '('; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); name += ")"; @@ -2940,6 +2961,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { switch (m_kind) { + case Kind::Declaration: + return {{"selector", TypeProvider::fixedBytes(4)}}; case Kind::External: case Kind::Creation: case Kind::BareCall: @@ -3146,6 +3169,7 @@ string FunctionType::externalSignature() const case Kind::External: case Kind::DelegateCall: case Kind::Event: + case Kind::Declaration: break; default: solAssert(false, "Invalid function type for requesting external signature."); @@ -3210,6 +3234,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const { + solAssert(m_kind != Kind::Declaration, ""); return TypeProvider::function( m_parameterTypes, m_returnParameterTypes, @@ -3387,14 +3412,6 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current auto const& currentBases = _currentScope->annotation().linearizedBaseContracts; isBase = (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end()); } - if (contract.isLibrary()) - for (FunctionDefinition const* function: contract.definedFunctions()) - if (function->isVisibleAsLibraryMember()) - members.emplace_back( - function->name(), - FunctionType(*function).asCallableFunction(true), - function - ); if (isBase) { // We are accessing the type of a base contract, so add all public and protected @@ -3404,6 +3421,17 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current } else { + bool inLibrary = contract.isLibrary(); + for (FunctionDefinition const* function: contract.definedFunctions()) + if ( + (inLibrary && function->isVisibleAsLibraryMember()) || + (!inLibrary && function->isPartOfExternalInterface()) + ) + members.emplace_back( + function->name(), + FunctionType(*function).asCallableFunction(inLibrary), + function + ); for (auto const& stru: contract.definedStructs()) members.emplace_back(stru->name(), stru->type(), stru); for (auto const& enu: contract.definedEnums()) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 3859e847f..2c617efdc 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1051,11 +1051,16 @@ public: ABIEncodeWithSignature, ABIDecode, GasLeft, ///< gasleft() - MetaType ///< type(...) + MetaType, ///< type(...) + /// Refers to a function declaration without calling context + /// (i.e. when accessed directly via the name of the containing contract). + /// Cannot be called. + Declaration }; /// Creates the type of a function. - explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); + /// @arg _kind must be Kind::Internal, Kind::External or Kind::Declaration. + explicit FunctionType(FunctionDefinition const& _function, Kind _kind = Kind::Declaration); /// Creates the accessor function type of a state variable. explicit FunctionType(VariableDeclaration const& _varDecl); /// Creates the function type of an event. @@ -1066,7 +1071,7 @@ public: FunctionType( strings const& _parameterTypes, strings const& _returnParameterTypes, - Kind _kind = Kind::Internal, + Kind _kind, bool _arbitraryParameters = false, StateMutability _stateMutability = StateMutability::NonPayable ): FunctionType( diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 921e2ecff..2e1ef44dc 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -550,6 +550,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(function.kind() == FunctionType::Kind::DelegateCall || function.kind() == FunctionType::Kind::Internal, ""); switch (function.kind()) { + case FunctionType::Kind::Declaration: + solAssert(false, "Attempted to generate code for calling a function definition."); + break; case FunctionType::Kind::Internal: { // Calling convention: Caller pushes return address and arguments @@ -1289,14 +1292,22 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) _memberAccess.expression().accept(*this); return false; } - // Another special case for `this.f.selector` which does not need the address. - // There are other uses of `.selector` which do need the address, but we want this - // specific use to be a pure expression. + // Another special case for `this.f.selector` and for ``C.f.selector`` which do not need the address. + // There are other uses of `.selector` which do need the address, but we want these + // specific uses to be pure expressions. if ( - _memberAccess.expression().annotation().type->category() == Type::Category::Function && - member == "selector" + auto const* functionType = dynamic_cast(_memberAccess.expression().annotation().type); + functionType && member == "selector" ) - if (auto const* expr = dynamic_cast(&_memberAccess.expression())) + { + if (functionType->kind() == FunctionType::Kind::Declaration) + { + m_context << functionType->externalIdentifier(); + /// need to store it as bytes4 + utils().leftShiftNumberOnStack(224); + return false; + } + else if (auto const* expr = dynamic_cast(&_memberAccess.expression())) if (auto const* exprInt = dynamic_cast(&expr->expression())) if (exprInt->name() == "this") if (Declaration const* declaration = expr->annotation().referencedDeclaration) @@ -1313,6 +1324,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) utils().leftShiftNumberOnStack(224); return false; } + } // Another special case for `address(this).balance`. Post-Istanbul, we can use the selfbalance // opcode. if ( diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 7ab3861cd..56e2ecbde 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -75,7 +75,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) } if (_contractDef.constructor()) { - FunctionType constrType(*_contractDef.constructor(), false); + FunctionType constrType(*_contractDef.constructor()); FunctionType const* externalFunctionType = constrType.interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; @@ -92,7 +92,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) for (auto const* fallbackOrReceive: {_contractDef.fallbackFunction(), _contractDef.receiveFunction()}) if (fallbackOrReceive) { - FunctionType const* externalFunctionType = FunctionType(*fallbackOrReceive, false).interfaceFunctionType(); + auto const* externalFunctionType = FunctionType(*fallbackOrReceive).interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = TokenTraits::toString(fallbackOrReceive->kind()); diff --git a/test/libsolidity/semanticTests/functionSelector/function_selector_via_contract_name.sol b/test/libsolidity/semanticTests/functionSelector/function_selector_via_contract_name.sol new file mode 100644 index 000000000..0a9e6d3ff --- /dev/null +++ b/test/libsolidity/semanticTests/functionSelector/function_selector_via_contract_name.sol @@ -0,0 +1,20 @@ +contract A { + function f() external {} + function g(uint256) external {} +} +contract B { + function f() external returns (uint256) {} + function g(uint256) external returns (uint256) {} +} +contract C { + function test1() external returns(bytes4, bytes4, bytes4, bytes4) { + return (A.f.selector, A.g.selector, B.f.selector, B.g.selector); + } + function test2() external returns(bytes4, bytes4, bytes4, bytes4) { + A a; B b; + return (a.f.selector, a.g.selector, b.f.selector, b.g.selector); + } +} +// ---- +// test1() -> left(0x26121ff0), left(0xe420264a), left(0x26121ff0), left(0xe420264a) +// test2() -> left(0x26121ff0), left(0xe420264a), left(0x26121ff0), left(0xe420264a) diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/assign_function_via_contract_name_to_var.sol b/test/libsolidity/syntaxTests/types/function_types/definition/assign_function_via_contract_name_to_var.sol new file mode 100644 index 000000000..546779548 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/assign_function_via_contract_name_to_var.sol @@ -0,0 +1,14 @@ +contract A { + function f() external {} + function g() external pure {} +} + +contract B { + function h() external { + function() external f = A.f; + function() external pure g = A.g; + } +} +// ---- +// TypeError: (128-155): Type function A.f() is not implicitly convertible to expected type function () external. +// TypeError: (165-197): Type function A.g() pure is not implicitly convertible to expected type function () pure external. diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/call_function_via_contract_name.sol b/test/libsolidity/syntaxTests/types/function_types/definition/call_function_via_contract_name.sol new file mode 100644 index 000000000..12b49007e --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/call_function_via_contract_name.sol @@ -0,0 +1,17 @@ +contract A { + function f() external {} + function g() external pure {} + function h() public pure {} +} + +contract B { + function i() external { + A.f(); + A.g(); + A.h(); // might be allowed in the future + } +} +// ---- +// TypeError: (160-165): Cannot call function via contract name. +// TypeError: (175-180): Cannot call function via contract name. +// TypeError: (190-195): Cannot call function via contract name. diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_contract_name.sol b/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_contract_name.sol new file mode 100644 index 000000000..ec53a0f60 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_contract_name.sol @@ -0,0 +1,9 @@ +contract A { + function f() external {} +} + +contract B { + function g() external pure { + A.f.selector; + } +} diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_interface_name.sol b/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_interface_name.sol new file mode 100644 index 000000000..e733b6701 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_interface_name.sol @@ -0,0 +1,9 @@ +interface I { + function f() external; +} + +contract B { + function g() external pure { + I.f.selector; + } +} diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_internal.sol b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_internal.sol new file mode 100644 index 000000000..0c2247fdb --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_internal.sol @@ -0,0 +1,11 @@ +contract A { + function f() internal {} +} + +contract B { + function g() external { + A.f; + } +} +// ---- +// TypeError: (94-97): Member "f" not found or not visible after argument-dependent lookup in type(contract A). diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_overloaded.sol b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_overloaded.sol new file mode 100644 index 000000000..23bed07d0 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_overloaded.sol @@ -0,0 +1,12 @@ +contract A { + function f() external {} + function f(uint256) external {} +} + +contract B { + function g() external { + A.f; + } +} +// ---- +// TypeError: (130-133): Member "f" not unique after argument-dependent lookup in type(contract A). diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_private.sol b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_private.sol new file mode 100644 index 000000000..eec0f69ba --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_private.sol @@ -0,0 +1,11 @@ +contract A { + function f() private {} +} + +contract B { + function g() external { + A.f; + } +} +// ---- +// TypeError: (93-96): Member "f" not found or not visible after argument-dependent lookup in type(contract A). diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_public.sol b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_public.sol new file mode 100644 index 000000000..28d1ff6d1 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_public.sol @@ -0,0 +1,9 @@ +contract A { + function f() public {} +} + +contract B { + function g() external pure { + A.f.selector; + } +}