Skip to content

Commit b54c6b9

Browse files
authored
🗿 Add error to balance changing matchers (#814)
* 🗿 Add error to balance changing matchers
1 parent f93abe9 commit b54c6b9

11 files changed

+374
-40
lines changed

‎.changeset/quiet-pumas-double.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ethereum-waffle/chai": patch
3+
---
4+
5+
Add delta to balance changing matchers

‎waffle-chai/src/matchers/changeEtherBalance.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,29 @@ export function supportChangeEtherBalance(Assertion: Chai.AssertionStatic) {
3131
}).then(([actualChange, address]: [BigNumber, string]) => {
3232
const isCurrentlyNegated = this.__flags.negate === true;
3333
this.__flags.negate = isNegated;
34-
this.assert(
35-
actualChange.eq(BigNumber.from(balanceChange)),
36-
`Expected "${address}" to change balance by ${balanceChange} wei, ` +
37-
`but it has changed by ${actualChange} wei`,
38-
`Expected "${address}" to not change balance by ${balanceChange} wei,`,
39-
balanceChange,
40-
actualChange
41-
);
34+
const margin = options?.errorMargin ? options.errorMargin : '0';
35+
if (BigNumber.from(margin).eq(0)) {
36+
this.assert(
37+
actualChange.eq(BigNumber.from(balanceChange)),
38+
`Expected "${address}" to change balance by ${balanceChange} wei, ` +
39+
`but it has changed by ${actualChange} wei`,
40+
`Expected "${address}" to not change balance by ${balanceChange} wei,`,
41+
balanceChange,
42+
actualChange
43+
);
44+
} else {
45+
const low = BigNumber.from(balanceChange).sub(margin);
46+
const high = BigNumber.from(balanceChange).add(margin);
47+
this.assert(
48+
actualChange.lte(high) &&
49+
actualChange.gte(low),
50+
`Expected "${address}" balance to change within [${[low, high]}] wei, ` +
51+
`but it has changed by ${actualChange} wei`,
52+
`Expected "${address}" balance to not change within [${[low, high]}] wei`,
53+
balanceChange,
54+
actualChange
55+
);
56+
}
4257
this.__flags.negate = isCurrentlyNegated;
4358
}
4459
);

‎waffle-chai/src/matchers/changeEtherBalances.ts

+28-10
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,34 @@ export function supportChangeEtherBalances(Assertion: Chai.AssertionStatic) {
3030
}).then(([actualChanges, accountAddresses]: [BigNumber[], string[]]) => {
3131
const isCurrentlyNegated = this.__flags.negate === true;
3232
this.__flags.negate = isNegated;
33-
this.assert(
34-
actualChanges.every((change, ind) =>
35-
change.eq(BigNumber.from(balanceChanges[ind]))
36-
),
37-
`Expected ${accountAddresses} to change balance by ${balanceChanges} wei, ` +
38-
`but it has changed by ${actualChanges} wei`,
39-
`Expected ${accountAddresses} to not change balance by ${balanceChanges} wei,`,
40-
balanceChanges.map((balanceChange) => balanceChange.toString()),
41-
actualChanges.map((actualChange) => actualChange.toString())
42-
);
33+
const margin = options?.errorMargin ? options.errorMargin : '0';
34+
if (BigNumber.from(margin).eq(0)) {
35+
this.assert(
36+
actualChanges.every((change, ind) =>
37+
change.lte(BigNumber.from(balanceChanges[ind]).add(margin)) &&
38+
change.gte(BigNumber.from(balanceChanges[ind]).sub(margin))
39+
),
40+
`Expected ${accountAddresses} to change balance by ${balanceChanges} wei, ` +
41+
`but it has changed by ${actualChanges} wei`,
42+
`Expected ${accountAddresses} to not change balance by ${balanceChanges} wei,`,
43+
balanceChanges.map((balanceChange) => balanceChange.toString()),
44+
actualChanges.map((actualChange) => actualChange.toString())
45+
);
46+
} else {
47+
actualChanges.forEach((change, ind) => {
48+
const low = BigNumber.from(balanceChanges[ind]).sub(margin);
49+
const high = BigNumber.from(balanceChanges[ind]).add(margin);
50+
this.assert(
51+
change.lte(high) &&
52+
change.gte(low),
53+
`Expected "${accountAddresses[ind]}" balance to change within [${[low, high]}] wei, ` +
54+
`but it has changed by ${change} wei`,
55+
`Expected "${accountAddresses[ind]}" balance to not change within [${[low, high]}] wei`,
56+
balanceChanges[ind],
57+
change
58+
);
59+
});
60+
}
4361
this.__flags.negate = isCurrentlyNegated;
4462
});
4563
this.then = derivedPromise.then.bind(derivedPromise);

‎waffle-chai/src/matchers/changeTokenBalance.ts

+26-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) {
77
this: any,
88
token: Contract,
99
account: Account | string,
10-
balanceChange: BigNumberish
10+
balanceChange: BigNumberish,
11+
errorMargin : BigNumberish
1112
) {
1213
callPromise(this);
1314
const isNegated = this.__flags.negate === true;
@@ -21,14 +22,30 @@ export function supportChangeTokenBalance(Assertion: Chai.AssertionStatic) {
2122
}).then(([actualChange, address]: [BigNumber, string]) => {
2223
const isCurrentlyNegated = this.__flags.negate === true;
2324
this.__flags.negate = isNegated;
24-
this.assert(
25-
actualChange.eq(BigNumber.from(balanceChange)),
26-
`Expected "${address}" to change balance by ${balanceChange} wei, ` +
27-
`but it has changed by ${actualChange} wei`,
28-
`Expected "${address}" to not change balance by ${balanceChange} wei,`,
29-
balanceChange,
30-
actualChange
31-
);
25+
if (errorMargin === undefined) errorMargin = '0';
26+
if (BigNumber.from(errorMargin).eq(0)) {
27+
this.assert(
28+
actualChange.lte(BigNumber.from(balanceChange).add(errorMargin)) &&
29+
actualChange.gte(BigNumber.from(balanceChange).sub(errorMargin)),
30+
`Expected "${address}" to change balance by ${balanceChange} wei, ` +
31+
`but it has changed by ${actualChange} wei`,
32+
`Expected "${address}" to not change balance by ${balanceChange} wei,`,
33+
balanceChange,
34+
actualChange
35+
);
36+
} else {
37+
const low = BigNumber.from(balanceChange).sub(errorMargin);
38+
const high = BigNumber.from(balanceChange).add(errorMargin);
39+
this.assert(
40+
actualChange.lte(high) &&
41+
actualChange.gte(low),
42+
`Expected "${address}" balance to change within [${[low, high]}] wei, ` +
43+
`but it has changed by ${actualChange} wei`,
44+
`Expected "${address}" balance to not change within [${[low, high]}] wei`,
45+
balanceChange,
46+
actualChange
47+
);
48+
}
3249
this.__flags.negate = isCurrentlyNegated;
3350
});
3451
this.then = derivedPromise.then.bind(derivedPromise);

‎waffle-chai/src/matchers/changeTokenBalances.ts

+30-11
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export function supportChangeTokenBalances(Assertion: Chai.AssertionStatic) {
77
this: any,
88
token: Contract,
99
accounts: (Account | string)[],
10-
balanceChanges: BigNumberish[]
10+
balanceChanges: BigNumberish[],
11+
errorMargin: BigNumberish
1112
) {
1213
callPromise(this);
1314
const isNegated = this.__flags.negate === true;
@@ -21,16 +22,34 @@ export function supportChangeTokenBalances(Assertion: Chai.AssertionStatic) {
2122
}).then(([actualChanges, accountAddresses]: [BigNumber[], string[]]) => {
2223
const isCurrentlyNegated = this.__flags.negate === true;
2324
this.__flags.negate = isNegated;
24-
this.assert(
25-
actualChanges.every((change, ind) =>
26-
change.eq(BigNumber.from(balanceChanges[ind]))
27-
),
28-
`Expected ${accountAddresses} to change balance by ${balanceChanges} wei, ` +
29-
`but it has changed by ${actualChanges} wei`,
30-
`Expected ${accountAddresses} to not change balance by ${balanceChanges} wei,`,
31-
balanceChanges.map((balanceChange) => balanceChange.toString()),
32-
actualChanges.map((actualChange) => actualChange.toString())
33-
);
25+
if (errorMargin === undefined) errorMargin = '0';
26+
if (BigNumber.from(errorMargin).eq(0)) {
27+
this.assert(
28+
actualChanges.every((change, ind) =>
29+
change.lte(BigNumber.from(balanceChanges[ind]).add(errorMargin)) &&
30+
change.gte(BigNumber.from(balanceChanges[ind]).sub(errorMargin))
31+
),
32+
`Expected ${accountAddresses} to change balance by ${balanceChanges} wei, ` +
33+
`but it has changed by ${actualChanges} wei`,
34+
`Expected ${accountAddresses} to not change balance by ${balanceChanges} wei,`,
35+
balanceChanges.map((balanceChange) => balanceChange.toString()),
36+
actualChanges.map((actualChange) => actualChange.toString())
37+
);
38+
} else {
39+
actualChanges.forEach((change, ind) => {
40+
const low = BigNumber.from(balanceChanges[ind]).sub(errorMargin);
41+
const high = BigNumber.from(balanceChanges[ind]).add(errorMargin);
42+
this.assert(
43+
change.lte(high) &&
44+
change.gte(low),
45+
`Expected "${accountAddresses[ind]}" balance to change within [${[low, high]}] wei, ` +
46+
`but it has changed by ${change} wei`,
47+
`Expected "${accountAddresses[ind]}" balance to not change within [${[low, high]}] wei`,
48+
balanceChanges[ind],
49+
change
50+
);
51+
});
52+
}
3453
this.__flags.negate = isCurrentlyNegated;
3554
});
3655
this.then = derivedPromise.then.bind(derivedPromise);

‎waffle-chai/src/matchers/misc/balance.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import {BigNumberish} from 'ethers';
12
import {ensure} from '../calledOnContract/utils';
23
import {Account, getAddressOf} from './account';
34

45
export interface BalanceChangeOptions {
56
includeFee?: boolean;
7+
errorMargin?: BigNumberish;
68
}
79

810
export function getAddresses(accounts: Account[]) {

‎waffle-chai/src/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ declare namespace Chai {
2323
changeBalances(accounts: any[], balances: any[]): AsyncAssertion;
2424
changeEtherBalance(account: any, balance: any, options?: any): AsyncAssertion;
2525
changeEtherBalances(accounts: any[], balances: any[], options?: any): AsyncAssertion;
26-
changeTokenBalance(token: any, account: any, balance: any): AsyncAssertion;
27-
changeTokenBalances(token: any, accounts: any[], balances: any[]): AsyncAssertion;
26+
changeTokenBalance(token: any, account: any, balance: any, errorMargin?: any): AsyncAssertion;
27+
changeTokenBalances(token: any, accounts: any[], balances: any[], errorMargin?: any): AsyncAssertion;
2828
calledOnContract(contract: any): void;
2929
calledOnContractWith(contract: any, parameters: any[]): void;
3030
}

‎waffle-chai/test/matchers/changeEtherBalanceTest.ts

+66
Original file line numberDiff line numberDiff line change
@@ -234,5 +234,71 @@ export const changeEtherBalanceTest = (
234234
).to.changeEtherBalance(contract, 200);
235235
});
236236
});
237+
238+
describe('Change balance, error margin', () => {
239+
it('positive', async () => {
240+
await expect(sender.sendTransaction({
241+
to: receiver.address,
242+
value: 200
243+
})).to.changeEtherBalance(receiver, 300, {errorMargin: 100});
244+
245+
await expect(sender.sendTransaction({
246+
to: receiver.address,
247+
value: 200
248+
})).to.changeEtherBalance(receiver, 100, {errorMargin: 100});
249+
});
250+
251+
it('negative', async () => {
252+
await expect(sender.sendTransaction({
253+
to: receiver.address,
254+
value: 200
255+
})).to.not.changeEtherBalance(receiver, 300, {errorMargin: 99});
256+
257+
await expect(sender.sendTransaction({
258+
to: receiver.address,
259+
value: 200
260+
})).to.not.changeEtherBalance(receiver, 100, {errorMargin: 99});
261+
});
262+
263+
describe('Throws', () => {
264+
it('too low', async () => {
265+
await expect(
266+
expect(await sender.sendTransaction({
267+
to: receiver.address,
268+
value: 200
269+
})).to.changeEtherBalance(receiver, 250, {errorMargin: 40})
270+
).to.be.eventually.rejectedWith(
271+
AssertionError,
272+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
273+
'but it has changed by 200 wei'
274+
);
275+
});
276+
277+
it('too high', async () => {
278+
await expect(
279+
expect(await sender.sendTransaction({
280+
to: receiver.address,
281+
value: 300
282+
})).to.changeEtherBalance(receiver, 250, {errorMargin: 40})
283+
).to.be.eventually.rejectedWith(
284+
AssertionError,
285+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
286+
'but it has changed by 300 wei'
287+
);
288+
});
289+
290+
it('negated', async () => {
291+
await expect(
292+
expect(await sender.sendTransaction({
293+
to: receiver.address,
294+
value: 250
295+
})).to.not.changeEtherBalance(receiver, 250, {errorMargin: 40})
296+
).to.be.eventually.rejectedWith(
297+
AssertionError,
298+
`Expected "${receiver.address}" balance to not change within [210,290] wei`
299+
);
300+
});
301+
});
302+
});
237303
});
238304
};

‎waffle-chai/test/matchers/changeEtherBalancesTest.ts

+69
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,73 @@ export const changeEtherBalancesTest = (
238238
});
239239
});
240240
});
241+
242+
describe('changeEtherBalances - error margin', () => {
243+
it('positive', async () => {
244+
await expect(sender.sendTransaction({
245+
to: receiver.address,
246+
value: 200
247+
})).to.changeEtherBalances([receiver, sender], [300, -300], {errorMargin: 100});
248+
});
249+
250+
it('negative', async () => {
251+
await expect(sender.sendTransaction({
252+
to: receiver.address,
253+
value: 200
254+
})).to.not.changeEtherBalances([receiver, sender], [300, -300], {errorMargin: 99});
255+
});
256+
257+
describe('Throws', () => {
258+
it('too high', async () => {
259+
await expect(
260+
expect(await sender.sendTransaction({
261+
to: receiver.address,
262+
value: 300
263+
})).to.changeEtherBalances([receiver, sender], [250, -250], {errorMargin: 40})
264+
).to.be.eventually.rejectedWith(
265+
AssertionError,
266+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
267+
'but it has changed by 300 wei'
268+
);
269+
});
270+
271+
it('too low', async () => {
272+
await expect(
273+
expect(await sender.sendTransaction({
274+
to: receiver.address,
275+
value: 200
276+
})).to.changeEtherBalances([receiver, sender], [250, -250], {errorMargin: 40})
277+
).to.be.eventually.rejectedWith(
278+
AssertionError,
279+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
280+
'but it has changed by 200 wei'
281+
);
282+
});
283+
284+
it('negated', async () => {
285+
await expect(
286+
expect(await sender.sendTransaction({
287+
to: receiver.address,
288+
value: 300
289+
})).to.not.changeEtherBalances([receiver, sender], [290, -290], {errorMargin: 40})
290+
).to.be.eventually.rejectedWith(
291+
AssertionError,
292+
`Expected "${receiver.address}" balance to not change within [250,330] wei`
293+
);
294+
});
295+
296+
it('second address', async () => {
297+
await expect(
298+
expect(await sender.sendTransaction({
299+
to: receiver.address,
300+
value: 200
301+
})).to.changeEtherBalances([receiver, sender], [210, -250], {errorMargin: 40})
302+
).to.be.eventually.rejectedWith(
303+
AssertionError,
304+
`Expected "${sender.address}" balance to change within [-290,-210] wei, ` +
305+
'but it has changed by -200 wei'
306+
);
307+
});
308+
});
309+
});
241310
};

0 commit comments

Comments
 (0)