@ethereum-waffle/chai
Version:
A sweet set of chai matchers for your blockchain testing needs.
94 lines (87 loc) • 3.95 kB
text/typescript
import {Contract, providers, utils} from 'ethers';
import {keccak256, toUtf8Bytes} from 'ethers/lib/utils';
import {callPromise} from '../call-promise';
import {waitForPendingTransaction} from './misc/transaction';
import {supportWithArgs} from './withArgs';
import {supportWithNamedArgs} from './withNamedArgs';
export function supportEmit(Assertion: Chai.AssertionStatic) {
const filterLogsWithTopics = (logs: providers.Log[], topic: any, contractAddress?: string) =>
logs.filter((log) => log.topics.includes(topic))
.filter((log) =>
log.address &&
(contractAddress === undefined || log.address.toLowerCase() === contractAddress.toLowerCase()
));
const assertEmit = (assertion: any, frag: utils.EventFragment, isNegated: boolean, from?: string) => {
const topic = keccak256(toUtf8Bytes(frag.format()));
const receipt: providers.TransactionReceipt = assertion.txReceipt;
assertion.args = filterLogsWithTopics(receipt.logs, topic, from);
const isCurrentlyNegated = assertion.__flags.negate === true;
assertion.__flags.negate = isNegated;
assertion.assert(assertion.args.length > 0,
`Expected event "${frag.name}" to be emitted, but it wasn't`,
`Expected event "${frag.name}" NOT to be emitted, but it was`
);
assertion.__flags.negate = isCurrentlyNegated;
};
Assertion.addMethod('emit', function (this: any, contractOrEventSig: Contract|string, eventName?: string) {
if (typeof this._obj === 'string') {
if (typeof contractOrEventSig === 'string') {
throw new Error('The emit by event signature matcher must be called on a transaction');
}
// Handle specific case of using transaction hash to specify transaction. Done for backwards compatibility.
this.callPromise = waitForPendingTransaction(this._obj, contractOrEventSig.provider)
.then(txReceipt => {
this.txReceipt = txReceipt;
});
} else {
callPromise(this);
}
const isNegated = this.__flags.negate === true;
this.callPromise = this.callPromise.then(() => {
if (!('txReceipt' in this)) {
throw new Error('The emit matcher must be called on a transaction');
}
let eventFragment: utils.EventFragment | undefined;
if (typeof contractOrEventSig === 'string') {
try {
eventFragment = utils.EventFragment.from(contractOrEventSig);
} catch (e) {
throw new Error(`Invalid event signature: "${contractOrEventSig}"`);
}
assertEmit(this, eventFragment, isNegated);
} else if (eventName) {
try {
eventFragment = contractOrEventSig.interface.getEvent(eventName);
} catch (e) {
// ignore error
}
if (eventFragment === undefined) {
this.assert(
this.__flags.negate,
`Expected event "${eventName}" to be emitted, but it doesn't` +
' exist in the contract. Please make sure you\'ve compiled' +
' its latest version before running the test.',
`WARNING: Expected event "${eventName}" NOT to be emitted.` +
' The event wasn\'t emitted because it doesn\'t' +
' exist in the contract. Please make sure you\'ve compiled' +
' its latest version before running the test.',
eventName,
''
);
return;
}
assertEmit(this, eventFragment, isNegated, contractOrEventSig.address);
this.contract = contractOrEventSig;
} else {
throw new Error('The emit matcher must be called with a contract and an event name or an event signature');
}
});
this.then = this.callPromise.then.bind(this.callPromise);
this.catch = this.callPromise.catch.bind(this.callPromise);
this.eventName = eventName;
this.txMatcher = 'emit';
return this;
});
supportWithArgs(Assertion);
supportWithNamedArgs(Assertion);
}