@ethereum-waffle/chai
Version:
A sweet set of chai matchers for your blockchain testing needs.
88 lines (81 loc) • 3.52 kB
text/typescript
import {BigNumber, BigNumberish, Contract, providers} from 'ethers';
import {callPromise} from '../call-promise';
import {Account, getAddressOf} from './misc/account';
export function supportChangeTokenBalances(Assertion: Chai.AssertionStatic) {
Assertion.addMethod('changeTokenBalances', function (
this: any,
token: Contract,
accounts: (Account | string)[],
balanceChanges: BigNumberish[],
errorMargin: BigNumberish
) {
callPromise(this);
const isNegated = this.__flags.negate === true;
const derivedPromise = this.callPromise.then(async () => {
if (!('txReceipt' in this)) {
throw new Error('The changeTokenBalances matcher must be called on a transaction');
}
const addresses = await getAddresses(accounts);
const actualChanges = await getBalanceChanges(this.txReceipt, token, addresses);
return [actualChanges, addresses];
}).then(([actualChanges, accountAddresses]: [BigNumber[], string[]]) => {
const isCurrentlyNegated = this.__flags.negate === true;
this.__flags.negate = isNegated;
if (errorMargin === undefined) errorMargin = '0';
if (BigNumber.from(errorMargin).eq(0)) {
this.assert(
actualChanges.every((change, ind) =>
change.lte(BigNumber.from(balanceChanges[ind]).add(errorMargin)) &&
change.gte(BigNumber.from(balanceChanges[ind]).sub(errorMargin))
),
`Expected ${accountAddresses} to change balance by ${balanceChanges} wei, ` +
`but it has changed by ${actualChanges} wei`,
`Expected ${accountAddresses} to not change balance by ${balanceChanges} wei,`,
balanceChanges.map((balanceChange) => balanceChange.toString()),
actualChanges.map((actualChange) => actualChange.toString())
);
} else {
actualChanges.forEach((change, ind) => {
const low = BigNumber.from(balanceChanges[ind]).sub(errorMargin);
const high = BigNumber.from(balanceChanges[ind]).add(errorMargin);
this.assert(
change.lte(high) &&
change.gte(low),
`Expected "${accountAddresses[ind]}" balance to change within [${[low, high]}] wei, ` +
`but it has changed by ${change} wei`,
`Expected "${accountAddresses[ind]}" balance to not change within [${[low, high]}] wei`,
balanceChanges[ind],
change
);
});
}
this.__flags.negate = isCurrentlyNegated;
});
this.then = derivedPromise.then.bind(derivedPromise);
this.catch = derivedPromise.catch.bind(derivedPromise);
this.callPromise = derivedPromise;
return this;
});
}
function getAddresses(accounts: (Account | string)[]) {
return Promise.all(accounts.map(
(account) => typeof account === 'string' ? account : getAddressOf(account)
));
}
async function getBalances(token: Contract, addresses: string[], blockNumber: number) {
return Promise.all(
addresses.map((address) => {
return token['balanceOf(address)'](address, {blockTag: blockNumber});
})
);
}
async function getBalanceChanges(
txReceipt: providers.TransactionReceipt,
token: Contract,
addresses: string[]
) {
const txBlockNumber = txReceipt.blockNumber;
const balancesBefore = await getBalances(token, addresses, txBlockNumber - 1);
const balancesAfter = await getBalances(token, addresses, txBlockNumber);
return balancesAfter.map((balance, ind) => balance.sub(balancesBefore[ind]));
}