Skip to content

Commit c2de8ff

Browse files
authored
Now testing events in constructors! (#1511)
* Added inTransaction tests. * Added expectEvent.inConstructor. * Changed inTransaction, removed decodeLogs. * Flipped comparison to improve the error message. * Improved expectEvent tests. * Migrated tests to use expectEvent. * Added roles constructor tests. * Fixed linter errors. * Made lodash a dev dependency. * Added more inLogs tests. * Update expectEvent.test.js * Removed lodash. * Moved role constructor tests to public role behavior. * Revert "Flipped comparison to improve the error message." This reverts commit 438c578. * Replaced chai-as-promised with shouldFail.
1 parent f0e12d5 commit c2de8ff

File tree

9 files changed

+218
-49
lines changed

9 files changed

+218
-49
lines changed

contracts/mocks/EventEmitter.sol

+19
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ contract EventEmitter {
1111
event String(string value);
1212
event LongUintBooleanString(uint256 uintValue, bool booleanValue, string stringValue);
1313

14+
constructor (uint8 uintValue, bool booleanValue, string stringValue) public {
15+
emit ShortUint(uintValue);
16+
emit Boolean(booleanValue);
17+
emit String(stringValue);
18+
}
19+
1420
function emitArgumentless() public {
1521
emit Argumentless();
1622
}
@@ -51,4 +57,17 @@ contract EventEmitter {
5157
emit LongUint(uintValue);
5258
emit Boolean(boolValue);
5359
}
60+
61+
function emitStringAndEmitIndirectly(string value, IndirectEventEmitter emitter) public {
62+
emit String(value);
63+
emitter.emitStringIndirectly(value);
64+
}
65+
}
66+
67+
contract IndirectEventEmitter {
68+
event IndirectString(string value);
69+
70+
function emitStringIndirectly(string value) public {
71+
emit IndirectString(value);
72+
}
5473
}

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,6 @@
5555
"truffle": "^4.1.13",
5656
"truffle-hdwallet-provider": "0.0.5",
5757
"web3-utils": "^1.0.0-beta.34"
58-
}
58+
},
59+
"dependencies": {}
5960
}

test/access/roles/PublicRole.behavior.js

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ function shouldBehaveLikePublicRole (authorized, otherAuthorized, [anyone], role
1919
(await this.contract[`is${rolename}`](anyone)).should.equal(false);
2020
});
2121

22+
it('emits events during construction', async function () {
23+
await expectEvent.inConstruction(this.contract, `${rolename}Added`, {
24+
account: authorized,
25+
});
26+
});
27+
2228
it('reverts when querying roles for the null account', async function () {
2329
await shouldFail.reverting(this.contract[`is${rolename}`](ZERO_ADDRESS));
2430
});

test/examples/SimpleToken.test.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { decodeLogs } = require('../helpers/decodeLogs');
1+
const expectEvent = require('../helpers/expectEvent');
22
const { ZERO_ADDRESS } = require('../helpers/constants');
33
const SimpleToken = artifacts.require('SimpleToken');
44

@@ -31,12 +31,10 @@ contract('SimpleToken', function ([_, creator]) {
3131

3232
creatorBalance.should.be.bignumber.equal(totalSupply);
3333

34-
const receipt = await web3.eth.getTransactionReceipt(this.token.transactionHash);
35-
const logs = decodeLogs(receipt.logs, SimpleToken, this.token.address);
36-
logs.length.should.equal(1);
37-
logs[0].event.should.equal('Transfer');
38-
logs[0].args.from.valueOf().should.equal(ZERO_ADDRESS);
39-
logs[0].args.to.valueOf().should.equal(creator);
40-
logs[0].args.value.should.be.bignumber.equal(totalSupply);
34+
await expectEvent.inConstruction(this.token, 'Transfer', {
35+
from: ZERO_ADDRESS,
36+
to: creator,
37+
value: totalSupply,
38+
});
4139
});
4240
});

test/helpers/decodeLogs.js

-12
This file was deleted.

test/helpers/expectEvent.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const SolidityEvent = require('web3/lib/web3/event.js');
2+
13
const BigNumber = web3.BigNumber;
24
const should = require('chai')
35
.use(require('chai-bignumber')(BigNumber))
@@ -16,8 +18,14 @@ function inLogs (logs, eventName, eventArgs = {}) {
1618
return event;
1719
}
1820

19-
async function inTransaction (tx, eventName, eventArgs = {}) {
20-
const { logs } = await tx;
21+
async function inConstruction (contract, eventName, eventArgs = {}) {
22+
return inTransaction(contract.transactionHash, contract.constructor, eventName, eventArgs);
23+
}
24+
25+
async function inTransaction (txHash, emitter, eventName, eventArgs = {}) {
26+
const receipt = await web3.eth.getTransactionReceipt(txHash);
27+
const logs = decodeLogs(receipt.logs, emitter.events);
28+
2129
return inLogs(logs, eventName, eventArgs);
2230
}
2331

@@ -35,7 +43,17 @@ function isBigNumber (object) {
3543
(object.constructor && object.constructor.name === 'BigNumber');
3644
}
3745

46+
function decodeLogs (logs, events) {
47+
return Array.prototype.concat(...logs.map(log =>
48+
log.topics.filter(topic => topic in events).map(topic => {
49+
const event = new SolidityEvent(null, events[topic], 0);
50+
return event.decode(log);
51+
})
52+
));
53+
}
54+
3855
module.exports = {
3956
inLogs,
57+
inConstruction,
4058
inTransaction,
4159
};

test/helpers/test/expectEvent.test.js

+147-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
const expectEvent = require('../expectEvent');
2+
const shouldFail = require('../shouldFail');
3+
24
const EventEmitter = artifacts.require('EventEmitter');
5+
const IndirectEventEmitter = artifacts.require('IndirectEventEmitter');
36

47
const BigNumber = web3.BigNumber;
58
const should = require('chai')
@@ -8,7 +11,57 @@ const should = require('chai')
811

912
describe('expectEvent', function () {
1013
beforeEach(async function () {
11-
this.emitter = await EventEmitter.new();
14+
this.constructionValues = {
15+
uint: 42,
16+
boolean: true,
17+
string: 'OpenZeppelin',
18+
};
19+
20+
this.emitter = await EventEmitter.new(
21+
this.constructionValues.uint,
22+
this.constructionValues.boolean,
23+
this.constructionValues.string
24+
);
25+
});
26+
27+
describe('inConstructor', function () {
28+
context('short uint value', function () {
29+
it('accepts emitted events with correct number', async function () {
30+
await expectEvent.inConstruction(this.emitter, 'ShortUint',
31+
{ value: this.constructionValues.uint }
32+
);
33+
});
34+
35+
it('throws if an incorrect value is passed', async function () {
36+
await shouldFail(expectEvent.inConstruction(this.emitter, 'ShortUint', { value: 23 }));
37+
});
38+
});
39+
40+
context('boolean value', function () {
41+
it('accepts emitted events with correct value', async function () {
42+
await expectEvent.inConstruction(this.emitter, 'Boolean', { value: this.constructionValues.boolean });
43+
});
44+
45+
it('throws if an incorrect value is passed', async function () {
46+
await shouldFail(expectEvent.inConstruction(this.emitter, 'Boolean',
47+
{ value: !this.constructionValues.boolean }
48+
));
49+
});
50+
});
51+
52+
context('string value', function () {
53+
it('accepts emitted events with correct string', async function () {
54+
await expectEvent.inConstruction(this.emitter, 'String', { value: this.constructionValues.string });
55+
});
56+
57+
it('throws if an incorrect string is passed', async function () {
58+
await shouldFail(expectEvent.inConstruction(this.emitter, 'String', { value: 'ClosedZeppelin' }));
59+
});
60+
});
61+
62+
it('throws if an unemitted event is requested', async function () {
63+
await shouldFail(expectEvent.inConstruction(this.emitter, 'UnemittedEvent'));
64+
});
1265
});
1366

1467
describe('inLogs', function () {
@@ -228,5 +281,98 @@ describe('expectEvent', function () {
228281
should.Throw(() => expectEvent.inLogs(this.logs, 'Boolean', { value: false }));
229282
});
230283
});
284+
285+
describe('with events emitted by an indirectly called contract', function () {
286+
beforeEach(async function () {
287+
this.secondEmitter = await IndirectEventEmitter.new();
288+
289+
this.value = 'OpenZeppelin';
290+
({ logs: this.logs } = await this.emitter.emitStringAndEmitIndirectly(this.value, this.secondEmitter.address));
291+
});
292+
293+
it('accepts events emitted by the directly called contract', function () {
294+
expectEvent.inLogs(this.logs, 'String', { value: this.value });
295+
});
296+
297+
it('throws when passing events emitted by the indirectly called contract', function () {
298+
should.Throw(() => expectEvent.inLogs(this.logs, 'IndirectString', { value: this.value }));
299+
});
300+
});
301+
});
302+
303+
describe('inTransaction', function () {
304+
describe('when emitting from called contract and indirect calls', function () {
305+
context('string value', function () {
306+
beforeEach(async function () {
307+
this.secondEmitter = await IndirectEventEmitter.new();
308+
309+
this.value = 'OpenZeppelin';
310+
const receipt = await this.emitter.emitStringAndEmitIndirectly(this.value, this.secondEmitter.address);
311+
this.txHash = receipt.tx;
312+
});
313+
314+
context('with directly called contract', function () {
315+
it('accepts emitted events with correct string', async function () {
316+
await expectEvent.inTransaction(this.txHash, EventEmitter, 'String', { value: this.value });
317+
});
318+
319+
it('throws if an unemitted event is requested', async function () {
320+
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'UnemittedEvent',
321+
{ value: this.value }
322+
));
323+
});
324+
325+
it('throws if an incorrect string is passed', async function () {
326+
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'String',
327+
{ value: 'ClosedZeppelin' }
328+
));
329+
});
330+
331+
it('throws if an event emitted from other contract is passed', async function () {
332+
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'IndirectString',
333+
{ value: this.value }
334+
));
335+
});
336+
337+
it('throws if an incorrect emitter is passed', async function () {
338+
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'String',
339+
{ value: this.value }
340+
));
341+
});
342+
});
343+
344+
context('with indirectly called contract', function () {
345+
it('accepts events emitted from other contracts', async function () {
346+
await expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString',
347+
{ value: this.value }
348+
);
349+
});
350+
351+
it('throws if an unemitted event is requested', async function () {
352+
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'UnemittedEvent',
353+
{ value: this.value }
354+
));
355+
});
356+
357+
it('throws if an incorrect string is passed', async function () {
358+
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString',
359+
{ value: 'ClosedZeppelin' }
360+
));
361+
});
362+
363+
it('throws if an event emitted from other contract is passed', async function () {
364+
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'String',
365+
{ value: this.value }
366+
));
367+
});
368+
369+
it('throws if an incorrect emitter is passed', async function () {
370+
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'IndirectString',
371+
{ value: this.value }
372+
));
373+
});
374+
});
375+
});
376+
});
231377
});
232378
});

test/token/ERC721/ERC721.behavior.js

+15-22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ const expectEvent = require('../../helpers/expectEvent');
22
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');
33
const shouldFail = require('../../helpers/shouldFail');
44
const { ZERO_ADDRESS } = require('../../helpers/constants');
5-
const { decodeLogs } = require('../../helpers/decodeLogs');
65
const { sendTransaction } = require('../../helpers/sendTransaction');
76

87
const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock.sol');
@@ -245,31 +244,25 @@ function shouldBehaveLikeERC721 (
245244
shouldTransferTokensByUsers(transferFun);
246245

247246
it('should call onERC721Received', async function () {
248-
const result = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });
249-
result.receipt.logs.length.should.be.equal(2);
250-
const [log] = decodeLogs([result.receipt.logs[1]], ERC721ReceiverMock, this.receiver.address);
251-
log.event.should.be.equal('Received');
252-
log.args.operator.should.be.equal(owner);
253-
log.args.from.should.be.equal(owner);
254-
log.args.tokenId.toNumber().should.be.equal(tokenId);
255-
log.args.data.should.be.equal(data);
247+
const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });
248+
249+
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
250+
operator: owner,
251+
from: owner,
252+
tokenId: tokenId,
253+
data: data,
254+
});
256255
});
257256

258257
it('should call onERC721Received from approved', async function () {
259-
const result = await transferFun.call(this, owner, this.receiver.address, tokenId, {
260-
from: approved,
258+
const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: approved });
259+
260+
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
261+
operator: approved,
262+
from: owner,
263+
tokenId: tokenId,
264+
data: data,
261265
});
262-
result.receipt.logs.length.should.be.equal(2);
263-
const [log] = decodeLogs(
264-
[result.receipt.logs[1]],
265-
ERC721ReceiverMock,
266-
this.receiver.address
267-
);
268-
log.event.should.be.equal('Received');
269-
log.args.operator.should.be.equal(approved);
270-
log.args.from.should.be.equal(owner);
271-
log.args.tokenId.toNumber().should.be.equal(tokenId);
272-
log.args.data.should.be.equal(data);
273266
});
274267

275268
describe('with an invalid token id', function () {

0 commit comments

Comments
 (0)