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/macros
|
||||||
|
import std/sequtils
|
||||||
import pkg/chronos
|
import pkg/chronos
|
||||||
import pkg/contractabi
|
import pkg/contractabi
|
||||||
import ./basics
|
import ./basics
|
||||||
|
@ -227,7 +229,7 @@ proc subscribe*[E: Event](contract: Contract,
|
||||||
Future[Subscription] =
|
Future[Subscription] =
|
||||||
|
|
||||||
let topic = topic($E, E.fieldTypes).toArray
|
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: [].} =
|
proc logHandler(log: Log) {.upraises: [].} =
|
||||||
if event =? E.decode(log.data, log.topics):
|
if event =? E.decode(log.data, log.topics):
|
||||||
|
@ -252,3 +254,54 @@ proc confirm*(tx: Future[?TransactionResponse],
|
||||||
)
|
)
|
||||||
|
|
||||||
return await response.confirm(confirmations, timeout)
|
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
|
Provider* = ref object of RootObj
|
||||||
ProviderError* = object of EthersError
|
ProviderError* = object of EthersError
|
||||||
Subscription* = ref object of RootObj
|
Subscription* = ref object of RootObj
|
||||||
Filter* = object
|
EventFilter* = ref object of RootObj
|
||||||
address*: Address
|
address*: Address
|
||||||
topics*: seq[Topic]
|
topics*: seq[Topic]
|
||||||
|
Filter* = ref object of EventFilter
|
||||||
|
fromBlock*: BlockTag
|
||||||
|
toBlock*: BlockTag
|
||||||
|
FilterByBlockHash* = ref object of EventFilter
|
||||||
|
blockHash*: BlockHash
|
||||||
Log* = object
|
Log* = object
|
||||||
|
blockNumber*: UInt256
|
||||||
data*: seq[byte]
|
data*: seq[byte]
|
||||||
|
logIndex*: UInt256
|
||||||
|
removed*: bool
|
||||||
topics*: seq[Topic]
|
topics*: seq[Topic]
|
||||||
TransactionHash* = array[32, byte]
|
TransactionHash* = array[32, byte]
|
||||||
BlockHash* = array[32, byte]
|
BlockHash* = array[32, byte]
|
||||||
|
@ -81,6 +89,10 @@ method sendTransaction*(provider: Provider,
|
||||||
Future[TransactionResponse] {.base.} =
|
Future[TransactionResponse] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
|
method getLogs*(provider: Provider,
|
||||||
|
filter: EventFilter): Future[seq[Log]] {.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"
|
||||||
|
@ -89,7 +101,7 @@ method getChainId*(provider: Provider): Future[UInt256] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method subscribe*(provider: Provider,
|
method subscribe*(provider: Provider,
|
||||||
filter: Filter,
|
filter: EventFilter,
|
||||||
callback: LogHandler):
|
callback: LogHandler):
|
||||||
Future[Subscription] {.base.} =
|
Future[Subscription] {.base.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
|
@ -144,6 +144,25 @@ method getTransactionReceipt*(provider: JsonRpcProvider,
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionReceipt(txHash)
|
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,
|
method estimateGas*(provider: JsonRpcProvider,
|
||||||
transaction: Transaction): Future[UInt256] {.async.} =
|
transaction: Transaction): Future[UInt256] {.async.} =
|
||||||
convertError:
|
convertError:
|
||||||
|
@ -167,7 +186,7 @@ method sendTransaction*(provider: JsonRpcProvider, rawTransaction: seq[byte]): F
|
||||||
return TransactionResponse(hash: hash, provider: provider)
|
return TransactionResponse(hash: hash, provider: provider)
|
||||||
|
|
||||||
method subscribe*(provider: JsonRpcProvider,
|
method subscribe*(provider: JsonRpcProvider,
|
||||||
filter: Filter,
|
filter: EventFilter,
|
||||||
onLog: LogHandler):
|
onLog: LogHandler):
|
||||||
Future[Subscription] {.async.} =
|
Future[Subscription] {.async.} =
|
||||||
convertError:
|
convertError:
|
||||||
|
|
|
@ -4,6 +4,7 @@ proc eth_blockNumber: UInt256
|
||||||
proc eth_call(transaction: Transaction, blockTag: BlockTag): seq[byte]
|
proc eth_call(transaction: Transaction, blockTag: BlockTag): seq[byte]
|
||||||
proc eth_gasPrice(): UInt256
|
proc eth_gasPrice(): UInt256
|
||||||
proc eth_getBlockByNumber(blockTag: BlockTag, includeTransactions: bool): ?Block
|
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_getBlockByHash(hash: BlockHash, includeTransactions: bool): ?Block
|
||||||
proc eth_getTransactionCount(address: Address, blockTag: BlockTag): UInt256
|
proc eth_getTransactionCount(address: Address, blockTag: BlockTag): UInt256
|
||||||
proc eth_estimateGas(transaction: Transaction): 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_sendRawTransaction(data: seq[byte]): TransactionHash
|
||||||
proc eth_getTransactionReceipt(hash: TransactionHash): ?TransactionReceipt
|
proc eth_getTransactionReceipt(hash: TransactionHash): ?TransactionReceipt
|
||||||
proc eth_sign(account: Address, message: seq[byte]): seq[byte]
|
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_subscribe(name: string): JsonNode
|
||||||
proc eth_unsubscribe(id: JsonNode): bool
|
proc eth_unsubscribe(id: JsonNode): bool
|
||||||
proc eth_newBlockFilter(): JsonNode
|
proc eth_newBlockFilter(): JsonNode
|
||||||
proc eth_newFilter(filter: Filter): 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
|
||||||
|
|
|
@ -21,7 +21,7 @@ method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
|
||||||
raiseAssert "not implemented"
|
raiseAssert "not implemented"
|
||||||
|
|
||||||
method subscribeLogs*(subscriptions: JsonRpcSubscriptions,
|
method subscribeLogs*(subscriptions: JsonRpcSubscriptions,
|
||||||
filter: Filter,
|
filter: EventFilter,
|
||||||
onLog: LogHandler):
|
onLog: LogHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async, base.} =
|
{.async, base.} =
|
||||||
|
@ -74,7 +74,7 @@ method subscribeBlocks(subscriptions: WebSocketSubscriptions,
|
||||||
return id
|
return id
|
||||||
|
|
||||||
method subscribeLogs(subscriptions: WebSocketSubscriptions,
|
method subscribeLogs(subscriptions: WebSocketSubscriptions,
|
||||||
filter: Filter,
|
filter: EventFilter,
|
||||||
onLog: LogHandler):
|
onLog: LogHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async.} =
|
{.async.} =
|
||||||
|
@ -148,7 +148,7 @@ method subscribeBlocks(subscriptions: PollingSubscriptions,
|
||||||
return id
|
return id
|
||||||
|
|
||||||
method subscribeLogs(subscriptions: PollingSubscriptions,
|
method subscribeLogs(subscriptions: PollingSubscriptions,
|
||||||
filter: Filter,
|
filter: EventFilter,
|
||||||
onLog: LogHandler):
|
onLog: LogHandler):
|
||||||
Future[JsonNode]
|
Future[JsonNode]
|
||||||
{.async.} =
|
{.async.} =
|
||||||
|
|
|
@ -188,3 +188,50 @@ for url in ["ws://localhost:8545", "http://localhost:8545"]:
|
||||||
await provider.mineBlocks(2) # two additional blocks
|
await provider.mineBlocks(2) # two additional blocks
|
||||||
let receipt = await confirming
|
let receipt = await confirming
|
||||||
check receipt.blockNumber.isSome
|
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