2022-01-18 13:24:46 +00:00
|
|
|
import std/json
|
2022-01-18 11:10:20 +00:00
|
|
|
import pkg/asynctest
|
|
|
|
import pkg/chronos
|
feat: Allow contract transactions to be waited on
Allow waiting for a specified number of confirmations for contract transactions.
This change only requires an optional TransactionResponse return type to be added to the contract function. This allows the transaction hash to be passed to `.wait`.
For example, previously the `mint` method looked like this without a return value:
```
method mint(token: TestToken, holder: Address, amount: UInt256) {.base, contract.}
```
it still works without a return value, but if we want to wait for a 3 confirmations, we can now define it like this:
```
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
```
and use like this:
```
let receipt = await token.connect(signer0)
.mint(accounts[1], 100.u256)
.wait(3) # wait for 3 confirmations
```
2022-05-17 04:57:18 +00:00
|
|
|
import pkg/ethers
|
2023-06-19 12:13:44 +00:00
|
|
|
import pkg/ethers/providers/jsonrpc/conversions
|
2022-05-17 02:34:22 +00:00
|
|
|
import pkg/stew/byteutils
|
|
|
|
import ./examples
|
|
|
|
import ./miner
|
2022-01-18 11:10:20 +00:00
|
|
|
|
|
|
|
suite "JsonRpcProvider":
|
|
|
|
|
|
|
|
var provider: JsonRpcProvider
|
|
|
|
|
|
|
|
setup:
|
|
|
|
provider = JsonRpcProvider.new("ws://localhost:8545")
|
|
|
|
|
|
|
|
test "can be instantiated with a default URL":
|
|
|
|
discard JsonRpcProvider.new()
|
|
|
|
|
|
|
|
test "can be instantiated with an HTTP URL":
|
|
|
|
discard JsonRpcProvider.new("http://localhost:8545")
|
|
|
|
|
|
|
|
test "can be instantiated with a websocket URL":
|
|
|
|
discard JsonRpcProvider.new("ws://localhost:8545")
|
|
|
|
|
|
|
|
test "lists all accounts":
|
|
|
|
let accounts = await provider.listAccounts()
|
|
|
|
check accounts.len > 0
|
2022-01-18 13:24:46 +00:00
|
|
|
|
|
|
|
test "sends raw messages to the provider":
|
|
|
|
let response = await provider.send("evm_mine")
|
|
|
|
check response == %"0x0"
|
2022-01-18 13:26:41 +00:00
|
|
|
|
|
|
|
test "returns block number":
|
|
|
|
let blocknumber1 = await provider.getBlockNumber()
|
|
|
|
discard await provider.send("evm_mine")
|
|
|
|
let blocknumber2 = await provider.getBlockNumber()
|
|
|
|
check blocknumber2 > blocknumber1
|
2022-03-16 13:02:44 +00:00
|
|
|
|
|
|
|
test "returns block":
|
|
|
|
let block1 = !await provider.getBlock(BlockTag.earliest)
|
|
|
|
let block2 = !await provider.getBlock(BlockTag.latest)
|
|
|
|
check block1.hash != block2.hash
|
2022-05-18 13:14:39 +00:00
|
|
|
check !block1.number < !block2.number
|
2022-03-16 13:02:44 +00:00
|
|
|
check block1.timestamp < block2.timestamp
|
2022-05-16 12:51:39 +00:00
|
|
|
|
|
|
|
test "subscribes to new blocks":
|
|
|
|
let oldBlock = !await provider.getBlock(BlockTag.latest)
|
|
|
|
var newBlock: Block
|
2022-05-18 13:14:39 +00:00
|
|
|
let blockHandler = proc(blck: Block) {.async.} = newBlock = blck
|
2022-05-16 12:51:39 +00:00
|
|
|
let subscription = await provider.subscribe(blockHandler)
|
|
|
|
discard await provider.send("evm_mine")
|
2022-05-18 13:14:39 +00:00
|
|
|
check !newBlock.number > !oldBlock.number
|
2022-05-16 12:51:39 +00:00
|
|
|
check newBlock.timestamp > oldBlock.timestamp
|
|
|
|
check newBlock.hash != oldBlock.hash
|
|
|
|
await subscription.unsubscribe()
|
2022-05-17 02:34:22 +00:00
|
|
|
|
|
|
|
test "can send a transaction":
|
|
|
|
let signer = provider.getSigner()
|
|
|
|
let transaction = Transaction.example
|
|
|
|
let populated = await signer.populateTransaction(transaction)
|
|
|
|
|
|
|
|
let txResp = await signer.sendTransaction(populated)
|
2022-07-12 11:13:17 +00:00
|
|
|
check txResp.hash.len == 32
|
|
|
|
check UInt256.fromHex("0x" & txResp.hash.toHex) > 0
|
2022-05-17 02:34:22 +00:00
|
|
|
|
|
|
|
test "can wait for a transaction to be confirmed":
|
|
|
|
let signer = provider.getSigner()
|
|
|
|
let transaction = Transaction.example
|
|
|
|
let populated = await signer.populateTransaction(transaction)
|
|
|
|
|
2022-05-18 13:14:39 +00:00
|
|
|
# must not be awaited so we can get newHeads inside of .wait
|
|
|
|
let futMined = provider.mineBlocks(5)
|
2022-05-17 02:34:22 +00:00
|
|
|
|
2022-05-18 13:14:39 +00:00
|
|
|
let receipt = await signer.sendTransaction(populated).confirm(3)
|
2022-05-17 02:34:22 +00:00
|
|
|
let endBlock = await provider.getBlockNumber()
|
|
|
|
|
|
|
|
check receipt.blockNumber.isSome # was eventually mined
|
2022-05-18 13:14:39 +00:00
|
|
|
|
|
|
|
# >= 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
|
2022-05-17 02:34:22 +00:00
|
|
|
|
|
|
|
test "waiting for block to be mined times out":
|
|
|
|
|
2022-05-18 13:14:39 +00:00
|
|
|
# must not be awaited so we can get newHeads inside of .wait
|
|
|
|
let futMined = provider.mineBlocks(7)
|
2022-05-17 02:34:22 +00:00
|
|
|
|
|
|
|
let startBlock = await provider.getBlockNumber()
|
|
|
|
let response = TransactionResponse(hash: TransactionHash.example,
|
|
|
|
provider: provider)
|
|
|
|
try:
|
2022-05-18 13:14:39 +00:00
|
|
|
discard await response.confirm(wantedConfirms = 2,
|
|
|
|
timeoutInBlocks = 5)
|
|
|
|
|
|
|
|
await futMined
|
2022-05-23 15:24:49 +00:00
|
|
|
except EthersError as e:
|
2022-05-17 02:34:22 +00:00
|
|
|
check e.msg == "Transaction was not mined in 5 blocks"
|
|
|
|
|
|
|
|
let endBlock = await provider.getBlockNumber()
|
2022-05-18 13:14:39 +00:00
|
|
|
|
|
|
|
# >= 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
|
|
|
|
|
2023-04-24 10:36:08 +00:00
|
|
|
test "Conversion: missing block hash in Block isNone":
|
|
|
|
|
|
|
|
var blkJson = %*{
|
|
|
|
"subscription": "0x20",
|
|
|
|
"result":{
|
|
|
|
"number": "0x1",
|
|
|
|
"hash": newJNull(),
|
|
|
|
"timestamp": "0x6285c293"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var blk = Block.fromJson(blkJson["result"])
|
|
|
|
check blk.hash.isNone
|
|
|
|
|
2022-05-18 13:14:39 +00:00
|
|
|
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":
|
|
|
|
|
2022-05-23 15:39:00 +00:00
|
|
|
var receipt: TransactionReceipt
|
2022-05-18 13:14:39 +00:00
|
|
|
var currentBlock = 1.u256
|
|
|
|
var wantedConfirms = 1
|
|
|
|
let blockHash = hexToByteArray[32](
|
|
|
|
"0x7b00154e06fe4f27a87208eba220efb4dbc52f7429549a39a17bba2e0d98b960"
|
|
|
|
).some
|
|
|
|
|
|
|
|
# missing blockHash
|
|
|
|
receipt = TransactionReceipt(
|
|
|
|
blockNumber: 1.u256.some
|
2022-05-23 15:39:00 +00:00
|
|
|
)
|
2022-05-18 13:14:39 +00:00
|
|
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
|
|
|
|
|
|
|
# missing block number
|
|
|
|
receipt = TransactionReceipt(
|
|
|
|
blockHash: blockHash
|
2022-05-23 15:39:00 +00:00
|
|
|
)
|
2022-05-18 13:14:39 +00:00
|
|
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
|
|
|
|
|
|
|
# block number is 0
|
|
|
|
receipt = TransactionReceipt(
|
|
|
|
blockNumber: 0.u256.some
|
2022-05-23 15:39:00 +00:00
|
|
|
)
|
2022-05-18 13:14:39 +00:00
|
|
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
|
|
|
|
|
|
|
# not enough confirms
|
|
|
|
receipt = TransactionReceipt(
|
|
|
|
blockNumber: 1.u256.some
|
2022-05-23 15:39:00 +00:00
|
|
|
)
|
2022-05-18 13:14:39 +00:00
|
|
|
check not receipt.hasBeenMined(currentBlock, wantedConfirms)
|
|
|
|
|
|
|
|
# success
|
|
|
|
receipt = TransactionReceipt(
|
|
|
|
blockNumber: 1.u256.some,
|
|
|
|
blockHash: blockHash
|
2022-05-23 15:39:00 +00:00
|
|
|
)
|
2022-05-18 13:14:39 +00:00
|
|
|
currentBlock = int.high.u256
|
|
|
|
wantedConfirms = int.high
|
|
|
|
check receipt.hasBeenMined(currentBlock, wantedConfirms)
|
|
|
|
|
2022-06-29 13:13:25 +00:00
|
|
|
test "raises JsonRpcProviderError when something goes wrong":
|
|
|
|
let provider = JsonRpcProvider.new("http://invalid.")
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.listAccounts()
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.send("evm_mine")
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.getBlockNumber()
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.getBlock(BlockTag.latest)
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.subscribe(proc(_: Block) {.async.} = discard)
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.getSigner().sendTransaction(Transaction.example)
|