From 702e38eb4c69fda016f2de6c47584b3538c4fcf9 Mon Sep 17 00:00:00 2001 From: Alejo Amiras Date: Tue, 10 Dec 2019 07:46:20 -0300 Subject: [PATCH 1/6] Adds negative check to event emittion --- src/expectEvent.js | 42 ++++++++++++++++++++++++++++ test/src/expectEvent.truffle.test.js | 23 +++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/expectEvent.js b/src/expectEvent.js index a8f8594..97e11a0 100644 --- a/src/expectEvent.js +++ b/src/expectEvent.js @@ -56,6 +56,29 @@ function inLogs (logs, eventName, eventArgs = {}) { return event; } +function dontExpectEvent (receipt, eventName) { + if (isWeb3Receipt(receipt)) { + const logs = flatten(Object.keys(receipt.events).map(name => { + if (Array.isArray(receipt.events[name])) { + return receipt.events[name].map(event => ({ event: name, args: event.returnValues })); + } else { + return ({ event: name, args: receipt.events[name].returnValues }); + } + })); + + notInLogs(logs, eventName); + } else if (isTruffleReceipt(receipt)) { + notInLogs(receipt.logs, eventName); + } else { + throw new Error('Unknown transaction receipt object'); + } +} + +function notInLogs (logs, eventName) { + const events = logs.filter(e => e.event === eventName); + expect(events.length === 0).to.equal(true, `Event ${eventName} was found`); +} + async function inConstruction (contract, eventName, eventArgs = {}) { if (!isTruffleContract(contract)) { throw new Error('expectEvent.inConstruction is only supported for truffle-contract objects'); @@ -64,6 +87,13 @@ async function inConstruction (contract, eventName, eventArgs = {}) { return inTransaction(contract.transactionHash, contract.constructor, eventName, eventArgs); } +async function notInConstruction (contract, eventName) { + if (!isTruffleContract(contract)) { + throw new Error('expectEvent.inConstruction is only supported for truffle-contract objects'); + } + return notInTransaction(contract.transactionHash, contract.constructor, eventName); +} + async function inTransaction (txHash, emitter, eventName, eventArgs = {}) { const receipt = await web3.eth.getTransactionReceipt(txHash); @@ -71,6 +101,13 @@ async function inTransaction (txHash, emitter, eventName, eventArgs = {}) { return inLogs(logs, eventName, eventArgs); } +async function notInTransaction (txHash, emitter, eventName) { + const receipt = await web3.eth.getTransactionReceipt(txHash); + + const logs = decodeLogs(receipt.logs, emitter, eventName); + return notInLogs(logs, eventName); +} + // This decodes longs for a single event type, and returns a decoded object in // the same form truffle-contract uses on its receipts function decodeLogs (logs, emitter, eventName) { @@ -138,4 +175,9 @@ function isTruffleContract (contract) { expectEvent.inLogs = deprecate(inLogs, 'expectEvent.inLogs() is deprecated. Use expectEvent() instead.'); expectEvent.inConstruction = inConstruction; expectEvent.inTransaction = inTransaction; + +expectEvent.not = dontExpectEvent; +expectEvent.not.inLogs = notInLogs; +expectEvent.not.inConstruction = notInConstruction; +expectEvent.not.inTransaction = notInTransaction; module.exports = expectEvent; diff --git a/test/src/expectEvent.truffle.test.js b/test/src/expectEvent.truffle.test.js index 184fc00..0683ea3 100644 --- a/test/src/expectEvent.truffle.test.js +++ b/test/src/expectEvent.truffle.test.js @@ -458,4 +458,27 @@ contract('expectEvent (truffle contracts)', function ([deployer]) { }); }); }); + + describe('not', function () { + // default = inLogs, default TBD. + describe('default', function () { + context('with no arguments', function () { + beforeEach(async function () { + this.receipt = await this.emitter.emitArgumentless(); + }); + it('accepts not emitted events', function () { + expectEvent.not(this.receipt, 'Nonexistant'); + }); + it('throws when event its emitted', function () { + expect(() => expectEvent.not(this.receipt, 'Argumentless')).to.throw(); + }); + }); + }); + describe('inConstruction', function () { + it('tests'); + }); + describe('inTransaction', function () { + it('tests'); + }); + }); }); From 9d178ccbf09cbb35e29c0de147d4525aa7846a7f Mon Sep 17 00:00:00 2001 From: Alejo Amiras Date: Wed, 18 Dec 2019 19:47:11 -0300 Subject: [PATCH 2/6] Added cases --- contracts/EventEmitter.sol | 1 + src/expectEvent.js | 31 ++++--------------------- test/src/expectEvent.truffle.test.js | 34 ++++++++++++++++++---------- 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/contracts/EventEmitter.sol b/contracts/EventEmitter.sol index a34fd72..a0b743d 100644 --- a/contracts/EventEmitter.sol +++ b/contracts/EventEmitter.sol @@ -4,6 +4,7 @@ import "./IndirectEventEmitter.sol"; contract EventEmitter { event Argumentless(); + event WillNeverBeEmitted(); event ShortUint(uint8 value); event ShortInt(int8 value); event LongUint(uint256 value); diff --git a/src/expectEvent.js b/src/expectEvent.js index 97e11a0..25fda56 100644 --- a/src/expectEvent.js +++ b/src/expectEvent.js @@ -56,29 +56,6 @@ function inLogs (logs, eventName, eventArgs = {}) { return event; } -function dontExpectEvent (receipt, eventName) { - if (isWeb3Receipt(receipt)) { - const logs = flatten(Object.keys(receipt.events).map(name => { - if (Array.isArray(receipt.events[name])) { - return receipt.events[name].map(event => ({ event: name, args: event.returnValues })); - } else { - return ({ event: name, args: receipt.events[name].returnValues }); - } - })); - - notInLogs(logs, eventName); - } else if (isTruffleReceipt(receipt)) { - notInLogs(receipt.logs, eventName); - } else { - throw new Error('Unknown transaction receipt object'); - } -} - -function notInLogs (logs, eventName) { - const events = logs.filter(e => e.event === eventName); - expect(events.length === 0).to.equal(true, `Event ${eventName} was found`); -} - async function inConstruction (contract, eventName, eventArgs = {}) { if (!isTruffleContract(contract)) { throw new Error('expectEvent.inConstruction is only supported for truffle-contract objects'); @@ -105,7 +82,10 @@ async function notInTransaction (txHash, emitter, eventName) { const receipt = await web3.eth.getTransactionReceipt(txHash); const logs = decodeLogs(receipt.logs, emitter, eventName); - return notInLogs(logs, eventName); + + const events = logs.filter(e => e.event === eventName); + + expect(events.length === 0).to.equal(true, `Event ${eventName} was found`); } // This decodes longs for a single event type, and returns a decoded object in @@ -176,8 +156,7 @@ expectEvent.inLogs = deprecate(inLogs, 'expectEvent.inLogs() is deprecated. Use expectEvent.inConstruction = inConstruction; expectEvent.inTransaction = inTransaction; -expectEvent.not = dontExpectEvent; -expectEvent.not.inLogs = notInLogs; +expectEvent.not = {}; expectEvent.not.inConstruction = notInConstruction; expectEvent.not.inTransaction = notInTransaction; module.exports = expectEvent; diff --git a/test/src/expectEvent.truffle.test.js b/test/src/expectEvent.truffle.test.js index 0683ea3..e40c87d 100644 --- a/test/src/expectEvent.truffle.test.js +++ b/test/src/expectEvent.truffle.test.js @@ -460,25 +460,35 @@ contract('expectEvent (truffle contracts)', function ([deployer]) { }); describe('not', function () { - // default = inLogs, default TBD. - describe('default', function () { + describe('inTransaction', function () { context('with no arguments', function () { beforeEach(async function () { - this.receipt = await this.emitter.emitArgumentless(); + const { receipt } = await this.emitter.emitArgumentless(); + this.txHash = receipt.transactionHash; }); - it('accepts not emitted events', function () { - expectEvent.not(this.receipt, 'Nonexistant'); + it('accepts not emitted events', async function () { + await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted'); }); - it('throws when event its emitted', function () { - expect(() => expectEvent.not(this.receipt, 'Argumentless')).to.throw(); + it('throws when event does not exist in ABI', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Nonexistant')); + }); + it('throws when event its emitted', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Argumentless')); }); }); + context('with arguments', function () { + it('accepts not emitted events'); + it('throws when event its emitted'); + }); + context('with events emitted by an indirectly called contract', function () { + it('accepts not emitted events'); + it('throws when event its emitted'); + }); }); - describe('inConstruction', function () { - it('tests'); - }); - describe('inTransaction', function () { - it('tests'); + describe('inConstructor', function () { + it('accepts not emitted events'); + it('throws when event does not exist in ABI'); + it('throws when event its emitted'); // test all three emitted events }); }); }); From e0bdcedaf228d4cf4125f9820e8f4662111fc618 Mon Sep 17 00:00:00 2001 From: Alejo Amiras Date: Wed, 18 Dec 2019 20:15:42 -0300 Subject: [PATCH 3/6] Truffle + web3 --- test/src/expectEvent.truffle.test.js | 40 ++++++++++++++++++---- test/src/expectEvent.web3.test.js | 50 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/test/src/expectEvent.truffle.test.js b/test/src/expectEvent.truffle.test.js index e40c87d..97b5285 100644 --- a/test/src/expectEvent.truffle.test.js +++ b/test/src/expectEvent.truffle.test.js @@ -477,18 +477,44 @@ contract('expectEvent (truffle contracts)', function ([deployer]) { }); }); context('with arguments', function () { - it('accepts not emitted events'); - it('throws when event its emitted'); + beforeEach(async function () { + this.value = 42; + const { receipt } = await this.emitter.emitShortUint(this.value); + this.txHash = receipt.transactionHash; + }); + it('accepts not emitted events', async function () { + await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted'); + }); + it('throws when event its emitted', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'ShortUint')); + }); }); context('with events emitted by an indirectly called contract', function () { - it('accepts not emitted events'); - it('throws when event its emitted'); + beforeEach(async function () { + this.value = 'OpenZeppelin'; + const { receipt } = await this.emitter.emitStringAndEmitIndirectly(this.value, this.secondEmitter.address); + this.txHash = receipt.transactionHash; + }); + it('accepts not emitted events', async function () { + await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted'); + }); + it('throws when event its emitted', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString')); + }); }); }); describe('inConstructor', function () { - it('accepts not emitted events'); - it('throws when event does not exist in ABI'); - it('throws when event its emitted'); // test all three emitted events + it('accepts not emitted events', async function () { + await expectEvent.not.inConstruction(this.emitter, 'WillNeverBeEmitted'); + }); + it('throws when event does not exist in ABI', async function () { + await assertFailure(expectEvent.not.inConstruction(this.emitter, 'Nonexistant')); + }); + it('throws when event its emitted', async function () { + await assertFailure(expectEvent.not.inConstruction(this.emitter, 'ShortUint')); + await assertFailure(expectEvent.not.inConstruction(this.emitter, 'Boolean')); + await assertFailure(expectEvent.not.inConstruction(this.emitter, 'String')); + }); }); }); }); diff --git a/test/src/expectEvent.web3.test.js b/test/src/expectEvent.web3.test.js index 6e1b7ae..2ea8304 100644 --- a/test/src/expectEvent.web3.test.js +++ b/test/src/expectEvent.web3.test.js @@ -430,4 +430,54 @@ contract('expectEvent (web3 contracts) ', function ([deployer]) { await assertFailure(expectEvent.inConstruction(this.emitter, 'ShortUint')); }); }); + + describe('not', function () { + describe('inTransaction', function () { + context('with no arguments', function () { + beforeEach(async function () { + ({ transactionHash: this.txHash } = await this.emitter.methods.emitArgumentless().send()); + }); + it('accepts not emitted events', async function () { + await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted'); + }); + it('throws when event does not exist in ABI', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Nonexistant')); + }); + it('throws when event its emitted', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'Argumentless')); + }); + }); + context('with arguments', function () { + beforeEach(async function () { + this.value = 42; + ({ transactionHash: this.txHash } = await this.emitter.methods.emitShortUint(this.value).send()); + }); + it('accepts not emitted events', async function () { + await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted'); + }); + it('throws when event its emitted', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, EventEmitter, 'ShortUint')); + }); + }); + context('with events emitted by an indirectly called contract', function () { + beforeEach(async function () { + this.value = 'OpenZeppelin'; + ({ transactionHash: this.txHash } = await this.emitter.methods.emitStringAndEmitIndirectly( + this.value, this.secondEmitter.options.address + ).send()); + }); + it('accepts not emitted events', async function () { + await expectEvent.not.inTransaction(this.txHash, EventEmitter, 'WillNeverBeEmitted'); + }); + it('throws when event its emitted', async function () { + await assertFailure(expectEvent.not.inTransaction(this.txHash, IndirectEventEmitter, 'IndirectString')); + }); + }); + }); + describe('inConstruction', function () { + it('is unsupported', async function () { + await assertFailure(expectEvent.not.inConstruction(this.emitter, 'ShortUint')); + }); + }); + }); }); From 3cafcf3d48a69d7d014e86dd631467e017caa53a Mon Sep 17 00:00:00 2001 From: Alejo Amiras Date: Sun, 22 Dec 2019 17:42:58 -0300 Subject: [PATCH 4/6] Added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1de1b61..9c1cff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.5.5 (unreleased) * Added function `advanceBlockTo`. ([#94](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/94)) + * Added `expectEvent.not` support to test negative cases. ([#104](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/104)) ## 0.5.4 (2019-11-13) * Fixed some RPC calls not having an `id` field. ([#92](https://github.com/OpenZeppelin/openzeppelin-test-helpers/pull/92)) From ae2a6dd2ae65afc009ef5aac5925dec302643c7e Mon Sep 17 00:00:00 2001 From: Alejo Amiras Date: Sat, 28 Dec 2019 20:43:50 -0300 Subject: [PATCH 5/6] Added not.inXXX to docs --- docs/modules/ROOT/pages/api.adoc | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/modules/ROOT/pages/api.adoc b/docs/modules/ROOT/pages/api.adoc index 4878da5..a1011bb 100644 --- a/docs/modules/ROOT/pages/api.adoc +++ b/docs/modules/ROOT/pages/api.adoc @@ -161,6 +161,43 @@ async function expectEvent.inConstruction(emitter, eventName, eventArgs = {}) Same as `inTransaction`, but for events emitted during the construction of `emitter`. Note that this is currently only supported for truffle contracts. +=== `not.inTransaction` + +```javascript +async function notInTransaction (txHash, emitter, eventName) +``` + +Asserts that an event with the name `eventName` was not emitted in an arbistrary transaction (of hash `txHash`) by an arbitrary contract (`emitter`, the contract instance), even if it was inderectly emitted (i.e. if it was called by another smart contract and not an externally owned account). + +```javascript +// With web3 contracts +const contract = await MyContract.deploy().send(); +const { transactionHash } = await contract.methods.foo().send(); +await expectEvent.not.inTransaction(transactionHash, contract, 'NotEmittedByFoo'); + +// With web3 contracts +const contract = await MyContract.deploy().send(); +const { transactionHash } = await contract.methods.foo().send(); +await expectEvent.not.inTransaction(transactionHash, contract, 'EmittedByFoo'); // Will fail + +// With truffle contracts +const contract = await MyContract.new(); +const { txHash } = await contract.foo(); +await expectEvent.not.inTransaction(txHash, contract, 'NotEmittedByFoo'); + +const contract = await MyContract.new(); +const { txHash } = await contract.foo(); +await expectEvent.not.inTransaction(txHash, contract, 'EmittedByFoo'); // Will fail +``` + +=== `not.inConstruction` + +```javascript +async function notInConstruction (contract, eventName) +``` + +Same as `not.inTransaction`, but for events emitted during the construction of emitter. Note that this is currently only supported for truffle contracts. + [[expect-revert]] == `expectRevert` From 220b3e954e5462be47a6a499ce47dfc7f70fb597 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Mon, 30 Dec 2019 19:36:43 -0300 Subject: [PATCH 6/6] edit docs for expectEvent.not.inTransaction --- docs/modules/ROOT/pages/api.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/api.adoc b/docs/modules/ROOT/pages/api.adoc index a1011bb..f0d156d 100644 --- a/docs/modules/ROOT/pages/api.adoc +++ b/docs/modules/ROOT/pages/api.adoc @@ -167,7 +167,7 @@ Same as `inTransaction`, but for events emitted during the construction of `emit async function notInTransaction (txHash, emitter, eventName) ``` -Asserts that an event with the name `eventName` was not emitted in an arbistrary transaction (of hash `txHash`) by an arbitrary contract (`emitter`, the contract instance), even if it was inderectly emitted (i.e. if it was called by another smart contract and not an externally owned account). +Asserts that the event with name `eventName` as declared in the `emitter` contract was not emitted in the transaction with hash `txHash`. Note that the assertion will also fail if the transaction was emitted indirectly (in a nested external function call). ```javascript // With web3 contracts