Introduce FunctionKind::Declaration and allow accessing function signatures via contract name.

This commit is contained in:
Daniel Kirchner 2020-01-07 11:42:37 +01:00
parent 17158995b5
commit 9535c0f520
18 changed files with 202 additions and 34 deletions

View File

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

View File

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

View File

@ -203,7 +203,7 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
vector<FunctionTypePointer> 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));
@ -333,7 +333,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 {};
}
@ -349,7 +349,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);
}
}
@ -360,7 +360,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

View File

@ -414,9 +414,9 @@ ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, Data
return static_cast<ReferenceType const*>(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<FunctionType>(_function, _isInternal);
return createAndGet<FunctionType>(_function, _kind);
}
FunctionType const* TypeProvider::function(VariableDeclaration const& _varDecl)

View File

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

View File

@ -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<VariableDeclaration> 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<FunctionType const&>(_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<FunctionDefinition const*>(m_declaration);
solAssert(functionDefinition, "");
auto const* contract = dynamic_cast<ContractDefinition const*>(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())

View File

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

View File

@ -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<FunctionType const*>(_memberAccess.expression().annotation().type);
functionType && member == "selector"
)
if (auto const* expr = dynamic_cast<MemberAccess const*>(&_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 const*>(&_memberAccess.expression()))
if (auto const* exprInt = dynamic_cast<Identifier const*>(&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 (

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
contract A {
function f() external {}
}
contract B {
function g() external pure {
A.f.selector;
}
}

View File

@ -0,0 +1,9 @@
interface I {
function f() external;
}
contract B {
function g() external pure {
I.f.selector;
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
contract A {
function f() public {}
}
contract B {
function g() external pure {
A.f.selector;
}
}