Skip to content

Commit b54c6b9

Browse files
authoredJan 3, 2023
🗿 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
};

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

+58
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,62 @@ export const changeTokenBalanceTest = (provider: TestProvider) => {
9191
).to.changeTokenBalance(token, contract, 200);
9292
});
9393
});
94+
95+
describe('ChangeTokenBalance - error margin', () => {
96+
it('positive', async () => {
97+
await expect(token.transfer(receiver.address, 200))
98+
.to.changeTokenBalance(token, receiver, 100, 100);
99+
100+
await expect(token.transfer(receiver.address, 200))
101+
.to.changeTokenBalance(token, receiver, 300, 100);
102+
});
103+
104+
it('negative', async () => {
105+
await expect(token.transfer(receiver.address, 200))
106+
.to.not.changeTokenBalance(token, receiver, 100, 99);
107+
108+
await expect(token.transfer(receiver.address, 200))
109+
.to.not.changeTokenBalance(token, receiver, 300, 99);
110+
});
111+
112+
describe('Throws', () => {
113+
it('too high', async () => {
114+
await expect(
115+
116+
expect(await token.transfer(receiver.address, 300))
117+
.to.changeTokenBalance(token, receiver, 250, 40)
118+
119+
).to.be.eventually.rejectedWith(
120+
AssertionError,
121+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
122+
'but it has changed by 300 wei'
123+
);
124+
});
125+
126+
it('too low', async () => {
127+
await expect(
128+
129+
expect(await token.transfer(receiver.address, 200))
130+
.to.changeTokenBalance(token, receiver, 250, 40)
131+
132+
).to.be.eventually.rejectedWith(
133+
AssertionError,
134+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
135+
'but it has changed by 200 wei'
136+
);
137+
});
138+
139+
it('negated', async () => {
140+
await expect(
141+
142+
expect(await token.transfer(receiver.address, 260))
143+
.to.not.changeTokenBalance(token, receiver, 250, 40)
144+
145+
).to.be.eventually.rejectedWith(
146+
AssertionError,
147+
`Expected "${receiver.address}" balance to not change within [210,290] wei`
148+
);
149+
});
150+
});
151+
});
94152
};

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

+65
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,69 @@ export const changeTokenBalancesTest = (provider: TestProvider) => {
7474
);
7575
});
7676
});
77+
78+
describe('changeTokenBalances - error margin', () => {
79+
it('positive', async () => {
80+
await expect(token.transfer(receiver.address, 200))
81+
.to.changeTokenBalances(token, [receiver, sender], [100, -100], 100);
82+
});
83+
84+
it('negative', async () => {
85+
await expect(token.transfer(receiver.address, 200))
86+
.to.not.changeTokenBalances(token, [receiver, sender], [100, -100], 99);
87+
});
88+
89+
describe('Throws', () => {
90+
it('too low', async () => {
91+
await expect(
92+
93+
expect(await token.transfer(receiver.address, 200))
94+
.to.changeTokenBalances(token, [receiver, sender], [250, -250], 40)
95+
96+
).to.be.eventually.rejectedWith(
97+
AssertionError,
98+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
99+
'but it has changed by 200 wei'
100+
);
101+
});
102+
103+
it('too high', async () => {
104+
await expect(
105+
106+
expect(await token.transfer(receiver.address, 300))
107+
.to.changeTokenBalances(token, [receiver, sender], [250, -250], 40)
108+
109+
).to.be.eventually.rejectedWith(
110+
AssertionError,
111+
`Expected "${receiver.address}" balance to change within [210,290] wei, ` +
112+
'but it has changed by 300 wei'
113+
);
114+
});
115+
116+
it('negated', async () => {
117+
await expect(
118+
119+
expect(await token.transfer(receiver.address, 260))
120+
.to.not.changeTokenBalances(token, [receiver, sender], [250, -250], 40)
121+
122+
).to.be.eventually.rejectedWith(
123+
AssertionError,
124+
`Expected "${receiver.address}" balance to not change within [210,290] wei`
125+
);
126+
});
127+
128+
it('second address', async () => {
129+
await expect(
130+
131+
expect(await token.transfer(receiver.address, 200))
132+
.to.changeTokenBalances(token, [receiver, sender], [210, -250], 40)
133+
134+
).to.be.eventually.rejectedWith(
135+
AssertionError,
136+
`Expected "${sender.address}" balance to change within [-290,-210] wei, ` +
137+
'but it has changed by -200 wei'
138+
);
139+
});
140+
});
141+
});
77142
};

0 commit comments

Comments
 (0)
Please sign in to comment.