Refactor based on PR comments
- `TransactionReceipt.blockHash` is optional - Block.number is optional (in case node doesn’t return this in the event) - Refactor confirmations waiting such that there is no polling for a receipt at the start - Make BlockHandler and SubscriptionHandler async - change casing of constants - change return type checking of contract method to check for `Confirmable` instead of `?TransactionRepsonse` - Reduce miner sleep to 10ms - Change `wait` and `Waitable` to `confirm` and `Confirmable` to avoid conflict with chrono’s `.wait`. - Update params on `.confirm` so that the compiler can restrict values of the `int` to `Positive` and `Natural`. - Add `Block` and `TransactionReceipt` conversion tests to test for missing block number and block hash. - Add tests for confirmation calculations and determining if a tx has been mined from its receipt. - Assume that blockNumber returned from node will be null or empty string, in which case we can parse as 0 and test for that condition.
This commit is contained in:
parent
a3e888128c
commit
c5c9534876
|
@ -17,6 +17,7 @@ type
|
||||||
signer: ?Signer
|
signer: ?Signer
|
||||||
address: Address
|
address: Address
|
||||||
ContractError* = object of EthersError
|
ContractError* = object of EthersError
|
||||||
|
Confirmable* = ?TransactionResponse
|
||||||
EventHandler*[E: Event] = proc(event: E) {.gcsafe, upraises:[].}
|
EventHandler*[E: Event] = proc(event: E) {.gcsafe, upraises:[].}
|
||||||
|
|
||||||
func new*(ContractType: type Contract,
|
func new*(ContractType: type Contract,
|
||||||
|
@ -102,17 +103,16 @@ func isConstant(procedure: NimNode): bool =
|
||||||
false
|
false
|
||||||
|
|
||||||
func isTxResponse(returntype: NimNode): bool =
|
func isTxResponse(returntype: NimNode): bool =
|
||||||
return returntype.kind == nnkPrefix and
|
return returntype.eqIdent($ Confirmable)
|
||||||
returntype[0].kind == nnkIdent and
|
|
||||||
returntype[0].strVal == "?" and
|
|
||||||
returntype[1].kind == nnkIdent and
|
|
||||||
returntype[1].strVal == $TransactionResponse
|
|
||||||
|
|
||||||
func addContractCall(procedure: var NimNode) =
|
func addContractCall(procedure: var NimNode) =
|
||||||
let contract = procedure[3][1][0]
|
let contract = procedure[3][1][0]
|
||||||
let function = $basename(procedure[0])
|
let function = $basename(procedure[0])
|
||||||
let parameters = getParameterTuple(procedure)
|
let parameters = getParameterTuple(procedure)
|
||||||
let returntype = procedure[3][0]
|
let returntype = procedure[3][0]
|
||||||
|
# procedure[5] =
|
||||||
|
# quote:
|
||||||
|
# static: checkReturnType(type(result))
|
||||||
procedure[6] =
|
procedure[6] =
|
||||||
if procedure.isConstant:
|
if procedure.isConstant:
|
||||||
if returntype.kind == nnkEmpty:
|
if returntype.kind == nnkEmpty:
|
||||||
|
@ -145,7 +145,6 @@ func addAsyncPragma(procedure: var NimNode) =
|
||||||
|
|
||||||
func checkReturnType(procedure: NimNode) =
|
func checkReturnType(procedure: NimNode) =
|
||||||
let returntype = procedure[3][0]
|
let returntype = procedure[3][0]
|
||||||
|
|
||||||
if returntype.kind != nnkEmpty:
|
if returntype.kind != nnkEmpty:
|
||||||
# Do not throw exception for methods that have a TransactionResponse
|
# Do not throw exception for methods that have a TransactionResponse
|
||||||
# return type as that is needed for .wait
|
# return type as that is needed for .wait
|
||||||
|
|
|
@ -33,23 +33,22 @@ type
|
||||||
transactionIndex*: UInt256
|
transactionIndex*: UInt256
|
||||||
gasUsed*: UInt256
|
gasUsed*: UInt256
|
||||||
logsBloom*: seq[byte]
|
logsBloom*: seq[byte]
|
||||||
blockHash*: BlockHash
|
blockHash*: ?BlockHash
|
||||||
transactionHash*: TransactionHash
|
transactionHash*: TransactionHash
|
||||||
logs*: seq[Log]
|
logs*: seq[Log]
|
||||||
blockNumber*: ?UInt256
|
blockNumber*: ?UInt256
|
||||||
cumulativeGasUsed*: UInt256
|
cumulativeGasUsed*: UInt256
|
||||||
status*: TransactionStatus
|
status*: TransactionStatus
|
||||||
LogHandler* = proc(log: Log) {.gcsafe, upraises:[].}
|
LogHandler* = proc(log: Log) {.gcsafe, upraises:[].}
|
||||||
BlockHandler* = proc(blck: Block) {.gcsafe, upraises:[].}
|
BlockHandler* = proc(blck: Block): Future[void] {.gcsafe, upraises:[].}
|
||||||
Topic* = array[32, byte]
|
Topic* = array[32, byte]
|
||||||
Block* = object
|
Block* = object
|
||||||
number*: UInt256
|
number*: ?UInt256
|
||||||
timestamp*: UInt256
|
timestamp*: UInt256
|
||||||
hash*: array[32, byte]
|
hash*: array[32, byte]
|
||||||
|
|
||||||
const DEFAULT_CONFIRMATIONS* {.intdefine.} = 12
|
const EthersDefaultConfirmations* {.intdefine.} = 12
|
||||||
const RECEIPT_TIMEOUT_BLKS* {.intdefine.} = 50 # in blocks
|
const EthersReceiptTimeoutBlks* {.intdefine.} = 50 # in blocks
|
||||||
const RECEIPT_POLLING_INTERVAL* {.intdefine.} = 1 # in seconds
|
|
||||||
|
|
||||||
method getBlockNumber*(provider: Provider): Future[UInt256] {.base.} =
|
method getBlockNumber*(provider: Provider): Future[UInt256] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
@ -71,6 +70,11 @@ method getTransactionCount*(provider: Provider,
|
||||||
Future[UInt256] {.base.} =
|
Future[UInt256] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
|
method getTransactionReceipt*(provider: Provider,
|
||||||
|
txHash: TransactionHash):
|
||||||
|
Future[?TransactionReceipt] {.base.} =
|
||||||
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method estimateGas*(provider: Provider,
|
method estimateGas*(provider: Provider,
|
||||||
transaction: Transaction): Future[UInt256] {.base.} =
|
transaction: Transaction): Future[UInt256] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
|
@ -25,7 +25,7 @@ type
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider
|
||||||
address: ?Address
|
address: ?Address
|
||||||
JsonRpcProviderError* = object of EthersError
|
JsonRpcProviderError* = object of EthersError
|
||||||
SubscriptionHandler = proc(id, arguments: JsonNode) {.gcsafe, upraises:[].}
|
SubscriptionHandler = proc(id, arguments: JsonNode): Future[void] {.gcsafe, upraises:[].}
|
||||||
|
|
||||||
template raiseProviderError(message: string) =
|
template raiseProviderError(message: string) =
|
||||||
raise newException(JsonRpcProviderError, message)
|
raise newException(JsonRpcProviderError, message)
|
||||||
|
@ -59,7 +59,8 @@ proc connect(provider: JsonRpcProvider, url: string) =
|
||||||
proc handleSubscription(arguments: JsonNode) {.upraises: [].} =
|
proc handleSubscription(arguments: JsonNode) {.upraises: [].} =
|
||||||
if id =? arguments["subscription"].catch and
|
if id =? arguments["subscription"].catch and
|
||||||
handler =? getSubscriptionHandler(id):
|
handler =? getSubscriptionHandler(id):
|
||||||
handler(id, arguments)
|
# fire and forget
|
||||||
|
discard handler(id, arguments)
|
||||||
|
|
||||||
proc subscribe: Future[RpcClient] {.async.} =
|
proc subscribe: Future[RpcClient] {.async.} =
|
||||||
let client = await RpcClient.connect(url)
|
let client = await RpcClient.connect(url)
|
||||||
|
@ -114,6 +115,7 @@ method getTransactionCount*(provider: JsonRpcProvider,
|
||||||
Future[UInt256] {.async.} =
|
Future[UInt256] {.async.} =
|
||||||
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 getTransactionReceipt*(provider: JsonRpcProvider,
|
method getTransactionReceipt*(provider: JsonRpcProvider,
|
||||||
txHash: TransactionHash):
|
txHash: TransactionHash):
|
||||||
Future[?TransactionReceipt] {.async.} =
|
Future[?TransactionReceipt] {.async.} =
|
||||||
|
@ -148,7 +150,7 @@ method subscribe*(provider: JsonRpcProvider,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
callback: LogHandler):
|
callback: LogHandler):
|
||||||
Future[Subscription] {.async.} =
|
Future[Subscription] {.async.} =
|
||||||
proc handler(id, arguments: JsonNode) =
|
proc handler(id, arguments: JsonNode) {.async.} =
|
||||||
if log =? Log.fromJson(arguments["result"]).catch:
|
if log =? Log.fromJson(arguments["result"]).catch:
|
||||||
callback(log)
|
callback(log)
|
||||||
return await provider.subscribe("logs", filter.some, handler)
|
return await provider.subscribe("logs", filter.some, handler)
|
||||||
|
@ -156,9 +158,9 @@ method subscribe*(provider: JsonRpcProvider,
|
||||||
method subscribe*(provider: JsonRpcProvider,
|
method subscribe*(provider: JsonRpcProvider,
|
||||||
callback: BlockHandler):
|
callback: BlockHandler):
|
||||||
Future[Subscription] {.async.} =
|
Future[Subscription] {.async.} =
|
||||||
proc handler(id, arguments: JsonNode) =
|
proc handler(id, arguments: JsonNode) {.async.} =
|
||||||
if blck =? Block.fromJson(arguments["result"]).catch:
|
if blck =? Block.fromJson(arguments["result"]).catch:
|
||||||
callback(blck)
|
await callback(blck)
|
||||||
return await provider.subscribe("newHeads", Filter.none, handler)
|
return await provider.subscribe("newHeads", Filter.none, handler)
|
||||||
|
|
||||||
method unsubscribe*(subscription: JsonRpcSubscription) {.async.} =
|
method unsubscribe*(subscription: JsonRpcSubscription) {.async.} =
|
||||||
|
@ -196,84 +198,111 @@ method sendTransaction*(signer: JsonRpcSigner,
|
||||||
|
|
||||||
return TransactionResponse(hash: hash, provider: signer.provider)
|
return TransactionResponse(hash: hash, provider: signer.provider)
|
||||||
|
|
||||||
method wait*(tx: TransactionResponse,
|
|
||||||
wantedConfirms = DEFAULT_CONFIRMATIONS,
|
# Removed from `confirm` closure and exported so it can be tested.
|
||||||
timeoutInBlocks = RECEIPT_TIMEOUT_BLKS.some): # will error if tx not mined in x blocks
|
# Likely there is a better way
|
||||||
|
func confirmations*(receiptBlk, atBlk: UInt256): UInt256 =
|
||||||
|
## Calculates the number of confirmations between two blocks
|
||||||
|
if atBlk < receiptBlk:
|
||||||
|
return 0.u256
|
||||||
|
else:
|
||||||
|
return (atBlk - receiptBlk) + 1 # add 1 for current block
|
||||||
|
|
||||||
|
# Removed from `confirm` closure and exported so it can be tested.
|
||||||
|
# Likely there is a better way
|
||||||
|
func hasBeenMined*(receipt: ?TransactionReceipt,
|
||||||
|
atBlock: UInt256,
|
||||||
|
wantedConfirms: int): bool =
|
||||||
|
## Returns true if the transaction receipt has been returned from the node
|
||||||
|
## with a valid block number and block hash and the specified number of
|
||||||
|
## blocks have passed since the tx was mined (confirmations)
|
||||||
|
|
||||||
|
if receipt.isSome and
|
||||||
|
receipt.get.blockNumber.isSome and
|
||||||
|
receipt.get.blockNumber.get > 0 and
|
||||||
|
# from ethers.js: "geth-etc" returns receipts before they are ready
|
||||||
|
receipt.get.blockHash.isSome:
|
||||||
|
|
||||||
|
let receiptBlock = receipt.get.blockNumber.get
|
||||||
|
return receiptBlock.confirmations(atBlock) >= wantedConfirms.u256
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
method confirm*(tx: TransactionResponse,
|
||||||
|
wantedConfirms: Positive = EthersDefaultConfirmations,
|
||||||
|
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
||||||
Future[TransactionReceipt]
|
Future[TransactionReceipt]
|
||||||
{.async, upraises: [JsonRpcProviderError].} = # raises for clarity
|
{.async, upraises: [JsonRpcProviderError].} = # raises for clarity
|
||||||
|
## Waits for a transaction to be mined and for the specified number of blocks
|
||||||
|
## to pass since it was mined (confirmations).
|
||||||
|
## A timeout, in blocks, can be specified that will raise a
|
||||||
|
## JsonRpcProviderError if too many blocks have passed without the tx
|
||||||
|
## having been mined.
|
||||||
|
## Note: this method requires that the JsonRpcProvider client connects
|
||||||
|
## using RpcWebSocketClient, otherwise it will raise a Defect.
|
||||||
|
|
||||||
var
|
var subscription: JsonRpcSubscription
|
||||||
receipt: ?TransactionReceipt
|
|
||||||
subscription: JsonRpcSubscription
|
|
||||||
|
|
||||||
let
|
let
|
||||||
provider = JsonRpcProvider(tx.provider)
|
provider = JsonRpcProvider(tx.provider)
|
||||||
retFut = newFuture[TransactionReceipt]("wait")
|
retFut = newFuture[TransactionReceipt]("wait")
|
||||||
|
|
||||||
proc confirmations(receipt: TransactionReceipt, atBlkNum: UInt256): UInt256 =
|
# used to check for block timeouts
|
||||||
|
|
||||||
var confirms = (atBlkNum - !receipt.blockNumber) + 1
|
|
||||||
if confirms <= 0: confirms = 1.u256
|
|
||||||
return confirms
|
|
||||||
|
|
||||||
proc newBlock(blk: Block) =
|
|
||||||
# has been mined, need to check # of confirmations thus far
|
|
||||||
let confirms = (!receipt).confirmations(blk.number)
|
|
||||||
if confirms >= wantedConfirms.u256:
|
|
||||||
# fire and forget
|
|
||||||
discard subscription.unsubscribe()
|
|
||||||
retFut.complete(!receipt)
|
|
||||||
|
|
||||||
let startBlock = await provider.getBlockNumber()
|
let startBlock = await provider.getBlockNumber()
|
||||||
|
|
||||||
# loop until the tx is mined, or times out (in blocks) if timeout specified
|
proc newBlock(blk: Block) {.async.} =
|
||||||
while receipt.isNone:
|
## subscription callback, called every time a new block event is sent from
|
||||||
receipt = await provider.getTransactionReceipt(tx.hash)
|
## the node
|
||||||
if receipt.isSome and (!receipt).blockNumber.isSome:
|
|
||||||
break
|
|
||||||
|
|
||||||
if timeoutInBlocks.isSome:
|
# if ethereum node doesn't include blockNumber in the event
|
||||||
let currBlock = await provider.getBlockNumber()
|
without blkNum =? blk.number:
|
||||||
let blocksPassed = (currBlock - startBlock) + 1
|
return
|
||||||
if blocksPassed >= (!timeoutInBlocks).u256:
|
|
||||||
raiseProviderError("Transaction was not mined in " &
|
|
||||||
$(!timeoutInBlocks) & " blocks")
|
|
||||||
|
|
||||||
await sleepAsync(RECEIPT_POLLING_INTERVAL.seconds)
|
let receipt = await provider.getTransactionReceipt(tx.hash)
|
||||||
|
if receipt.hasBeenMined(blkNum, wantedConfirms):
|
||||||
|
# fire and forget
|
||||||
|
discard subscription.unsubscribe()
|
||||||
|
retFut.complete(receipt.get)
|
||||||
|
|
||||||
# has been mined, need to check # of confirmations thus far
|
elif timeoutInBlocks > 0:
|
||||||
let confirms = (!receipt).confirmations(startBlock)
|
let blocksPassed = (blkNum - startBlock) + 1
|
||||||
if confirms >= wantedConfirms.u256:
|
if blocksPassed >= timeoutInBlocks.u256:
|
||||||
return !receipt
|
discard subscription.unsubscribe()
|
||||||
|
retFut.fail(
|
||||||
|
newException(JsonRpcProviderError, "Transaction was not mined in " &
|
||||||
|
$timeoutInBlocks & " blocks"))
|
||||||
|
|
||||||
|
# If our tx is already mined, return the receipt. Otherwise, check each
|
||||||
|
# new block to see if the tx has been mined
|
||||||
|
let receipt = await provider.getTransactionReceipt(tx.hash)
|
||||||
|
if receipt.hasBeenMined(startBlock, wantedConfirms):
|
||||||
|
return receipt.get
|
||||||
else:
|
else:
|
||||||
let sub = await provider.subscribe(newBlock)
|
let sub = await provider.subscribe(newBlock)
|
||||||
subscription = JsonRpcSubscription(sub)
|
subscription = JsonRpcSubscription(sub)
|
||||||
return (await retFut)
|
return (await retFut)
|
||||||
|
|
||||||
method wait*(tx: Future[TransactionResponse],
|
method confirm*(tx: Future[TransactionResponse],
|
||||||
wantedConfirms = DEFAULT_CONFIRMATIONS,
|
wantedConfirms: Positive = EthersDefaultConfirmations,
|
||||||
timeoutInBlocks = RECEIPT_TIMEOUT_BLKS.some):
|
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
||||||
Future[TransactionReceipt] {.async.} =
|
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).wait(3)`
|
## `await signer.sendTransaction(populated).confirm(3)`
|
||||||
|
|
||||||
let txResp = await tx
|
let txResp = await tx
|
||||||
return await txResp.wait(wantedConfirms, timeoutInBlocks)
|
return await txResp.confirm(wantedConfirms, timeoutInBlocks)
|
||||||
|
|
||||||
method wait*(tx: Future[?TransactionResponse],
|
method confirm*(tx: Future[?TransactionResponse],
|
||||||
wantedConfirms = DEFAULT_CONFIRMATIONS,
|
wantedConfirms: Positive = EthersDefaultConfirmations,
|
||||||
timeoutInBlocks = RECEIPT_TIMEOUT_BLKS.some):
|
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
||||||
Future[TransactionReceipt] {.async.} =
|
Future[TransactionReceipt] {.async.} =
|
||||||
## Convenience method that allows wait to be chained to a contract
|
## Convenience method that allows wait to be chained to a contract
|
||||||
## transaction, eg:
|
## transaction, eg:
|
||||||
## `await token.connect(signer0)
|
## `await token.connect(signer0)
|
||||||
## .mint(accounts[1], 100.u256)
|
## .mint(accounts[1], 100.u256)
|
||||||
## .wait(3)`
|
## .confirm(3)`
|
||||||
|
|
||||||
let txResp = await tx
|
without txResp =? (await tx):
|
||||||
if txResp.isNone:
|
|
||||||
raiseProviderError("Transaction hash required. Possibly was a call instead of a send?")
|
raiseProviderError("Transaction hash required. Possibly was a call instead of a send?")
|
||||||
|
|
||||||
return await (!txResp).wait(wantedConfirms, timeoutInBlocks)
|
return await txResp.confirm(wantedConfirms, timeoutInBlocks)
|
||||||
|
|
|
@ -5,4 +5,6 @@ import pkg/ethers/providers/jsonrpc
|
||||||
proc mineBlocks*(provider: JsonRpcProvider, blks: int) {.async.} =
|
proc mineBlocks*(provider: JsonRpcProvider, blks: int) {.async.} =
|
||||||
for i in 1..blks:
|
for i in 1..blks:
|
||||||
discard await provider.send("evm_mine")
|
discard await provider.send("evm_mine")
|
||||||
await sleepAsync(1.seconds)
|
# Gives time for the subscription to occur in `.wait`.
|
||||||
|
# Likely needed in slower environments, like CI.
|
||||||
|
await sleepAsync(2.milliseconds)
|
||||||
|
|
|
@ -19,7 +19,7 @@ method totalSupply*(erc20: Erc20): UInt256 {.base, contract, view.}
|
||||||
method balanceOf*(erc20: Erc20, account: Address): UInt256 {.base, contract, view.}
|
method balanceOf*(erc20: Erc20, account: Address): UInt256 {.base, contract, view.}
|
||||||
method allowance*(erc20: Erc20, owner, spender: Address): UInt256 {.base, contract, view.}
|
method allowance*(erc20: Erc20, owner, spender: Address): UInt256 {.base, contract, view.}
|
||||||
method transfer*(erc20: Erc20, recipient: Address, amount: UInt256) {.base, contract.}
|
method transfer*(erc20: Erc20, recipient: Address, amount: UInt256) {.base, contract.}
|
||||||
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
|
method mint(token: TestToken, holder: Address, amount: UInt256): Confirmable {.base, contract.}
|
||||||
|
|
||||||
suite "Contracts":
|
suite "Contracts":
|
||||||
|
|
||||||
|
@ -122,14 +122,20 @@ suite "Contracts":
|
||||||
check transfers == @[Transfer(receiver: accounts[0], value: 100.u256)]
|
check transfers == @[Transfer(receiver: accounts[0], value: 100.u256)]
|
||||||
|
|
||||||
test "can wait for contract interaction tx to be mined":
|
test "can wait for contract interaction tx to be mined":
|
||||||
# must be spawned so we can get newHeads inside of .wait
|
# must not be awaited so we can get newHeads inside of .wait
|
||||||
asyncSpawn provider.mineBlocks(3)
|
let futMined = provider.mineBlocks(10)
|
||||||
|
|
||||||
let signer0 = provider.getSigner(accounts[0])
|
let signer0 = provider.getSigner(accounts[0])
|
||||||
let receipt = await token.connect(signer0)
|
let receipt = await token.connect(signer0)
|
||||||
.mint(accounts[1], 100.u256)
|
.mint(accounts[1], 100.u256)
|
||||||
.wait(3) # wait for 3 confirmations
|
.confirm(3) # wait for 3 confirmations
|
||||||
let endBlock = await provider.getBlockNumber()
|
let endBlock = await provider.getBlockNumber()
|
||||||
|
|
||||||
check receipt.blockNumber.isSome # was eventually mined
|
check receipt.blockNumber.isSome # was eventually mined
|
||||||
check (endBlock - !receipt.blockNumber) + 1 == 3 # +1 for the block the tx was mined in
|
|
||||||
|
# >= 3 because more blocks may have been mined by the time the
|
||||||
|
# check in `.wait` was done.
|
||||||
|
# +1 for the block the tx was mined in
|
||||||
|
check (endBlock - !receipt.blockNumber) + 1 >= 3
|
||||||
|
|
||||||
|
await futMined
|
||||||
|
|
|
@ -40,16 +40,16 @@ suite "JsonRpcProvider":
|
||||||
let block1 = !await provider.getBlock(BlockTag.earliest)
|
let block1 = !await provider.getBlock(BlockTag.earliest)
|
||||||
let block2 = !await provider.getBlock(BlockTag.latest)
|
let block2 = !await provider.getBlock(BlockTag.latest)
|
||||||
check block1.hash != block2.hash
|
check block1.hash != block2.hash
|
||||||
check block1.number < block2.number
|
check !block1.number < !block2.number
|
||||||
check block1.timestamp < block2.timestamp
|
check block1.timestamp < block2.timestamp
|
||||||
|
|
||||||
test "subscribes to new blocks":
|
test "subscribes to new blocks":
|
||||||
let oldBlock = !await provider.getBlock(BlockTag.latest)
|
let oldBlock = !await provider.getBlock(BlockTag.latest)
|
||||||
var newBlock: Block
|
var newBlock: Block
|
||||||
let blockHandler = proc(blck: Block) = newBlock = blck
|
let blockHandler = proc(blck: Block) {.async.} = 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 newBlock.number > oldBlock.number
|
check !newBlock.number > !oldBlock.number
|
||||||
check newBlock.timestamp > oldBlock.timestamp
|
check newBlock.timestamp > oldBlock.timestamp
|
||||||
check newBlock.hash != oldBlock.hash
|
check newBlock.hash != oldBlock.hash
|
||||||
await subscription.unsubscribe()
|
await subscription.unsubscribe()
|
||||||
|
@ -67,28 +67,180 @@ suite "JsonRpcProvider":
|
||||||
let transaction = Transaction.example
|
let transaction = Transaction.example
|
||||||
let populated = await signer.populateTransaction(transaction)
|
let populated = await signer.populateTransaction(transaction)
|
||||||
|
|
||||||
# must be spawned so we can get newHeads inside of .wait
|
# must not be awaited so we can get newHeads inside of .wait
|
||||||
asyncSpawn provider.mineBlocks(3)
|
let futMined = provider.mineBlocks(5)
|
||||||
|
|
||||||
let receipt = await signer.sendTransaction(populated).wait(3)
|
let receipt = await signer.sendTransaction(populated).confirm(3)
|
||||||
let endBlock = await provider.getBlockNumber()
|
let endBlock = await provider.getBlockNumber()
|
||||||
|
|
||||||
check receipt.blockNumber.isSome # was eventually mined
|
check receipt.blockNumber.isSome # was eventually mined
|
||||||
check (endBlock - !receipt.blockNumber) + 1 == 3 # +1 for the block the tx was mined in
|
|
||||||
|
# >= 3 because more blocks may have been mined by the time the
|
||||||
|
# check in `.wait` was done.
|
||||||
|
# +1 for the block the tx was mined in
|
||||||
|
check (endBlock - !receipt.blockNumber) + 1 >= 3
|
||||||
|
|
||||||
|
await futMined
|
||||||
|
|
||||||
test "waiting for block to be mined times out":
|
test "waiting for block to be mined times out":
|
||||||
|
|
||||||
# must be spawned so we can get newHeads inside of .wait
|
# must not be awaited so we can get newHeads inside of .wait
|
||||||
asyncSpawn provider.mineBlocks(10)
|
let futMined = provider.mineBlocks(7)
|
||||||
|
|
||||||
let startBlock = await provider.getBlockNumber()
|
let startBlock = await provider.getBlockNumber()
|
||||||
let response = TransactionResponse(hash: TransactionHash.example,
|
let response = TransactionResponse(hash: TransactionHash.example,
|
||||||
provider: provider)
|
provider: provider)
|
||||||
try:
|
try:
|
||||||
discard await response.wait(wantedConfirms = 2,
|
discard await response.confirm(wantedConfirms = 2,
|
||||||
timeoutInBlocks = 5.some)
|
timeoutInBlocks = 5)
|
||||||
|
|
||||||
|
await futMined
|
||||||
except JsonRpcProviderError as e:
|
except JsonRpcProviderError as e:
|
||||||
check e.msg == "Transaction was not mined in 5 blocks"
|
check e.msg == "Transaction was not mined in 5 blocks"
|
||||||
|
|
||||||
let endBlock = await provider.getBlockNumber()
|
let endBlock = await provider.getBlockNumber()
|
||||||
check (endBlock - startBlock) + 1 == 5 # +1 including start block
|
|
||||||
|
# >= 5 because more blocks may have been mined by the time the
|
||||||
|
# check in `.wait` was done.
|
||||||
|
# +1 for including the start block
|
||||||
|
check (endBlock - startBlock) + 1 >= 5 # +1 including start block
|
||||||
|
if not futMined.completed and not futMined.finished: await futMined
|
||||||
|
|
||||||
|
test "Conversion: missing block number in Block isNone":
|
||||||
|
|
||||||
|
var blkJson = %*{
|
||||||
|
"subscription": "0x20",
|
||||||
|
"result":{
|
||||||
|
"number": newJNull(),
|
||||||
|
"hash":"0x2d7d68c8f48b4213d232a1f12cab8c9fac6195166bb70a5fb21397984b9fe1c7",
|
||||||
|
"timestamp":"0x6285c293"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blk = Block.fromJson(blkJson["result"])
|
||||||
|
check blk.number.isNone
|
||||||
|
|
||||||
|
blkJson["result"]["number"] = newJString("")
|
||||||
|
|
||||||
|
blk = Block.fromJson(blkJson["result"])
|
||||||
|
check blk.number.isSome
|
||||||
|
check blk.number.get.isZero
|
||||||
|
|
||||||
|
test "Conversion: missing block number in TransactionReceipt isNone":
|
||||||
|
|
||||||
|
var txReceiptJson = %*{
|
||||||
|
"sender": newJNull(),
|
||||||
|
"to": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
|
||||||
|
"contractAddress": newJNull(),
|
||||||
|
"transactionIndex": "0x0",
|
||||||
|
"gasUsed": "0x10db1",
|
||||||
|
"logsBloom": "0x00000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000840020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000020000000000000000000000000000000000001000000000000000000000000000000",
|
||||||
|
"blockHash": "0x7b00154e06fe4f27a87208eba220efb4dbc52f7429549a39a17bba2e0d98b960",
|
||||||
|
"transactionHash": "0xa64f07b370cbdcce381ec9bfb6c8004684341edfb6848fd418189969d4b9139c",
|
||||||
|
"logs": [
|
||||||
|
{
|
||||||
|
"data": "0x0000000000000000000000000000000000000000000000000000000000000064",
|
||||||
|
"topics": [
|
||||||
|
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockNumber": newJNull(),
|
||||||
|
"cumulativeGasUsed": "0x10db1",
|
||||||
|
"status": "0000000000000001"
|
||||||
|
}
|
||||||
|
|
||||||
|
var txReceipt = TransactionReceipt.fromJson(txReceiptJson)
|
||||||
|
check txReceipt.blockNumber.isNone
|
||||||
|
|
||||||
|
txReceiptJson["blockNumber"] = newJString("")
|
||||||
|
txReceipt = TransactionReceipt.fromJson(txReceiptJson)
|
||||||
|
check txReceipt.blockNumber.isSome
|
||||||
|
check txReceipt.blockNumber.get.isZero
|
||||||
|
|
||||||
|
test "Conversion: missing block hash in TransactionReceipt isNone":
|
||||||
|
|
||||||
|
var txReceiptJson = %*{
|
||||||
|
"sender": newJNull(),
|
||||||
|
"to": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
|
||||||
|
"contractAddress": newJNull(),
|
||||||
|
"transactionIndex": "0x0",
|
||||||
|
"gasUsed": "0x10db1",
|
||||||
|
"logsBloom": "0x00000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000840020000000000000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000020000000000000000000000000000000000001000000000000000000000000000000",
|
||||||
|
"blockHash": newJNull(),
|
||||||
|
"transactionHash": "0xa64f07b370cbdcce381ec9bfb6c8004684341edfb6848fd418189969d4b9139c",
|
||||||
|
"logs": [
|
||||||
|
{
|
||||||
|
"data": "0x0000000000000000000000000000000000000000000000000000000000000064",
|
||||||
|
"topics": [
|
||||||
|
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockNumber": newJNull(),
|
||||||
|
"cumulativeGasUsed": "0x10db1",
|
||||||
|
"status": "0000000000000001"
|
||||||
|
}
|
||||||
|
|
||||||
|
var txReceipt = TransactionReceipt.fromJson(txReceiptJson)
|
||||||
|
check txReceipt.blockHash.isNone
|
||||||
|
|
||||||
|
test "confirmations calculated correctly":
|
||||||
|
# when receipt block number is higher than current block number,
|
||||||
|
# should return 0
|
||||||
|
check confirmations(2.u256, 1.u256) == 0.u256
|
||||||
|
|
||||||
|
# Same receipt and current block counts as one confirmation
|
||||||
|
check confirmations(1.u256, 1.u256) == 1.u256
|
||||||
|
|
||||||
|
check confirmations(1.u256, 2.u256) == 2.u256
|
||||||
|
|
||||||
|
test "checks if transation has been mined correctly":
|
||||||
|
|
||||||
|
var receipt = TransactionReceipt.none
|
||||||
|
var currentBlock = 1.u256
|
||||||
|
var wantedConfirms = 1
|
||||||
|
let blockHash = hexToByteArray[32](
|
||||||
|
"0x7b00154e06fe4f27a87208eba220efb4dbc52f7429549a39a17bba2e0d98b960"
|
||||||
|
).some
|
||||||
|
|
||||||
|
# if TransactionReceipt is none, should not be considered mined
|
||||||
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
||||||
|
|
||||||
|
# missing blockHash
|
||||||
|
receipt = TransactionReceipt(
|
||||||
|
blockNumber: 1.u256.some
|
||||||
|
).some
|
||||||
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
||||||
|
|
||||||
|
# missing block number
|
||||||
|
receipt = TransactionReceipt(
|
||||||
|
blockHash: blockHash
|
||||||
|
).some
|
||||||
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
||||||
|
|
||||||
|
# block number is 0
|
||||||
|
receipt = TransactionReceipt(
|
||||||
|
blockNumber: 0.u256.some
|
||||||
|
).some
|
||||||
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
||||||
|
|
||||||
|
# not enough confirms
|
||||||
|
receipt = TransactionReceipt(
|
||||||
|
blockNumber: 1.u256.some
|
||||||
|
).some
|
||||||
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
||||||
|
|
||||||
|
# success
|
||||||
|
receipt = TransactionReceipt(
|
||||||
|
blockNumber: 1.u256.some,
|
||||||
|
blockHash: blockHash
|
||||||
|
).some
|
||||||
|
currentBlock = int.high.u256
|
||||||
|
wantedConfirms = int.high
|
||||||
|
check receipt.hasBeenMined(currentBlock, wantedConfirms)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue