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:
parent
e8d0fdf1a9
commit
e0ac15b3ba
|
@ -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()
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = async ({deployments, getNamedAccounts}) => {
|
||||
const { deployer } = await getNamedAccounts()
|
||||
await deployments.deploy('TestHelpers', { from: deployer })
|
||||
}
|
||||
|
||||
module.exports.tags = ["TestHelpers"];
|
Loading…
Reference in New Issue