mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1688 from ethereum/interface-keyword
Support strict interface contracts
This commit is contained in:
commit
6fb27dee63
@ -1,5 +1,8 @@
|
||||
### 0.4.11 (unreleased)
|
||||
|
||||
Features:
|
||||
* Support ``interface`` contracts.
|
||||
|
||||
### 0.4.10 (2017-03-15)
|
||||
|
||||
Features:
|
||||
|
@ -922,6 +922,35 @@ Such contracts cannot be compiled (even if they contain implemented functions al
|
||||
|
||||
If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract.
|
||||
|
||||
.. index:: ! contract;interface, ! interface contract
|
||||
|
||||
**********
|
||||
Interfaces
|
||||
**********
|
||||
|
||||
Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions:
|
||||
|
||||
#. Cannot inherit other contracts or interfaces.
|
||||
#. Cannot define constructor.
|
||||
#. Cannot define variables.
|
||||
#. Cannot define structs.
|
||||
#. Cannot define enums.
|
||||
|
||||
Some of these restrictions might be lifted in the future.
|
||||
|
||||
Interfaces are basically limited to what the Contract ABI can represent and the conversion between the ABI and
|
||||
an Interface should be possible without any information loss.
|
||||
|
||||
Interfaces are denoted by their own keyword:
|
||||
|
||||
::
|
||||
|
||||
interface Token {
|
||||
function transfer(address recipient, uint amount);
|
||||
}
|
||||
|
||||
Contracts can inherit interfaces as they would inherit other contracts.
|
||||
|
||||
.. index:: ! library, callcode, delegatecall
|
||||
|
||||
.. _libraries:
|
||||
|
@ -64,8 +64,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
m_scope = &_contract;
|
||||
|
||||
// We force our own visiting order here.
|
||||
//@TODO structs will be visited again below, but it is probably fine.
|
||||
// We force our own visiting order here. The structs have to be excluded below.
|
||||
set<ASTNode const*> visited;
|
||||
for (auto const& s: _contract.definedStructs())
|
||||
visited.insert(s);
|
||||
ASTNode::listAccept(_contract.definedStructs(), *this);
|
||||
ASTNode::listAccept(_contract.baseContracts(), *this);
|
||||
|
||||
@ -113,7 +115,9 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
|
||||
_contract.annotation().isFullyImplemented = false;
|
||||
}
|
||||
|
||||
ASTNode::listAccept(_contract.subNodes(), *this);
|
||||
for (auto const& n: _contract.subNodes())
|
||||
if (!visited.count(n.get()))
|
||||
n->accept(*this);
|
||||
|
||||
checkContractExternalTypeClashes(_contract);
|
||||
// check for hash collisions in function signatures
|
||||
@ -354,6 +358,9 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
|
||||
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
|
||||
solAssert(base, "Base contract not available.");
|
||||
|
||||
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
|
||||
typeError(_inheritance.location(), "Interfaces cannot inherit.");
|
||||
|
||||
if (base->isLibrary())
|
||||
typeError(_inheritance.location(), "Libraries cannot be inherited from.");
|
||||
|
||||
@ -396,6 +403,9 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||
|
||||
bool TypeChecker::visit(StructDefinition const& _struct)
|
||||
{
|
||||
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
|
||||
typeError(_struct.location(), "Structs cannot be defined in interfaces.");
|
||||
|
||||
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
||||
if (!type(*member)->canBeStored())
|
||||
typeError(member->location(), "Type cannot be used in struct.");
|
||||
@ -451,6 +461,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts :
|
||||
vector<ContractDefinition const*>()
|
||||
);
|
||||
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
|
||||
{
|
||||
if (_function.isImplemented())
|
||||
typeError(_function.location(), "Functions in interfaces cannot have an implementation.");
|
||||
if (_function.visibility() < FunctionDefinition::Visibility::Public)
|
||||
typeError(_function.location(), "Functions in interfaces cannot be internal or private.");
|
||||
if (_function.isConstructor())
|
||||
typeError(_function.location(), "Constructor cannot be defined in interfaces.");
|
||||
}
|
||||
if (_function.isImplemented())
|
||||
_function.body().accept(*this);
|
||||
return false;
|
||||
@ -458,6 +477,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
|
||||
bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
{
|
||||
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
|
||||
typeError(_variable.location(), "Variables cannot be declared in interfaces.");
|
||||
|
||||
// Variables can be declared without type (with "var"), in which case the first assignment
|
||||
// sets the type.
|
||||
// Note that assignments before the first declaration are legal because of the special scoping
|
||||
@ -504,6 +526,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(EnumDefinition const& _enum)
|
||||
{
|
||||
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
|
||||
typeError(_enum.location(), "Enumerable cannot be declared in interfaces.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void TypeChecker::visitManually(
|
||||
ModifierInvocation const& _modifier,
|
||||
vector<ContractDefinition const*> const& _bases
|
||||
|
@ -83,6 +83,7 @@ private:
|
||||
virtual bool visit(StructDefinition const& _struct) override;
|
||||
virtual bool visit(FunctionDefinition const& _function) override;
|
||||
virtual bool visit(VariableDeclaration const& _variable) override;
|
||||
virtual bool visit(EnumDefinition const& _enum) override;
|
||||
/// We need to do this manually because we want to pass the bases of the current contract in
|
||||
/// case this is a base constructor call.
|
||||
void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases);
|
||||
|
@ -316,19 +316,21 @@ protected:
|
||||
class ContractDefinition: public Declaration, public Documented
|
||||
{
|
||||
public:
|
||||
enum class ContractKind { Interface, Contract, Library };
|
||||
|
||||
ContractDefinition(
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
ASTPointer<ASTString> const& _documentation,
|
||||
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
|
||||
std::vector<ASTPointer<ASTNode>> const& _subNodes,
|
||||
bool _isLibrary
|
||||
ContractKind _contractKind = ContractKind::Contract
|
||||
):
|
||||
Declaration(_location, _name),
|
||||
Documented(_documentation),
|
||||
m_baseContracts(_baseContracts),
|
||||
m_subNodes(_subNodes),
|
||||
m_isLibrary(_isLibrary)
|
||||
m_contractKind(_contractKind)
|
||||
{}
|
||||
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
@ -344,7 +346,7 @@ public:
|
||||
std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); }
|
||||
std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); }
|
||||
std::vector<EventDefinition const*> const& interfaceEvents() const;
|
||||
bool isLibrary() const { return m_isLibrary; }
|
||||
bool isLibrary() const { return m_contractKind == ContractKind::Library; }
|
||||
|
||||
/// @returns a map of canonical function signatures to FunctionDefinitions
|
||||
/// as intended for use by the ABI.
|
||||
@ -371,10 +373,12 @@ public:
|
||||
|
||||
virtual ContractDefinitionAnnotation& annotation() const override;
|
||||
|
||||
ContractKind contractKind() const { return m_contractKind; }
|
||||
|
||||
private:
|
||||
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
|
||||
std::vector<ASTPointer<ASTNode>> m_subNodes;
|
||||
bool m_isLibrary;
|
||||
ContractKind m_contractKind;
|
||||
|
||||
// parsed Natspec documentation of the contract.
|
||||
Json::Value m_userDocumentation;
|
||||
|
@ -82,9 +82,10 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
|
||||
case Token::Import:
|
||||
nodes.push_back(parseImportDirective());
|
||||
break;
|
||||
case Token::Interface:
|
||||
case Token::Contract:
|
||||
case Token::Library:
|
||||
nodes.push_back(parseContractDefinition(token == Token::Library));
|
||||
nodes.push_back(parseContractDefinition(token));
|
||||
break;
|
||||
default:
|
||||
fatalParserError(string("Expected import directive or contract definition."));
|
||||
@ -193,13 +194,30 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
|
||||
return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases));
|
||||
}
|
||||
|
||||
ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary)
|
||||
ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token)
|
||||
{
|
||||
switch(_token)
|
||||
{
|
||||
case Token::Interface:
|
||||
return ContractDefinition::ContractKind::Interface;
|
||||
case Token::Contract:
|
||||
return ContractDefinition::ContractKind::Contract;
|
||||
case Token::Library:
|
||||
return ContractDefinition::ContractKind::Library;
|
||||
default:
|
||||
fatalParserError("Unsupported contract type.");
|
||||
}
|
||||
// FIXME: fatalParserError is not considered as throwing here
|
||||
return ContractDefinition::ContractKind::Contract;
|
||||
}
|
||||
|
||||
ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind)
|
||||
{
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
ASTPointer<ASTString> docString;
|
||||
if (m_scanner->currentCommentLiteral() != "")
|
||||
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
|
||||
expectToken(_isLibrary ? Token::Library : Token::Contract);
|
||||
expectToken(_expectedKind);
|
||||
ASTPointer<ASTString> name = expectIdentifierToken();
|
||||
vector<ASTPointer<InheritanceSpecifier>> baseContracts;
|
||||
if (m_scanner->currentToken() == Token::Is)
|
||||
@ -252,7 +270,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary)
|
||||
docString,
|
||||
baseContracts,
|
||||
subNodes,
|
||||
_isLibrary
|
||||
tokenToContractKind(_expectedKind)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,8 @@ private:
|
||||
///@name Parsing functions for the AST nodes
|
||||
ASTPointer<PragmaDirective> parsePragmaDirective();
|
||||
ASTPointer<ImportDirective> parseImportDirective();
|
||||
ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary);
|
||||
ContractDefinition::ContractKind tokenToContractKind(Token::Value _token);
|
||||
ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind);
|
||||
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
|
||||
Declaration::Visibility parseVisibilitySpecifier(Token::Value _token);
|
||||
FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers);
|
||||
|
@ -157,6 +157,7 @@ namespace solidity
|
||||
K(Hex, "hex", 0) \
|
||||
K(If, "if", 0) \
|
||||
K(Indexed, "indexed", 0) \
|
||||
K(Interface, "interface", 0) \
|
||||
K(Internal, "internal", 0) \
|
||||
K(Import, "import", 0) \
|
||||
K(Is, "is", 0) \
|
||||
@ -225,7 +226,6 @@ namespace solidity
|
||||
K(Final, "final", 0) \
|
||||
K(In, "in", 0) \
|
||||
K(Inline, "inline", 0) \
|
||||
K(Interface, "interface", 0) \
|
||||
K(Let, "let", 0) \
|
||||
K(Match, "match", 0) \
|
||||
K(NullLiteral, "null", 0) \
|
||||
|
@ -9266,6 +9266,41 @@ BOOST_AUTO_TEST_CASE(scientific_notation)
|
||||
BOOST_CHECK(callContractFunction("k()") == encodeArgs(u256(-25)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
interface I {
|
||||
event A();
|
||||
function f() returns (bool);
|
||||
function() payable;
|
||||
}
|
||||
|
||||
contract A is I {
|
||||
function f() returns (bool) {
|
||||
return g();
|
||||
}
|
||||
|
||||
function g() returns (bool) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function() payable {
|
||||
}
|
||||
}
|
||||
|
||||
contract C {
|
||||
function f(address _interfaceAddress) returns (bool) {
|
||||
I i = I(_interfaceAddress);
|
||||
return i.f();
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode, 0, "A");
|
||||
u160 const recipient = m_contractAddress;
|
||||
compileAndRun(sourceCode, 0, "C");
|
||||
BOOST_CHECK(callContractFunction("f(address)", recipient) == encodeArgs(true));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
|
@ -5327,6 +5327,151 @@ BOOST_AUTO_TEST_CASE(cyclic_dependency_for_constants)
|
||||
CHECK_SUCCESS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
}
|
||||
)";
|
||||
success(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_constructor)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
function I();
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Constructor cannot be defined in interfaces");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_functions)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
function();
|
||||
function f();
|
||||
}
|
||||
)";
|
||||
success(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_function_bodies)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
function f() {
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Functions in interfaces cannot have an implementation");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_function_internal)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
function f() internal;
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_function_private)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
function f() private;
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_events)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
event E();
|
||||
}
|
||||
)";
|
||||
success(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_inheritance)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface A {
|
||||
}
|
||||
interface I is A {
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Interfaces cannot inherit");
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_structs)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
struct A {
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Structs cannot be defined in interfaces");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_variables)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
uint a;
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Variables cannot be declared in interfaces");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface_enums)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
enum A { B, C }
|
||||
}
|
||||
)";
|
||||
CHECK_ERROR(text, TypeError, "Enumerable cannot be declared in interfaces");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_interface)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
function f();
|
||||
}
|
||||
contract C is I {
|
||||
function f() {
|
||||
}
|
||||
}
|
||||
)";
|
||||
success(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_interface_complex)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface I {
|
||||
event A();
|
||||
function f();
|
||||
function g();
|
||||
function();
|
||||
}
|
||||
contract C is I {
|
||||
function f() {
|
||||
}
|
||||
}
|
||||
)";
|
||||
success(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
|
@ -1493,6 +1493,15 @@ BOOST_AUTO_TEST_CASE(scientific_notation)
|
||||
BOOST_CHECK(successParse(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(interface)
|
||||
{
|
||||
char const* text = R"(
|
||||
interface Interface {
|
||||
function f();
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(successParse(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
|
@ -115,7 +115,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers)
|
||||
TypePointer multiArray = make_shared<ArrayType>(DataLocation::Storage, stringArray);
|
||||
BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr");
|
||||
|
||||
ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, false);
|
||||
ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, ContractDefinition::ContractKind::Contract);
|
||||
BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$");
|
||||
BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user