|
1 | 1 | import {Contract, providers, utils} from 'ethers'; |
| 2 | +import {keccak256, toUtf8Bytes} from 'ethers/lib/utils'; |
2 | 3 | import {callPromise} from '../call-promise'; |
3 | 4 | import {waitForPendingTransaction} from './misc/transaction'; |
4 | 5 | import {supportWithArgs} from './withArgs'; |
5 | 6 | import {supportWithNamedArgs} from './withNamedArgs'; |
6 | 7 |
|
7 | 8 | export function supportEmit(Assertion: Chai.AssertionStatic) { |
8 | | - const filterLogsWithTopics = (logs: providers.Log[], topic: any, contractAddress: string) => |
| 9 | + const filterLogsWithTopics = (logs: providers.Log[], topic: any, contractAddress?: string) => |
9 | 10 | logs.filter((log) => log.topics.includes(topic)) |
10 | | - .filter((log) => log.address && log.address.toLowerCase() === contractAddress.toLowerCase()); |
| 11 | + .filter((log) => |
| 12 | + log.address && |
| 13 | + (contractAddress === undefined || log.address.toLowerCase() === contractAddress.toLowerCase() |
| 14 | + )); |
11 | 15 |
|
12 | | - Assertion.addMethod('emit', function (this: any, contract: Contract, eventName: string) { |
| 16 | + const assertEmit = (assertion: any, frag: utils.EventFragment, isNegated: boolean, from?: string) => { |
| 17 | + const topic = keccak256(toUtf8Bytes(frag.format())); |
| 18 | + const receipt: providers.TransactionReceipt = assertion.txReceipt; |
| 19 | + assertion.args = filterLogsWithTopics(receipt.logs, topic, from); |
| 20 | + const isCurrentlyNegated = assertion.__flags.negate === true; |
| 21 | + assertion.__flags.negate = isNegated; |
| 22 | + assertion.assert(assertion.args.length > 0, |
| 23 | + `Expected event "${frag.name}" to be emitted, but it wasn't`, |
| 24 | + `Expected event "${frag.name}" NOT to be emitted, but it was` |
| 25 | + ); |
| 26 | + assertion.__flags.negate = isCurrentlyNegated; |
| 27 | + }; |
| 28 | + |
| 29 | + Assertion.addMethod('emit', function (this: any, contractOrEventSig: Contract|string, eventName?: string) { |
13 | 30 | if (typeof this._obj === 'string') { |
| 31 | + if (typeof contractOrEventSig === 'string') { |
| 32 | + throw new Error('The emit by event signature matcher must be called on a transaction'); |
| 33 | + } |
14 | 34 | // Handle specific case of using transaction hash to specify transaction. Done for backwards compatibility. |
15 | | - this.callPromise = waitForPendingTransaction(this._obj, contract.provider) |
| 35 | + this.callPromise = waitForPendingTransaction(this._obj, contractOrEventSig.provider) |
16 | 36 | .then(txReceipt => { |
17 | 37 | this.txReceipt = txReceipt; |
18 | 38 | }); |
19 | 39 | } else { |
20 | 40 | callPromise(this); |
21 | 41 | } |
22 | 42 | const isNegated = this.__flags.negate === true; |
23 | | - this.callPromise = this.callPromise |
24 | | - .then(() => { |
25 | | - if (!('txReceipt' in this)) { |
26 | | - throw new Error('The emit matcher must be called on a transaction'); |
| 43 | + this.callPromise = this.callPromise.then(() => { |
| 44 | + if (!('txReceipt' in this)) { |
| 45 | + throw new Error('The emit matcher must be called on a transaction'); |
| 46 | + } |
| 47 | + let eventFragment: utils.EventFragment | undefined; |
| 48 | + if (typeof contractOrEventSig === 'string') { |
| 49 | + try { |
| 50 | + eventFragment = utils.EventFragment.from(contractOrEventSig); |
| 51 | + } catch (e) { |
| 52 | + throw new Error(`Invalid event signature: "${contractOrEventSig}"`); |
27 | 53 | } |
28 | | - const receipt: providers.TransactionReceipt = this.txReceipt; |
29 | | - let eventFragment: utils.EventFragment | undefined; |
| 54 | + assertEmit(this, eventFragment, isNegated); |
| 55 | + } else if (eventName) { |
30 | 56 | try { |
31 | | - eventFragment = contract.interface.getEvent(eventName); |
| 57 | + eventFragment = contractOrEventSig.interface.getEvent(eventName); |
32 | 58 | } catch (e) { |
33 | | - // ignore error |
| 59 | + // ignore error |
34 | 60 | } |
35 | 61 | if (eventFragment === undefined) { |
36 | 62 | this.assert( |
37 | | - isNegated, |
| 63 | + this.__flags.negate, |
38 | 64 | `Expected event "${eventName}" to be emitted, but it doesn't` + |
39 | | - ' exist in the contract. Please make sure you\'ve compiled' + |
40 | | - ' its latest version before running the test.', |
| 65 | + ' exist in the contract. Please make sure you\'ve compiled' + |
| 66 | + ' its latest version before running the test.', |
41 | 67 | `WARNING: Expected event "${eventName}" NOT to be emitted.` + |
42 | | - ' The event wasn\'t emitted because it doesn\'t' + |
43 | | - ' exist in the contract. Please make sure you\'ve compiled' + |
44 | | - ' its latest version before running the test.', |
| 68 | + ' The event wasn\'t emitted because it doesn\'t' + |
| 69 | + ' exist in the contract. Please make sure you\'ve compiled' + |
| 70 | + ' its latest version before running the test.', |
45 | 71 | eventName, |
46 | 72 | '' |
47 | 73 | ); |
48 | 74 | return; |
49 | 75 | } |
| 76 | + assertEmit(this, eventFragment, isNegated, contractOrEventSig.address); |
| 77 | + |
| 78 | + this.contract = contractOrEventSig; |
| 79 | + } else { |
| 80 | + throw new Error('The emit matcher must be called with a contract and an event name or an event signature'); |
| 81 | + } |
| 82 | + }); |
50 | 83 |
|
51 | | - const topic = contract.interface.getEventTopic(eventFragment); |
52 | | - this.args = filterLogsWithTopics(receipt.logs, topic, contract.address); |
53 | | - // As this callback will be resolved after the chain of matchers is finished, we need to |
54 | | - // know if the matcher has been negated or not. To simulate chai behaviour, we keep track of whether |
55 | | - // the matcher has been negated or not and set the internal chai flag __flags.negate to the same value. |
56 | | - // After the assertion is finished, we set the flag back to original value to not affect other assertions. |
57 | | - const isCurrentlyNegated = this.__flags.negate === true; |
58 | | - this.__flags.negate = isNegated; |
59 | | - this.assert(this.args.length > 0, |
60 | | - `Expected event "${eventName}" to be emitted, but it wasn't`, |
61 | | - `Expected event "${eventName}" NOT to be emitted, but it was` |
62 | | - ); |
63 | | - this.__flags.negate = isCurrentlyNegated; |
64 | | - }); |
65 | 84 | this.then = this.callPromise.then.bind(this.callPromise); |
66 | 85 | this.catch = this.callPromise.catch.bind(this.callPromise); |
67 | | - this.contract = contract; |
68 | 86 | this.eventName = eventName; |
69 | 87 | this.txMatcher = 'emit'; |
70 | 88 | return this; |
|
0 commit comments