Skip to content

Commit

Permalink
Now testing events in constructors! (#1511)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
nventuro authored Nov 27, 2018
1 parent f0e12d5 commit c2de8ff
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 49 deletions.
19 changes: 19 additions & 0 deletions contracts/mocks/EventEmitter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ contract EventEmitter {
event String(string value);
event LongUintBooleanString(uint256 uintValue, bool booleanValue, string stringValue);

constructor (uint8 uintValue, bool booleanValue, string stringValue) public {
emit ShortUint(uintValue);
emit Boolean(booleanValue);
emit String(stringValue);
}

function emitArgumentless() public {
emit Argumentless();
}
Expand Down Expand Up @@ -51,4 +57,17 @@ contract EventEmitter {
emit LongUint(uintValue);
emit Boolean(boolValue);
}

function emitStringAndEmitIndirectly(string value, IndirectEventEmitter emitter) public {
emit String(value);
emitter.emitStringIndirectly(value);
}
}

contract IndirectEventEmitter {
event IndirectString(string value);

function emitStringIndirectly(string value) public {
emit IndirectString(value);
}
}
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@
"truffle": "^4.1.13",
"truffle-hdwallet-provider": "0.0.5",
"web3-utils": "^1.0.0-beta.34"
}
},
"dependencies": {}
}
6 changes: 6 additions & 0 deletions test/access/roles/PublicRole.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ function shouldBehaveLikePublicRole (authorized, otherAuthorized, [anyone], role
(await this.contract[`is${rolename}`](anyone)).should.equal(false);
});

it('emits events during construction', async function () {
await expectEvent.inConstruction(this.contract, `${rolename}Added`, {
account: authorized,
});
});

it('reverts when querying roles for the null account', async function () {
await shouldFail.reverting(this.contract[`is${rolename}`](ZERO_ADDRESS));
});
Expand Down
14 changes: 6 additions & 8 deletions test/examples/SimpleToken.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { decodeLogs } = require('../helpers/decodeLogs');
const expectEvent = require('../helpers/expectEvent');
const { ZERO_ADDRESS } = require('../helpers/constants');
const SimpleToken = artifacts.require('SimpleToken');

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

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

const receipt = await web3.eth.getTransactionReceipt(this.token.transactionHash);
const logs = decodeLogs(receipt.logs, SimpleToken, this.token.address);
logs.length.should.equal(1);
logs[0].event.should.equal('Transfer');
logs[0].args.from.valueOf().should.equal(ZERO_ADDRESS);
logs[0].args.to.valueOf().should.equal(creator);
logs[0].args.value.should.be.bignumber.equal(totalSupply);
await expectEvent.inConstruction(this.token, 'Transfer', {
from: ZERO_ADDRESS,
to: creator,
value: totalSupply,
});
});
});
12 changes: 0 additions & 12 deletions test/helpers/decodeLogs.js

This file was deleted.

22 changes: 20 additions & 2 deletions test/helpers/expectEvent.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const SolidityEvent = require('web3/lib/web3/event.js');

const BigNumber = web3.BigNumber;
const should = require('chai')
.use(require('chai-bignumber')(BigNumber))
Expand All @@ -16,8 +18,14 @@ function inLogs (logs, eventName, eventArgs = {}) {
return event;
}

async function inTransaction (tx, eventName, eventArgs = {}) {
const { logs } = await tx;
async function inConstruction (contract, eventName, eventArgs = {}) {
return inTransaction(contract.transactionHash, contract.constructor, eventName, eventArgs);
}

async function inTransaction (txHash, emitter, eventName, eventArgs = {}) {
const receipt = await web3.eth.getTransactionReceipt(txHash);
const logs = decodeLogs(receipt.logs, emitter.events);

return inLogs(logs, eventName, eventArgs);
}

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

function decodeLogs (logs, events) {
return Array.prototype.concat(...logs.map(log =>
log.topics.filter(topic => topic in events).map(topic => {
const event = new SolidityEvent(null, events[topic], 0);
return event.decode(log);
})
));
}

module.exports = {
inLogs,
inConstruction,
inTransaction,
};
148 changes: 147 additions & 1 deletion test/helpers/test/expectEvent.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const expectEvent = require('../expectEvent');
const shouldFail = require('../shouldFail');

const EventEmitter = artifacts.require('EventEmitter');
const IndirectEventEmitter = artifacts.require('IndirectEventEmitter');

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

describe('expectEvent', function () {
beforeEach(async function () {
this.emitter = await EventEmitter.new();
this.constructionValues = {
uint: 42,
boolean: true,
string: 'OpenZeppelin',
};

this.emitter = await EventEmitter.new(
this.constructionValues.uint,
this.constructionValues.boolean,
this.constructionValues.string
);
});

describe('inConstructor', function () {
context('short uint value', function () {
it('accepts emitted events with correct number', async function () {
await expectEvent.inConstruction(this.emitter, 'ShortUint',
{ value: this.constructionValues.uint }
);
});

it('throws if an incorrect value is passed', async function () {
await shouldFail(expectEvent.inConstruction(this.emitter, 'ShortUint', { value: 23 }));
});
});

context('boolean value', function () {
it('accepts emitted events with correct value', async function () {
await expectEvent.inConstruction(this.emitter, 'Boolean', { value: this.constructionValues.boolean });
});

it('throws if an incorrect value is passed', async function () {
await shouldFail(expectEvent.inConstruction(this.emitter, 'Boolean',
{ value: !this.constructionValues.boolean }
));
});
});

context('string value', function () {
it('accepts emitted events with correct string', async function () {
await expectEvent.inConstruction(this.emitter, 'String', { value: this.constructionValues.string });
});

it('throws if an incorrect string is passed', async function () {
await shouldFail(expectEvent.inConstruction(this.emitter, 'String', { value: 'ClosedZeppelin' }));
});
});

it('throws if an unemitted event is requested', async function () {
await shouldFail(expectEvent.inConstruction(this.emitter, 'UnemittedEvent'));
});
});

describe('inLogs', function () {
Expand Down Expand Up @@ -228,5 +281,98 @@ describe('expectEvent', function () {
should.Throw(() => expectEvent.inLogs(this.logs, 'Boolean', { value: false }));
});
});

describe('with events emitted by an indirectly called contract', function () {
beforeEach(async function () {
this.secondEmitter = await IndirectEventEmitter.new();

this.value = 'OpenZeppelin';
({ logs: this.logs } = await this.emitter.emitStringAndEmitIndirectly(this.value, this.secondEmitter.address));
});

it('accepts events emitted by the directly called contract', function () {
expectEvent.inLogs(this.logs, 'String', { value: this.value });
});

it('throws when passing events emitted by the indirectly called contract', function () {
should.Throw(() => expectEvent.inLogs(this.logs, 'IndirectString', { value: this.value }));
});
});
});

describe('inTransaction', function () {
describe('when emitting from called contract and indirect calls', function () {
context('string value', function () {
beforeEach(async function () {
this.secondEmitter = await IndirectEventEmitter.new();

this.value = 'OpenZeppelin';
const receipt = await this.emitter.emitStringAndEmitIndirectly(this.value, this.secondEmitter.address);
this.txHash = receipt.tx;
});

context('with directly called contract', function () {
it('accepts emitted events with correct string', async function () {
await expectEvent.inTransaction(this.txHash, EventEmitter, 'String', { value: this.value });
});

it('throws if an unemitted event is requested', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'UnemittedEvent',
{ value: this.value }
));
});

it('throws if an incorrect string is passed', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'String',
{ value: 'ClosedZeppelin' }
));
});

it('throws if an event emitted from other contract is passed', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'IndirectString',
{ value: this.value }
));
});

it('throws if an incorrect emitter is passed', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'String',
{ value: this.value }
));
});
});

context('with indirectly called contract', function () {
it('accepts events emitted from other contracts', async function () {
await expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString',
{ value: this.value }
);
});

it('throws if an unemitted event is requested', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'UnemittedEvent',
{ value: this.value }
));
});

it('throws if an incorrect string is passed', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString',
{ value: 'ClosedZeppelin' }
));
});

it('throws if an event emitted from other contract is passed', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, IndirectEventEmitter, 'String',
{ value: this.value }
));
});

it('throws if an incorrect emitter is passed', async function () {
await shouldFail(expectEvent.inTransaction(this.txHash, EventEmitter, 'IndirectString',
{ value: this.value }
));
});
});
});
});
});
});
37 changes: 15 additions & 22 deletions test/token/ERC721/ERC721.behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const expectEvent = require('../../helpers/expectEvent');
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');
const shouldFail = require('../../helpers/shouldFail');
const { ZERO_ADDRESS } = require('../../helpers/constants');
const { decodeLogs } = require('../../helpers/decodeLogs');
const { sendTransaction } = require('../../helpers/sendTransaction');

const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock.sol');
Expand Down Expand Up @@ -245,31 +244,25 @@ function shouldBehaveLikeERC721 (
shouldTransferTokensByUsers(transferFun);

it('should call onERC721Received', async function () {
const result = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });
result.receipt.logs.length.should.be.equal(2);
const [log] = decodeLogs([result.receipt.logs[1]], ERC721ReceiverMock, this.receiver.address);
log.event.should.be.equal('Received');
log.args.operator.should.be.equal(owner);
log.args.from.should.be.equal(owner);
log.args.tokenId.toNumber().should.be.equal(tokenId);
log.args.data.should.be.equal(data);
const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner });

await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
operator: owner,
from: owner,
tokenId: tokenId,
data: data,
});
});

it('should call onERC721Received from approved', async function () {
const result = await transferFun.call(this, owner, this.receiver.address, tokenId, {
from: approved,
const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: approved });

await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
operator: approved,
from: owner,
tokenId: tokenId,
data: data,
});
result.receipt.logs.length.should.be.equal(2);
const [log] = decodeLogs(
[result.receipt.logs[1]],
ERC721ReceiverMock,
this.receiver.address
);
log.event.should.be.equal('Received');
log.args.operator.should.be.equal(approved);
log.args.from.should.be.equal(owner);
log.args.tokenId.toNumber().should.be.equal(tokenId);
log.args.data.should.be.equal(data);
});

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

0 comments on commit c2de8ff

Please sign in to comment.