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:
Eric Mastro 2022-05-18 23:14:39 +10:00 committed by Eric Mastro
parent a3e888128c
commit c5c9534876
6 changed files with 276 additions and 84 deletions

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)