Merge pull request #3538 from ethereum/emitEvents

emit pseudo-keyword for events.
This commit is contained in:
chriseth 2018-02-22 22:42:04 +01:00 committed by GitHub
commit 2b62c201be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 327 additions and 27 deletions

View File

@ -4,6 +4,7 @@ Features:
* Code Generator: Assert that ``k != 0`` for ``molmod(a, b, k)`` and ``addmod(a, b, k)`` as experimental 0.5.0 feature.
* Standard JSON: Reject badly formatted invalid JSON inputs.
* Type Checker: Disallow uninitialized storage pointers as experimental 0.5.0 feature.
* Support and recommend using ``emit EventName();`` to call events explicitly.
* Syntax Analyser: Do not warn about experimental features if they do not concern code generation.
Bugfixes:

View File

@ -724,10 +724,12 @@ All non-indexed arguments will be stored in the data part of the log.
);
function deposit(bytes32 _id) public payable {
// Any call to this function (even deeply nested) can
// be detected from the JavaScript API by filtering
// for `Deposit` to be called.
Deposit(msg.sender, _id, msg.value);
// Events are emitted using `emit`, followed by
// the name of the event and the arguments
// (if any) in parentheses. Any such invocation
// (even deeply nested) can be detected from
// the JavaScript API by filtering for `Deposit`.
emit Deposit(msg.sender, _id, msg.value);
}
}

View File

@ -63,7 +63,7 @@ StateMutability = 'pure' | 'constant' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | SimpleStatement ) ';'
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
@ -77,6 +77,7 @@ Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
EmitStatement = 'emit' FunctionCall
VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )?
IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'

View File

@ -80,7 +80,7 @@ registering with username and password - all you need is an Ethereum keypair.
::
pragma solidity ^0.4.0;
pragma solidity ^0.4.20; // should actually be 0.4.21
contract Coin {
// The keyword "public" makes those variables
@ -107,7 +107,7 @@ registering with username and password - all you need is an Ethereum keypair.
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Sent(msg.sender, receiver, amount);
emit Sent(msg.sender, receiver, amount);
}
}
@ -157,10 +157,10 @@ single account.
.. index:: event
The line ``event Sent(address from, address to, uint amount);`` declares
a so-called "event" which is fired in the last line of the function
a so-called "event" which is emitted in the last line of the function
``send``. User interfaces (as well as server applications of course) can
listen for those events being fired on the blockchain without much
cost. As soon as it is fired, the listener will also receive the
listen for those events being emitted on the blockchain without much
cost. As soon as it is emitted, the listener will also receive the
arguments ``from``, ``to`` and ``amount``, which makes it easy to track
transactions. In order to listen for this event, you would use ::

View File

@ -214,7 +214,7 @@ activate themselves.
::
pragma solidity ^0.4.11;
pragma solidity ^0.4.20; // should actually be 0.4.21
contract SimpleAuction {
// Parameters of the auction. Times are either
@ -282,7 +282,7 @@ activate themselves.
}
highestBidder = msg.sender;
highestBid = msg.value;
HighestBidIncreased(msg.sender, msg.value);
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Withdraw a bid that was overbid.
@ -325,7 +325,7 @@ activate themselves.
// 2. Effects
ended = true;
AuctionEnded(highestBidder, highestBid);
emit AuctionEnded(highestBidder, highestBid);
// 3. Interaction
beneficiary.transfer(highestBid);
@ -371,7 +371,7 @@ high or low invalid bids.
::
pragma solidity ^0.4.11;
pragma solidity ^0.4.20; // should actually be 0.4.21
contract BlindAuction {
struct Bid {
@ -509,7 +509,7 @@ high or low invalid bids.
onlyAfter(revealEnd)
{
require(!ended);
AuctionEnded(highestBidder, highestBid);
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
@ -524,7 +524,7 @@ Safe Remote Purchase
::
pragma solidity ^0.4.11;
pragma solidity ^0.4.20; // should actually be 0.4.21
contract Purchase {
uint public value;
@ -574,7 +574,7 @@ Safe Remote Purchase
onlySeller
inState(State.Created)
{
Aborted();
emit Aborted();
state = State.Inactive;
seller.transfer(this.balance);
}
@ -589,7 +589,7 @@ Safe Remote Purchase
condition(msg.value == (2 * value))
payable
{
PurchaseConfirmed();
emit PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
@ -601,7 +601,7 @@ Safe Remote Purchase
onlyBuyer
inState(State.Locked)
{
ItemReceived();
emit ItemReceived();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.

View File

@ -86,14 +86,14 @@ Events are convenience interfaces with the EVM logging facilities.
::
pragma solidity ^0.4.0;
pragma solidity ^0.4.20; // should actually be 0.4.21
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // Event
function bid() public payable {
// ...
HighestBidIncreased(msg.sender, msg.value); // Triggering event
emit HighestBidIncreased(msg.sender, msg.value); // Triggering event
}
}

View File

@ -470,7 +470,7 @@ Example that shows how to use internal function types::
Another example that uses external function types::
pragma solidity ^0.4.11;
pragma solidity ^0.4.20; // should actually be 0.4.21
contract Oracle {
struct Request {
@ -481,7 +481,7 @@ Another example that uses external function types::
event NewRequest(uint);
function query(bytes data, function(bytes memory) external callback) public {
requests.push(Request(data, callback));
NewRequest(requests.length - 1);
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, bytes response) public {
// Here goes the check that the reply comes from a trusted source

View File

@ -955,6 +955,16 @@ void TypeChecker::endVisit(Return const& _return)
}
}
void TypeChecker::endVisit(EmitStatement const& _emit)
{
if (
_emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall ||
dynamic_cast<FunctionType const&>(*type(_emit.eventCall().expression())).kind() != FunctionType::Kind::Event
)
m_errorReporter.typeError(_emit.eventCall().expression().location(), "Expression has to be an event invocation.");
m_insideEmitStatement = false;
}
bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
{
if (!_statement.initialValue())
@ -1531,6 +1541,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
else if (functionName->name() == "suicide" && functionType->kind() == FunctionType::Kind::Selfdestruct)
m_errorReporter.warning(_functionCall.location(), "\"suicide\" has been deprecated in favour of \"selfdestruct\"");
}
if (!m_insideEmitStatement && functionType->kind() == FunctionType::Kind::Event)
{
if (m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
m_errorReporter.typeError(_functionCall.location(), "Event invocations have to be prefixed by \"emit\".");
else
m_errorReporter.warning(_functionCall.location(), "Invoking events without \"emit\" prefix is deprecated.");
}
TypePointers parameterTypes = functionType->parameterTypes();

View File

@ -94,6 +94,8 @@ private:
virtual bool visit(WhileStatement const& _whileStatement) override;
virtual bool visit(ForStatement const& _forStatement) override;
virtual void endVisit(Return const& _return) override;
virtual bool visit(EmitStatement const&) override { m_insideEmitStatement = true; return true; }
virtual void endVisit(EmitStatement const& _emit) override;
virtual bool visit(VariableDeclarationStatement const& _variable) override;
virtual void endVisit(ExpressionStatement const& _statement) override;
virtual bool visit(Conditional const& _conditional) override;
@ -130,6 +132,9 @@ private:
ContractDefinition const* m_scope = nullptr;
/// Flag indicating whether we are currently inside an EmitStatement.
bool m_insideEmitStatement = false;
ErrorReporter& m_errorReporter;
};

View File

@ -1196,6 +1196,27 @@ public:
virtual void accept(ASTConstVisitor& _visitor) const override;
};
/**
* The emit statement is used to emit events: emit EventName(arg1, ..., argn)
*/
class EmitStatement: public Statement
{
public:
explicit EmitStatement(
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<FunctionCall> const& _functionCall
):
Statement(_location, _docString), m_eventCall(_functionCall) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
FunctionCall const& eventCall() const { return *m_eventCall; }
private:
ASTPointer<FunctionCall> m_eventCall;
};
/**
* Definition of a variable as a statement inside a function. It requires a type name (which can
* also be "var") but the actual assignment can be missing.

View File

@ -69,6 +69,7 @@ class Continue;
class Break;
class Return;
class Throw;
class EmitStatement;
class VariableDeclarationStatement;
class ExpressionStatement;
class Expression;

View File

@ -540,7 +540,15 @@ bool ASTJsonConverter::visit(Return const& _node)
bool ASTJsonConverter::visit(Throw const& _node)
{
setJsonNode(_node, "Throw", {});;
setJsonNode(_node, "Throw", {});
return false;
}
bool ASTJsonConverter::visit(EmitStatement const& _node)
{
setJsonNode(_node, "EmitStatement", {
make_pair("eventCall", toJson(_node.eventCall()))
});
return false;
}

View File

@ -91,6 +91,7 @@ public:
bool visit(Break const& _node) override;
bool visit(Return const& _node) override;
bool visit(Throw const& _node) override;
bool visit(EmitStatement const& _node) override;
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Conditional const& _node) override;

View File

@ -258,6 +258,13 @@ bool ASTPrinter::visit(Throw const& _node)
return goDeeper();
}
bool ASTPrinter::visit(EmitStatement const& _node)
{
writeLine("EmitStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(VariableDeclarationStatement const& _node)
{
writeLine("VariableDeclarationStatement");
@ -517,6 +524,11 @@ void ASTPrinter::endVisit(Throw const&)
m_indentation--;
}
void ASTPrinter::endVisit(EmitStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(VariableDeclarationStatement const&)
{
m_indentation--;

View File

@ -76,6 +76,7 @@ public:
bool visit(Break const& _node) override;
bool visit(Return const& _node) override;
bool visit(Throw const& _node) override;
bool visit(EmitStatement const& _node) override;
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Conditional const& _node) override;
@ -120,6 +121,7 @@ public:
void endVisit(Break const&) override;
void endVisit(Return const&) override;
void endVisit(Throw const&) override;
void endVisit(EmitStatement const&) override;
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Conditional const&) override;

View File

@ -73,6 +73,7 @@ public:
virtual bool visit(Break& _node) { return visitNode(_node); }
virtual bool visit(Return& _node) { return visitNode(_node); }
virtual bool visit(Throw& _node) { return visitNode(_node); }
virtual bool visit(EmitStatement& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclarationStatement& _node) { return visitNode(_node); }
virtual bool visit(ExpressionStatement& _node) { return visitNode(_node); }
virtual bool visit(Conditional& _node) { return visitNode(_node); }
@ -118,6 +119,7 @@ public:
virtual void endVisit(Break& _node) { endVisitNode(_node); }
virtual void endVisit(Return& _node) { endVisitNode(_node); }
virtual void endVisit(Throw& _node) { endVisitNode(_node); }
virtual void endVisit(EmitStatement& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclarationStatement& _node) { endVisitNode(_node); }
virtual void endVisit(ExpressionStatement& _node) { endVisitNode(_node); }
virtual void endVisit(Conditional& _node) { endVisitNode(_node); }
@ -175,6 +177,7 @@ public:
virtual bool visit(Break const& _node) { return visitNode(_node); }
virtual bool visit(Return const& _node) { return visitNode(_node); }
virtual bool visit(Throw const& _node) { return visitNode(_node); }
virtual bool visit(EmitStatement const& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclarationStatement const& _node) { return visitNode(_node); }
virtual bool visit(ExpressionStatement const& _node) { return visitNode(_node); }
virtual bool visit(Conditional const& _node) { return visitNode(_node); }
@ -220,6 +223,7 @@ public:
virtual void endVisit(Break const& _node) { endVisitNode(_node); }
virtual void endVisit(Return const& _node) { endVisitNode(_node); }
virtual void endVisit(Throw const& _node) { endVisitNode(_node); }
virtual void endVisit(EmitStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclarationStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(ExpressionStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(Conditional const& _node) { endVisitNode(_node); }

View File

@ -541,6 +541,20 @@ void Throw::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void EmitStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
m_eventCall->accept(_visitor);
_visitor.endVisit(*this);
}
void EmitStatement::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
m_eventCall->accept(_visitor);
_visitor.endVisit(*this);
}
void ExpressionStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))

View File

@ -903,6 +903,15 @@ bool ContractCompiler::visit(Throw const& _throw)
return false;
}
bool ContractCompiler::visit(EmitStatement const& _emit)
{
CompilerContext::LocationSetter locationSetter(m_context, _emit);
StackHeightChecker checker(m_context);
compileExpression(_emit.eventCall());
checker.check();
return false;
}
bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{
StackHeightChecker checker(m_context);

View File

@ -109,6 +109,7 @@ private:
virtual bool visit(Break const& _breakStatement) override;
virtual bool visit(Return const& _return) override;
virtual bool visit(Throw const& _throw) override;
virtual bool visit(EmitStatement const& _emit) override;
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
virtual bool visit(ExpressionStatement const& _expressionStatement) override;
virtual bool visit(PlaceholderStatement const&) override;

View File

@ -897,7 +897,9 @@ ASTPointer<Statement> Parser::parseStatement()
case Token::Assembly:
return parseInlineAssembly(docString);
case Token::Identifier:
if (m_insideModifier && m_scanner->currentLiteral() == "_")
if (m_scanner->currentLiteral() == "emit")
statement = parseEmitStatement(docString);
else if (m_insideModifier && m_scanner->currentLiteral() == "_")
{
statement = ASTNodeFactory(*this).createNode<PlaceholderStatement>(docString);
m_scanner->next();
@ -1015,6 +1017,38 @@ ASTPointer<ForStatement> Parser::parseForStatement(ASTPointer<ASTString> const&
);
}
ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const& _docString)
{
ASTNodeFactory nodeFactory(*this);
m_scanner->next();
ASTNodeFactory eventCallNodeFactory(*this);
if (m_scanner->currentToken() != Token::Identifier)
fatalParserError("Expected event name or path.");
vector<ASTPointer<PrimaryExpression>> path;
while (true)
{
path.push_back(parseIdentifier());
if (m_scanner->currentToken() != Token::Period)
break;
m_scanner->next();
};
auto eventName = expressionFromIndexAccessStructure(path, {});
expectToken(Token::LParen);
vector<ASTPointer<Expression>> arguments;
vector<ASTPointer<ASTString>> names;
std::tie(arguments, names) = parseFunctionCallArguments();
eventCallNodeFactory.markEndPosition();
nodeFactory.markEndPosition();
expectToken(Token::RParen);
auto eventCall = eventCallNodeFactory.createNode<FunctionCall>(eventName, arguments, names);
auto statement = nodeFactory.createNode<EmitStatement>(_docString, eventCall);
return statement;
}
ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const& _docString)
{
RecursionGuard recursionGuard(*this);

View File

@ -104,6 +104,7 @@ private:
ASTPointer<WhileStatement> parseWhileStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<WhileStatement> parseDoWhileStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<ForStatement> parseForStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<EmitStatement> parseEmitStatement(ASTPointer<ASTString> const& docString);
/// A "simple statement" can be a variable declaration statement or an expression statement.
ASTPointer<Statement> parseSimpleStatement(ASTPointer<ASTString> const& _docString);
ASTPointer<VariableDeclarationStatement> parseVariableDeclarationStatement(

View File

@ -40,7 +40,7 @@ contract StandardToken is Token {
if (balance[_from] >= _value && balance[_to] + _value >= balance[_to]) {
balance[_from] -= _value;
balance[_to] += _value;
Transfer(_from, _to, _value);
emit Transfer(_from, _to, _value);
return true;
} else {
return false;
@ -49,7 +49,7 @@ contract StandardToken is Token {
function approve(address _spender, uint256 _value) public returns (bool success) {
m_allowance[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
emit Approval(msg.sender, _spender, _value);
return true;
}

View File

@ -2967,6 +2967,29 @@ BOOST_AUTO_TEST_CASE(event)
}
}
BOOST_AUTO_TEST_CASE(event_emit)
{
char const* sourceCode = R"(
contract ClientReceipt {
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
function deposit(bytes32 _id) payable {
emit Deposit(msg.sender, _id, msg.value);
}
}
)";
compileAndRun(sourceCode);
u256 value(18);
u256 id(0x1234);
callContractFunctionWithValue("deposit(bytes32)", value, id);
BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
BOOST_CHECK_EQUAL(h256(m_logs[0].data), h256(u256(value)));
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 3);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,bytes32,uint256)")));
BOOST_CHECK_EQUAL(m_logs[0].topics[1], h256(m_sender, h256::AlignRight));
BOOST_CHECK_EQUAL(m_logs[0].topics[2], h256(id));
}
BOOST_AUTO_TEST_CASE(event_no_arguments)
{
char const* sourceCode = R"(
@ -3009,6 +3032,28 @@ BOOST_AUTO_TEST_CASE(event_access_through_base_name)
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("x()")));
}
BOOST_AUTO_TEST_CASE(event_access_through_base_name_emit)
{
char const* sourceCode = R"(
contract A {
event x();
}
contract B is A {
function f() returns (uint) {
emit A.x();
return 1;
}
}
)";
compileAndRun(sourceCode);
callContractFunction("f()");
BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
BOOST_CHECK(m_logs[0].data.empty());
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("x()")));
}
BOOST_AUTO_TEST_CASE(events_with_same_name)
{
char const* sourceCode = R"(
@ -3107,6 +3152,58 @@ BOOST_AUTO_TEST_CASE(events_with_same_name_inherited)
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)")));
}
BOOST_AUTO_TEST_CASE(events_with_same_name_inherited_emit)
{
char const* sourceCode = R"(
contract A {
event Deposit();
}
contract B {
event Deposit(address _addr);
}
contract ClientReceipt is A, B {
event Deposit(address _addr, uint _amount);
function deposit() returns (uint) {
emit Deposit();
return 1;
}
function deposit(address _addr) returns (uint) {
emit Deposit(_addr);
return 1;
}
function deposit(address _addr, uint _amount) returns (uint) {
emit Deposit(_addr, _amount);
return 1;
}
}
)";
u160 const c_loggedAddress = m_contractAddress;
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1)));
BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
BOOST_CHECK(m_logs[0].data.empty());
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()")));
ABI_CHECK(callContractFunction("deposit(address)", c_loggedAddress), encodeArgs(u256(1)));
BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress));
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)")));
ABI_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)), encodeArgs(u256(1)));
BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100));
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)")));
}
BOOST_AUTO_TEST_CASE(event_anonymous)
{
char const* sourceCode = R"(

View File

@ -7785,6 +7785,62 @@ BOOST_AUTO_TEST_CASE(no_address_members_on_contract)
CHECK_ERROR(text, TypeError, "Member \"delegatecall\" not found or not visible after argument-dependent lookup in contract");
}
BOOST_AUTO_TEST_CASE(emit_events)
{
char const* text = R"(
contract C {
event e();
function f() public {
emit e();
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
text = R"(
contract C {
event e(uint a, string b);
function f() public {
emit e(2, "abc");
emit e({b: "abc", a: 8});
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
text = R"(
contract A { event e(uint a, string b); }
contract C is A {
function f() public {
emit A.e(2, "abc");
emit A.e({b: "abc", a: 8});
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(old_style_events_050)
{
char const* text = R"(
contract C {
event e();
function f() public {
e();
}
}
)";
CHECK_WARNING(text, "without \"emit\" prefix");
text = R"(
pragma experimental "v0.5.0";
contract C {
event e();
function f() public {
e();
}
}
)";
CHECK_ERROR(text, TypeError, "have to be prefixed");
}
BOOST_AUTO_TEST_SUITE_END()
}

View File

@ -1708,6 +1708,19 @@ BOOST_AUTO_TEST_CASE(newInvalidTypeName)
CHECK_PARSE_ERROR(text, "Expected explicit type name");
}
BOOST_AUTO_TEST_CASE(emitWithoutEvent)
{
char const* text = R"(
contract C {
event A();
function f() {
emit A;
}
}
)";
CHECK_PARSE_ERROR(text, "Expected token LParen got 'Semicolon'");
}
BOOST_AUTO_TEST_SUITE_END()
}