Merge pull request #1688 from ethereum/interface-keyword

Support strict interface contracts
This commit is contained in:
chriseth 2017-03-21 17:54:05 +01:00 committed by GitHub
commit 6fb27dee63
12 changed files with 288 additions and 14 deletions

View File

@ -1,5 +1,8 @@
### 0.4.11 (unreleased)
Features:
* Support ``interface`` contracts.
### 0.4.10 (2017-03-15)
Features:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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