mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-03 22:23:06 +00:00
Compare commits
No commits in common. "main" and "v0.10.1" have entirely different histories.
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@ -1,11 +1,6 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on: [push, pull_request, workflow_dispatch]
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@ -13,12 +8,10 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
nim: [2.0.14, 2.2.2]
|
nim: [1.6.20, stable]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
|
|
||||||
- name: Install Nim
|
- name: Install Nim
|
||||||
uses: iffy/install-nim@v4
|
uses: iffy/install-nim@v4
|
||||||
@ -39,7 +32,7 @@ jobs:
|
|||||||
run: npm start &
|
run: npm start &
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: nimble install -y --maximumtaggedversions=2
|
run: nimble install -y
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: nimble test -y
|
run: nimble test -y
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,4 +6,3 @@ nimble.paths
|
|||||||
.idea
|
.idea
|
||||||
.nimble
|
.nimble
|
||||||
.envrc
|
.envrc
|
||||||
nimbledeps
|
|
||||||
|
|||||||
29
Readme.md
29
Readme.md
@ -14,13 +14,7 @@ Use the [Nimble][2] package manager to add `ethers` to an existing
|
|||||||
project. Add the following to its .nimble file:
|
project. Add the following to its .nimble file:
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
requires "ethers >= 2.0.0 & < 3.0.0"
|
requires "ethers >= 0.10.0 & < 0.11.0"
|
||||||
```
|
|
||||||
|
|
||||||
To avoid conflicts with previous versions of `contractabi`, use the following command to install dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nimble install --maximumtaggedversions=2
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
@ -137,22 +131,14 @@ You can now subscribe to Transfer events by calling `subscribe` on the contract
|
|||||||
instance.
|
instance.
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
proc handleTransfer(transferResult: ?!Transfer) =
|
proc handleTransfer(transfer: Transfer) =
|
||||||
if transferResult.isOk:
|
echo "received transfer: ", transfer
|
||||||
echo "received transfer: ", transferResult.value
|
|
||||||
else:
|
|
||||||
echo "error during transfer: ", transferResult.error.msg
|
|
||||||
|
|
||||||
let subscription = await token.subscribe(Transfer, handleTransfer)
|
let subscription = await token.subscribe(Transfer, handleTransfer)
|
||||||
```
|
```
|
||||||
|
|
||||||
When a Transfer event is emitted, the `handleTransfer` proc that you just
|
When a Transfer event is emitted, the `handleTransfer` proc that you just
|
||||||
defined will be called with a [Result](https://github.com/arnetheduck/nim-results) type
|
defined will be called.
|
||||||
which contains the event value.
|
|
||||||
|
|
||||||
In case there is some underlying error in the event subscription, the handler will
|
|
||||||
be called as well, but the Result will contain error instead, so do proper error
|
|
||||||
management in your handlers.
|
|
||||||
|
|
||||||
When you're no longer interested in these events, you can unsubscribe:
|
When you're no longer interested in these events, you can unsubscribe:
|
||||||
|
|
||||||
@ -204,13 +190,6 @@ This library ships with some optional modules that provides convenience utilitie
|
|||||||
|
|
||||||
- `ethers/erc20` module provides you with ERC20 token implementation and its events
|
- `ethers/erc20` module provides you with ERC20 token implementation and its events
|
||||||
|
|
||||||
Hardhat websockets workaround
|
|
||||||
---------
|
|
||||||
|
|
||||||
If you're working with Hardhat, you might encounter an issue where [websocket subscriptions stop working after 5 minutes](https://github.com/NomicFoundation/hardhat/issues/2053).
|
|
||||||
|
|
||||||
This library provides a workaround using the compile time `ws_resubscribe` symbol. When this symbol is defined and set to a value greater than 0, websocket subscriptions will automatically resubscribe after the amount of time (in seconds) specified. The recommended value is 240 seconds (4 minutes), eg `--define:ws_resubscribe=240`.
|
|
||||||
|
|
||||||
Contribution
|
Contribution
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,3 @@
|
|||||||
when fileExists("nimble.paths"):
|
when fileExists("nimble.paths"):
|
||||||
include "nimble.paths"
|
include "nimble.paths"
|
||||||
# end Nimble config
|
# end Nimble config
|
||||||
|
|
||||||
when (NimMajor, NimMinor) >= (2, 0):
|
|
||||||
--mm:refc
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import ./ethers/provider
|
import ./ethers/provider
|
||||||
import ./ethers/signer
|
import ./ethers/signer
|
||||||
import ./ethers/providers/jsonrpc
|
import ./ethers/providers/jsonrpc
|
||||||
import ./ethers/contracts
|
import ./ethers/contract
|
||||||
import ./ethers/wallet
|
import ./ethers/wallet
|
||||||
|
|
||||||
export provider
|
export provider
|
||||||
export signer
|
export signer
|
||||||
export jsonrpc
|
export jsonrpc
|
||||||
export contracts
|
export contract
|
||||||
export wallet
|
export wallet
|
||||||
@ -1,18 +1,18 @@
|
|||||||
version = "2.1.0"
|
version = "0.10.1"
|
||||||
author = "Nim Ethers Authors"
|
author = "Nim Ethers Authors"
|
||||||
description = "library for interacting with Ethereum"
|
description = "library for interacting with Ethereum"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
requires "nim >= 2.0.14"
|
requires "nim >= 1.6.0"
|
||||||
requires "chronicles >= 0.10.3 & < 0.13.0"
|
requires "chronicles >= 0.10.3 & < 0.11.0"
|
||||||
requires "chronos >= 4.0.4 & < 4.1.0"
|
requires "chronos >= 4.0.0 & < 4.1.0"
|
||||||
requires "contractabi >= 0.7.2 & < 0.8.0"
|
requires "contractabi >= 0.6.0 & < 0.7.0"
|
||||||
requires "questionable >= 0.10.2 & < 0.11.0"
|
requires "questionable >= 0.10.2 & < 0.11.0"
|
||||||
requires "json_rpc >= 0.5.0 & < 0.6.0"
|
requires "https://github.com/codex-storage/nim-json-rpc >= 0.5.0 & < 0.6.0"
|
||||||
requires "serde >= 1.2.1 & < 1.3.0"
|
requires "serde >= 1.2.1 & < 1.3.0"
|
||||||
requires "stint >= 0.8.1 & < 0.9.0"
|
requires "https://github.com/codex-storage/nim-stint-versioned >= 1.0.0 & < 2.0.0"
|
||||||
requires "stew >= 0.2.0"
|
requires "https://github.com/codex-storage/nim-stew-versioned >= 1.0.0 & < 2.0.0"
|
||||||
requires "eth >= 0.6.0 & < 0.10.0"
|
requires "https://github.com/codex-storage/nim-eth-versioned >= 1.0.0 & < 2.0.0"
|
||||||
|
|
||||||
task test, "Run the test suite":
|
task test, "Run the test suite":
|
||||||
# exec "nimble install -d -y"
|
# exec "nimble install -d -y"
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import pkg/stint
|
import pkg/stint
|
||||||
import pkg/questionable
|
|
||||||
|
|
||||||
{.push raises:[].}
|
{.push raises:[].}
|
||||||
|
|
||||||
@ -42,10 +41,3 @@ func `==`*(a, b: BlockTag): bool =
|
|||||||
a.stringValue == b.stringValue
|
a.stringValue == b.stringValue
|
||||||
of numberBlockTag:
|
of numberBlockTag:
|
||||||
a.numberValue == b.numberValue
|
a.numberValue == b.numberValue
|
||||||
|
|
||||||
func number*(blockTag: BlockTag): ?UInt256 =
|
|
||||||
case blockTag.kind
|
|
||||||
of stringBlockTag:
|
|
||||||
UInt256.none
|
|
||||||
of numberBlockTag:
|
|
||||||
blockTag.numberValue.some
|
|
||||||
|
|||||||
376
ethers/contract.nim
Normal file
376
ethers/contract.nim
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
import pkg/serde
|
||||||
|
import std/macros
|
||||||
|
import std/sequtils
|
||||||
|
import pkg/chronicles
|
||||||
|
import pkg/chronos
|
||||||
|
import pkg/contractabi
|
||||||
|
import ./basics
|
||||||
|
import ./provider
|
||||||
|
import ./signer
|
||||||
|
import ./events
|
||||||
|
import ./errors
|
||||||
|
import ./errors/conversion
|
||||||
|
import ./fields
|
||||||
|
|
||||||
|
export basics
|
||||||
|
export provider
|
||||||
|
export events
|
||||||
|
export errors.SolidityError
|
||||||
|
export errors.errors
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "ethers contract"
|
||||||
|
|
||||||
|
type
|
||||||
|
Contract* = ref object of RootObj
|
||||||
|
provider: Provider
|
||||||
|
signer: ?Signer
|
||||||
|
address: Address
|
||||||
|
TransactionOverrides* = ref object of RootObj
|
||||||
|
nonce*: ?UInt256
|
||||||
|
chainId*: ?UInt256
|
||||||
|
gasPrice*: ?UInt256
|
||||||
|
maxFee*: ?UInt256
|
||||||
|
maxPriorityFee*: ?UInt256
|
||||||
|
gasLimit*: ?UInt256
|
||||||
|
CallOverrides* = ref object of TransactionOverrides
|
||||||
|
blockTag*: ?BlockTag
|
||||||
|
ContractError* = object of EthersError
|
||||||
|
Confirmable* = object
|
||||||
|
response*: ?TransactionResponse
|
||||||
|
convert*: ConvertCustomErrors
|
||||||
|
EventHandler*[E: Event] = proc(event: E) {.gcsafe, raises:[].}
|
||||||
|
|
||||||
|
func new*(ContractType: type Contract,
|
||||||
|
address: Address,
|
||||||
|
provider: Provider): ContractType =
|
||||||
|
ContractType(provider: provider, address: address)
|
||||||
|
|
||||||
|
func new*(ContractType: type Contract,
|
||||||
|
address: Address,
|
||||||
|
signer: Signer): ContractType =
|
||||||
|
ContractType(signer: some signer, provider: signer.provider, address: address)
|
||||||
|
|
||||||
|
func connect*[T: Contract](contract: T, provider: Provider | Signer): T =
|
||||||
|
T.new(contract.address, provider)
|
||||||
|
|
||||||
|
func provider*(contract: Contract): Provider =
|
||||||
|
contract.provider
|
||||||
|
|
||||||
|
func signer*(contract: Contract): ?Signer =
|
||||||
|
contract.signer
|
||||||
|
|
||||||
|
func address*(contract: Contract): Address =
|
||||||
|
contract.address
|
||||||
|
|
||||||
|
template raiseContractError(message: string) =
|
||||||
|
raise newException(ContractError, message)
|
||||||
|
|
||||||
|
proc createTransaction(contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
overrides = TransactionOverrides()): Transaction =
|
||||||
|
let selector = selector(function, typeof parameters).toArray
|
||||||
|
let data = @selector & AbiEncoder.encode(parameters)
|
||||||
|
Transaction(
|
||||||
|
to: contract.address,
|
||||||
|
data: data,
|
||||||
|
nonce: overrides.nonce,
|
||||||
|
chainId: overrides.chainId,
|
||||||
|
gasPrice: overrides.gasPrice,
|
||||||
|
maxFee: overrides.maxFee,
|
||||||
|
maxPriorityFee: overrides.maxPriorityFee,
|
||||||
|
gasLimit: overrides.gasLimit,
|
||||||
|
)
|
||||||
|
|
||||||
|
proc decodeResponse(T: type, bytes: seq[byte]): T =
|
||||||
|
without decoded =? AbiDecoder.decode(bytes, T):
|
||||||
|
raiseContractError "unable to decode return value as " & $T
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
proc call(provider: Provider,
|
||||||
|
transaction: Transaction,
|
||||||
|
overrides: TransactionOverrides): Future[seq[byte]] {.async: (raises: [ProviderError]).} =
|
||||||
|
if overrides of CallOverrides and
|
||||||
|
blockTag =? CallOverrides(overrides).blockTag:
|
||||||
|
await provider.call(transaction, blockTag)
|
||||||
|
else:
|
||||||
|
await provider.call(transaction)
|
||||||
|
|
||||||
|
proc call(contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
overrides = TransactionOverrides()) {.async: (raises: [ProviderError, SignerError]).} =
|
||||||
|
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||||
|
|
||||||
|
if signer =? contract.signer and transaction.sender.isNone:
|
||||||
|
transaction.sender = some(await signer.getAddress())
|
||||||
|
|
||||||
|
discard await contract.provider.call(transaction, overrides)
|
||||||
|
|
||||||
|
proc call(contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
ReturnType: type,
|
||||||
|
overrides = TransactionOverrides()): Future[ReturnType] {.async: (raises: [ProviderError, SignerError, ContractError]).} =
|
||||||
|
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||||
|
|
||||||
|
if signer =? contract.signer and transaction.sender.isNone:
|
||||||
|
transaction.sender = some(await signer.getAddress())
|
||||||
|
|
||||||
|
let response = await contract.provider.call(transaction, overrides)
|
||||||
|
return decodeResponse(ReturnType, response)
|
||||||
|
|
||||||
|
proc send(
|
||||||
|
contract: Contract,
|
||||||
|
function: string,
|
||||||
|
parameters: tuple,
|
||||||
|
overrides = TransactionOverrides()
|
||||||
|
): Future[?TransactionResponse] {.async: (raises: [AsyncLockError, CancelledError, CatchableError]).} =
|
||||||
|
|
||||||
|
if signer =? contract.signer:
|
||||||
|
withLock(signer):
|
||||||
|
let transaction = createTransaction(contract, function, parameters, overrides)
|
||||||
|
let populated = await signer.populateTransaction(transaction)
|
||||||
|
trace "sending contract transaction", function, params = $parameters
|
||||||
|
let txResp = await signer.sendTransaction(populated)
|
||||||
|
return txResp.some
|
||||||
|
else:
|
||||||
|
await call(contract, function, parameters, overrides)
|
||||||
|
return TransactionResponse.none
|
||||||
|
|
||||||
|
func getParameterTuple(procedure: NimNode): NimNode =
|
||||||
|
let parameters = procedure[3]
|
||||||
|
var tupl = newNimNode(nnkTupleConstr, parameters)
|
||||||
|
for parameter in parameters[2..^1]:
|
||||||
|
for name in parameter[0..^3]:
|
||||||
|
tupl.add name
|
||||||
|
return tupl
|
||||||
|
|
||||||
|
func getErrorTypes(procedure: NimNode): NimNode =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
var tupl = newNimNode(nnkTupleConstr)
|
||||||
|
for pragma in pragmas:
|
||||||
|
if pragma.kind == nnkExprColonExpr:
|
||||||
|
if pragma[0].eqIdent "errors":
|
||||||
|
pragma[1].expectKind(nnkBracket)
|
||||||
|
for error in pragma[1]:
|
||||||
|
tupl.add error
|
||||||
|
if tupl.len == 0:
|
||||||
|
quote do: tuple[]
|
||||||
|
else:
|
||||||
|
tupl
|
||||||
|
|
||||||
|
func isGetter(procedure: NimNode): bool =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
for pragma in pragmas:
|
||||||
|
if pragma.eqIdent "getter":
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
func isConstant(procedure: NimNode): bool =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
for pragma in pragmas:
|
||||||
|
if pragma.eqIdent "view":
|
||||||
|
return true
|
||||||
|
elif pragma.eqIdent "pure":
|
||||||
|
return true
|
||||||
|
elif pragma.eqIdent "getter":
|
||||||
|
return true
|
||||||
|
false
|
||||||
|
|
||||||
|
func isMultipleReturn(returnType: NimNode): bool =
|
||||||
|
(returnType.kind == nnkPar and returnType.len > 1) or
|
||||||
|
(returnType.kind == nnkTupleConstr) or
|
||||||
|
(returnType.kind == nnkTupleTy)
|
||||||
|
|
||||||
|
func addOverrides(procedure: var NimNode) =
|
||||||
|
procedure[3].add(
|
||||||
|
newIdentDefs(
|
||||||
|
ident("overrides"),
|
||||||
|
newEmptyNode(),
|
||||||
|
quote do: TransactionOverrides()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func addContractCall(procedure: var NimNode) =
|
||||||
|
let contract = procedure[3][1][0]
|
||||||
|
let function = $basename(procedure[0])
|
||||||
|
let parameters = getParameterTuple(procedure)
|
||||||
|
let returnType = procedure[3][0]
|
||||||
|
let isGetter = procedure.isGetter
|
||||||
|
|
||||||
|
procedure.addOverrides()
|
||||||
|
let errors = getErrorTypes(procedure)
|
||||||
|
|
||||||
|
func call: NimNode =
|
||||||
|
if returnType.kind == nnkEmpty:
|
||||||
|
quote:
|
||||||
|
await call(`contract`, `function`, `parameters`, overrides)
|
||||||
|
elif returnType.isMultipleReturn or isGetter:
|
||||||
|
quote:
|
||||||
|
return await call(
|
||||||
|
`contract`, `function`, `parameters`, `returnType`, overrides
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
quote:
|
||||||
|
# solidity functions return a tuple, so wrap return type in a tuple
|
||||||
|
let tupl = await call(
|
||||||
|
`contract`, `function`, `parameters`, (`returnType`,), overrides
|
||||||
|
)
|
||||||
|
return tupl[0]
|
||||||
|
|
||||||
|
func send: NimNode =
|
||||||
|
if returnType.kind == nnkEmpty:
|
||||||
|
quote:
|
||||||
|
discard await send(`contract`, `function`, `parameters`, overrides)
|
||||||
|
else:
|
||||||
|
quote:
|
||||||
|
when typeof(result) isnot Confirmable:
|
||||||
|
{.error:
|
||||||
|
"unexpected return type, " &
|
||||||
|
"missing {.view.}, {.pure.} or {.getter.} ?"
|
||||||
|
.}
|
||||||
|
let response = await send(`contract`, `function`, `parameters`, overrides)
|
||||||
|
let convert = customErrorConversion(`errors`)
|
||||||
|
Confirmable(response: response, convert: convert)
|
||||||
|
|
||||||
|
procedure[6] =
|
||||||
|
if procedure.isConstant:
|
||||||
|
call()
|
||||||
|
else:
|
||||||
|
send()
|
||||||
|
|
||||||
|
func addErrorHandling(procedure: var NimNode) =
|
||||||
|
let body = procedure[6]
|
||||||
|
let errors = getErrorTypes(procedure)
|
||||||
|
procedure[6] = quote do:
|
||||||
|
try:
|
||||||
|
`body`
|
||||||
|
except ProviderError as error:
|
||||||
|
if data =? error.data:
|
||||||
|
let convert = customErrorConversion(`errors`)
|
||||||
|
raise convert(error)
|
||||||
|
else:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
func addFuture(procedure: var NimNode) =
|
||||||
|
let returntype = procedure[3][0]
|
||||||
|
if returntype.kind != nnkEmpty:
|
||||||
|
procedure[3][0] = quote: Future[`returntype`]
|
||||||
|
|
||||||
|
func addAsyncPragma(procedure: var NimNode) =
|
||||||
|
let pragmas = procedure[4]
|
||||||
|
if pragmas.kind == nnkEmpty:
|
||||||
|
procedure[4] = newNimNode(nnkPragma)
|
||||||
|
procedure[4].add ident("async")
|
||||||
|
|
||||||
|
macro contract*(procedure: untyped{nkProcDef|nkMethodDef}): untyped =
|
||||||
|
|
||||||
|
let parameters = procedure[3]
|
||||||
|
let body = procedure[6]
|
||||||
|
|
||||||
|
parameters.expectMinLen(2) # at least return type and contract instance
|
||||||
|
body.expectKind(nnkEmpty)
|
||||||
|
|
||||||
|
var contractcall = copyNimTree(procedure)
|
||||||
|
contractcall.addContractCall()
|
||||||
|
contractcall.addErrorHandling()
|
||||||
|
contractcall.addFuture()
|
||||||
|
contractcall.addAsyncPragma()
|
||||||
|
contractcall
|
||||||
|
|
||||||
|
template view* {.pragma.}
|
||||||
|
template pure* {.pragma.}
|
||||||
|
template getter* {.pragma.}
|
||||||
|
|
||||||
|
proc subscribe*[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
handler: EventHandler[E]):
|
||||||
|
Future[Subscription] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = EventFilter(address: contract.address, topics: @[topic])
|
||||||
|
|
||||||
|
proc logHandler(log: Log) {.raises: [].} =
|
||||||
|
if event =? E.decode(log.data, log.topics):
|
||||||
|
handler(event)
|
||||||
|
|
||||||
|
contract.provider.subscribe(filter, logHandler)
|
||||||
|
|
||||||
|
proc confirm(tx: Confirmable, confirmations, timeout: int):
|
||||||
|
Future[TransactionReceipt] {.async.} =
|
||||||
|
|
||||||
|
without response =? tx.response:
|
||||||
|
raise newException(
|
||||||
|
EthersError,
|
||||||
|
"Transaction hash required. Possibly was a call instead of a send?"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await response.confirm(confirmations, timeout)
|
||||||
|
except ProviderError as error:
|
||||||
|
let convert = tx.convert
|
||||||
|
raise convert(error)
|
||||||
|
|
||||||
|
proc confirm*(tx: Future[Confirmable],
|
||||||
|
confirmations: int = EthersDefaultConfirmations,
|
||||||
|
timeout: int = EthersReceiptTimeoutBlks):
|
||||||
|
Future[TransactionReceipt] {.async.} =
|
||||||
|
## Convenience method that allows confirm to be chained to a contract
|
||||||
|
## transaction, eg:
|
||||||
|
## `await token.connect(signer0)
|
||||||
|
## .mint(accounts[1], 100.u256)
|
||||||
|
## .confirm(3)`
|
||||||
|
return await (await tx).confirm(confirmations, timeout)
|
||||||
|
|
||||||
|
proc queryFilter[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
filter: EventFilter):
|
||||||
|
Future[seq[E]] {.async.} =
|
||||||
|
|
||||||
|
var logs = await contract.provider.getLogs(filter)
|
||||||
|
logs.keepItIf(not it.removed)
|
||||||
|
|
||||||
|
var events: seq[E] = @[]
|
||||||
|
for log in logs:
|
||||||
|
if event =? E.decode(log.data, log.topics):
|
||||||
|
events.add event
|
||||||
|
|
||||||
|
return events
|
||||||
|
|
||||||
|
proc queryFilter*[E: Event](contract: Contract,
|
||||||
|
_: type E):
|
||||||
|
Future[seq[E]] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = EventFilter(address: contract.address,
|
||||||
|
topics: @[topic])
|
||||||
|
|
||||||
|
contract.queryFilter(E, filter)
|
||||||
|
|
||||||
|
proc queryFilter*[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
blockHash: BlockHash):
|
||||||
|
Future[seq[E]] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = FilterByBlockHash(address: contract.address,
|
||||||
|
topics: @[topic],
|
||||||
|
blockHash: blockHash)
|
||||||
|
|
||||||
|
contract.queryFilter(E, filter)
|
||||||
|
|
||||||
|
proc queryFilter*[E: Event](contract: Contract,
|
||||||
|
_: type E,
|
||||||
|
fromBlock: BlockTag,
|
||||||
|
toBlock: BlockTag):
|
||||||
|
Future[seq[E]] =
|
||||||
|
|
||||||
|
let topic = topic($E, E.fieldTypes).toArray
|
||||||
|
let filter = Filter(address: contract.address,
|
||||||
|
topics: @[topic],
|
||||||
|
fromBlock: fromBlock,
|
||||||
|
toBlock: toBlock)
|
||||||
|
|
||||||
|
contract.queryFilter(E, filter)
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import std/macros
|
|
||||||
import ./contracts/contract
|
|
||||||
import ./contracts/overrides
|
|
||||||
import ./contracts/confirmation
|
|
||||||
import ./contracts/events
|
|
||||||
import ./contracts/filters
|
|
||||||
import ./contracts/syntax
|
|
||||||
import ./contracts/gas
|
|
||||||
import ./contracts/function
|
|
||||||
|
|
||||||
export contract
|
|
||||||
export overrides
|
|
||||||
export confirmation
|
|
||||||
export events
|
|
||||||
export filters
|
|
||||||
export syntax.view
|
|
||||||
export syntax.pure
|
|
||||||
export syntax.getter
|
|
||||||
export syntax.errors
|
|
||||||
export gas.estimateGas
|
|
||||||
|
|
||||||
{.push raises: [].}
|
|
||||||
|
|
||||||
macro contract*(procedure: untyped{nkProcDef | nkMethodDef}): untyped =
|
|
||||||
procedure.params.expectMinLen(2) # at least return type and contract instance
|
|
||||||
procedure.body.expectKind(nnkEmpty)
|
|
||||||
|
|
||||||
newStmtList(
|
|
||||||
createContractFunction(procedure),
|
|
||||||
createGasEstimationCall(procedure)
|
|
||||||
)
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import ../provider
|
|
||||||
import ./errors/conversion
|
|
||||||
|
|
||||||
{.push raises: [].}
|
|
||||||
|
|
||||||
type Confirmable* = object
|
|
||||||
response*: ?TransactionResponse
|
|
||||||
convert*: ConvertCustomErrors
|
|
||||||
|
|
||||||
proc confirm(tx: Confirmable, confirmations, timeout: int):
|
|
||||||
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
|
||||||
|
|
||||||
without response =? tx.response:
|
|
||||||
raise newException(
|
|
||||||
EthersError,
|
|
||||||
"Transaction hash required. Possibly was a call instead of a send?"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return await response.confirm(confirmations, timeout)
|
|
||||||
except ProviderError as error:
|
|
||||||
let convert = tx.convert
|
|
||||||
raise convert(error)
|
|
||||||
|
|
||||||
proc confirm*(tx: Future[Confirmable],
|
|
||||||
confirmations: int = EthersDefaultConfirmations,
|
|
||||||
timeout: int = EthersReceiptTimeoutBlks):
|
|
||||||
Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
|
||||||
## Convenience method that allows confirm to be chained to a contract
|
|
||||||
## transaction, eg:
|
|
||||||
## `await token.connect(signer0)
|
|
||||||
## .mint(accounts[1], 100.u256)
|
|
||||||
## .confirm(3)`
|
|
||||||
try:
|
|
||||||
return await (await tx).confirm(confirmations, timeout)
|
|
||||||
except CancelledError as e:
|
|
||||||
raise e
|
|
||||||
except EthersError as e:
|
|
||||||
raise e
|
|
||||||
except CatchableError as e:
|
|
||||||
raise newException(
|
|
||||||
EthersError,
|
|
||||||
"Error when trying to confirm the contract transaction: " & e.msg
|
|
||||||
)
|
|
||||||
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import ../basics
|
|
||||||
import ../provider
|
|
||||||
import ../signer
|
|
||||||
|
|
||||||
{.push raises:[].}
|
|
||||||
|
|
||||||
type Contract* = ref object of RootObj
|
|
||||||
provider: Provider
|
|
||||||
signer: ?Signer
|
|
||||||
address: Address
|
|
||||||
|
|
||||||
func new*(ContractType: type Contract,
|
|
||||||
address: Address,
|
|
||||||
provider: Provider): ContractType =
|
|
||||||
ContractType(provider: provider, address: address)
|
|
||||||
|
|
||||||
func new*(ContractType: type Contract,
|
|
||||||
address: Address,
|
|
||||||
signer: Signer): ContractType {.raises: [SignerError].} =
|
|
||||||
ContractType(signer: some signer, provider: signer.provider, address: address)
|
|
||||||
|
|
||||||
func connect*[C: Contract](contract: C, provider: Provider): C =
|
|
||||||
C.new(contract.address, provider)
|
|
||||||
|
|
||||||
func connect*[C: Contract](contract: C, signer: Signer): C {.raises: [SignerError].} =
|
|
||||||
C.new(contract.address, signer)
|
|
||||||
|
|
||||||
func provider*(contract: Contract): Provider =
|
|
||||||
contract.provider
|
|
||||||
|
|
||||||
func signer*(contract: Contract): ?Signer =
|
|
||||||
contract.signer
|
|
||||||
|
|
||||||
func address*(contract: Contract): Address =
|
|
||||||
contract.address
|
|
||||||
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import ../basics
|
|
||||||
import ./contract
|
|
||||||
import ./overrides
|
|
||||||
|
|
||||||
type ContractCall*[Arguments: tuple] = object
|
|
||||||
contract: Contract
|
|
||||||
function: string
|
|
||||||
arguments: Arguments
|
|
||||||
overrides: TransactionOverrides
|
|
||||||
|
|
||||||
func init*[Arguments: tuple](
|
|
||||||
_: type ContractCall,
|
|
||||||
contract: Contract,
|
|
||||||
function: string,
|
|
||||||
arguments: Arguments,
|
|
||||||
overrides: TransactionOverrides
|
|
||||||
): ContractCall[arguments] =
|
|
||||||
ContractCall[Arguments](
|
|
||||||
contract: contract,
|
|
||||||
function: function,
|
|
||||||
arguments: arguments,
|
|
||||||
overrides: overrides
|
|
||||||
)
|
|
||||||
|
|
||||||
func contract*(call: ContractCall): Contract =
|
|
||||||
call.contract
|
|
||||||
|
|
||||||
func function*(call: ContractCall): string =
|
|
||||||
call.function
|
|
||||||
|
|
||||||
func arguments*(call: ContractCall): auto =
|
|
||||||
call.arguments
|
|
||||||
|
|
||||||
func overrides*(call: ContractCall): TransactionOverrides =
|
|
||||||
call.overrides
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import std/macros
|
|
||||||
import ./errors/conversion
|
|
||||||
|
|
||||||
func getErrorTypes*(procedure: NimNode): NimNode =
|
|
||||||
let pragmas = procedure[4]
|
|
||||||
var tupl = newNimNode(nnkTupleConstr)
|
|
||||||
for pragma in pragmas:
|
|
||||||
if pragma.kind == nnkExprColonExpr:
|
|
||||||
if pragma[0].eqIdent "errors":
|
|
||||||
pragma[1].expectKind(nnkBracket)
|
|
||||||
for error in pragma[1]:
|
|
||||||
tupl.add error
|
|
||||||
if tupl.len == 0:
|
|
||||||
quote do: tuple[]
|
|
||||||
else:
|
|
||||||
tupl
|
|
||||||
|
|
||||||
func addErrorHandling*(procedure: var NimNode) =
|
|
||||||
let body = procedure[6]
|
|
||||||
let errors = getErrorTypes(procedure)
|
|
||||||
procedure.body = quote do:
|
|
||||||
try:
|
|
||||||
`body`
|
|
||||||
except ProviderError as error:
|
|
||||||
if data =? error.data:
|
|
||||||
let convert = customErrorConversion(`errors`)
|
|
||||||
raise convert(error)
|
|
||||||
else:
|
|
||||||
raise error
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
import std/sequtils
|
|
||||||
import pkg/contractabi
|
|
||||||
import ../basics
|
|
||||||
import ../provider
|
|
||||||
import ./contract
|
|
||||||
import ./events
|
|
||||||
import ./fields
|
|
||||||
|
|
||||||
type EventHandler*[E: Event] = proc(event: ?!E) {.gcsafe, raises:[].}
|
|
||||||
|
|
||||||
proc subscribe*[E: Event](contract: Contract,
|
|
||||||
_: type E,
|
|
||||||
handler: EventHandler[E]):
|
|
||||||
Future[Subscription] =
|
|
||||||
|
|
||||||
let topic = topic($E, E.fieldTypes).toArray
|
|
||||||
let filter = EventFilter(address: contract.address, topics: @[topic])
|
|
||||||
|
|
||||||
proc logHandler(logResult: ?!Log) {.raises: [].} =
|
|
||||||
without log =? logResult, error:
|
|
||||||
handler(failure(E, error))
|
|
||||||
return
|
|
||||||
|
|
||||||
if event =? E.decode(log.data, log.topics):
|
|
||||||
handler(success(event))
|
|
||||||
|
|
||||||
contract.provider.subscribe(filter, logHandler)
|
|
||||||
|
|
||||||
proc queryFilter[E: Event](contract: Contract,
|
|
||||||
_: type E,
|
|
||||||
filter: EventFilter):
|
|
||||||
Future[seq[E]] {.async.} =
|
|
||||||
|
|
||||||
var logs = await contract.provider.getLogs(filter)
|
|
||||||
logs.keepItIf(not it.removed)
|
|
||||||
|
|
||||||
var events: seq[E] = @[]
|
|
||||||
for log in logs:
|
|
||||||
if event =? E.decode(log.data, log.topics):
|
|
||||||
events.add event
|
|
||||||
|
|
||||||
return events
|
|
||||||
|
|
||||||
proc queryFilter*[E: Event](contract: Contract,
|
|
||||||
_: type E):
|
|
||||||
Future[seq[E]] =
|
|
||||||
|
|
||||||
let topic = topic($E, E.fieldTypes).toArray
|
|
||||||
let filter = EventFilter(address: contract.address,
|
|
||||||
topics: @[topic])
|
|
||||||
|
|
||||||
contract.queryFilter(E, filter)
|
|
||||||
|
|
||||||
proc queryFilter*[E: Event](contract: Contract,
|
|
||||||
_: type E,
|
|
||||||
blockHash: BlockHash):
|
|
||||||
Future[seq[E]] =
|
|
||||||
|
|
||||||
let topic = topic($E, E.fieldTypes).toArray
|
|
||||||
let filter = FilterByBlockHash(address: contract.address,
|
|
||||||
topics: @[topic],
|
|
||||||
blockHash: blockHash)
|
|
||||||
|
|
||||||
contract.queryFilter(E, filter)
|
|
||||||
|
|
||||||
proc queryFilter*[E: Event](contract: Contract,
|
|
||||||
_: type E,
|
|
||||||
fromBlock: BlockTag,
|
|
||||||
toBlock: BlockTag):
|
|
||||||
Future[seq[E]] =
|
|
||||||
|
|
||||||
let topic = topic($E, E.fieldTypes).toArray
|
|
||||||
let filter = Filter(address: contract.address,
|
|
||||||
topics: @[topic],
|
|
||||||
fromBlock: fromBlock,
|
|
||||||
toBlock: toBlock)
|
|
||||||
|
|
||||||
contract.queryFilter(E, filter)
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
import std/macros
|
|
||||||
import ./errors/conversion
|
|
||||||
import ./syntax
|
|
||||||
import ./transactions
|
|
||||||
import ./errors
|
|
||||||
|
|
||||||
func addContractCall(procedure: var NimNode) =
|
|
||||||
let contractCall = getContractCall(procedure)
|
|
||||||
let returnType = procedure.params[0]
|
|
||||||
let isGetter = procedure.isGetter
|
|
||||||
|
|
||||||
let errors = getErrorTypes(procedure)
|
|
||||||
|
|
||||||
func call: NimNode =
|
|
||||||
if returnType.kind == nnkEmpty:
|
|
||||||
quote:
|
|
||||||
await callTransaction(`contractCall`)
|
|
||||||
elif returnType.isMultipleReturn or isGetter:
|
|
||||||
quote:
|
|
||||||
return await callTransaction(`contractCall`, `returnType`)
|
|
||||||
else:
|
|
||||||
quote:
|
|
||||||
# solidity functions return a tuple, so wrap return type in a tuple
|
|
||||||
let tupl = await callTransaction(`contractCall`, (`returnType`,))
|
|
||||||
return tupl[0]
|
|
||||||
|
|
||||||
func send: NimNode =
|
|
||||||
if returnType.kind == nnkEmpty:
|
|
||||||
quote:
|
|
||||||
discard await sendTransaction(`contractCall`)
|
|
||||||
else:
|
|
||||||
quote:
|
|
||||||
when typeof(result) isnot Confirmable:
|
|
||||||
{.error:
|
|
||||||
"unexpected return type, " &
|
|
||||||
"missing {.view.}, {.pure.} or {.getter.} ?"
|
|
||||||
.}
|
|
||||||
let response = await sendTransaction(`contractCall`)
|
|
||||||
let convert = customErrorConversion(`errors`)
|
|
||||||
Confirmable(response: response, convert: convert)
|
|
||||||
|
|
||||||
procedure.body =
|
|
||||||
if procedure.isConstant:
|
|
||||||
call()
|
|
||||||
else:
|
|
||||||
send()
|
|
||||||
|
|
||||||
func addFuture(procedure: var NimNode) =
|
|
||||||
let returntype = procedure[3][0]
|
|
||||||
if returntype.kind != nnkEmpty:
|
|
||||||
procedure[3][0] = quote: Future[`returntype`]
|
|
||||||
|
|
||||||
func createContractFunction*(procedure: NimNode): NimNode =
|
|
||||||
result = copyNimTree(procedure)
|
|
||||||
result.addOverridesParameter()
|
|
||||||
result.addContractCall()
|
|
||||||
result.addErrorHandling()
|
|
||||||
result.addFuture()
|
|
||||||
result.addAsyncPragma()
|
|
||||||
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import std/macros
|
|
||||||
import ../basics
|
|
||||||
import ../provider
|
|
||||||
import ../signer
|
|
||||||
import ./contract
|
|
||||||
import ./contractcall
|
|
||||||
import ./transactions
|
|
||||||
import ./overrides
|
|
||||||
import ./errors
|
|
||||||
import ./syntax
|
|
||||||
|
|
||||||
type ContractGasEstimations[C] = distinct C
|
|
||||||
|
|
||||||
func estimateGas*[C: Contract](contract: C): ContractGasEstimations[C] =
|
|
||||||
ContractGasEstimations[C](contract)
|
|
||||||
|
|
||||||
proc estimateGas(
|
|
||||||
call: ContractCall
|
|
||||||
): Future[UInt256] {.async: (raises: [CancelledError, ProviderError, EthersError]).} =
|
|
||||||
let transaction = createTransaction(call)
|
|
||||||
var blockTag = BlockTag.pending
|
|
||||||
if call.overrides of CallOverrides:
|
|
||||||
if tag =? CallOverrides(call.overrides).blockTag:
|
|
||||||
blockTag = tag
|
|
||||||
if signer =? call.contract.signer:
|
|
||||||
await signer.estimateGas(transaction, blockTag)
|
|
||||||
else:
|
|
||||||
await call.contract.provider.estimateGas(transaction, blockTag)
|
|
||||||
|
|
||||||
func wrapFirstParameter(procedure: var NimNode) =
|
|
||||||
let contractType = procedure.params[1][1]
|
|
||||||
let gasEstimationsType = quote do: ContractGasEstimations[`contractType`]
|
|
||||||
procedure.params[1][1] = gasEstimationsType
|
|
||||||
|
|
||||||
func setReturnType(procedure: var NimNode) =
|
|
||||||
procedure.params[0] = quote do: Future[UInt256]
|
|
||||||
|
|
||||||
func addEstimateCall(procedure: var NimNode) =
|
|
||||||
let contractCall = getContractCall(procedure)
|
|
||||||
procedure.body = quote do:
|
|
||||||
return await estimateGas(`contractCall`)
|
|
||||||
|
|
||||||
func createGasEstimationCall*(procedure: NimNode): NimNode =
|
|
||||||
result = copyNimTree(procedure)
|
|
||||||
result.wrapFirstParameter()
|
|
||||||
result.addOverridesParameter()
|
|
||||||
result.setReturnType()
|
|
||||||
result.addAsyncPragma()
|
|
||||||
result.addUsedPragma()
|
|
||||||
result.addEstimateCall()
|
|
||||||
result.addErrorHandling()
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import ../basics
|
|
||||||
import ../blocktag
|
|
||||||
|
|
||||||
type
|
|
||||||
TransactionOverrides* = ref object of RootObj
|
|
||||||
nonce*: ?UInt256
|
|
||||||
chainId*: ?UInt256
|
|
||||||
gasPrice*: ?UInt256
|
|
||||||
maxFeePerGas*: ?UInt256
|
|
||||||
maxPriorityFeePerGas*: ?UInt256
|
|
||||||
gasLimit*: ?UInt256
|
|
||||||
CallOverrides* = ref object of TransactionOverrides
|
|
||||||
blockTag*: ?BlockTag
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
import std/macros
|
|
||||||
import ./contractcall
|
|
||||||
|
|
||||||
template view* {.pragma.}
|
|
||||||
template pure* {.pragma.}
|
|
||||||
template getter* {.pragma.}
|
|
||||||
template errors*(types) {.pragma.}
|
|
||||||
|
|
||||||
func isGetter*(procedure: NimNode): bool =
|
|
||||||
let pragmas = procedure[4]
|
|
||||||
for pragma in pragmas:
|
|
||||||
if pragma.eqIdent "getter":
|
|
||||||
return true
|
|
||||||
false
|
|
||||||
|
|
||||||
func isConstant*(procedure: NimNode): bool =
|
|
||||||
let pragmas = procedure[4]
|
|
||||||
for pragma in pragmas:
|
|
||||||
if pragma.eqIdent "view":
|
|
||||||
return true
|
|
||||||
elif pragma.eqIdent "pure":
|
|
||||||
return true
|
|
||||||
elif pragma.eqIdent "getter":
|
|
||||||
return true
|
|
||||||
false
|
|
||||||
|
|
||||||
func isMultipleReturn*(returnType: NimNode): bool =
|
|
||||||
(returnType.kind == nnkPar and returnType.len > 1) or
|
|
||||||
(returnType.kind == nnkTupleConstr) or
|
|
||||||
(returnType.kind == nnkTupleTy)
|
|
||||||
|
|
||||||
func getContract(procedure: NimNode): NimNode =
|
|
||||||
let firstArgument = procedure.params[1][0]
|
|
||||||
quote do:
|
|
||||||
Contract(`firstArgument`)
|
|
||||||
|
|
||||||
func getFunctionName(procedure: NimNode): string =
|
|
||||||
$basename(procedure[0])
|
|
||||||
|
|
||||||
func getArgumentTuple(procedure: NimNode): NimNode =
|
|
||||||
let parameters = procedure.params
|
|
||||||
var arguments = newNimNode(nnkTupleConstr, parameters)
|
|
||||||
for parameter in parameters[2..^2]:
|
|
||||||
for name in parameter[0..^3]:
|
|
||||||
arguments.add name
|
|
||||||
return arguments
|
|
||||||
|
|
||||||
func getOverrides(procedure: NimNode): NimNode =
|
|
||||||
procedure.params.last[^3]
|
|
||||||
|
|
||||||
func getContractCall*(procedure: NimNode): NimNode =
|
|
||||||
let contract = getContract(procedure)
|
|
||||||
let function = getFunctionName(procedure)
|
|
||||||
let arguments = getArgumentTuple(procedure)
|
|
||||||
let overrides = getOverrides(procedure)
|
|
||||||
quote do:
|
|
||||||
ContractCall.init(`contract`, `function`, `arguments`, `overrides`)
|
|
||||||
|
|
||||||
func addOverridesParameter*(procedure: var NimNode) =
|
|
||||||
let overrides = genSym(nskParam, "overrides")
|
|
||||||
procedure.params.add(
|
|
||||||
newIdentDefs(
|
|
||||||
overrides,
|
|
||||||
newEmptyNode(),
|
|
||||||
quote do: TransactionOverrides()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
func addAsyncPragma*(procedure: var NimNode) =
|
|
||||||
procedure.addPragma nnkExprColonExpr.newTree(
|
|
||||||
quote do: async,
|
|
||||||
quote do: (raises: [CancelledError, ProviderError, EthersError])
|
|
||||||
)
|
|
||||||
|
|
||||||
func addUsedPragma*(procedure: var NimNode) =
|
|
||||||
procedure.addPragma(quote do: used)
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
import pkg/contractabi
|
|
||||||
import pkg/chronicles
|
|
||||||
import ../basics
|
|
||||||
import ../provider
|
|
||||||
import ../signer
|
|
||||||
import ../transaction
|
|
||||||
import ./contract
|
|
||||||
import ./contractcall
|
|
||||||
import ./overrides
|
|
||||||
|
|
||||||
{.push raises: [].}
|
|
||||||
|
|
||||||
logScope:
|
|
||||||
topics = "ethers contract"
|
|
||||||
|
|
||||||
proc createTransaction*(call: ContractCall): Transaction =
|
|
||||||
let selector = selector(call.function, typeof call.arguments).toArray
|
|
||||||
let data = @selector & AbiEncoder.encode(call.arguments)
|
|
||||||
Transaction(
|
|
||||||
to: call.contract.address,
|
|
||||||
data: data,
|
|
||||||
nonce: call.overrides.nonce,
|
|
||||||
chainId: call.overrides.chainId,
|
|
||||||
gasPrice: call.overrides.gasPrice,
|
|
||||||
maxFeePerGas: call.overrides.maxFeePerGas,
|
|
||||||
maxPriorityFeePerGas: call.overrides.maxPriorityFeePerGas,
|
|
||||||
gasLimit: call.overrides.gasLimit,
|
|
||||||
)
|
|
||||||
|
|
||||||
proc decodeResponse(T: type, bytes: seq[byte]): T {.raises: [ContractError].} =
|
|
||||||
without decoded =? AbiDecoder.decode(bytes, T):
|
|
||||||
raise newException(ContractError, "unable to decode return value as " & $T)
|
|
||||||
return decoded
|
|
||||||
|
|
||||||
proc call(
|
|
||||||
provider: Provider, transaction: Transaction, overrides: TransactionOverrides
|
|
||||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
if overrides of CallOverrides and blockTag =? CallOverrides(overrides).blockTag:
|
|
||||||
await provider.call(transaction, blockTag)
|
|
||||||
else:
|
|
||||||
await provider.call(transaction)
|
|
||||||
|
|
||||||
proc callTransaction*(call: ContractCall) {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
|
||||||
var transaction = createTransaction(call)
|
|
||||||
|
|
||||||
if signer =? call.contract.signer and transaction.sender.isNone:
|
|
||||||
transaction.sender = some(await signer.getAddress())
|
|
||||||
|
|
||||||
discard await call.contract.provider.call(transaction, call.overrides)
|
|
||||||
|
|
||||||
proc callTransaction*(call: ContractCall, ReturnType: type): Future[ReturnType] {.async: (raises: [ProviderError, SignerError, ContractError, CancelledError]).} =
|
|
||||||
var transaction = createTransaction(call)
|
|
||||||
|
|
||||||
if signer =? call.contract.signer and transaction.sender.isNone:
|
|
||||||
transaction.sender = some(await signer.getAddress())
|
|
||||||
|
|
||||||
let response = await call.contract.provider.call(transaction, call.overrides)
|
|
||||||
return decodeResponse(ReturnType, response)
|
|
||||||
|
|
||||||
proc sendTransaction*(call: ContractCall): Future[?TransactionResponse] {.async: (raises: [SignerError, ProviderError, CancelledError]).} =
|
|
||||||
if signer =? call.contract.signer:
|
|
||||||
withLock(signer):
|
|
||||||
let transaction = createTransaction(call)
|
|
||||||
let populated = await signer.populateTransaction(transaction)
|
|
||||||
trace "sending contract transaction", function = call.function, params = $call.arguments
|
|
||||||
let txResp = await signer.sendTransaction(populated)
|
|
||||||
return txResp.some
|
|
||||||
else:
|
|
||||||
await callTransaction(call)
|
|
||||||
return TransactionResponse.none
|
|
||||||
|
|
||||||
@ -1,18 +1,7 @@
|
|||||||
import ./basics
|
import ./basics
|
||||||
|
|
||||||
type
|
type SolidityError* = object of EthersError
|
||||||
SolidityError* = object of EthersError
|
|
||||||
ContractError* = object of EthersError
|
|
||||||
SignerError* = object of EthersError
|
|
||||||
SubscriptionError* = object of EthersError
|
|
||||||
ProviderError* = object of EthersError
|
|
||||||
data*: ?seq[byte]
|
|
||||||
|
|
||||||
{.push raises:[].}
|
{.push raises:[].}
|
||||||
|
|
||||||
proc toErr*[E1: ref CatchableError, E2: EthersError](
|
template errors*(types) {.pragma.}
|
||||||
e1: E1,
|
|
||||||
_: type E2,
|
|
||||||
msg: string = e1.msg): ref E2 =
|
|
||||||
|
|
||||||
return newException(E2, msg, e1)
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import ../../basics
|
import ../basics
|
||||||
import ../../provider
|
import ../provider
|
||||||
import ./encoding
|
import ./encoding
|
||||||
|
|
||||||
type ConvertCustomErrors* =
|
type ConvertCustomErrors* =
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import pkg/contractabi
|
import pkg/contractabi
|
||||||
import pkg/contractabi/selector
|
import pkg/contractabi/selector
|
||||||
import ../../basics
|
import ../basics
|
||||||
import ../../errors
|
import ../errors
|
||||||
|
|
||||||
func selector(E: type): FunctionSelector =
|
func selector(E: type): FunctionSelector =
|
||||||
when compiles(E.arguments):
|
when compiles(E.arguments):
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import std/macros
|
import std/macros
|
||||||
import pkg/contractabi
|
import pkg/contractabi
|
||||||
import ../basics
|
import ./basics
|
||||||
import ../provider
|
import ./provider
|
||||||
|
|
||||||
type
|
type
|
||||||
Event* = object of RootObj
|
Event* = object of RootObj
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import pkg/chronicles
|
import pkg/chronicles
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import pkg/questionable
|
|
||||||
import ./basics
|
import ./basics
|
||||||
import ./transaction
|
import ./transaction
|
||||||
import ./blocktag
|
import ./blocktag
|
||||||
@ -9,12 +8,13 @@ import ./errors
|
|||||||
export basics
|
export basics
|
||||||
export transaction
|
export transaction
|
||||||
export blocktag
|
export blocktag
|
||||||
export errors
|
|
||||||
|
|
||||||
{.push raises: [].}
|
{.push raises: [].}
|
||||||
|
|
||||||
type
|
type
|
||||||
Provider* = ref object of RootObj
|
Provider* = ref object of RootObj
|
||||||
|
ProviderError* = object of EthersError
|
||||||
|
data*: ?seq[byte]
|
||||||
EstimateGasError* = object of ProviderError
|
EstimateGasError* = object of ProviderError
|
||||||
transaction*: Transaction
|
transaction*: Transaction
|
||||||
Subscription* = ref object of RootObj
|
Subscription* = ref object of RootObj
|
||||||
@ -56,14 +56,13 @@ type
|
|||||||
effectiveGasPrice*: ?UInt256
|
effectiveGasPrice*: ?UInt256
|
||||||
status*: TransactionStatus
|
status*: TransactionStatus
|
||||||
transactionType* {.serialize("type"), deserialize("type").}: TransactionType
|
transactionType* {.serialize("type"), deserialize("type").}: TransactionType
|
||||||
LogHandler* = proc(log: ?!Log) {.gcsafe, raises:[].}
|
LogHandler* = proc(log: Log) {.gcsafe, raises:[].}
|
||||||
BlockHandler* = proc(blck: ?!Block) {.gcsafe, raises:[].}
|
BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].}
|
||||||
Topic* = array[32, byte]
|
Topic* = array[32, byte]
|
||||||
Block* {.serialize.} = object
|
Block* {.serialize.} = object
|
||||||
number*: ?UInt256
|
number*: ?UInt256
|
||||||
timestamp*: UInt256
|
timestamp*: UInt256
|
||||||
hash*: ?BlockHash
|
hash*: ?BlockHash
|
||||||
baseFeePerGas* : ?UInt256
|
|
||||||
PastTransaction* {.serialize.} = object
|
PastTransaction* {.serialize.} = object
|
||||||
blockHash*: BlockHash
|
blockHash*: BlockHash
|
||||||
blockNumber*: UInt256
|
blockNumber*: UInt256
|
||||||
@ -103,90 +102,96 @@ func toTransaction*(past: PastTransaction): Transaction =
|
|||||||
)
|
)
|
||||||
|
|
||||||
method getBlockNumber*(
|
method getBlockNumber*(
|
||||||
provider: Provider
|
provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getBlock*(
|
method getBlock*(
|
||||||
provider: Provider, tag: BlockTag
|
provider: Provider,
|
||||||
): Future[?Block] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
tag: BlockTag): Future[?Block] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method call*(
|
method call*(
|
||||||
provider: Provider, tx: Transaction, blockTag = BlockTag.latest
|
provider: Provider,
|
||||||
): Future[seq[byte]] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
tx: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[seq[byte]] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getGasPrice*(
|
method getGasPrice*(
|
||||||
provider: Provider
|
provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
doAssert false, "not implemented"
|
|
||||||
|
|
||||||
method getMaxPriorityFeePerGas*(
|
|
||||||
provider: Provider
|
|
||||||
): Future[UInt256] {.base, async: (raises: [CancelledError]).} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
provider: Provider, address: Address, blockTag = BlockTag.latest
|
provider: Provider,
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
address: Address,
|
||||||
|
blockTag = BlockTag.latest): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransaction*(
|
method getTransaction*(
|
||||||
provider: Provider, txHash: TransactionHash
|
provider: Provider,
|
||||||
): Future[?PastTransaction] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
txHash: TransactionHash): Future[?PastTransaction] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransactionReceipt*(
|
method getTransactionReceipt*(
|
||||||
provider: Provider, txHash: TransactionHash
|
provider: Provider,
|
||||||
): Future[?TransactionReceipt] {.
|
txHash: TransactionHash): Future[?TransactionReceipt] {.base, async: (raises:[ProviderError]).} =
|
||||||
base, async: (raises: [ProviderError, CancelledError])
|
|
||||||
.} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
provider: Provider, rawTransaction: seq[byte]
|
provider: Provider,
|
||||||
): Future[TransactionResponse] {.
|
rawTransaction: seq[byte]): Future[TransactionResponse] {.base, async: (raises:[ProviderError]).} =
|
||||||
base, async: (raises: [ProviderError, CancelledError])
|
|
||||||
.} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getLogs*(
|
method getLogs*(
|
||||||
provider: Provider, filter: EventFilter
|
provider: Provider,
|
||||||
): Future[seq[Log]] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
filter: EventFilter): Future[seq[Log]] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method estimateGas*(
|
method estimateGas*(
|
||||||
provider: Provider, transaction: Transaction, blockTag = BlockTag.latest
|
provider: Provider,
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
transaction: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getChainId*(
|
method getChainId*(
|
||||||
provider: Provider
|
provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} =
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: Provider, filter: EventFilter, callback: LogHandler
|
provider: Provider,
|
||||||
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
filter: EventFilter,
|
||||||
|
callback: LogHandler): Future[Subscription] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: Provider, callback: BlockHandler
|
provider: Provider,
|
||||||
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
callback: BlockHandler): Future[Subscription] {.base, async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method unsubscribe*(
|
method unsubscribe*(
|
||||||
subscription: Subscription
|
subscription: Subscription) {.base, async: (raises:[ProviderError]).} =
|
||||||
) {.base, async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method isSyncing*(provider: Provider): Future[bool] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
method isSyncing*(provider: Provider): Future[bool] {.base, async.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
proc replay*(
|
proc replay*(
|
||||||
provider: Provider, tx: Transaction, blockNumber: UInt256
|
provider: Provider,
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
tx: Transaction,
|
||||||
|
blockNumber: UInt256) {.async: (raises:[ProviderError]).} =
|
||||||
# Replay transaction at block. Useful for fetching revert reasons, which will
|
# Replay transaction at block. Useful for fetching revert reasons, which will
|
||||||
# be present in the raised error message. The replayed block number should
|
# be present in the raised error message. The replayed block number should
|
||||||
# include the state of the chain in the block previous to the block in which
|
# include the state of the chain in the block previous to the block in which
|
||||||
@ -198,8 +203,8 @@ proc replay*(
|
|||||||
discard await provider.call(tx, BlockTag.init(blockNumber))
|
discard await provider.call(tx, BlockTag.init(blockNumber))
|
||||||
|
|
||||||
proc ensureSuccess(
|
proc ensureSuccess(
|
||||||
provider: Provider, receipt: TransactionReceipt
|
provider: Provider,
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
receipt: TransactionReceipt) {.async: (raises: [ProviderError]).} =
|
||||||
## If the receipt.status is Failed, the tx is replayed to obtain a revert
|
## If the receipt.status is Failed, the tx is replayed to obtain a revert
|
||||||
## reason, after which a ProviderError with the revert reason is raised.
|
## reason, after which a ProviderError with the revert reason is raised.
|
||||||
## If no revert reason was obtained
|
## If no revert reason was obtained
|
||||||
@ -222,7 +227,7 @@ proc confirm*(
|
|||||||
tx: TransactionResponse,
|
tx: TransactionResponse,
|
||||||
confirmations = EthersDefaultConfirmations,
|
confirmations = EthersDefaultConfirmations,
|
||||||
timeout = EthersReceiptTimeoutBlks): Future[TransactionReceipt]
|
timeout = EthersReceiptTimeoutBlks): Future[TransactionReceipt]
|
||||||
{.async: (raises: [CancelledError, ProviderError, SubscriptionError, EthersError]).} =
|
{.async: (raises: [CancelledError, ProviderError, EthersError]).} =
|
||||||
|
|
||||||
## Waits for a transaction to be mined and for the specified number of blocks
|
## Waits for a transaction to be mined and for the specified number of blocks
|
||||||
## to pass since it was mined (confirmations). The number of confirmations
|
## to pass since it was mined (confirmations). The number of confirmations
|
||||||
@ -233,12 +238,6 @@ proc confirm*(
|
|||||||
assert confirmations > 0
|
assert confirmations > 0
|
||||||
|
|
||||||
var blockNumber: UInt256
|
var blockNumber: UInt256
|
||||||
|
|
||||||
## We need initialized succesfull Result, because the first iteration of the `while` loop
|
|
||||||
## bellow is triggered "manually" by calling `await updateBlockNumber` and not by block
|
|
||||||
## subscription. If left uninitialized then the Result is in error state and error is raised.
|
|
||||||
## This result is not used for block value, but for block subscription errors.
|
|
||||||
var blockSubscriptionResult: ?!Block = success(Block(number: UInt256.none, timestamp: 0.u256, hash: BlockHash.none))
|
|
||||||
let blockEvent = newAsyncEvent()
|
let blockEvent = newAsyncEvent()
|
||||||
|
|
||||||
proc updateBlockNumber {.async: (raises: []).} =
|
proc updateBlockNumber {.async: (raises: []).} =
|
||||||
@ -247,17 +246,11 @@ proc confirm*(
|
|||||||
if number > blockNumber:
|
if number > blockNumber:
|
||||||
blockNumber = number
|
blockNumber = number
|
||||||
blockEvent.fire()
|
blockEvent.fire()
|
||||||
except ProviderError, CancelledError:
|
except ProviderError:
|
||||||
# there's nothing we can do here
|
# there's nothing we can do here
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc onBlock(blckResult: ?!Block) =
|
proc onBlock(_: Block) =
|
||||||
blockSubscriptionResult = blckResult
|
|
||||||
|
|
||||||
if blckResult.isErr:
|
|
||||||
blockEvent.fire()
|
|
||||||
return
|
|
||||||
|
|
||||||
# ignore block parameter; hardhat may call this with pending blocks
|
# ignore block parameter; hardhat may call this with pending blocks
|
||||||
asyncSpawn updateBlockNumber()
|
asyncSpawn updateBlockNumber()
|
||||||
|
|
||||||
@ -271,16 +264,6 @@ proc confirm*(
|
|||||||
await blockEvent.wait()
|
await blockEvent.wait()
|
||||||
blockEvent.clear()
|
blockEvent.clear()
|
||||||
|
|
||||||
if blockSubscriptionResult.isErr:
|
|
||||||
let error = blockSubscriptionResult.error()
|
|
||||||
|
|
||||||
if error of SubscriptionError:
|
|
||||||
raise (ref SubscriptionError)(error)
|
|
||||||
elif error of CancelledError:
|
|
||||||
raise (ref CancelledError)(error)
|
|
||||||
else:
|
|
||||||
raise error.toErr(ProviderError)
|
|
||||||
|
|
||||||
if blockNumber >= finish:
|
if blockNumber >= finish:
|
||||||
await subscription.unsubscribe()
|
await subscription.unsubscribe()
|
||||||
raise newException(EthersError, "tx not mined before timeout")
|
raise newException(EthersError, "tx not mined before timeout")
|
||||||
@ -299,24 +282,13 @@ proc confirm*(
|
|||||||
proc confirm*(
|
proc confirm*(
|
||||||
tx: Future[TransactionResponse],
|
tx: Future[TransactionResponse],
|
||||||
confirmations: int = EthersDefaultConfirmations,
|
confirmations: int = EthersDefaultConfirmations,
|
||||||
timeout: int = EthersReceiptTimeoutBlks): Future[TransactionReceipt] {.async: (raises: [CancelledError, EthersError]).} =
|
timeout: int = EthersReceiptTimeoutBlks): Future[TransactionReceipt] {.async.} =
|
||||||
## Convenience method that allows wait to be chained to a sendTransaction
|
## Convenience method that allows wait to be chained to a sendTransaction
|
||||||
## call, eg:
|
## call, eg:
|
||||||
## `await signer.sendTransaction(populated).confirm(3)`
|
## `await signer.sendTransaction(populated).confirm(3)`
|
||||||
try:
|
|
||||||
let txResp = await tx
|
|
||||||
return await txResp.confirm(confirmations, timeout)
|
|
||||||
except CancelledError as e:
|
|
||||||
raise e
|
|
||||||
except EthersError as e:
|
|
||||||
raise e
|
|
||||||
except CatchableError as e:
|
|
||||||
raise newException(
|
|
||||||
EthersError,
|
|
||||||
"Error when trying to confirm the provider transaction: " & e.msg
|
|
||||||
)
|
|
||||||
|
|
||||||
method close*(
|
let txResp = await tx
|
||||||
provider: Provider
|
return await txResp.confirm(confirmations, timeout)
|
||||||
) {.base, async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
|
method close*(provider: Provider) {.base, async: (raises:[ProviderError]).} =
|
||||||
discard
|
discard
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import std/tables
|
import std/tables
|
||||||
import std/uri
|
import std/uri
|
||||||
import pkg/chronicles
|
import pkg/chronicles
|
||||||
import pkg/eth/common/eth_types except Block, Log, Address, Transaction
|
import pkg/eth/common/eth_types_json_serialization
|
||||||
import pkg/json_rpc/rpcclient
|
import pkg/json_rpc/rpcclient
|
||||||
import pkg/json_rpc/errors
|
import pkg/json_rpc/errors
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
@ -28,7 +28,6 @@ type
|
|||||||
JsonRpcProvider* = ref object of Provider
|
JsonRpcProvider* = ref object of Provider
|
||||||
client: Future[RpcClient]
|
client: Future[RpcClient]
|
||||||
subscriptions: Future[JsonRpcSubscriptions]
|
subscriptions: Future[JsonRpcSubscriptions]
|
||||||
maxPriorityFeePerGas: UInt256
|
|
||||||
|
|
||||||
JsonRpcSubscription* = ref object of Subscription
|
JsonRpcSubscription* = ref object of Subscription
|
||||||
subscriptions: JsonRpcSubscriptions
|
subscriptions: JsonRpcSubscriptions
|
||||||
@ -44,7 +43,6 @@ type
|
|||||||
|
|
||||||
const defaultUrl = "http://localhost:8545"
|
const defaultUrl = "http://localhost:8545"
|
||||||
const defaultPollingInterval = 4.seconds
|
const defaultPollingInterval = 4.seconds
|
||||||
const defaultMaxPriorityFeePerGas = 1_000_000_000.u256
|
|
||||||
|
|
||||||
proc jsonHeaders: seq[(string, string)] =
|
proc jsonHeaders: seq[(string, string)] =
|
||||||
@[("Content-Type", "application/json")]
|
@[("Content-Type", "application/json")]
|
||||||
@ -52,14 +50,13 @@ proc jsonHeaders: seq[(string, string)] =
|
|||||||
proc new*(
|
proc new*(
|
||||||
_: type JsonRpcProvider,
|
_: type JsonRpcProvider,
|
||||||
url=defaultUrl,
|
url=defaultUrl,
|
||||||
pollingInterval=defaultPollingInterval,
|
pollingInterval=defaultPollingInterval): JsonRpcProvider {.raises: [JsonRpcProviderError].} =
|
||||||
maxPriorityFeePerGas=defaultMaxPriorityFeePerGas): JsonRpcProvider {.raises: [JsonRpcProviderError].} =
|
|
||||||
|
|
||||||
var initialized: Future[void]
|
var initialized: Future[void]
|
||||||
var client: RpcClient
|
var client: RpcClient
|
||||||
var subscriptions: JsonRpcSubscriptions
|
var subscriptions: JsonRpcSubscriptions
|
||||||
|
|
||||||
proc initialize() {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
proc initialize {.async: (raises:[JsonRpcProviderError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
case parseUri(url).scheme
|
case parseUri(url).scheme
|
||||||
of "ws", "wss":
|
of "ws", "wss":
|
||||||
@ -75,47 +72,43 @@ proc new*(
|
|||||||
pollingInterval = pollingInterval)
|
pollingInterval = pollingInterval)
|
||||||
subscriptions.start()
|
subscriptions.start()
|
||||||
|
|
||||||
proc awaitClient(): Future[RpcClient] {.
|
proc awaitClient: Future[RpcClient] {.async:(raises:[JsonRpcProviderError]).} =
|
||||||
async: (raises: [JsonRpcProviderError, CancelledError])
|
|
||||||
.} =
|
|
||||||
convertError:
|
convertError:
|
||||||
await initialized
|
await initialized
|
||||||
return client
|
return client
|
||||||
|
|
||||||
proc awaitSubscriptions(): Future[JsonRpcSubscriptions] {.
|
proc awaitSubscriptions: Future[JsonRpcSubscriptions] {.async:(raises:[JsonRpcProviderError]).} =
|
||||||
async: (raises: [JsonRpcProviderError, CancelledError])
|
|
||||||
.} =
|
|
||||||
convertError:
|
convertError:
|
||||||
await initialized
|
await initialized
|
||||||
return subscriptions
|
return subscriptions
|
||||||
|
|
||||||
initialized = initialize()
|
initialized = initialize()
|
||||||
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions(), maxPriorityFeePerGas: maxPriorityFeePerGas)
|
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions())
|
||||||
|
|
||||||
proc callImpl(
|
proc callImpl(
|
||||||
client: RpcClient, call: string, args: JsonNode
|
client: RpcClient,
|
||||||
): Future[JsonNode] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
call: string,
|
||||||
try:
|
args: JsonNode): Future[JsonNode] {.async: (raises: [JsonRpcProviderError]).} =
|
||||||
let response = await client.call(call, %args)
|
|
||||||
without json =? JsonNode.fromJson(response.string), error:
|
without response =? (await client.call(call, %args)).catch, error:
|
||||||
raiseJsonRpcProviderError "Failed to parse response '" & response.string & "': " &
|
|
||||||
error.msg
|
|
||||||
return json
|
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except CatchableError as error:
|
|
||||||
raiseJsonRpcProviderError error.msg
|
raiseJsonRpcProviderError error.msg
|
||||||
|
without json =? JsonNode.fromJson(response.string), error:
|
||||||
|
raiseJsonRpcProviderError "Failed to parse response: " & error.msg
|
||||||
|
json
|
||||||
|
|
||||||
proc send*(
|
proc send*(
|
||||||
provider: JsonRpcProvider, call: string, arguments: seq[JsonNode] = @[]
|
provider: JsonRpcProvider,
|
||||||
): Future[JsonNode] {.async: (raises: [ProviderError, CancelledError]).} =
|
call: string,
|
||||||
|
arguments: seq[JsonNode] = @[]): Future[JsonNode]
|
||||||
|
{.async: (raises: [JsonRpcProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.callImpl(call, %arguments)
|
return await client.callImpl(call, %arguments)
|
||||||
|
|
||||||
proc listAccounts*(
|
proc listAccounts*(provider: JsonRpcProvider): Future[seq[Address]]
|
||||||
provider: JsonRpcProvider
|
{.async: (raises: [JsonRpcProviderError]).} =
|
||||||
): Future[seq[Address]] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_accounts()
|
return await client.eth_accounts()
|
||||||
@ -127,78 +120,73 @@ proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
|||||||
JsonRpcSigner(provider: provider, address: some address)
|
JsonRpcSigner(provider: provider, address: some address)
|
||||||
|
|
||||||
method getBlockNumber*(
|
method getBlockNumber*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_blockNumber()
|
return await client.eth_blockNumber()
|
||||||
|
|
||||||
method getBlock*(
|
method getBlock*(
|
||||||
provider: JsonRpcProvider, tag: BlockTag
|
provider: JsonRpcProvider,
|
||||||
): Future[?Block] {.async: (raises: [ProviderError, CancelledError]).} =
|
tag: BlockTag): Future[?Block] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getBlockByNumber(tag, false)
|
return await client.eth_getBlockByNumber(tag, false)
|
||||||
|
|
||||||
method call*(
|
method call*(
|
||||||
provider: JsonRpcProvider, tx: Transaction, blockTag = BlockTag.latest
|
provider: JsonRpcProvider,
|
||||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
tx: Transaction,
|
||||||
|
blockTag = BlockTag.latest): Future[seq[byte]] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_call(tx, blockTag)
|
return await client.eth_call(tx, blockTag)
|
||||||
|
|
||||||
method getGasPrice*(
|
method getGasPrice*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_gasPrice()
|
return await client.eth_gasPrice()
|
||||||
|
|
||||||
method getMaxPriorityFeePerGas*(
|
|
||||||
provider: JsonRpcProvider
|
|
||||||
): Future[UInt256] {.async: (raises: [CancelledError]).} =
|
|
||||||
try:
|
|
||||||
convertError:
|
|
||||||
let client = await provider.client
|
|
||||||
return await client.eth_maxPriorityFeePerGas()
|
|
||||||
except JsonRpcProviderError:
|
|
||||||
# If the provider does not provide the implementation
|
|
||||||
# let's just remove the manual value
|
|
||||||
return provider.maxPriorityFeePerGas
|
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
|
provider: JsonRpcProvider,
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
address: Address,
|
||||||
|
blockTag = BlockTag.latest): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionCount(address, blockTag)
|
return await client.eth_getTransactionCount(address, blockTag)
|
||||||
|
|
||||||
method getTransaction*(
|
method getTransaction*(
|
||||||
provider: JsonRpcProvider, txHash: TransactionHash
|
provider: JsonRpcProvider,
|
||||||
): Future[?PastTransaction] {.async: (raises: [ProviderError, CancelledError]).} =
|
txHash: TransactionHash): Future[?PastTransaction] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionByHash(txHash)
|
return await client.eth_getTransactionByHash(txHash)
|
||||||
|
|
||||||
method getTransactionReceipt*(
|
method getTransactionReceipt*(
|
||||||
provider: JsonRpcProvider, txHash: TransactionHash
|
provider: JsonRpcProvider,
|
||||||
): Future[?TransactionReceipt] {.async: (raises: [ProviderError, CancelledError]).} =
|
txHash: TransactionHash): Future[?TransactionReceipt] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionReceipt(txHash)
|
return await client.eth_getTransactionReceipt(txHash)
|
||||||
|
|
||||||
method getLogs*(
|
method getLogs*(
|
||||||
provider: JsonRpcProvider, filter: EventFilter
|
provider: JsonRpcProvider,
|
||||||
): Future[seq[Log]] {.async: (raises: [ProviderError, CancelledError]).} =
|
filter: EventFilter): Future[seq[Log]] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
let logsJson =
|
let logsJson = if filter of Filter:
|
||||||
if filter of Filter:
|
await client.eth_getLogs(Filter(filter))
|
||||||
await client.eth_getLogs(Filter(filter))
|
elif filter of FilterByBlockHash:
|
||||||
elif filter of FilterByBlockHash:
|
await client.eth_getLogs(FilterByBlockHash(filter))
|
||||||
await client.eth_getLogs(FilterByBlockHash(filter))
|
else:
|
||||||
else:
|
await client.eth_getLogs(filter)
|
||||||
await client.eth_getLogs(filter)
|
|
||||||
|
|
||||||
var logs: seq[Log] = @[]
|
var logs: seq[Log] = @[]
|
||||||
for logJson in logsJson.getElems:
|
for logJson in logsJson.getElems:
|
||||||
@ -208,10 +196,10 @@ method getLogs*(
|
|||||||
return logs
|
return logs
|
||||||
|
|
||||||
method estimateGas*(
|
method estimateGas*(
|
||||||
provider: JsonRpcProvider,
|
provider: JsonRpcProvider,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
blockTag = BlockTag.latest,
|
blockTag = BlockTag.latest): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
try:
|
try:
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
@ -221,24 +209,24 @@ method estimateGas*(
|
|||||||
msg: "Estimate gas failed: " & error.msg,
|
msg: "Estimate gas failed: " & error.msg,
|
||||||
data: error.data,
|
data: error.data,
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
parent: error,
|
parent: error
|
||||||
)
|
)
|
||||||
|
|
||||||
method getChainId*(
|
method getChainId*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
try:
|
try:
|
||||||
return await client.eth_chainId()
|
return await client.eth_chainId()
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
return parse(await client.net_version(), UInt256)
|
return parse(await client.net_version(), UInt256)
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
provider: JsonRpcProvider, rawTransaction: seq[byte]
|
provider: JsonRpcProvider,
|
||||||
): Future[TransactionResponse] {.async: (raises: [ProviderError, CancelledError]).} =
|
rawTransaction: seq[byte]): Future[TransactionResponse]
|
||||||
|
{.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let
|
let
|
||||||
client = await provider.client
|
client = await provider.client
|
||||||
@ -247,40 +235,41 @@ method sendTransaction*(
|
|||||||
return TransactionResponse(hash: hash, provider: provider)
|
return TransactionResponse(hash: hash, provider: provider)
|
||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: JsonRpcProvider, filter: EventFilter, onLog: LogHandler
|
provider: JsonRpcProvider,
|
||||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
filter: EventFilter,
|
||||||
|
onLog: LogHandler): Future[Subscription] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
let id = await subscriptions.subscribeLogs(filter, onLog)
|
let id = await subscriptions.subscribeLogs(filter, onLog)
|
||||||
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: JsonRpcProvider, onBlock: BlockHandler
|
provider: JsonRpcProvider,
|
||||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
onBlock: BlockHandler): Future[Subscription] {.async: (raises:[ProviderError]).} =
|
||||||
|
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
let id = await subscriptions.subscribeBlocks(onBlock)
|
let id = await subscriptions.subscribeBlocks(onBlock)
|
||||||
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
||||||
|
|
||||||
method unsubscribe*(
|
method unsubscribe*(
|
||||||
subscription: JsonRpcSubscription
|
subscription: JsonRpcSubscription) {.async: (raises:[ProviderError]).} =
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = subscription.subscriptions
|
let subscriptions = subscription.subscriptions
|
||||||
let id = subscription.id
|
let id = subscription.id
|
||||||
await subscriptions.unsubscribe(id)
|
await subscriptions.unsubscribe(id)
|
||||||
|
|
||||||
method isSyncing*(
|
method isSyncing*(provider: JsonRpcProvider): Future[bool] {.async.} =
|
||||||
provider: JsonRpcProvider
|
|
||||||
): Future[bool] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
let response = await provider.send("eth_syncing")
|
let response = await provider.send("eth_syncing")
|
||||||
if response.kind == JsonNodeKind.JObject:
|
if response.kind == JsonNodeKind.JObject:
|
||||||
return true
|
return true
|
||||||
return response.getBool()
|
return response.getBool()
|
||||||
|
|
||||||
method close*(
|
method close*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider) {.async: (raises:[ProviderError]).} =
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
@ -301,8 +290,6 @@ proc raiseJsonRpcSignerError(
|
|||||||
template convertSignerError(body) =
|
template convertSignerError(body) =
|
||||||
try:
|
try:
|
||||||
body
|
body
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except JsonRpcError as error:
|
except JsonRpcError as error:
|
||||||
raiseJsonRpcSignerError(error.msg)
|
raiseJsonRpcSignerError(error.msg)
|
||||||
except CatchableError as error:
|
except CatchableError as error:
|
||||||
@ -314,8 +301,9 @@ method provider*(signer: JsonRpcSigner): Provider
|
|||||||
signer.provider
|
signer.provider
|
||||||
|
|
||||||
method getAddress*(
|
method getAddress*(
|
||||||
signer: JsonRpcSigner
|
signer: JsonRpcSigner): Future[Address]
|
||||||
): Future[Address] {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
{.async: (raises:[ProviderError, SignerError]).} =
|
||||||
|
|
||||||
if address =? signer.address:
|
if address =? signer.address:
|
||||||
return address
|
return address
|
||||||
|
|
||||||
@ -326,18 +314,19 @@ method getAddress*(
|
|||||||
raiseJsonRpcSignerError "no address found"
|
raiseJsonRpcSignerError "no address found"
|
||||||
|
|
||||||
method signMessage*(
|
method signMessage*(
|
||||||
signer: JsonRpcSigner, message: seq[byte]
|
signer: JsonRpcSigner,
|
||||||
): Future[seq[byte]] {.async: (raises: [SignerError, CancelledError]).} =
|
message: seq[byte]): Future[seq[byte]] {.async: (raises:[SignerError]).} =
|
||||||
|
|
||||||
convertSignerError:
|
convertSignerError:
|
||||||
let client = await signer.provider.client
|
let client = await signer.provider.client
|
||||||
let address = await signer.getAddress()
|
let address = await signer.getAddress()
|
||||||
return await client.personal_sign(message, address)
|
return await client.personal_sign(message, address)
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
signer: JsonRpcSigner, transaction: Transaction
|
signer: JsonRpcSigner,
|
||||||
): Future[TransactionResponse] {.
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
async: (raises: [SignerError, ProviderError, CancelledError])
|
{.async: (raises:[SignerError, ProviderError]).} =
|
||||||
.} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let
|
let
|
||||||
client = await signer.provider.client
|
client = await signer.provider.client
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import std/strutils
|
import std/strutils
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ../../basics
|
import ../../basics
|
||||||
import ../../errors
|
|
||||||
import ../../provider
|
import ../../provider
|
||||||
import ./conversions
|
import ./conversions
|
||||||
|
|
||||||
export errors
|
|
||||||
|
|
||||||
{.push raises:[].}
|
{.push raises:[].}
|
||||||
|
|
||||||
type JsonRpcProviderError* = object of ProviderError
|
type JsonRpcProviderError* = object of ProviderError
|
||||||
@ -40,8 +37,6 @@ proc raiseJsonRpcProviderError*(
|
|||||||
template convertError*(body) =
|
template convertError*(body) =
|
||||||
try:
|
try:
|
||||||
body
|
body
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except JsonRpcError as error:
|
except JsonRpcError as error:
|
||||||
raiseJsonRpcProviderError(error.msg)
|
raiseJsonRpcProviderError(error.msg)
|
||||||
except CatchableError as error:
|
except CatchableError as error:
|
||||||
|
|||||||
@ -21,4 +21,3 @@ proc eth_newBlockFilter(): JsonNode
|
|||||||
proc eth_newFilter(filter: EventFilter): JsonNode
|
proc eth_newFilter(filter: EventFilter): JsonNode
|
||||||
proc eth_getFilterChanges(id: JsonNode): JsonNode
|
proc eth_getFilterChanges(id: JsonNode): JsonNode
|
||||||
proc eth_uninstallFilter(id: JsonNode): bool
|
proc eth_uninstallFilter(id: JsonNode): bool
|
||||||
proc eth_maxPriorityFeePerGas(): UInt256
|
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import std/tables
|
import std/tables
|
||||||
import std/sequtils
|
import std/sequtils
|
||||||
import std/strutils
|
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/questionable
|
|
||||||
import pkg/json_rpc/rpcclient
|
import pkg/json_rpc/rpcclient
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import ../../basics
|
import ../../basics
|
||||||
import ../../errors
|
|
||||||
import ../../provider
|
import ../../provider
|
||||||
include ../../nimshims/hashes
|
include ../../nimshims/hashes
|
||||||
import ./rpccalls
|
import ./rpccalls
|
||||||
@ -19,25 +16,12 @@ type
|
|||||||
client: RpcClient
|
client: RpcClient
|
||||||
callbacks: Table[JsonNode, SubscriptionCallback]
|
callbacks: Table[JsonNode, SubscriptionCallback]
|
||||||
methodHandlers: Table[string, MethodHandler]
|
methodHandlers: Table[string, MethodHandler]
|
||||||
# Used by both PollingSubscriptions and WebsocketSubscriptions to store
|
|
||||||
# subscription filters so the subscriptions can be recreated. With
|
|
||||||
# PollingSubscriptions, the RPC node might prune/forget about them, and with
|
|
||||||
# WebsocketSubscriptions, when using hardhat, subscriptions are dropped after 5
|
|
||||||
# minutes.
|
|
||||||
logFilters: Table[JsonNode, EventFilter]
|
|
||||||
MethodHandler* = proc (j: JsonNode) {.gcsafe, raises: [].}
|
MethodHandler* = proc (j: JsonNode) {.gcsafe, raises: [].}
|
||||||
SubscriptionCallback = proc(id: JsonNode, arguments: ?!JsonNode) {.gcsafe, raises:[].}
|
SubscriptionCallback = proc(id, arguments: JsonNode) {.gcsafe, raises:[].}
|
||||||
|
SubscriptionError* = object of EthersError
|
||||||
|
|
||||||
{.push raises:[].}
|
{.push raises:[].}
|
||||||
|
|
||||||
template convertErrorsToSubscriptionError(body) =
|
|
||||||
try:
|
|
||||||
body
|
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except CatchableError as error:
|
|
||||||
raise error.toErr(SubscriptionError)
|
|
||||||
|
|
||||||
template `or`(a: JsonNode, b: typed): JsonNode =
|
template `or`(a: JsonNode, b: typed): JsonNode =
|
||||||
if a.isNil: b else: a
|
if a.isNil: b else: a
|
||||||
|
|
||||||
@ -59,6 +43,7 @@ func start*(subscriptions: JsonRpcSubscriptions) =
|
|||||||
# true = continue processing message using json_rpc's default message handler
|
# true = continue processing message using json_rpc's default message handler
|
||||||
return ok true
|
return ok true
|
||||||
|
|
||||||
|
|
||||||
proc setMethodHandler(
|
proc setMethodHandler(
|
||||||
subscriptions: JsonRpcSubscriptions,
|
subscriptions: JsonRpcSubscriptions,
|
||||||
`method`: string,
|
`method`: string,
|
||||||
@ -69,157 +54,80 @@ proc setMethodHandler(
|
|||||||
method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
|
method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
|
||||||
onBlock: BlockHandler):
|
onBlock: BlockHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async: (raises: [SubscriptionError, CancelledError]), base,.} =
|
{.async, base.} =
|
||||||
raiseAssert "not implemented"
|
raiseAssert "not implemented"
|
||||||
|
|
||||||
method subscribeLogs*(subscriptions: JsonRpcSubscriptions,
|
method subscribeLogs*(subscriptions: JsonRpcSubscriptions,
|
||||||
filter: EventFilter,
|
filter: EventFilter,
|
||||||
onLog: LogHandler):
|
onLog: LogHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async: (raises: [SubscriptionError, CancelledError]), base.} =
|
{.async, base.} =
|
||||||
raiseAssert "not implemented"
|
raiseAssert "not implemented"
|
||||||
|
|
||||||
method unsubscribe*(subscriptions: JsonRpcSubscriptions,
|
method unsubscribe*(subscriptions: JsonRpcSubscriptions,
|
||||||
id: JsonNode)
|
id: JsonNode)
|
||||||
{.async: (raises: [CancelledError]), base.} =
|
{.async, base.} =
|
||||||
raiseAssert "not implemented "
|
raiseAssert "not implemented"
|
||||||
|
|
||||||
method close*(subscriptions: JsonRpcSubscriptions) {.async: (raises: []), base.} =
|
method close*(subscriptions: JsonRpcSubscriptions) {.async, base.} =
|
||||||
let ids = toSeq subscriptions.callbacks.keys
|
let ids = toSeq subscriptions.callbacks.keys
|
||||||
for id in ids:
|
for id in ids:
|
||||||
try:
|
await subscriptions.unsubscribe(id)
|
||||||
await subscriptions.unsubscribe(id)
|
|
||||||
except CatchableError as e:
|
|
||||||
error "JsonRpc unsubscription failed", error = e.msg, id = id
|
|
||||||
|
|
||||||
proc getCallback(subscriptions: JsonRpcSubscriptions,
|
proc getCallback(subscriptions: JsonRpcSubscriptions,
|
||||||
id: JsonNode): ?SubscriptionCallback {. raises:[].} =
|
id: JsonNode): ?SubscriptionCallback =
|
||||||
try:
|
try:
|
||||||
if not id.isNil and id in subscriptions.callbacks:
|
if not id.isNil and id in subscriptions.callbacks:
|
||||||
return subscriptions.callbacks[id].some
|
subscriptions.callbacks[id].some
|
||||||
except: discard
|
else:
|
||||||
|
SubscriptionCallback.none
|
||||||
|
except KeyError:
|
||||||
|
SubscriptionCallback.none
|
||||||
|
|
||||||
# Web sockets
|
# Web sockets
|
||||||
|
|
||||||
# Default re-subscription period is seconds
|
|
||||||
const WsResubscribe {.intdefine.}: int = 0
|
|
||||||
|
|
||||||
type
|
type
|
||||||
WebSocketSubscriptions = ref object of JsonRpcSubscriptions
|
WebSocketSubscriptions = ref object of JsonRpcSubscriptions
|
||||||
logFiltersLock: AsyncLock
|
|
||||||
resubscribeFut: Future[void]
|
|
||||||
resubscribeInterval: int
|
|
||||||
|
|
||||||
template withLock*(subscriptions: WebSocketSubscriptions, body: untyped) =
|
|
||||||
if subscriptions.logFiltersLock.isNil:
|
|
||||||
subscriptions.logFiltersLock = newAsyncLock()
|
|
||||||
|
|
||||||
await subscriptions.logFiltersLock.acquire()
|
|
||||||
try:
|
|
||||||
body
|
|
||||||
finally:
|
|
||||||
subscriptions.logFiltersLock.release()
|
|
||||||
|
|
||||||
# This is a workaround to manage the 5 minutes limit due to hardhat.
|
|
||||||
# See https://github.com/NomicFoundation/hardhat/issues/2053#issuecomment-1061374064
|
|
||||||
proc resubscribeWebsocketEventsOnTimeout*(subscriptions: WebsocketSubscriptions) {.async: (raises: [CancelledError]).} =
|
|
||||||
while true:
|
|
||||||
await sleepAsync(subscriptions.resubscribeInterval.seconds)
|
|
||||||
try:
|
|
||||||
withLock(subscriptions):
|
|
||||||
for id, callback in subscriptions.callbacks:
|
|
||||||
|
|
||||||
var newId: JsonNode
|
|
||||||
if id in subscriptions.logFilters:
|
|
||||||
let filter = subscriptions.logFilters[id]
|
|
||||||
newId = await subscriptions.client.eth_subscribe("logs", filter)
|
|
||||||
subscriptions.logFilters[newId] = filter
|
|
||||||
subscriptions.logFilters.del(id)
|
|
||||||
else:
|
|
||||||
newId = await subscriptions.client.eth_subscribe("newHeads")
|
|
||||||
|
|
||||||
subscriptions.callbacks[newId] = callback
|
|
||||||
subscriptions.callbacks.del(id)
|
|
||||||
discard await subscriptions.client.eth_unsubscribe(id)
|
|
||||||
except CancelledError as e:
|
|
||||||
raise e
|
|
||||||
except CatchableError as e:
|
|
||||||
error "WS resubscription failed" , error = e.msg
|
|
||||||
|
|
||||||
proc new*(_: type JsonRpcSubscriptions,
|
proc new*(_: type JsonRpcSubscriptions,
|
||||||
client: RpcWebSocketClient,
|
client: RpcWebSocketClient): JsonRpcSubscriptions =
|
||||||
resubscribeInterval = WsResubscribe): JsonRpcSubscriptions =
|
|
||||||
let subscriptions = WebSocketSubscriptions(client: client, resubscribeInterval: resubscribeInterval)
|
|
||||||
|
|
||||||
|
let subscriptions = WebSocketSubscriptions(client: client)
|
||||||
proc subscriptionHandler(arguments: JsonNode) {.raises:[].} =
|
proc subscriptionHandler(arguments: JsonNode) {.raises:[].} =
|
||||||
let id = arguments{"subscription"} or newJString("")
|
let id = arguments{"subscription"} or newJString("")
|
||||||
if callback =? subscriptions.getCallback(id):
|
if callback =? subscriptions.getCallback(id):
|
||||||
callback(id, success(arguments))
|
callback(id, arguments)
|
||||||
subscriptions.setMethodHandler("eth_subscription", subscriptionHandler)
|
subscriptions.setMethodHandler("eth_subscription", subscriptionHandler)
|
||||||
|
|
||||||
if resubscribeInterval > 0:
|
|
||||||
if resubscribeInterval >= 300:
|
|
||||||
warn "Resubscription interval greater than 300 seconds is useless for hardhat workaround", resubscribeInterval = resubscribeInterval
|
|
||||||
|
|
||||||
subscriptions.resubscribeFut = resubscribeWebsocketEventsOnTimeout(subscriptions)
|
|
||||||
|
|
||||||
subscriptions
|
subscriptions
|
||||||
|
|
||||||
method subscribeBlocks(subscriptions: WebSocketSubscriptions,
|
method subscribeBlocks(subscriptions: WebSocketSubscriptions,
|
||||||
onBlock: BlockHandler):
|
onBlock: BlockHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async: (raises: [SubscriptionError, CancelledError]).} =
|
{.async.} =
|
||||||
proc callback(id: JsonNode, argumentsResult: ?!JsonNode) {.raises: [].} =
|
proc callback(id, arguments: JsonNode) {.raises: [].} =
|
||||||
without arguments =? argumentsResult, error:
|
if blck =? Block.fromJson(arguments{"result"}):
|
||||||
onBlock(failure(Block, error.toErr(SubscriptionError)))
|
onBlock(blck)
|
||||||
return
|
let id = await subscriptions.client.eth_subscribe("newHeads")
|
||||||
|
subscriptions.callbacks[id] = callback
|
||||||
let res = Block.fromJson(arguments{"result"}).mapFailure(SubscriptionError)
|
return id
|
||||||
onBlock(res)
|
|
||||||
|
|
||||||
convertErrorsToSubscriptionError:
|
|
||||||
withLock(subscriptions):
|
|
||||||
let id = await subscriptions.client.eth_subscribe("newHeads")
|
|
||||||
subscriptions.callbacks[id] = callback
|
|
||||||
return id
|
|
||||||
|
|
||||||
method subscribeLogs(subscriptions: WebSocketSubscriptions,
|
method subscribeLogs(subscriptions: WebSocketSubscriptions,
|
||||||
filter: EventFilter,
|
filter: EventFilter,
|
||||||
onLog: LogHandler):
|
onLog: LogHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async: (raises: [SubscriptionError, CancelledError]).} =
|
{.async.} =
|
||||||
proc callback(id: JsonNode, argumentsResult: ?!JsonNode) =
|
proc callback(id, arguments: JsonNode) =
|
||||||
without arguments =? argumentsResult, error:
|
if log =? Log.fromJson(arguments{"result"}):
|
||||||
onLog(failure(Log, error.toErr(SubscriptionError)))
|
onLog(log)
|
||||||
return
|
let id = await subscriptions.client.eth_subscribe("logs", filter)
|
||||||
|
subscriptions.callbacks[id] = callback
|
||||||
let res = Log.fromJson(arguments{"result"}).mapFailure(SubscriptionError)
|
return id
|
||||||
onLog(res)
|
|
||||||
|
|
||||||
convertErrorsToSubscriptionError:
|
|
||||||
withLock(subscriptions):
|
|
||||||
let id = await subscriptions.client.eth_subscribe("logs", filter)
|
|
||||||
subscriptions.callbacks[id] = callback
|
|
||||||
subscriptions.logFilters[id] = filter
|
|
||||||
return id
|
|
||||||
|
|
||||||
method unsubscribe*(subscriptions: WebSocketSubscriptions,
|
method unsubscribe*(subscriptions: WebSocketSubscriptions,
|
||||||
id: JsonNode)
|
id: JsonNode)
|
||||||
{.async: (raises: [CancelledError]).} =
|
{.async.} =
|
||||||
try:
|
subscriptions.callbacks.del(id)
|
||||||
withLock(subscriptions):
|
discard await subscriptions.client.eth_unsubscribe(id)
|
||||||
subscriptions.callbacks.del(id)
|
|
||||||
discard await subscriptions.client.eth_unsubscribe(id)
|
|
||||||
except CancelledError as e:
|
|
||||||
raise e
|
|
||||||
except CatchableError:
|
|
||||||
# Ignore if uninstallation of the subscribiton fails.
|
|
||||||
discard
|
|
||||||
|
|
||||||
method close*(subscriptions: WebSocketSubscriptions) {.async: (raises: []).} =
|
|
||||||
await procCall JsonRpcSubscriptions(subscriptions).close()
|
|
||||||
if not subscriptions.resubscribeFut.isNil:
|
|
||||||
await subscriptions.resubscribeFut.cancelAndWait()
|
|
||||||
|
|
||||||
# Polling
|
# Polling
|
||||||
|
|
||||||
@ -227,6 +135,11 @@ type
|
|||||||
PollingSubscriptions* = ref object of JsonRpcSubscriptions
|
PollingSubscriptions* = ref object of JsonRpcSubscriptions
|
||||||
polling: Future[void]
|
polling: Future[void]
|
||||||
|
|
||||||
|
# We need to keep around the filters that are used to create log filters on the RPC node
|
||||||
|
# as there might be a time when they need to be recreated as RPC node might prune/forget
|
||||||
|
# about them
|
||||||
|
logFilters: Table[JsonNode, EventFilter]
|
||||||
|
|
||||||
# Used when filters are recreated to translate from the id that user
|
# Used when filters are recreated to translate from the id that user
|
||||||
# originally got returned to new filter id
|
# originally got returned to new filter id
|
||||||
subscriptionMapping: Table[JsonNode, JsonNode]
|
subscriptionMapping: Table[JsonNode, JsonNode]
|
||||||
@ -237,7 +150,7 @@ proc new*(_: type JsonRpcSubscriptions,
|
|||||||
|
|
||||||
let subscriptions = PollingSubscriptions(client: client)
|
let subscriptions = PollingSubscriptions(client: client)
|
||||||
|
|
||||||
proc resubscribe(id: JsonNode): Future[?!void] {.async: (raises: [CancelledError]).} =
|
proc resubscribe(id: JsonNode) {.async: (raises: [CancelledError]).} =
|
||||||
try:
|
try:
|
||||||
var newId: JsonNode
|
var newId: JsonNode
|
||||||
# Log filters are stored in logFilters, block filters are not persisted
|
# Log filters are stored in logFilters, block filters are not persisted
|
||||||
@ -249,49 +162,36 @@ proc new*(_: type JsonRpcSubscriptions,
|
|||||||
else:
|
else:
|
||||||
newId = await subscriptions.client.eth_newBlockFilter()
|
newId = await subscriptions.client.eth_newBlockFilter()
|
||||||
subscriptions.subscriptionMapping[id] = newId
|
subscriptions.subscriptionMapping[id] = newId
|
||||||
except CancelledError as e:
|
except CancelledError as error:
|
||||||
raise e
|
raise error
|
||||||
except CatchableError as e:
|
except CatchableError:
|
||||||
return failure(void, e.toErr(SubscriptionError, "HTTP polling: There was an exception while getting subscription changes: " & e.msg))
|
# there's nothing further we can do here
|
||||||
|
discard
|
||||||
|
|
||||||
return success()
|
proc getChanges(id: JsonNode): Future[JsonNode] {.async: (raises: [CancelledError]).} =
|
||||||
|
|
||||||
proc getChanges(id: JsonNode): Future[?!JsonNode] {.async: (raises: [CancelledError]).} =
|
|
||||||
if mappedId =? subscriptions.subscriptionMapping.?[id]:
|
if mappedId =? subscriptions.subscriptionMapping.?[id]:
|
||||||
try:
|
try:
|
||||||
let changes = await subscriptions.client.eth_getFilterChanges(mappedId)
|
let changes = await subscriptions.client.eth_getFilterChanges(mappedId)
|
||||||
if changes.kind == JArray:
|
if changes.kind == JArray:
|
||||||
return success(changes)
|
return changes
|
||||||
except JsonRpcError as e:
|
except JsonRpcError:
|
||||||
if error =? (await resubscribe(id)).errorOption:
|
await resubscribe(id)
|
||||||
return failure(JsonNode, error)
|
|
||||||
|
|
||||||
# TODO: we could still miss some events between losing the subscription
|
# TODO: we could still miss some events between losing the subscription
|
||||||
# and resubscribing. We should probably adopt a strategy like ethers.js,
|
# and resubscribing. We should probably adopt a strategy like ethers.js,
|
||||||
# whereby we keep track of the latest block number that we've seen
|
# whereby we keep track of the latest block number that we've seen
|
||||||
# filter changes for:
|
# filter changes for:
|
||||||
# https://github.com/ethers-io/ethers.js/blob/f97b92bbb1bde22fcc44100af78d7f31602863ab/packages/providers/src.ts/base-provider.ts#L977
|
# https://github.com/ethers-io/ethers.js/blob/f97b92bbb1bde22fcc44100af78d7f31602863ab/packages/providers/src.ts/base-provider.ts#L977
|
||||||
|
except CancelledError as error:
|
||||||
if not ("filter not found" in e.msg):
|
raise error
|
||||||
return failure(JsonNode, e.toErr(SubscriptionError, "HTTP polling: There was an exception while getting subscription changes: " & e.msg))
|
except CatchableError:
|
||||||
except CancelledError as e:
|
# there's nothing we can do here
|
||||||
raise e
|
discard
|
||||||
except SubscriptionError as e:
|
return newJArray()
|
||||||
return failure(JsonNode, e)
|
|
||||||
except CatchableError as e:
|
|
||||||
return failure(JsonNode, e.toErr(SubscriptionError, "HTTP polling: There was an exception while getting subscription changes: " & e.msg))
|
|
||||||
return success(newJArray())
|
|
||||||
|
|
||||||
proc poll(id: JsonNode) {.async: (raises: [CancelledError]).} =
|
proc poll(id: JsonNode) {.async: (raises: [CancelledError]).} =
|
||||||
without callback =? subscriptions.getCallback(id):
|
for change in await getChanges(id):
|
||||||
return
|
if callback =? subscriptions.getCallback(id):
|
||||||
|
callback(id, change)
|
||||||
without changes =? (await getChanges(id)), error:
|
|
||||||
callback(id, failure(JsonNode, error))
|
|
||||||
return
|
|
||||||
|
|
||||||
for change in changes:
|
|
||||||
callback(id, success(change))
|
|
||||||
|
|
||||||
proc poll {.async: (raises: []).} =
|
proc poll {.async: (raises: []).} =
|
||||||
try:
|
try:
|
||||||
@ -306,69 +206,56 @@ proc new*(_: type JsonRpcSubscriptions,
|
|||||||
asyncSpawn subscriptions.polling
|
asyncSpawn subscriptions.polling
|
||||||
subscriptions
|
subscriptions
|
||||||
|
|
||||||
method close*(subscriptions: PollingSubscriptions) {.async: (raises: []).} =
|
method close*(subscriptions: PollingSubscriptions) {.async.} =
|
||||||
await subscriptions.polling.cancelAndWait()
|
await subscriptions.polling.cancelAndWait()
|
||||||
await procCall JsonRpcSubscriptions(subscriptions).close()
|
await procCall JsonRpcSubscriptions(subscriptions).close()
|
||||||
|
|
||||||
method subscribeBlocks(subscriptions: PollingSubscriptions,
|
method subscribeBlocks(subscriptions: PollingSubscriptions,
|
||||||
onBlock: BlockHandler):
|
onBlock: BlockHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async: (raises: [SubscriptionError, CancelledError]).} =
|
{.async.} =
|
||||||
|
|
||||||
proc getBlock(hash: BlockHash) {.async: (raises:[]).} =
|
proc getBlock(hash: BlockHash) {.async.} =
|
||||||
try:
|
try:
|
||||||
if blck =? (await subscriptions.client.eth_getBlockByHash(hash, false)):
|
if blck =? (await subscriptions.client.eth_getBlockByHash(hash, false)):
|
||||||
onBlock(success(blck))
|
onBlock(blck)
|
||||||
except CancelledError:
|
except CatchableError:
|
||||||
discard
|
discard
|
||||||
except CatchableError as e:
|
|
||||||
let error = e.toErr(SubscriptionError, "HTTP polling: There was an exception while getting subscription's block: " & e.msg)
|
|
||||||
onBlock(failure(Block, error))
|
|
||||||
|
|
||||||
proc callback(id: JsonNode, changeResult: ?!JsonNode) {.raises:[].} =
|
|
||||||
without change =? changeResult, e:
|
|
||||||
onBlock(failure(Block, e.toErr(SubscriptionError)))
|
|
||||||
return
|
|
||||||
|
|
||||||
|
proc callback(id, change: JsonNode) =
|
||||||
if hash =? BlockHash.fromJson(change):
|
if hash =? BlockHash.fromJson(change):
|
||||||
asyncSpawn getBlock(hash)
|
asyncSpawn getBlock(hash)
|
||||||
|
|
||||||
convertErrorsToSubscriptionError:
|
let id = await subscriptions.client.eth_newBlockFilter()
|
||||||
let id = await subscriptions.client.eth_newBlockFilter()
|
subscriptions.callbacks[id] = callback
|
||||||
subscriptions.callbacks[id] = callback
|
subscriptions.subscriptionMapping[id] = id
|
||||||
subscriptions.subscriptionMapping[id] = id
|
return id
|
||||||
return id
|
|
||||||
|
|
||||||
method subscribeLogs(subscriptions: PollingSubscriptions,
|
method subscribeLogs(subscriptions: PollingSubscriptions,
|
||||||
filter: EventFilter,
|
filter: EventFilter,
|
||||||
onLog: LogHandler):
|
onLog: LogHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async: (raises: [SubscriptionError, CancelledError]).} =
|
{.async.} =
|
||||||
|
|
||||||
proc callback(id: JsonNode, argumentsResult: ?!JsonNode) =
|
proc callback(id, change: JsonNode) =
|
||||||
without arguments =? argumentsResult, error:
|
if log =? Log.fromJson(change):
|
||||||
onLog(failure(Log, error.toErr(SubscriptionError)))
|
onLog(log)
|
||||||
return
|
|
||||||
|
|
||||||
let res = Log.fromJson(arguments).mapFailure(SubscriptionError)
|
let id = await subscriptions.client.eth_newFilter(filter)
|
||||||
onLog(res)
|
subscriptions.callbacks[id] = callback
|
||||||
|
subscriptions.logFilters[id] = filter
|
||||||
convertErrorsToSubscriptionError:
|
subscriptions.subscriptionMapping[id] = id
|
||||||
let id = await subscriptions.client.eth_newFilter(filter)
|
return id
|
||||||
subscriptions.callbacks[id] = callback
|
|
||||||
subscriptions.logFilters[id] = filter
|
|
||||||
subscriptions.subscriptionMapping[id] = id
|
|
||||||
return id
|
|
||||||
|
|
||||||
method unsubscribe*(subscriptions: PollingSubscriptions,
|
method unsubscribe*(subscriptions: PollingSubscriptions,
|
||||||
id: JsonNode)
|
id: JsonNode)
|
||||||
{.async: (raises: [CancelledError]).} =
|
{.async.} =
|
||||||
|
subscriptions.logFilters.del(id)
|
||||||
|
subscriptions.callbacks.del(id)
|
||||||
|
let sub = subscriptions.subscriptionMapping[id]
|
||||||
|
subscriptions.subscriptionMapping.del(id)
|
||||||
try:
|
try:
|
||||||
subscriptions.logFilters.del(id)
|
discard await subscriptions.client.eth_uninstallFilter(sub)
|
||||||
subscriptions.callbacks.del(id)
|
|
||||||
if sub =? subscriptions.subscriptionMapping.?[id]:
|
|
||||||
subscriptions.subscriptionMapping.del(id)
|
|
||||||
discard await subscriptions.client.eth_uninstallFilter(sub)
|
|
||||||
except CancelledError as e:
|
except CancelledError as e:
|
||||||
raise e
|
raise e
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
|
|||||||
@ -1,26 +1,22 @@
|
|||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
import pkg/chronicles
|
|
||||||
import ./basics
|
import ./basics
|
||||||
import ./errors
|
|
||||||
import ./provider
|
import ./provider
|
||||||
|
|
||||||
export basics
|
export basics
|
||||||
export errors
|
|
||||||
|
|
||||||
{.push raises: [].}
|
{.push raises: [].}
|
||||||
|
|
||||||
type
|
type
|
||||||
Signer* = ref object of RootObj
|
Signer* = ref object of RootObj
|
||||||
populateLock: AsyncLock
|
populateLock: AsyncLock
|
||||||
|
SignerError* = object of EthersError
|
||||||
|
|
||||||
template raiseSignerError*(message: string, parent: ref CatchableError = nil) =
|
template raiseSignerError(message: string, parent: ref ProviderError = nil) =
|
||||||
raise newException(SignerError, message, parent)
|
raise newException(SignerError, message, parent)
|
||||||
|
|
||||||
template convertError(body) =
|
template convertError(body) =
|
||||||
try:
|
try:
|
||||||
body
|
body
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except ProviderError as error:
|
except ProviderError as error:
|
||||||
raise error # do not convert provider errors
|
raise error # do not convert provider errors
|
||||||
except CatchableError as error:
|
except CatchableError as error:
|
||||||
@ -31,66 +27,59 @@ method provider*(
|
|||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getAddress*(
|
method getAddress*(
|
||||||
signer: Signer
|
signer: Signer): Future[Address]
|
||||||
): Future[Address] {.
|
{.base, async: (raises:[ProviderError, SignerError]).} =
|
||||||
base, async: (raises: [ProviderError, SignerError, CancelledError])
|
|
||||||
.} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method signMessage*(
|
method signMessage*(
|
||||||
signer: Signer, message: seq[byte]
|
signer: Signer,
|
||||||
): Future[seq[byte]] {.base, async: (raises: [SignerError, CancelledError]).} =
|
message: seq[byte]): Future[seq[byte]]
|
||||||
|
{.base, async: (raises: [SignerError]).} =
|
||||||
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
signer: Signer, transaction: Transaction
|
signer: Signer,
|
||||||
): Future[TransactionResponse] {.
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
{.base, async: (raises:[SignerError, ProviderError]).} =
|
||||||
.} =
|
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getGasPrice*(
|
method getGasPrice*(
|
||||||
signer: Signer
|
signer: Signer): Future[UInt256]
|
||||||
): Future[UInt256] {.
|
{.base, async: (raises: [ProviderError, SignerError]).} =
|
||||||
base, async: (raises: [ProviderError, SignerError, CancelledError])
|
|
||||||
.} =
|
|
||||||
return await signer.provider.getGasPrice()
|
return await signer.provider.getGasPrice()
|
||||||
|
|
||||||
method getMaxPriorityFeePerGas*(
|
|
||||||
signer: Signer
|
|
||||||
): Future[UInt256] {.async: (raises: [SignerError, CancelledError]).} =
|
|
||||||
return await signer.provider.getMaxPriorityFeePerGas()
|
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
signer: Signer, blockTag = BlockTag.latest
|
signer: Signer,
|
||||||
): Future[UInt256] {.
|
blockTag = BlockTag.latest): Future[UInt256]
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
{.base, async: (raises:[SignerError, ProviderError]).} =
|
||||||
.} =
|
|
||||||
convertError:
|
convertError:
|
||||||
let address = await signer.getAddress()
|
let address = await signer.getAddress()
|
||||||
return await signer.provider.getTransactionCount(address, blockTag)
|
return await signer.provider.getTransactionCount(address, blockTag)
|
||||||
|
|
||||||
method estimateGas*(
|
method estimateGas*(
|
||||||
signer: Signer, transaction: Transaction, blockTag = BlockTag.latest
|
signer: Signer,
|
||||||
): Future[UInt256] {.
|
transaction: Transaction,
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
blockTag = BlockTag.latest): Future[UInt256]
|
||||||
.} =
|
{.base, async: (raises:[SignerError, ProviderError]).} =
|
||||||
|
|
||||||
var transaction = transaction
|
var transaction = transaction
|
||||||
transaction.sender = some(await signer.getAddress())
|
transaction.sender = some(await signer.getAddress())
|
||||||
return await signer.provider.estimateGas(transaction, blockTag)
|
return await signer.provider.estimateGas(transaction, blockTag)
|
||||||
|
|
||||||
method getChainId*(
|
method getChainId*(
|
||||||
signer: Signer
|
signer: Signer): Future[UInt256]
|
||||||
): Future[UInt256] {.
|
{.base, async: (raises: [ProviderError, SignerError]).} =
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
|
||||||
.} =
|
|
||||||
return await signer.provider.getChainId()
|
return await signer.provider.getChainId()
|
||||||
|
|
||||||
method getNonce(
|
method getNonce(
|
||||||
signer: Signer
|
signer: Signer): Future[UInt256] {.base, async: (raises: [SignerError, ProviderError]).} =
|
||||||
): Future[UInt256] {.
|
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
|
||||||
.} =
|
|
||||||
return await signer.getTransactionCount(BlockTag.pending)
|
return await signer.getTransactionCount(BlockTag.pending)
|
||||||
|
|
||||||
template withLock*(signer: Signer, body: untyped) =
|
template withLock*(signer: Signer, body: untyped) =
|
||||||
@ -101,10 +90,7 @@ template withLock*(signer: Signer, body: untyped) =
|
|||||||
try:
|
try:
|
||||||
body
|
body
|
||||||
finally:
|
finally:
|
||||||
try:
|
signer.populateLock.release()
|
||||||
signer.populateLock.release()
|
|
||||||
except AsyncLockError as e:
|
|
||||||
raiseSignerError e.msg, e
|
|
||||||
|
|
||||||
method populateTransaction*(
|
method populateTransaction*(
|
||||||
signer: Signer,
|
signer: Signer,
|
||||||
@ -130,26 +116,8 @@ method populateTransaction*(
|
|||||||
populated.sender = some(address)
|
populated.sender = some(address)
|
||||||
if transaction.chainId.isNone:
|
if transaction.chainId.isNone:
|
||||||
populated.chainId = some(await signer.getChainId())
|
populated.chainId = some(await signer.getChainId())
|
||||||
|
if transaction.gasPrice.isNone and (transaction.maxFee.isNone or transaction.maxPriorityFee.isNone):
|
||||||
let blk = await signer.provider.getBlock(BlockTag.latest)
|
populated.gasPrice = some(await signer.getGasPrice())
|
||||||
|
|
||||||
if baseFeePerGas =? blk.?baseFeePerGas:
|
|
||||||
let maxPriorityFeePerGas = transaction.maxPriorityFeePerGas |? (await signer.provider.getMaxPriorityFeePerGas())
|
|
||||||
populated.maxPriorityFeePerGas = some(maxPriorityFeePerGas)
|
|
||||||
|
|
||||||
# Multiply by 2 because during times of congestion, baseFeePerGas can increase by 12.5% per block.
|
|
||||||
# https://github.com/ethers-io/ethers.js/discussions/3601#discussioncomment-4461273
|
|
||||||
let maxFeePerGas = transaction.maxFeePerGas |? (baseFeePerGas * 2 + maxPriorityFeePerGas)
|
|
||||||
populated.maxFeePerGas = some(maxFeePerGas)
|
|
||||||
|
|
||||||
populated.gasPrice = none(UInt256)
|
|
||||||
|
|
||||||
trace "EIP-1559 is supported", maxPriorityFeePerGas = maxPriorityFeePerGas, maxFeePerGas = maxFeePerGas
|
|
||||||
else:
|
|
||||||
populated.gasPrice = some(transaction.gasPrice |? (await signer.getGasPrice()))
|
|
||||||
populated.maxFeePerGas = none(UInt256)
|
|
||||||
populated.maxPriorityFeePerGas = none(UInt256)
|
|
||||||
trace "EIP-1559 is not supported", gasPrice = populated.gasPrice
|
|
||||||
|
|
||||||
if transaction.nonce.isNone and transaction.gasLimit.isNone:
|
if transaction.nonce.isNone and transaction.gasLimit.isNone:
|
||||||
# when both nonce and gasLimit are not populated, we must ensure getNonce is
|
# when both nonce and gasLimit are not populated, we must ensure getNonce is
|
||||||
@ -178,7 +146,7 @@ method populateTransaction*(
|
|||||||
method cancelTransaction*(
|
method cancelTransaction*(
|
||||||
signer: Signer,
|
signer: Signer,
|
||||||
tx: Transaction
|
tx: Transaction
|
||||||
): Future[TransactionResponse] {.base, async: (raises: [SignerError, CancelledError, ProviderError]).} =
|
): Future[TransactionResponse] {.base, async: (raises: [SignerError, CancelledError, AsyncLockError, ProviderError]).} =
|
||||||
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
||||||
# with the failed tx's nonce
|
# with the failed tx's nonce
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ type Wallet* = ref object of Signer
|
|||||||
|
|
||||||
proc new*(_: type Wallet, privateKey: PrivateKey): Wallet =
|
proc new*(_: type Wallet, privateKey: PrivateKey): Wallet =
|
||||||
let publicKey = privateKey.toPublicKey()
|
let publicKey = privateKey.toPublicKey()
|
||||||
let address = Address(publicKey.toCanonicalAddress())
|
let address = Address.init(publicKey.toCanonicalAddress())
|
||||||
Wallet(privateKey: privateKey, publicKey: publicKey, address: address)
|
Wallet(privateKey: privateKey, publicKey: publicKey, address: address)
|
||||||
|
|
||||||
proc new*(_: type Wallet, privateKey: PrivateKey, provider: Provider): Wallet =
|
proc new*(_: type Wallet, privateKey: PrivateKey, provider: Provider): Wallet =
|
||||||
@ -53,13 +53,13 @@ proc createRandom*(_: type Wallet): Wallet =
|
|||||||
result = Wallet()
|
result = Wallet()
|
||||||
result.privateKey = PrivateKey.random(getRng()[])
|
result.privateKey = PrivateKey.random(getRng()[])
|
||||||
result.publicKey = result.privateKey.toPublicKey()
|
result.publicKey = result.privateKey.toPublicKey()
|
||||||
result.address = Address(result.publicKey.toCanonicalAddress())
|
result.address = Address.init(result.publicKey.toCanonicalAddress())
|
||||||
|
|
||||||
proc createRandom*(_: type Wallet, provider: Provider): Wallet =
|
proc createRandom*(_: type Wallet, provider: Provider): Wallet =
|
||||||
result = Wallet()
|
result = Wallet()
|
||||||
result.privateKey = PrivateKey.random(getRng()[])
|
result.privateKey = PrivateKey.random(getRng()[])
|
||||||
result.publicKey = result.privateKey.toPublicKey()
|
result.publicKey = result.privateKey.toPublicKey()
|
||||||
result.address = Address(result.publicKey.toCanonicalAddress())
|
result.address = Address.init(result.publicKey.toCanonicalAddress())
|
||||||
result.provider = some provider
|
result.provider = some provider
|
||||||
|
|
||||||
method provider*(wallet: Wallet): Provider {.gcsafe, raises: [SignerError].} =
|
method provider*(wallet: Wallet): Provider {.gcsafe, raises: [SignerError].} =
|
||||||
@ -69,7 +69,7 @@ method provider*(wallet: Wallet): Provider {.gcsafe, raises: [SignerError].} =
|
|||||||
|
|
||||||
method getAddress*(
|
method getAddress*(
|
||||||
wallet: Wallet): Future[Address]
|
wallet: Wallet): Future[Address]
|
||||||
{.async: (raises:[ProviderError, SignerError, CancelledError]).} =
|
{.async: (raises:[ProviderError, SignerError]).} =
|
||||||
|
|
||||||
return wallet.address
|
return wallet.address
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ proc signTransaction*(wallet: Wallet,
|
|||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
transaction: Transaction): Future[TransactionResponse]
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
{.async: (raises:[SignerError, ProviderError, CancelledError]).} =
|
{.async: (raises:[SignerError, ProviderError]).} =
|
||||||
|
|
||||||
let signed = await signTransaction(wallet, transaction)
|
let signed = await signTransaction(wallet, transaction)
|
||||||
return await provider(wallet).sendTransaction(signed)
|
return await provider(wallet).sendTransaction(signed)
|
||||||
|
|||||||
@ -9,7 +9,5 @@ func raiseWalletError*(message: string) {.raises: [WalletError].}=
|
|||||||
template convertError*(body) =
|
template convertError*(body) =
|
||||||
try:
|
try:
|
||||||
body
|
body
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except CatchableError as error:
|
except CatchableError as error:
|
||||||
raiseWalletError(error.msg)
|
raiseWalletError(error.msg)
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import pkg/eth/keys
|
import pkg/eth/keys
|
||||||
import pkg/eth/rlp
|
import pkg/eth/rlp
|
||||||
import pkg/eth/common/transaction as eth
|
import pkg/eth/common/transaction as eth
|
||||||
import pkg/eth/common/transaction_utils
|
|
||||||
import pkg/eth/common/eth_hash
|
import pkg/eth/common/eth_hash
|
||||||
import ../../basics
|
import ../../basics
|
||||||
import ../../transaction as ethers
|
import ../../transaction as ethers
|
||||||
import ../../provider
|
import ../../provider
|
||||||
import ./error
|
import ./error
|
||||||
from pkg/eth/common/eth_types import EthAddress
|
|
||||||
|
|
||||||
type
|
type
|
||||||
Transaction = ethers.Transaction
|
Transaction = ethers.Transaction
|
||||||
@ -26,18 +24,17 @@ func toSignableTransaction(transaction: Transaction): SignableTransaction =
|
|||||||
raiseWalletError "missing gas limit"
|
raiseWalletError "missing gas limit"
|
||||||
|
|
||||||
signable.nonce = nonce.truncate(uint64)
|
signable.nonce = nonce.truncate(uint64)
|
||||||
signable.chainId = chainId
|
signable.chainId = ChainId(chainId.truncate(uint64))
|
||||||
signable.gasLimit = GasInt(gasLimit.truncate(uint64))
|
signable.gasLimit = GasInt(gasLimit.truncate(uint64))
|
||||||
|
signable.to = some EthAddress(transaction.to)
|
||||||
signable.to = Opt.some(EthAddress(transaction.to))
|
|
||||||
signable.value = transaction.value
|
signable.value = transaction.value
|
||||||
signable.payload = transaction.data
|
signable.payload = transaction.data
|
||||||
|
|
||||||
if maxFeePerGas =? transaction.maxFeePerGas and
|
if maxFee =? transaction.maxFee and
|
||||||
maxPriorityFeePerGas =? transaction.maxPriorityFeePerGas:
|
maxPriorityFee =? transaction.maxPriorityFee:
|
||||||
signable.txType = TxEip1559
|
signable.txType = TxEip1559
|
||||||
signable.maxFeePerGas = GasInt(maxFeePerGas.truncate(uint64))
|
signable.maxFee = GasInt(maxFee.truncate(uint64))
|
||||||
signable.maxPriorityFeePerGas = GasInt(maxPriorityFeePerGas.truncate(uint64))
|
signable.maxPriorityFee = GasInt(maxPriorityFee.truncate(uint64))
|
||||||
elif gasPrice =? transaction.gasPrice:
|
elif gasPrice =? transaction.gasPrice:
|
||||||
signable.txType = TxLegacy
|
signable.txType = TxLegacy
|
||||||
signable.gasPrice = GasInt(gasPrice.truncate(uint64))
|
signable.gasPrice = GasInt(gasPrice.truncate(uint64))
|
||||||
@ -48,7 +45,21 @@ func toSignableTransaction(transaction: Transaction): SignableTransaction =
|
|||||||
|
|
||||||
func sign(key: PrivateKey, transaction: SignableTransaction): seq[byte] =
|
func sign(key: PrivateKey, transaction: SignableTransaction): seq[byte] =
|
||||||
var transaction = transaction
|
var transaction = transaction
|
||||||
transaction.signature = transaction.sign(key, true)
|
|
||||||
|
# Temporary V value, used to signal to the hashing function
|
||||||
|
# that we'd like to use an EIP-155 signature
|
||||||
|
transaction.V = int64(uint64(transaction.chainId)) * 2 + 35
|
||||||
|
|
||||||
|
let hash = transaction.txHashNoSignature().data
|
||||||
|
let signature = key.sign(SkMessage(hash)).toRaw()
|
||||||
|
|
||||||
|
transaction.R = UInt256.fromBytesBE(signature[0..<32])
|
||||||
|
transaction.S = UInt256.fromBytesBE(signature[32..<64])
|
||||||
|
transaction.V = int64(signature[64])
|
||||||
|
|
||||||
|
if transaction.txType == TxLegacy:
|
||||||
|
transaction.V += int64(uint64(transaction.chainId)) * 2 + 35
|
||||||
|
|
||||||
rlp.encode(transaction)
|
rlp.encode(transaction)
|
||||||
|
|
||||||
func sign*(key: PrivateKey, transaction: Transaction): seq[byte] =
|
func sign*(key: PrivateKey, transaction: Transaction): seq[byte] =
|
||||||
|
|||||||
@ -15,8 +15,8 @@ type
|
|||||||
nonce*: ?UInt256
|
nonce*: ?UInt256
|
||||||
chainId*: ?UInt256
|
chainId*: ?UInt256
|
||||||
gasPrice*: ?UInt256
|
gasPrice*: ?UInt256
|
||||||
maxPriorityFeePerGas*: ?UInt256
|
maxFee*: ?UInt256
|
||||||
maxFeePerGas*: ?UInt256
|
maxPriorityFee*: ?UInt256
|
||||||
gasLimit*: ?UInt256
|
gasLimit*: ?UInt256
|
||||||
transactionType* {.serialize("type").}: ?TransactionType
|
transactionType* {.serialize("type").}: ?TransactionType
|
||||||
|
|
||||||
|
|||||||
@ -13,13 +13,13 @@ method provider*(signer: MockSigner): Provider =
|
|||||||
|
|
||||||
method getAddress*(
|
method getAddress*(
|
||||||
signer: MockSigner): Future[Address]
|
signer: MockSigner): Future[Address]
|
||||||
{.async: (raises:[ProviderError, SignerError, CancelledError]).} =
|
{.async: (raises:[ProviderError, SignerError]).} =
|
||||||
|
|
||||||
return signer.address
|
return signer.address
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
signer: MockSigner,
|
signer: MockSigner,
|
||||||
transaction: Transaction): Future[TransactionResponse]
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
{.async: (raises:[SignerError, ProviderError, CancelledError]).} =
|
{.async: (raises:[SignerError, ProviderError]).} =
|
||||||
|
|
||||||
signer.transactions.add(transaction)
|
signer.transactions.add(transaction)
|
||||||
|
|||||||
@ -9,12 +9,11 @@ import pkg/json_rpc/errors
|
|||||||
|
|
||||||
type MockRpcHttpServer* = ref object
|
type MockRpcHttpServer* = ref object
|
||||||
filters*: seq[string]
|
filters*: seq[string]
|
||||||
nextGetChangesReturnsError*: bool
|
|
||||||
srv: RpcHttpServer
|
srv: RpcHttpServer
|
||||||
|
|
||||||
proc new*(_: type MockRpcHttpServer): MockRpcHttpServer =
|
proc new*(_: type MockRpcHttpServer): MockRpcHttpServer =
|
||||||
let srv = newRpcHttpServer(["127.0.0.1:0"])
|
let srv = newRpcHttpServer(["127.0.0.1:0"])
|
||||||
MockRpcHttpServer(filters: @[], srv: srv, nextGetChangesReturnsError: false)
|
MockRpcHttpServer(filters: @[], srv: srv)
|
||||||
|
|
||||||
proc invalidateFilter*(server: MockRpcHttpServer, jsonId: JsonNode) =
|
proc invalidateFilter*(server: MockRpcHttpServer, jsonId: JsonNode) =
|
||||||
server.filters.keepItIf it != jsonId.getStr
|
server.filters.keepItIf it != jsonId.getStr
|
||||||
@ -31,9 +30,6 @@ proc start*(server: MockRpcHttpServer) =
|
|||||||
return filterId
|
return filterId
|
||||||
|
|
||||||
server.srv.router.rpc("eth_getFilterChanges") do(id: string) -> seq[string]:
|
server.srv.router.rpc("eth_getFilterChanges") do(id: string) -> seq[string]:
|
||||||
if server.nextGetChangesReturnsError:
|
|
||||||
raise (ref ApplicationError)(code: -32000, msg: "unknown error")
|
|
||||||
|
|
||||||
if id notin server.filters:
|
if id notin server.filters:
|
||||||
raise (ref ApplicationError)(code: -32000, msg: "filter not found")
|
raise (ref ApplicationError)(code: -32000, msg: "filter not found")
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/ethers/providers/jsonrpc/conversions
|
import pkg/ethers/providers/jsonrpc/conversions
|
||||||
@ -49,7 +49,7 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
|||||||
let oldBlock = !await provider.getBlock(BlockTag.latest)
|
let oldBlock = !await provider.getBlock(BlockTag.latest)
|
||||||
discard await provider.send("evm_mine")
|
discard await provider.send("evm_mine")
|
||||||
var newBlock: Block
|
var newBlock: Block
|
||||||
let blockHandler = proc(blck: ?!Block) {.raises:[].}= newBlock = blck.value
|
let blockHandler = proc(blck: Block) = newBlock = blck
|
||||||
let subscription = await provider.subscribe(blockHandler)
|
let subscription = await provider.subscribe(blockHandler)
|
||||||
discard await provider.send("evm_mine")
|
discard await provider.send("evm_mine")
|
||||||
check eventually newBlock.number.isSome
|
check eventually newBlock.number.isSome
|
||||||
@ -98,7 +98,7 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
|||||||
expect JsonRpcProviderError:
|
expect JsonRpcProviderError:
|
||||||
discard await provider.getBlock(BlockTag.latest)
|
discard await provider.getBlock(BlockTag.latest)
|
||||||
expect JsonRpcProviderError:
|
expect JsonRpcProviderError:
|
||||||
discard await provider.subscribe(proc(_: ?!Block) = discard)
|
discard await provider.subscribe(proc(_: Block) = discard)
|
||||||
expect JsonRpcProviderError:
|
expect JsonRpcProviderError:
|
||||||
discard await provider.getSigner().sendTransaction(Transaction.example)
|
discard await provider.getSigner().sendTransaction(Transaction.example)
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ../../examples
|
import ../../examples
|
||||||
@ -55,27 +55,20 @@ suite "JsonRpcSigner":
|
|||||||
let transaction = Transaction.example
|
let transaction = Transaction.example
|
||||||
let populated = await signer.populateTransaction(transaction)
|
let populated = await signer.populateTransaction(transaction)
|
||||||
check !populated.sender == await signer.getAddress()
|
check !populated.sender == await signer.getAddress()
|
||||||
|
check !populated.gasPrice == await signer.getGasPrice()
|
||||||
check !populated.nonce == await signer.getTransactionCount(BlockTag.pending)
|
check !populated.nonce == await signer.getTransactionCount(BlockTag.pending)
|
||||||
check !populated.gasLimit == await signer.estimateGas(transaction)
|
check !populated.gasLimit == await signer.estimateGas(transaction)
|
||||||
check !populated.chainId == await signer.getChainId()
|
check !populated.chainId == await signer.getChainId()
|
||||||
|
|
||||||
let blk = !(await signer.provider.getBlock(BlockTag.latest))
|
|
||||||
check !populated.maxPriorityFeePerGas == await signer.getMaxPriorityFeePerGas()
|
|
||||||
check !populated.maxFeePerGas == !blk.baseFeePerGas * 2.u256 + !populated.maxPriorityFeePerGas
|
|
||||||
|
|
||||||
test "populate does not overwrite existing fields":
|
test "populate does not overwrite existing fields":
|
||||||
let signer = provider.getSigner()
|
let signer = provider.getSigner()
|
||||||
var transaction = Transaction.example
|
var transaction = Transaction.example
|
||||||
transaction.sender = some await signer.getAddress()
|
transaction.sender = some await signer.getAddress()
|
||||||
transaction.nonce = some UInt256.example
|
transaction.nonce = some UInt256.example
|
||||||
transaction.chainId = some await signer.getChainId()
|
transaction.chainId = some await signer.getChainId()
|
||||||
transaction.maxPriorityFeePerGas = some UInt256.example
|
transaction.gasPrice = some UInt256.example
|
||||||
transaction.gasLimit = some UInt256.example
|
transaction.gasLimit = some UInt256.example
|
||||||
let populated = await signer.populateTransaction(transaction)
|
let populated = await signer.populateTransaction(transaction)
|
||||||
|
|
||||||
let blk = !(await signer.provider.getBlock(BlockTag.latest))
|
|
||||||
transaction.maxFeePerGas = some(!blk.baseFeePerGas * 2.u256 + !populated.maxPriorityFeePerGas)
|
|
||||||
|
|
||||||
check populated == transaction
|
check populated == transaction
|
||||||
|
|
||||||
test "populate fails when sender does not match signer address":
|
test "populate fails when sender does not match signer address":
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import std/os
|
import std/os
|
||||||
|
import std/sequtils
|
||||||
import std/importutils
|
import std/importutils
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import pkg/json_rpc/rpcclient
|
import pkg/json_rpc/rpcclient
|
||||||
import pkg/json_rpc/rpcserver
|
import pkg/json_rpc/rpcserver
|
||||||
@ -26,8 +27,8 @@ template subscriptionTests(subscriptions, client) =
|
|||||||
|
|
||||||
test "subscribes to new blocks":
|
test "subscribes to new blocks":
|
||||||
var latestBlock: Block
|
var latestBlock: Block
|
||||||
proc callback(blck: ?!Block) =
|
proc callback(blck: Block) =
|
||||||
latestBlock = blck.value
|
latestBlock = blck
|
||||||
let subscription = await subscriptions.subscribeBlocks(callback)
|
let subscription = await subscriptions.subscribeBlocks(callback)
|
||||||
discard await client.call("evm_mine", newJArray())
|
discard await client.call("evm_mine", newJArray())
|
||||||
check eventually latestBlock.number.isSome
|
check eventually latestBlock.number.isSome
|
||||||
@ -37,9 +38,8 @@ template subscriptionTests(subscriptions, client) =
|
|||||||
|
|
||||||
test "stops listening to new blocks when unsubscribed":
|
test "stops listening to new blocks when unsubscribed":
|
||||||
var count = 0
|
var count = 0
|
||||||
proc callback(blck: ?!Block) =
|
proc callback(blck: Block) =
|
||||||
if blck.isOk:
|
inc count
|
||||||
inc count
|
|
||||||
let subscription = await subscriptions.subscribeBlocks(callback)
|
let subscription = await subscriptions.subscribeBlocks(callback)
|
||||||
discard await client.call("evm_mine", newJArray())
|
discard await client.call("evm_mine", newJArray())
|
||||||
check eventually count > 0
|
check eventually count > 0
|
||||||
@ -49,20 +49,10 @@ template subscriptionTests(subscriptions, client) =
|
|||||||
await sleepAsync(100.millis)
|
await sleepAsync(100.millis)
|
||||||
check count == 0
|
check count == 0
|
||||||
|
|
||||||
test "unsubscribing from a non-existent subscription does not do any harm":
|
|
||||||
await subscriptions.unsubscribe(newJInt(0))
|
|
||||||
|
|
||||||
test "duplicate unsubscribe is harmless":
|
|
||||||
proc callback(blck: ?!Block) = discard
|
|
||||||
let subscription = await subscriptions.subscribeBlocks(callback)
|
|
||||||
await subscriptions.unsubscribe(subscription)
|
|
||||||
await subscriptions.unsubscribe(subscription)
|
|
||||||
|
|
||||||
test "stops listening to new blocks when provider is closed":
|
test "stops listening to new blocks when provider is closed":
|
||||||
var count = 0
|
var count = 0
|
||||||
proc callback(blck: ?!Block) =
|
proc callback(blck: Block) =
|
||||||
if blck.isOk:
|
inc count
|
||||||
inc count
|
|
||||||
discard await subscriptions.subscribeBlocks(callback)
|
discard await subscriptions.subscribeBlocks(callback)
|
||||||
discard await client.call("evm_mine", newJArray())
|
discard await client.call("evm_mine", newJArray())
|
||||||
check eventually count > 0
|
check eventually count > 0
|
||||||
@ -107,14 +97,13 @@ suite "HTTP polling subscriptions":
|
|||||||
|
|
||||||
subscriptionTests(subscriptions, client)
|
subscriptionTests(subscriptions, client)
|
||||||
|
|
||||||
suite "HTTP polling subscriptions - mock tests":
|
suite "HTTP polling subscriptions - filter not found":
|
||||||
|
|
||||||
var subscriptions: PollingSubscriptions
|
var subscriptions: PollingSubscriptions
|
||||||
var client: RpcHttpClient
|
var client: RpcHttpClient
|
||||||
var mockServer: MockRpcHttpServer
|
var mockServer: MockRpcHttpServer
|
||||||
|
|
||||||
privateAccess(PollingSubscriptions)
|
privateAccess(PollingSubscriptions)
|
||||||
privateAccess(JsonRpcSubscriptions)
|
|
||||||
|
|
||||||
proc startServer() {.async.} =
|
proc startServer() {.async.} =
|
||||||
mockServer = MockRpcHttpServer.new()
|
mockServer = MockRpcHttpServer.new()
|
||||||
@ -141,7 +130,7 @@ suite "HTTP polling subscriptions - mock tests":
|
|||||||
|
|
||||||
test "filter not found error recreates log filter":
|
test "filter not found error recreates log filter":
|
||||||
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||||
let emptyHandler = proc(log: ?!Log) = discard
|
let emptyHandler = proc(log: Log) = discard
|
||||||
|
|
||||||
check subscriptions.logFilters.len == 0
|
check subscriptions.logFilters.len == 0
|
||||||
check subscriptions.subscriptionMapping.len == 0
|
check subscriptions.subscriptionMapping.len == 0
|
||||||
@ -159,7 +148,7 @@ suite "HTTP polling subscriptions - mock tests":
|
|||||||
|
|
||||||
test "recreated log filter can be still unsubscribed using the original id":
|
test "recreated log filter can be still unsubscribed using the original id":
|
||||||
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||||
let emptyHandler = proc(log: ?!Log) = discard
|
let emptyHandler = proc(log: Log) = discard
|
||||||
let id = await subscriptions.subscribeLogs(filter, emptyHandler)
|
let id = await subscriptions.subscribeLogs(filter, emptyHandler)
|
||||||
mockServer.invalidateFilter(id)
|
mockServer.invalidateFilter(id)
|
||||||
check eventually subscriptions.subscriptionMapping[id] != id
|
check eventually subscriptions.subscriptionMapping[id] != id
|
||||||
@ -170,7 +159,7 @@ suite "HTTP polling subscriptions - mock tests":
|
|||||||
check not subscriptions.subscriptionMapping.hasKey id
|
check not subscriptions.subscriptionMapping.hasKey id
|
||||||
|
|
||||||
test "filter not found error recreates block filter":
|
test "filter not found error recreates block filter":
|
||||||
let emptyHandler = proc(blck: ?!Block) = discard
|
let emptyHandler = proc(blck: Block) = discard
|
||||||
|
|
||||||
check subscriptions.subscriptionMapping.len == 0
|
check subscriptions.subscriptionMapping.len == 0
|
||||||
let id = await subscriptions.subscribeBlocks(emptyHandler)
|
let id = await subscriptions.subscribeBlocks(emptyHandler)
|
||||||
@ -181,7 +170,7 @@ suite "HTTP polling subscriptions - mock tests":
|
|||||||
check eventually subscriptions.subscriptionMapping[id] != id
|
check eventually subscriptions.subscriptionMapping[id] != id
|
||||||
|
|
||||||
test "recreated block filter can be still unsubscribed using the original id":
|
test "recreated block filter can be still unsubscribed using the original id":
|
||||||
let emptyHandler = proc(blck: ?!Block) = discard
|
let emptyHandler = proc(blck: Block) = discard
|
||||||
let id = await subscriptions.subscribeBlocks(emptyHandler)
|
let id = await subscriptions.subscribeBlocks(emptyHandler)
|
||||||
mockServer.invalidateFilter(id)
|
mockServer.invalidateFilter(id)
|
||||||
check eventually subscriptions.subscriptionMapping[id] != id
|
check eventually subscriptions.subscriptionMapping[id] != id
|
||||||
@ -192,7 +181,7 @@ suite "HTTP polling subscriptions - mock tests":
|
|||||||
|
|
||||||
test "polling continues with new filter after temporary error":
|
test "polling continues with new filter after temporary error":
|
||||||
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||||
let emptyHandler = proc(log: ?!Log) = discard
|
let emptyHandler = proc(log: Log) = discard
|
||||||
|
|
||||||
let id = await subscriptions.subscribeLogs(filter, emptyHandler)
|
let id = await subscriptions.subscribeLogs(filter, emptyHandler)
|
||||||
|
|
||||||
@ -202,17 +191,3 @@ suite "HTTP polling subscriptions - mock tests":
|
|||||||
await startServer()
|
await startServer()
|
||||||
|
|
||||||
check eventually subscriptions.subscriptionMapping[id] != id
|
check eventually subscriptions.subscriptionMapping[id] != id
|
||||||
|
|
||||||
test "calls callback with failed result on error":
|
|
||||||
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
|
||||||
var failedResultReceived = false
|
|
||||||
|
|
||||||
proc handler(log: ?!Log) =
|
|
||||||
if log.isErr:
|
|
||||||
failedResultReceived = true
|
|
||||||
|
|
||||||
let id = await subscriptions.subscribeLogs(filter, handler)
|
|
||||||
|
|
||||||
await sleepAsync(50.milliseconds)
|
|
||||||
mockServer.nextGetChangesReturnsError = true
|
|
||||||
check eventually failedResultReceived
|
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
import std/os
|
|
||||||
import std/importutils
|
|
||||||
import pkg/asynctest/chronos/unittest
|
|
||||||
import pkg/json_rpc/rpcclient
|
|
||||||
import ethers/provider
|
|
||||||
import ethers/providers/jsonrpc/subscriptions
|
|
||||||
|
|
||||||
import ../../examples
|
|
||||||
|
|
||||||
suite "Websocket re-subscriptions":
|
|
||||||
privateAccess(JsonRpcSubscriptions)
|
|
||||||
|
|
||||||
var subscriptions: JsonRpcSubscriptions
|
|
||||||
var client: RpcWebSocketClient
|
|
||||||
var resubscribeInterval: int
|
|
||||||
|
|
||||||
setup:
|
|
||||||
resubscribeInterval = 3
|
|
||||||
client = newRpcWebSocketClient()
|
|
||||||
await client.connect("ws://" & getEnv("ETHERS_TEST_PROVIDER", "localhost:8545"))
|
|
||||||
subscriptions = JsonRpcSubscriptions.new(client, resubscribeInterval = resubscribeInterval)
|
|
||||||
subscriptions.start()
|
|
||||||
|
|
||||||
teardown:
|
|
||||||
await subscriptions.close()
|
|
||||||
await client.close()
|
|
||||||
|
|
||||||
test "unsubscribing from a log filter while subscriptions are being resubscribed does not cause a concurrency error":
|
|
||||||
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
|
||||||
let emptyHandler = proc(log: ?!Log) = discard
|
|
||||||
|
|
||||||
for i in 1..10:
|
|
||||||
discard await subscriptions.subscribeLogs(filter, emptyHandler)
|
|
||||||
|
|
||||||
# Wait until the re-subscription starts
|
|
||||||
await sleepAsync(resubscribeInterval.seconds)
|
|
||||||
|
|
||||||
# Attempt to modify callbacks while its being iterated
|
|
||||||
discard await subscriptions.subscribeLogs(filter, emptyHandler)
|
|
||||||
|
|
||||||
test "resubscribe events take effect with new subscription IDs in the log filters":
|
|
||||||
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
|
||||||
let emptyHandler = proc(log: ?!Log) = discard
|
|
||||||
let id = await subscriptions.subscribeLogs(filter, emptyHandler)
|
|
||||||
|
|
||||||
check id in subscriptions.logFilters
|
|
||||||
check subscriptions.logFilters.len == 1
|
|
||||||
|
|
||||||
# Make sure the subscription is done
|
|
||||||
await sleepAsync((resubscribeInterval + 1).seconds)
|
|
||||||
|
|
||||||
# The previous subscription should not be in the log filters
|
|
||||||
check id notin subscriptions.logFilters
|
|
||||||
|
|
||||||
# There is still one subscription which is the new one
|
|
||||||
check subscriptions.logFilters.len == 1
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import ./jsonrpc/testJsonRpcProvider
|
import ./jsonrpc/testJsonRpcProvider
|
||||||
import ./jsonrpc/testJsonRpcSigner
|
import ./jsonrpc/testJsonRpcSigner
|
||||||
import ./jsonrpc/testJsonRpcSubscriptions
|
import ./jsonrpc/testJsonRpcSubscriptions
|
||||||
import ./jsonrpc/testWsResubscription
|
|
||||||
import ./jsonrpc/testConversions
|
import ./jsonrpc/testConversions
|
||||||
import ./jsonrpc/testErrors
|
import ./jsonrpc/testErrors
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,5 @@ import ./testErc20
|
|||||||
import ./testGasEstimation
|
import ./testGasEstimation
|
||||||
import ./testErrorDecoding
|
import ./testErrorDecoding
|
||||||
import ./testCustomErrors
|
import ./testCustomErrors
|
||||||
import ./testBlockTag
|
|
||||||
|
|
||||||
{.warning[UnusedImport]:off.}
|
{.warning[UnusedImport]:off.}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ author = "Nim Ethers Authors"
|
|||||||
description = "Tests for Nim Ethers library"
|
description = "Tests for Nim Ethers library"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
requires "asynctest >= 0.5.4 & < 0.6.0"
|
requires "asynctest >= 0.4.0 & < 0.5.0"
|
||||||
|
|
||||||
task test, "Run the test suite":
|
task test, "Run the test suite":
|
||||||
exec "nimble install -d -y"
|
exec "nimble install -d -y"
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
import std/unittest
|
|
||||||
import std/strformat
|
|
||||||
|
|
||||||
import pkg/stint
|
|
||||||
import pkg/questionable
|
|
||||||
|
|
||||||
import ethers/blocktag
|
|
||||||
|
|
||||||
|
|
||||||
type
|
|
||||||
PredefinedTags = enum earliest, latest, pending
|
|
||||||
|
|
||||||
suite "BlockTag":
|
|
||||||
for predefinedTag in PredefinedTags:
|
|
||||||
test fmt"can be created with predefined special type: {predefinedTag}":
|
|
||||||
var blockTag: BlockTag
|
|
||||||
case predefinedTag:
|
|
||||||
of earliest: blockTag = BlockTag.earliest
|
|
||||||
of latest: blockTag = BlockTag.latest
|
|
||||||
of pending: blockTag = BlockTag.pending
|
|
||||||
check $blockTag == $predefinedTag
|
|
||||||
|
|
||||||
test "can be created with a number":
|
|
||||||
let blockTag = BlockTag.init(42.u256)
|
|
||||||
check blockTag.number == 42.u256.some
|
|
||||||
|
|
||||||
test "can be converted to string in hex format for BlockTags with number":
|
|
||||||
let blockTag = BlockTag.init(42.u256)
|
|
||||||
check $blockTag == "0x2a"
|
|
||||||
|
|
||||||
test "can be compared for equality when BlockTag with number":
|
|
||||||
let blockTag1 = BlockTag.init(42.u256)
|
|
||||||
let blockTag2 = BlockTag.init(42.u256)
|
|
||||||
let blockTag3 = BlockTag.init(43.u256)
|
|
||||||
check blockTag1 == blockTag2
|
|
||||||
check blockTag1 != blockTag3
|
|
||||||
|
|
||||||
for predefinedTag in [BlockTag.earliest, BlockTag.latest, BlockTag.pending]:
|
|
||||||
test fmt"can be compared for equality when predefined tag: {predefinedTag}":
|
|
||||||
case $predefinedTag:
|
|
||||||
of "earliest":
|
|
||||||
check predefinedTag == BlockTag.earliest
|
|
||||||
check predefinedTag != BlockTag.latest
|
|
||||||
check predefinedTag != BlockTag.pending
|
|
||||||
of "latest":
|
|
||||||
check predefinedTag != BlockTag.earliest
|
|
||||||
check predefinedTag == BlockTag.latest
|
|
||||||
check predefinedTag != BlockTag.pending
|
|
||||||
of "pending":
|
|
||||||
check predefinedTag != BlockTag.earliest
|
|
||||||
check predefinedTag != BlockTag.latest
|
|
||||||
check predefinedTag == BlockTag.pending
|
|
||||||
|
|
||||||
for predefinedTag in [BlockTag.earliest, BlockTag.latest, BlockTag.pending]:
|
|
||||||
test fmt"number accessor returns None for BlockTags with string: {predefinedTag}":
|
|
||||||
check predefinedTag.number == UInt256.none
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import pkg/serde
|
import pkg/serde
|
||||||
import std/os
|
import std/os
|
||||||
import std/options
|
import std/options
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
import pkg/stint
|
import pkg/stint
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
@ -64,7 +64,7 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
|||||||
test "can call constant functions without a return type":
|
test "can call constant functions without a return type":
|
||||||
token = TestToken.new(token.address, provider.getSigner())
|
token = TestToken.new(token.address, provider.getSigner())
|
||||||
proc mint(token: TestToken, holder: Address, amount: UInt256) {.contract, view.}
|
proc mint(token: TestToken, holder: Address, amount: UInt256) {.contract, view.}
|
||||||
await token.mint(accounts[1], 100.u256)
|
await mint(token, accounts[1], 100.u256)
|
||||||
check (await balanceOf(token, accounts[1])) == 0.u256
|
check (await balanceOf(token, accounts[1])) == 0.u256
|
||||||
|
|
||||||
test "can call non-constant functions without a return type":
|
test "can call non-constant functions without a return type":
|
||||||
@ -74,6 +74,7 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
|||||||
check (await balanceOf(token, accounts[1])) == 100.u256
|
check (await balanceOf(token, accounts[1])) == 100.u256
|
||||||
|
|
||||||
test "can call non-constant functions with a Confirmable return type":
|
test "can call non-constant functions with a Confirmable return type":
|
||||||
|
|
||||||
token = TestToken.new(token.address, provider.getSigner())
|
token = TestToken.new(token.address, provider.getSigner())
|
||||||
proc mint(token: TestToken,
|
proc mint(token: TestToken,
|
||||||
holder: Address,
|
holder: Address,
|
||||||
@ -107,17 +108,17 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
|||||||
check (await token.connect(provider).balanceOf(accounts[1])) == 25.u256
|
check (await token.connect(provider).balanceOf(accounts[1])) == 25.u256
|
||||||
check (await token.connect(provider).balanceOf(accounts[2])) == 25.u256
|
check (await token.connect(provider).balanceOf(accounts[2])) == 25.u256
|
||||||
|
|
||||||
test "takes custom values for nonce, gasprice and maxPriorityFeePerGas":
|
test "takes custom values for nonce, gasprice and gaslimit":
|
||||||
let overrides = TransactionOverrides(
|
let overrides = TransactionOverrides(
|
||||||
nonce: some 100.u256,
|
nonce: some 100.u256,
|
||||||
maxPriorityFeePerGas: some 200.u256,
|
gasPrice: some 200.u256,
|
||||||
gasLimit: some 300.u256
|
gasLimit: some 300.u256
|
||||||
)
|
)
|
||||||
let signer = MockSigner.new(provider)
|
let signer = MockSigner.new(provider)
|
||||||
discard await token.connect(signer).mint(accounts[0], 42.u256, overrides)
|
discard await token.connect(signer).mint(accounts[0], 42.u256, overrides)
|
||||||
check signer.transactions.len == 1
|
check signer.transactions.len == 1
|
||||||
check signer.transactions[0].nonce == overrides.nonce
|
check signer.transactions[0].nonce == overrides.nonce
|
||||||
check signer.transactions[0].maxPriorityFeePerGas == overrides.maxPriorityFeePerGas
|
check signer.transactions[0].gasPrice == overrides.gasPrice
|
||||||
check signer.transactions[0].gasLimit == overrides.gasLimit
|
check signer.transactions[0].gasLimit == overrides.gasLimit
|
||||||
|
|
||||||
test "can call functions for different block heights":
|
test "can call functions for different block heights":
|
||||||
@ -145,22 +146,10 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
|||||||
discard await token.transfer(accounts[1], 50.u256, beforeMint)
|
discard await token.transfer(accounts[1], 50.u256, beforeMint)
|
||||||
discard await token.transfer(accounts[1], 50.u256, afterMint)
|
discard await token.transfer(accounts[1], 50.u256, afterMint)
|
||||||
|
|
||||||
test "can estimate gas of a function call":
|
|
||||||
proc mint(token: TestToken, holder: Address, amount: UInt256) {.contract.}
|
|
||||||
let estimate = await token.estimateGas.mint(accounts[1], 100.u256)
|
|
||||||
let correctGas = TransactionOverrides(gasLimit: some estimate)
|
|
||||||
await token.mint(accounts[1], 100.u256, correctGas)
|
|
||||||
let invalidGas = TransactionOverrides(gasLimit: some (estimate - 1))
|
|
||||||
expect ProviderError:
|
|
||||||
await token.mint(accounts[1], 100.u256, invalidGas)
|
|
||||||
|
|
||||||
test "receives events when subscribed":
|
test "receives events when subscribed":
|
||||||
var transfers: seq[Transfer]
|
var transfers: seq[Transfer]
|
||||||
|
|
||||||
proc handleTransfer(transferRes: ?!Transfer) =
|
proc handleTransfer(transfer: Transfer) =
|
||||||
without transfer =? transferRes, error:
|
|
||||||
echo error.msg
|
|
||||||
|
|
||||||
transfers.add(transfer)
|
transfers.add(transfer)
|
||||||
|
|
||||||
let signer0 = provider.getSigner(accounts[0])
|
let signer0 = provider.getSigner(accounts[0])
|
||||||
@ -182,9 +171,8 @@ for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
|||||||
test "stops receiving events when unsubscribed":
|
test "stops receiving events when unsubscribed":
|
||||||
var transfers: seq[Transfer]
|
var transfers: seq[Transfer]
|
||||||
|
|
||||||
proc handleTransfer(transferRes: ?!Transfer) =
|
proc handleTransfer(transfer: Transfer) =
|
||||||
if transfer =? transferRes:
|
transfers.add(transfer)
|
||||||
transfers.add(transfer)
|
|
||||||
|
|
||||||
let signer0 = provider.getSigner(accounts[0])
|
let signer0 = provider.getSigner(accounts[0])
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import ./hardhat
|
import ./hardhat
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ suite "Contract custom errors":
|
|||||||
expect ErrorWithArguments:
|
expect ErrorWithArguments:
|
||||||
await contract.revertsMultipleErrors(simple = false)
|
await contract.revertsMultipleErrors(simple = false)
|
||||||
|
|
||||||
test "handles gas estimation errors when calling a contract function":
|
test "handles gas estimation errors":
|
||||||
proc revertsTransaction(contract: TestCustomErrors)
|
proc revertsTransaction(contract: TestCustomErrors)
|
||||||
{.contract, errors:[ErrorWithArguments].}
|
{.contract, errors:[ErrorWithArguments].}
|
||||||
|
|
||||||
@ -109,18 +109,6 @@ suite "Contract custom errors":
|
|||||||
check error.arguments.one == 1.u256
|
check error.arguments.one == 1.u256
|
||||||
check error.arguments.two == true
|
check error.arguments.two == true
|
||||||
|
|
||||||
test "handles errors when only doing gas estimation":
|
|
||||||
proc revertsTransaction(contract: TestCustomErrors)
|
|
||||||
{.contract, errors:[ErrorWithArguments], used.}
|
|
||||||
|
|
||||||
let contract = contract.connect(provider.getSigner())
|
|
||||||
try:
|
|
||||||
discard await contract.estimateGas.revertsTransaction()
|
|
||||||
fail()
|
|
||||||
except ErrorWithArguments as error:
|
|
||||||
check error.arguments.one == 1.u256
|
|
||||||
check error.arguments.two == true
|
|
||||||
|
|
||||||
test "handles transaction submission errors":
|
test "handles transaction submission errors":
|
||||||
proc revertsTransaction(contract: TestCustomErrors)
|
proc revertsTransaction(contract: TestCustomErrors)
|
||||||
{.contract, errors:[ErrorWithArguments].}
|
{.contract, errors:[ErrorWithArguments].}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import ./hardhat
|
import ./hardhat
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
import pkg/stint
|
import pkg/stint
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import std/strutils
|
|||||||
import pkg/questionable/results
|
import pkg/questionable/results
|
||||||
import pkg/contractabi
|
import pkg/contractabi
|
||||||
import pkg/ethers/errors
|
import pkg/ethers/errors
|
||||||
import pkg/ethers/contracts/errors/encoding
|
import pkg/ethers/errors/encoding
|
||||||
|
|
||||||
suite "Decoding of custom errors":
|
suite "Decoding of custom errors":
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/contractabi
|
import pkg/contractabi
|
||||||
import ./examples
|
import ./examples
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import ./hardhat
|
import ./hardhat
|
||||||
@ -28,7 +28,7 @@ suite "gas estimation":
|
|||||||
discard await provider.send("evm_revert", @[snapshot])
|
discard await provider.send("evm_revert", @[snapshot])
|
||||||
await provider.close()
|
await provider.close()
|
||||||
|
|
||||||
test "contract function calls use pending block for gas estimations":
|
test "uses pending block for gas estimations":
|
||||||
let latest = CallOverrides(blockTag: some BlockTag.latest)
|
let latest = CallOverrides(blockTag: some BlockTag.latest)
|
||||||
let pending = CallOverrides(blockTag: some BlockTag.pending)
|
let pending = CallOverrides(blockTag: some BlockTag.pending)
|
||||||
|
|
||||||
@ -38,40 +38,6 @@ suite "gas estimation":
|
|||||||
# ensure that time of latest block and pending block differ
|
# ensure that time of latest block and pending block differ
|
||||||
check (await contract.getTime(overrides=latest)) != time
|
check (await contract.getTime(overrides=latest)) != time
|
||||||
|
|
||||||
# only succeeds when gas estimation is done using the pending block,
|
# fails with "Transaction ran out of gas" when gas estimation
|
||||||
# otherwise it will fail with "Transaction ran out of gas"
|
# is not done using the pending block
|
||||||
await contract.checkTimeEquals(time)
|
await contract.checkTimeEquals(time)
|
||||||
|
|
||||||
test "contract gas estimation uses pending block":
|
|
||||||
let latest = CallOverrides(blockTag: some BlockTag.latest)
|
|
||||||
let pending = CallOverrides(blockTag: some BlockTag.pending)
|
|
||||||
|
|
||||||
# retrieve time of pending block
|
|
||||||
let time = await contract.getTime(overrides=pending)
|
|
||||||
|
|
||||||
# ensure that time of latest block and pending block differ
|
|
||||||
check (await contract.getTime(overrides=latest)) != time
|
|
||||||
|
|
||||||
# estimate gas
|
|
||||||
let gas = await contract.estimateGas.checkTimeEquals(time)
|
|
||||||
let overrides = TransactionOverrides(gasLimit: some gas)
|
|
||||||
|
|
||||||
# only succeeds when gas estimation is done using the pending block,
|
|
||||||
# otherwise it will fail with "Transaction ran out of gas"
|
|
||||||
await contract.checkTimeEquals(time, overrides)
|
|
||||||
|
|
||||||
test "contract gas estimation honors a block tag override":
|
|
||||||
let latest = CallOverrides(blockTag: some BlockTag.latest)
|
|
||||||
let pending = CallOverrides(blockTag: some BlockTag.pending)
|
|
||||||
|
|
||||||
# retrieve time of pending block
|
|
||||||
let time = await contract.getTime(overrides=pending)
|
|
||||||
|
|
||||||
# ensure that time of latest block and pending block differ
|
|
||||||
check (await contract.getTime(overrides=latest)) != time
|
|
||||||
|
|
||||||
# estimate gas
|
|
||||||
let gasLatest = await contract.estimateGas.checkTimeEquals(time, latest)
|
|
||||||
let gasPending = await contract.estimateGas.checkTimeEquals(time, pending)
|
|
||||||
|
|
||||||
check gasLatest != gasPending
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import ./hardhat
|
import ./hardhat
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
import pkg/ethers/testing
|
import pkg/ethers/testing
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import std/os
|
import std/os
|
||||||
import pkg/asynctest/chronos/unittest
|
import pkg/asynctest
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import ../ethers
|
import ../ethers
|
||||||
@ -80,8 +80,8 @@ suite "Wallet":
|
|||||||
to: wallet.address,
|
to: wallet.address,
|
||||||
nonce: some 0.u256,
|
nonce: some 0.u256,
|
||||||
chainId: some 31337.u256,
|
chainId: some 31337.u256,
|
||||||
maxFeePerGas: some 2_000_000_000.u256,
|
maxFee: some 2_000_000_000.u256,
|
||||||
maxPriorityFeePerGas: some 1_000_000_000.u256,
|
maxPriorityFee: some 1_000_000_000.u256,
|
||||||
gasLimit: some 21_000.u256
|
gasLimit: some 21_000.u256
|
||||||
)
|
)
|
||||||
let signedTx = await wallet.signTransaction(tx)
|
let signedTx = await wallet.signTransaction(tx)
|
||||||
@ -115,8 +115,8 @@ suite "Wallet":
|
|||||||
let wallet = !Wallet.new(pk_with_funds, provider)
|
let wallet = !Wallet.new(pk_with_funds, provider)
|
||||||
let overrides = TransactionOverrides(
|
let overrides = TransactionOverrides(
|
||||||
nonce: some 0.u256,
|
nonce: some 0.u256,
|
||||||
maxFeePerGas: some 1_000_000_000.u256,
|
maxFee: some 1_000_000_000.u256,
|
||||||
maxPriorityFeePerGas: some 1_000_000_000.u256,
|
maxPriorityFee: some 1_000_000_000.u256,
|
||||||
gasLimit: some 22_000.u256)
|
gasLimit: some 22_000.u256)
|
||||||
let testToken = Erc20.new(wallet.address, wallet)
|
let testToken = Erc20.new(wallet.address, wallet)
|
||||||
await testToken.transfer(wallet.address, 24.u256, overrides)
|
await testToken.transfer(wallet.address, 24.u256, overrides)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user