add revert helpers for testing

Add the following helpers to help detect transaction reverts:
1. `reverts`
2. `revertsWith`
3. `doesNotRevert`
4. `doesNotRevertWith`
This commit is contained in:
Eric Mastro 2022-09-15 20:38:25 +10:00 committed by Eric Mastro
parent e8d0fdf1a9
commit e0ac15b3ba
4 changed files with 211 additions and 0 deletions

53
testmodule/helpers.nim Normal file
View File

@ -0,0 +1,53 @@
import std/json
import std/strutils
import pkg/ethers
proc revertReason*(e: ref EthersError): string =
try:
let json = parseJson(e.msg)
var msg = json{"message"}.getStr
const revertPrefixes = @[
# hardhat
"Error: VM Exception while processing transaction: reverted with " &
"reason string ",
# ganache
"VM Exception while processing transaction: revert "
]
for prefix in revertPrefixes.items:
msg = msg.replace(prefix)
msg = msg.replace("\'")
return msg
except JsonParsingError:
return ""
template reverts*(body: untyped): untyped =
let asyncproc = proc(): Future[bool] {.async.} =
try:
body
return false
except EthersError:
return true
except CatchableError:
return false
waitFor asyncproc()
template revertsWith*(reason: string, body: untyped): untyped =
let asyncproc = proc(): Future[bool] {.async.} =
try:
body
return false
except EthersError as e:
return reason == revertReason(e)
except CatchableError as e:
return false
waitFor asyncproc()
template doesNotRevert*(body: untyped): untyped =
let asyncproc = proc(): Future[bool] {.async.} =
return not reverts(body)
waitFor asyncproc()
template doesNotRevertWith*(reason: string, body: untyped): untyped =
let asyncproc = proc(): Future[bool] {.async.} =
return not revertsWith(reason, body)
waitFor asyncproc()

143
testmodule/testHelpers.nim Normal file
View File

@ -0,0 +1,143 @@
import std/json
import std/strformat
import pkg/asynctest
import pkg/chronos
import pkg/ethers
import ./hardhat
import ./helpers
suite "Revert helpers":
let revertReason = "revert reason"
let rpcResponse = %* {
"message": "Error: VM Exception while processing transaction: " &
fmt"reverted with reason string '{revertReason}'"
}
test "can use block syntax async":
let ethCallAsync = proc() {.async.} =
raise newException(EthersError, $rpcResponse)
check:
reverts:
await ethCallAsync()
test "can use block syntax sync":
let ethCall = proc() =
raise newException(EthersError, $rpcResponse)
check:
reverts:
ethCall()
test "can use parameter syntax async":
let ethCallAsync = proc() {.async.} =
raise newException(EthersError, $rpcResponse)
check:
reverts (await ethCallAsync())
test "can use parameter syntax sync":
let ethCall = proc() =
raise newException(EthersError, $rpcResponse)
check:
reverts ethCall()
test "successfully checks revert reason async":
let ethCallAsync = proc() {.async.} =
raise newException(EthersError, $rpcResponse)
check:
revertsWith revertReason:
await ethCallAsync()
test "successfully checks revert reason sync":
let ethCall = proc() =
raise newException(EthersError, $rpcResponse)
check:
revertsWith revertReason:
ethCall()
test "correctly indicates there was no revert":
let ethCall = proc() = discard
check:
doesNotRevert:
ethCall()
test "only checks EthersErrors":
let ethCall = proc() =
raise newException(ValueError, $rpcResponse)
check:
doesNotRevert:
ethCall()
test "revertsWith is false when there is no revert":
let ethCall = proc() = discard
check:
doesNotRevertWith revertReason:
ethCall()
test "revertsWith is false when not an EthersError":
let ethCall = proc() =
raise newException(ValueError, $rpcResponse)
check:
doesNotRevertWith revertReason:
ethCall()
test "revertsWith is false when the revert reason doesn't match":
let ethCall = proc() =
raise newException(EthersError, "other reason")
check:
doesNotRevertWith revertReason:
ethCall()
test "revertsWith handles non-standard revert prefix":
let nonStdMsg = fmt"Provider VM Exception: reverted with {revertReason}"
let nonStdRpcResponse = %* { "message": nonStdMsg }
let ethCall = proc() =
raise newException(EthersError, $nonStdRpcResponse)
check:
revertsWith nonStdMsg:
ethCall()
type
TestHelpers* = ref object of Contract
method revertsWith*(self: TestHelpers,
revertReason: string) {.base, contract, view.}
suite "Revert helpers - current provider":
var helpersContract: TestHelpers
var provider: JsonRpcProvider
var snapshot: JsonNode
var accounts: seq[Address]
let revertReason = "revert reason"
setup:
provider = JsonRpcProvider.new("ws://127.0.0.1:8545")
snapshot = await provider.send("evm_snapshot")
accounts = await provider.listAccounts()
let deployment = readDeployment()
helpersContract = TestHelpers.new(!deployment.address(TestHelpers), provider)
teardown:
discard await provider.send("evm_revert", @[snapshot])
test "revert prefix is emitted from current provider":
check:
revertsWith revertReason:
await helpersContract.revertsWith(revertReason)

View File

@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestHelpers {
function revertsWith(string calldata revertReason) public pure {
require(false, revertReason);
}
}

View File

@ -0,0 +1,6 @@
module.exports = async ({deployments, getNamedAccounts}) => {
const { deployer } = await getNamedAccounts()
await deployments.deploy('TestHelpers', { from: deployer })
}
module.exports.tags = ["TestHelpers"];