@tevm/test-matchers
Version:
Vite test matchers for Tevm or EVM-related testing in TypeScript.
92 lines (81 loc) • 3.47 kB
text/typescript
import type { AbiError } from 'abitype'
import { AbiItem } from 'ox'
import type { Abi, Client, ContractErrorName, Hex } from 'viem'
import { isHex } from 'viem'
import type { MatcherResult } from '../../chainable/types.js'
import type { ContainsContractAbi, ContainsTransactionAny } from '../../common/types.js'
import { handleTransaction } from './handleTransaction.js'
import type { ToBeRevertedWithState } from './types.js'
export const toBeRevertedWithError = async <
TAbi extends Abi | undefined = Abi | undefined,
TErrorName extends TAbi extends Abi ? ContractErrorName<TAbi> : never = TAbi extends Abi
? ContractErrorName<TAbi>
: never,
TContract extends TAbi extends Abi ? ContainsContractAbi<TAbi> : never = TAbi extends Abi
? ContainsContractAbi<TAbi>
: never,
>(
received: ContainsTransactionAny | Promise<ContainsTransactionAny>,
client: Client,
contractOrErrorIdentifier: TContract | Hex | string,
errorName?: TErrorName,
): Promise<MatcherResult<ToBeRevertedWithState>> => {
const { error, isRevert, isError, revertReason, decodedRevertData, rawRevertData } = await handleTransaction(
received,
{ client },
)
if (isError) {
throw new Error('Expected transaction to be or not be reverted, but a different error was thrown', { cause: error })
}
const actualSelector = decodedRevertData
? AbiItem.getSelector(decodedRevertData.abiItem)
: rawRevertData?.slice(0, 10)
if (actualSelector === undefined) throw new Error('Could not get selector from revert data')
// Handle error signature or selector
if (typeof contractOrErrorIdentifier === 'string') {
const errorSelector = isHex(contractOrErrorIdentifier)
? contractOrErrorIdentifier
: AbiItem.getSelector(
contractOrErrorIdentifier.startsWith('error')
? contractOrErrorIdentifier
: `error ${contractOrErrorIdentifier}`,
)
const pass = isRevert && actualSelector === errorSelector
return {
pass,
actual: actualSelector,
expected: errorSelector,
message: () =>
pass
? `Expected transaction not to be reverted with ${contractOrErrorIdentifier.startsWith('0x') ? 'selector' : 'signature'} ${contractOrErrorIdentifier}, but it reverted ${revertReason ? `with:\n\n${revertReason}` : 'without reason'}`
: `Expected transaction to be reverted with ${contractOrErrorIdentifier.startsWith('0x') ? 'selector' : 'signature'} ${contractOrErrorIdentifier}`,
state: {
decodedRevertData,
rawRevertData,
},
}
}
// Contract + error name case
if (typeof errorName !== 'string') throw new Error('You need to provide an error name as a second argument')
const contract = contractOrErrorIdentifier
const errorAbi = contract.abi.find((item): item is AbiError => item.type === 'error' && item.name === errorName)
if (!errorAbi)
throw new Error(
`Error ${errorName} not found in contract ABI. Please make sure you've compiled the latest version before running the test.`,
)
const pass = isRevert && actualSelector === AbiItem.getSelector(errorAbi)
return {
pass,
actual: `error ${decodedRevertData?.errorName ?? rawRevertData?.slice(0, 10)}`,
expected: `error ${errorName}`,
message: () =>
pass
? `Expected transaction not to be reverted with error ${errorName}, but it reverted ${revertReason ? `with:\n\n${revertReason}` : 'without reason'}`
: `Expected transaction to be reverted with error ${errorName}`,
state: {
decodedRevertData,
rawRevertData,
contract,
},
}
}