170 lines
6.3 KiB
Nim

import std/importutils
import std/sequtils
import std/typetraits
import stew/byteutils
import pkg/asynctest/chronos/unittest
import pkg/serde
import pkg/questionable
import pkg/ethers/providers/jsonrpc
import pkg/ethers/providers/jsonrpc/errors
import pkg/ethers/erc20
import pkg/json_rpc/clients/httpclient
import ./mocks/mockHttpServer
import ../../examples
import ../../hardhat
suite "JSON RPC errors":
test "converts JSON RPC error to Nim error":
let error = %*{ "message": "some error" }
check JsonRpcProviderError.new(error).msg == "some error"
test "converts error data to bytes":
let error = %*{
"message": "VM Exception: reverted with 'some error'",
"data": "0xabcd"
}
check JsonRpcProviderError.new(error).data == some @[0xab'u8, 0xcd'u8]
test "converts nested error data to bytes":
let error = %*{
"message": "VM Exception: reverted with 'some error'",
"data": {
"message": "VM Exception: reverted with 'some error'",
"data": "0xabcd"
}
}
check JsonRpcProviderError.new(error).data == some @[0xab'u8, 0xcd'u8]
type
TestToken = ref object of Erc20Token
method mint(token: TestToken, holder: Address, amount: UInt256): Confirmable {.base, contract.}
suite "Network errors - HTTP":
var provider: JsonRpcProvider
var mockServer: MockHttpServer
var token: TestToken
setup:
mockServer = MockHttpServer.init(initTAddress("127.0.0.1:0"))
mockServer.start()
provider = JsonRpcProvider.new("http://" & $mockServer.address)
let deployment = readDeployment()
token = TestToken.new(!deployment.address(TestToken), provider)
teardown:
await provider.close()
await mockServer.stop()
proc registerRpcMethods(response: RpcResponse) =
mockServer.registerRpcResponse("eth_accounts", response)
mockServer.registerRpcResponse("eth_call", response)
mockServer.registerRpcResponse("eth_sendTransaction", response)
mockServer.registerRpcResponse("eth_sendRawTransaction", response)
mockServer.registerRpcResponse("eth_newBlockFilter", response)
mockServer.registerRpcResponse("eth_newFilter", response)
# mockServer.registerRpcResponse("eth_subscribe", response) # TODO: handle
# eth_subscribe for websockets
proc testCustomResponse(errorName: string, responseHttpCode: HttpCode, responseText: string, errorType: type CatchableError) =
let response = proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
try:
return await request.respond(responseHttpCode, responseText)
except HttpWriteError as exc:
return defaultResponse(exc)
let testNamePrefix = errorName & " error response is converted to " & errorType.name & " for "
test testNamePrefix & "sending a manual RPC method request":
registerRpcMethods(response)
expect errorType:
discard await provider.send("eth_accounts")
test testNamePrefix & "calling a provider method that converts errors when calling a generated RPC request":
registerRpcMethods(response)
expect errorType:
discard await provider.listAccounts()
test testNamePrefix & "calling a view method of a contract":
registerRpcMethods(response)
expect errorType:
discard await token.balanceOf(Address.example)
test testNamePrefix & "calling a contract method that executes a transaction":
registerRpcMethods(response)
expect errorType:
token = TestToken.new(token.address, provider.getSigner())
discard await token.mint(
Address.example, 100.u256,
TransactionOverrides(gasLimit: 100.u256.some, chainId: 1.u256.some)
)
test testNamePrefix & "sending a manual transaction":
registerRpcMethods(response)
expect errorType:
let tx = Transaction.example
discard await provider.getSigner().sendTransaction(tx)
test testNamePrefix & "sending a raw transaction":
registerRpcMethods(response)
expect errorType:
const pk_with_funds = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
let wallet = !Wallet.new(pk_with_funds)
let tx = Transaction(
to: wallet.address,
nonce: some 0.u256,
chainId: some 31337.u256,
gasPrice: some 1_000_000_000.u256,
gasLimit: some 21_000.u256,
)
let signedTx = await wallet.signTransaction(tx)
discard await provider.sendTransaction(signedTx)
test testNamePrefix & "subscribing to blocks":
registerRpcMethods(response)
expect errorType:
let emptyHandler = proc(blckResult: ?!Block) = discard
discard await provider.subscribe(emptyHandler)
test testNamePrefix & "subscribing to logs":
registerRpcMethods(response)
expect errorType:
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
let emptyHandler = proc(log: ?!Log) = discard
discard await provider.subscribe(filter, emptyHandler)
testCustomResponse("429", Http429, "Too many requests", HttpRequestLimitError)
testCustomResponse("408", Http408, "Request timed out", HttpRequestTimeoutError)
testCustomResponse("non-429", Http500, "Server error", JsonRpcProviderError)
test "raises RpcNetworkError when reading response headers times out":
privateAccess(JsonRpcProvider)
privateAccess(RpcHttpClient)
let responseTimeout = proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
try:
await sleepAsync(5.minutes)
return await request.respond(Http200, "OK")
except HttpWriteError as exc:
return defaultResponse(exc)
let rpcClient = await provider.client
let client: RpcHttpClient = (RpcHttpClient)(rpcClient)
client.httpSession = HttpSessionRef.new(headersTimeout = 1.millis)
mockServer.registerRpcResponse("eth_accounts", responseTimeout)
expect RpcNetworkError:
discard await provider.send("eth_accounts")
test "raises RpcNetworkError when connection is closed":
await mockServer.stop()
expect RpcNetworkError:
discard await provider.send("eth_accounts")
# We don't need to recreate each and every possible exception condition (eg
# "Connection timed out"), as they are all wrapped up in RpcPostError and
# converted. The tests above cover this conversion.