diff --git a/ethers/contract.nim b/ethers/contract.nim index f079aa5..84d5d8f 100644 --- a/ethers/contract.nim +++ b/ethers/contract.nim @@ -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) diff --git a/ethers/provider.nim b/ethers/provider.nim index c09fa69..5524dca 100644 --- a/ethers/provider.nim +++ b/ethers/provider.nim @@ -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" diff --git a/ethers/providers/jsonrpc.nim b/ethers/providers/jsonrpc.nim index cbd1f52..c5fe90f 100644 --- a/ethers/providers/jsonrpc.nim +++ b/ethers/providers/jsonrpc.nim @@ -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: diff --git a/ethers/providers/jsonrpc/signatures.nim b/ethers/providers/jsonrpc/signatures.nim index f30cc3d..63bc395 100644 --- a/ethers/providers/jsonrpc/signatures.nim +++ b/ethers/providers/jsonrpc/signatures.nim @@ -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 diff --git a/ethers/providers/jsonrpc/subscriptions.nim b/ethers/providers/jsonrpc/subscriptions.nim index a5598a4..d1f64fe 100644 --- a/ethers/providers/jsonrpc/subscriptions.nim +++ b/ethers/providers/jsonrpc/subscriptions.nim @@ -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.} = diff --git a/testmodule/testContracts.nim b/testmodule/testContracts.nim index 4b7f853..954e94e 100644 --- a/testmodule/testContracts.nim +++ b/testmodule/testContracts.nim @@ -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) + ]