mirror of
https://github.com/codex-storage/nim-ethers.git
synced 2025-01-10 19:36:28 +00:00
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
53
testmodule/helpers.nim
Normal file
53
testmodule/helpers.nim
Normal 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
143
testmodule/testHelpers.nim
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
9
testnode/contracts/TestHelpers.sol
Normal file
9
testnode/contracts/TestHelpers.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
6
testnode/deploy/testhelpers.js
Normal file
6
testnode/deploy/testhelpers.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = async ({deployments, getNamedAccounts}) => {
|
||||||
|
const { deployer } = await getNamedAccounts()
|
||||||
|
await deployments.deploy('TestHelpers', { from: deployer })
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.tags = ["TestHelpers"];
|
Loading…
x
Reference in New Issue
Block a user