Query past contract events (#51)
Based on ethers.js's queryFilter, allows querying of past contract events, by querying the logs for a contract's event topic. * queryFilter to query past logs * Allow querying of past block log events * Can query by block number or block hash
This commit is contained in:
parent
c49311fca2
commit
12d7a35203
|
@ -1,4 +1,6 @@
|
|||
import std/json
|
||||
import std/macros
|
||||
import std/sequtils
|
||||
import pkg/chronos
|
||||
import pkg/contractabi
|
||||
import ./basics
|
||||
|
@ -227,7 +229,7 @@ proc subscribe*[E: Event](contract: Contract,
|
|||
Future[Subscription] =
|
||||
|
||||
let topic = topic($E, E.fieldTypes).toArray
|
||||
let filter = Filter(address: contract.address, topics: @[topic])
|
||||
let filter = EventFilter(address: contract.address, topics: @[topic])
|
||||
|
||||
proc logHandler(log: Log) {.upraises: [].} =
|
||||
if event =? E.decode(log.data, log.topics):
|
||||
|
@ -252,3 +254,54 @@ proc confirm*(tx: Future[?TransactionResponse],
|
|||
)
|
||||
|
||||
return await response.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)
|
||||
|
|
|
@ -12,11 +12,19 @@ type
|
|||
Provider* = ref object of RootObj
|
||||
ProviderError* = object of EthersError
|
||||
Subscription* = ref object of RootObj
|
||||
Filter* = object
|
||||
EventFilter* = ref object of RootObj
|
||||
address*: Address
|
||||
topics*: seq[Topic]
|
||||
Filter* = ref object of EventFilter
|
||||
fromBlock*: BlockTag
|
||||
toBlock*: BlockTag
|
||||
FilterByBlockHash* = ref object of EventFilter
|
||||
blockHash*: BlockHash
|
||||
Log* = object
|
||||
blockNumber*: UInt256
|
||||
data*: seq[byte]
|
||||
logIndex*: UInt256
|
||||
removed*: bool
|
||||
topics*: seq[Topic]
|
||||
TransactionHash* = array[32, byte]
|
||||
BlockHash* = array[32, byte]
|
||||
|
@ -81,6 +89,10 @@ method sendTransaction*(provider: Provider,
|
|||
Future[TransactionResponse] {.base.} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method getLogs*(provider: Provider,
|
||||
filter: EventFilter): Future[seq[Log]] {.base.} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method estimateGas*(provider: Provider,
|
||||
transaction: Transaction): Future[UInt256] {.base.} =
|
||||
doAssert false, "not implemented"
|
||||
|
@ -89,7 +101,7 @@ method getChainId*(provider: Provider): Future[UInt256] {.base.} =
|
|||
doAssert false, "not implemented"
|
||||
|
||||
method subscribe*(provider: Provider,
|
||||
filter: Filter,
|
||||
filter: EventFilter,
|
||||
callback: LogHandler):
|
||||
Future[Subscription] {.base.} =
|
||||
doAssert false, "not implemented"
|
||||
|
|
|
@ -138,12 +138,31 @@ method getTransactionCount*(provider: JsonRpcProvider,
|
|||
return await client.eth_getTransactionCount(address, blockTag)
|
||||
|
||||
method getTransactionReceipt*(provider: JsonRpcProvider,
|
||||
txHash: TransactionHash):
|
||||
Future[?TransactionReceipt] {.async.} =
|
||||
txHash: TransactionHash):
|
||||
Future[?TransactionReceipt] {.async.} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_getTransactionReceipt(txHash)
|
||||
|
||||
method getLogs*(provider: JsonRpcProvider,
|
||||
filter: EventFilter):
|
||||
Future[seq[Log]] {.async.} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
let logsJson = if filter of Filter:
|
||||
await client.eth_getLogs(Filter(filter))
|
||||
elif filter of FilterByBlockHash:
|
||||
await client.eth_getLogs(FilterByBlockHash(filter))
|
||||
else:
|
||||
await client.eth_getLogs(filter)
|
||||
|
||||
var logs: seq[Log] = @[]
|
||||
for logJson in logsJson.getElems:
|
||||
if log =? Log.fromJson(logJson).catch:
|
||||
logs.add log
|
||||
|
||||
return logs
|
||||
|
||||
method estimateGas*(provider: JsonRpcProvider,
|
||||
transaction: Transaction): Future[UInt256] {.async.} =
|
||||
convertError:
|
||||
|
@ -167,7 +186,7 @@ method sendTransaction*(provider: JsonRpcProvider, rawTransaction: seq[byte]): F
|
|||
return TransactionResponse(hash: hash, provider: provider)
|
||||
|
||||
method subscribe*(provider: JsonRpcProvider,
|
||||
filter: Filter,
|
||||
filter: EventFilter,
|
||||
onLog: LogHandler):
|
||||
Future[Subscription] {.async.} =
|
||||
convertError:
|
||||
|
|
|
@ -4,6 +4,7 @@ proc eth_blockNumber: UInt256
|
|||
proc eth_call(transaction: Transaction, blockTag: BlockTag): seq[byte]
|
||||
proc eth_gasPrice(): UInt256
|
||||
proc eth_getBlockByNumber(blockTag: BlockTag, includeTransactions: bool): ?Block
|
||||
proc eth_getLogs(filter: EventFilter | Filter | FilterByBlockHash): JsonNode
|
||||
proc eth_getBlockByHash(hash: BlockHash, includeTransactions: bool): ?Block
|
||||
proc eth_getTransactionCount(address: Address, blockTag: BlockTag): UInt256
|
||||
proc eth_estimateGas(transaction: Transaction): UInt256
|
||||
|
@ -12,10 +13,10 @@ proc eth_sendTransaction(transaction: Transaction): TransactionHash
|
|||
proc eth_sendRawTransaction(data: seq[byte]): TransactionHash
|
||||
proc eth_getTransactionReceipt(hash: TransactionHash): ?TransactionReceipt
|
||||
proc eth_sign(account: Address, message: seq[byte]): seq[byte]
|
||||
proc eth_subscribe(name: string, filter: Filter): JsonNode
|
||||
proc eth_subscribe(name: string, filter: EventFilter): JsonNode
|
||||
proc eth_subscribe(name: string): JsonNode
|
||||
proc eth_unsubscribe(id: JsonNode): bool
|
||||
proc eth_newBlockFilter(): JsonNode
|
||||
proc eth_newFilter(filter: Filter): JsonNode
|
||||
proc eth_newFilter(filter: EventFilter): JsonNode
|
||||
proc eth_getFilterChanges(id: JsonNode): JsonNode
|
||||
proc eth_uninstallFilter(id: JsonNode): bool
|
||||
|
|
|
@ -21,7 +21,7 @@ method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
|
|||
raiseAssert "not implemented"
|
||||
|
||||
method subscribeLogs*(subscriptions: JsonRpcSubscriptions,
|
||||
filter: Filter,
|
||||
filter: EventFilter,
|
||||
onLog: LogHandler):
|
||||
Future[JsonNode]
|
||||
{.async, base.} =
|
||||
|
@ -74,7 +74,7 @@ method subscribeBlocks(subscriptions: WebSocketSubscriptions,
|
|||
return id
|
||||
|
||||
method subscribeLogs(subscriptions: WebSocketSubscriptions,
|
||||
filter: Filter,
|
||||
filter: EventFilter,
|
||||
onLog: LogHandler):
|
||||
Future[JsonNode]
|
||||
{.async.} =
|
||||
|
@ -148,7 +148,7 @@ method subscribeBlocks(subscriptions: PollingSubscriptions,
|
|||
return id
|
||||
|
||||
method subscribeLogs(subscriptions: PollingSubscriptions,
|
||||
filter: Filter,
|
||||
filter: EventFilter,
|
||||
onLog: LogHandler):
|
||||
Future[JsonNode]
|
||||
{.async.} =
|
||||
|
|
|
@ -188,3 +188,50 @@ for url in ["ws://localhost:8545", "http://localhost:8545"]:
|
|||
await provider.mineBlocks(2) # two additional blocks
|
||||
let receipt = await confirming
|
||||
check receipt.blockNumber.isSome
|
||||
|
||||
test "can query last block event log":
|
||||
|
||||
let signer0 = provider.getSigner(accounts[0])
|
||||
|
||||
discard await token.connect(signer0).mint(accounts[0], 100.u256)
|
||||
discard await token.connect(signer0).transfer(accounts[1], 50.u256)
|
||||
|
||||
let logs = await token.queryFilter(Transfer)
|
||||
|
||||
check eventually logs == @[
|
||||
Transfer(sender: accounts[0], receiver: accounts[1], value: 50.u256)
|
||||
]
|
||||
|
||||
test "can query past event logs by specifying from and to blocks":
|
||||
|
||||
let signer0 = provider.getSigner(accounts[0])
|
||||
let signer1 = provider.getSigner(accounts[1])
|
||||
|
||||
discard await token.connect(signer0).mint(accounts[0], 100.u256)
|
||||
discard await token.connect(signer0).transfer(accounts[1], 50.u256)
|
||||
discard await token.connect(signer1).transfer(accounts[2], 25.u256)
|
||||
|
||||
let currentBlock = await provider.getBlockNumber()
|
||||
let logs = await token.queryFilter(Transfer,
|
||||
BlockTag.init(currentBlock - 1),
|
||||
BlockTag.latest)
|
||||
|
||||
check logs == @[
|
||||
Transfer(sender: accounts[0], receiver: accounts[1], value: 50.u256),
|
||||
Transfer(sender: accounts[1], receiver: accounts[2], value: 25.u256)
|
||||
]
|
||||
|
||||
test "can query past event logs by specifying a block hash":
|
||||
|
||||
let signer0 = provider.getSigner(accounts[0])
|
||||
|
||||
let receipt = await token.connect(signer0)
|
||||
.mint(accounts[0], 100.u256)
|
||||
.confirm(1)
|
||||
discard await token.connect(signer0).transfer(accounts[1], 50.u256)
|
||||
|
||||
let logs = await token.queryFilter(Transfer, !receipt.blockHash)
|
||||
|
||||
check logs == @[
|
||||
Transfer(receiver: accounts[0], value: 100.u256)
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue