Skip to content

Commit

Permalink
Merge pull request #5219 from NomicFoundation/predicate-functions-bal…
Browse files Browse the repository at this point in the history
…ance-matchers

Accept predicate functions in balance and token balance change matchers
  • Loading branch information
schaable committed May 16, 2024
2 parents 029680d + 9b98a50 commit 974bec0
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 112 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-ladybugs-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/hardhat-chai-matchers": patch
---

Accept predicate functions in the `changeEtherBalance`, `changeEthersBalances`, `changeTokenBalance` and `changeTokenBalances` matchers (thanks @SevenSwen and @k06a!)
23 changes: 15 additions & 8 deletions packages/hardhat-chai-matchers/src/internal/changeEtherBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function supportChangeEtherBalance(
function (
this: any,
account: Addressable | string,
balanceChange: BigNumberish,
balanceChange: BigNumberish | ((change: bigint) => boolean),
options?: BalanceChangeOptions
) {
const { toBigInt } = require("ethers") as typeof EthersT;
Expand All @@ -41,13 +41,20 @@ export function supportChangeEtherBalance(
]) => {
const assert = buildAssert(negated, checkBalanceChange);

const expectedChange = toBigInt(balanceChange);

assert(
actualChange === expectedChange,
`Expected the ether balance of "${address}" to change by ${balanceChange.toString()} wei, but it changed by ${actualChange.toString()} wei`,
`Expected the ether balance of "${address}" NOT to change by ${balanceChange.toString()} wei, but it did`
);
if (typeof balanceChange === "function") {
assert(
balanceChange(actualChange),
`Expected the ether balance change of "${address}" to satisfy the predicate, but it didn't (balance change: ${actualChange.toString()} wei)`,
`Expected the ether balance change of "${address}" to NOT satisfy the predicate, but it did (balance change: ${actualChange.toString()} wei)`
);
} else {
const expectedChange = toBigInt(balanceChange);
assert(
actualChange === expectedChange,
`Expected the ether balance of "${address}" to change by ${balanceChange.toString()} wei, but it changed by ${actualChange.toString()} wei`,
`Expected the ether balance of "${address}" NOT to change by ${balanceChange.toString()} wei, but it did`
);
}
};

const derivedPromise = Promise.all([
Expand Down
112 changes: 72 additions & 40 deletions packages/hardhat-chai-matchers/src/internal/changeEtherBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function supportChangeEtherBalances(
function (
this: any,
accounts: Array<Addressable | string>,
balanceChanges: BigNumberish[],
balanceChanges: BigNumberish[] | ((changes: bigint[]) => boolean),
options?: BalanceChangeOptions
) {
const { toBigInt } = require("ethers") as typeof EthersT;
Expand All @@ -37,51 +37,61 @@ export function supportChangeEtherBalances(
chaiUtils
);

validateInput(this._obj, accounts, balanceChanges);

const checkBalanceChanges = ([actualChanges, accountAddresses]: [
bigint[],
string[]
]) => {
const assert = buildAssert(negated, checkBalanceChanges);

assert(
actualChanges.every(
(change, ind) => change === toBigInt(balanceChanges[ind])
),
() => {
const lines: string[] = [];
actualChanges.forEach((change: bigint, i) => {
if (change !== toBigInt(balanceChanges[i])) {
lines.push(
`Expected the ether balance of ${
accountAddresses[i]
} (the ${ordinal(
i + 1
)} address in the list) to change by ${balanceChanges[
i
].toString()} wei, but it changed by ${change.toString()} wei`
);
}
});
return lines.join("\n");
},
() => {
const lines: string[] = [];
actualChanges.forEach((change: bigint, i) => {
if (change === toBigInt(balanceChanges[i])) {
lines.push(
`Expected the ether balance of ${
accountAddresses[i]
} (the ${ordinal(
i + 1
)} address in the list) NOT to change by ${balanceChanges[
i
].toString()} wei, but it did`
);
}
});
return lines.join("\n");
}
);
if (typeof balanceChanges === "function") {
assert(
balanceChanges(actualChanges),
"Expected the balance changes of the accounts to satisfy the predicate, but they didn't",
"Expected the balance changes of the accounts to NOT satisfy the predicate, but they did"
);
} else {
assert(
actualChanges.every(
(change, ind) => change === toBigInt(balanceChanges[ind])
),
() => {
const lines: string[] = [];
actualChanges.forEach((change: bigint, i) => {
if (change !== toBigInt(balanceChanges[i])) {
lines.push(
`Expected the ether balance of ${
accountAddresses[i]
} (the ${ordinal(
i + 1
)} address in the list) to change by ${balanceChanges[
i
].toString()} wei, but it changed by ${change.toString()} wei`
);
}
});
return lines.join("\n");
},
() => {
const lines: string[] = [];
actualChanges.forEach((change: bigint, i) => {
if (change === toBigInt(balanceChanges[i])) {
lines.push(
`Expected the ether balance of ${
accountAddresses[i]
} (the ${ordinal(
i + 1
)} address in the list) NOT to change by ${balanceChanges[
i
].toString()} wei, but it did`
);
}
});
return lines.join("\n");
}
);
}
};

const derivedPromise = Promise.all([
Expand All @@ -96,6 +106,28 @@ export function supportChangeEtherBalances(
);
}

function validateInput(
obj: any,
accounts: Array<Addressable | string>,
balanceChanges: EthersT.BigNumberish[] | ((changes: bigint[]) => boolean)
) {
try {
if (
Array.isArray(balanceChanges) &&
accounts.length !== balanceChanges.length
) {
throw new Error(
`The number of accounts (${accounts.length}) is different than the number of expected balance changes (${balanceChanges.length})`
);
}
} catch (e) {
// if the input validation fails, we discard the subject since it could
// potentially be a rejected promise
Promise.resolve(obj).catch(() => {});
throw e;
}
}

export async function getBalanceChanges(
transaction: TransactionResponse | Promise<TransactionResponse>,
accounts: Array<Addressable | string>,
Expand Down
67 changes: 43 additions & 24 deletions packages/hardhat-chai-matchers/src/internal/changeTokenBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function supportChangeTokenBalance(
this: any,
token: Token,
account: Addressable | string,
balanceChange: EthersT.BigNumberish
balanceChange: EthersT.BigNumberish | ((change: bigint) => boolean)
) {
const ethers = require("ethers") as typeof EthersT;

Expand All @@ -66,11 +66,19 @@ export function supportChangeTokenBalance(
]) => {
const assert = buildAssert(negated, checkBalanceChange);

assert(
actualChange === ethers.toBigInt(balanceChange),
`Expected the balance of ${tokenDescription} tokens for "${address}" to change by ${balanceChange.toString()}, but it changed by ${actualChange.toString()}`,
`Expected the balance of ${tokenDescription} tokens for "${address}" NOT to change by ${balanceChange.toString()}, but it did`
);
if (typeof balanceChange === "function") {
assert(
balanceChange(actualChange),
`Expected the balance of ${tokenDescription} tokens for "${address}" to satisfy the predicate, but it didn't (token balance change: ${actualChange.toString()} wei)`,
`Expected the balance of ${tokenDescription} tokens for "${address}" to NOT satisfy the predicate, but it did (token balance change: ${actualChange.toString()} wei)`
);
} else {
assert(
actualChange === ethers.toBigInt(balanceChange),
`Expected the balance of ${tokenDescription} tokens for "${address}" to change by ${balanceChange.toString()}, but it changed by ${actualChange.toString()}`,
`Expected the balance of ${tokenDescription} tokens for "${address}" NOT to change by ${balanceChange.toString()}, but it did`
);
}
};

const derivedPromise = Promise.all([
Expand All @@ -92,7 +100,7 @@ export function supportChangeTokenBalance(
this: any,
token: Token,
accounts: Array<Addressable | string>,
balanceChanges: EthersT.BigNumberish[]
balanceChanges: EthersT.BigNumberish[] | ((changes: bigint[]) => boolean)
) {
const ethers = require("ethers") as typeof EthersT;

Expand Down Expand Up @@ -124,21 +132,29 @@ export function supportChangeTokenBalance(
]: [bigint[], string[], string]) => {
const assert = buildAssert(negated, checkBalanceChanges);

assert(
actualChanges.every(
(change, ind) => change === ethers.toBigInt(balanceChanges[ind])
),
`Expected the balances of ${tokenDescription} tokens for ${
addresses as any
} to change by ${
balanceChanges as any
}, respectively, but they changed by ${actualChanges as any}`,
`Expected the balances of ${tokenDescription} tokens for ${
addresses as any
} NOT to change by ${
balanceChanges as any
}, respectively, but they did`
);
if (typeof balanceChanges === "function") {
assert(
balanceChanges(actualChanges),
`Expected the balance changes of ${tokenDescription} to satisfy the predicate, but they didn't`,
`Expected the balance changes of ${tokenDescription} to NOT satisfy the predicate, but they did`
);
} else {
assert(
actualChanges.every(
(change, ind) => change === ethers.toBigInt(balanceChanges[ind])
),
`Expected the balances of ${tokenDescription} tokens for ${
addresses as any
} to change by ${
balanceChanges as any
}, respectively, but they changed by ${actualChanges as any}`,
`Expected the balances of ${tokenDescription} tokens for ${
addresses as any
} NOT to change by ${
balanceChanges as any
}, respectively, but they did`
);
}
};

const derivedPromise = Promise.all([
Expand All @@ -159,12 +175,15 @@ function validateInput(
obj: any,
token: Token,
accounts: Array<Addressable | string>,
balanceChanges: EthersT.BigNumberish[]
balanceChanges: EthersT.BigNumberish[] | ((changes: bigint[]) => boolean)
) {
try {
checkToken(token, CHANGE_TOKEN_BALANCES_MATCHER);

if (accounts.length !== balanceChanges.length) {
if (
Array.isArray(balanceChanges) &&
accounts.length !== balanceChanges.length
) {
throw new Error(
`The number of accounts (${accounts.length}) is different than the number of expected balance changes (${balanceChanges.length})`
);
Expand Down
4 changes: 2 additions & 2 deletions packages/hardhat-chai-matchers/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ declare namespace Chai {
): AsyncAssertion;
changeEtherBalances(
accounts: any[],
balances: any[],
balances: any[] | ((changes: bigint[]) => boolean),
options?: any
): AsyncAssertion;
changeTokenBalance(token: any, account: any, balance: any): AsyncAssertion;
changeTokenBalances(
token: any,
account: any[],
balance: any[]
balance: any[] | ((changes: bigint[]) => boolean)
): AsyncAssertion;
}

Expand Down

0 comments on commit 974bec0

Please sign in to comment.