From f6f0dbc07bc05b39f8df9f3abd710f6cc28417c4 Mon Sep 17 00:00:00 2001 From: Eric <5089238+emizzle@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:36:53 +1100 Subject: [PATCH] Update exceptions Use {.async: (raises: [...].} where needed Annotate provider with {.push raises:[].} Format signatures --- ethers.nimble | 2 +- ethers/provider.nim | 122 +++++++++++++---------- ethers/providers/jsonrpc.nim | 144 +++++++++++++++++---------- ethers/providers/jsonrpc/looping.nim | 4 +- ethers/signer.nim | 22 ++-- ethers/wallet.nim | 2 +- ethers/wallet/error.nim | 2 +- 7 files changed, 182 insertions(+), 116 deletions(-) diff --git a/ethers.nimble b/ethers.nimble index 14d96e2..8cf1ad6 100644 --- a/ethers.nimble +++ b/ethers.nimble @@ -5,7 +5,7 @@ license = "MIT" requires "nim >= 1.6.0" requires "chronicles >= 0.10.3 & < 0.11.0" -requires "chronos >= 3.0.0 & < 4.0.0" +requires "chronos#f0a2d4df61302d24baa6c0f1c257f92045c9ee57" requires "contractabi >= 0.6.0 & < 0.7.0" requires "questionable >= 0.10.2 & < 0.11.0" requires "json_rpc" diff --git a/ethers/provider.nim b/ethers/provider.nim index f660f88..51b1683 100644 --- a/ethers/provider.nim +++ b/ethers/provider.nim @@ -97,68 +97,94 @@ func toTransaction*(past: PastTransaction): Transaction = chainId: past.chainId ) -method getBlockNumber*(provider: Provider): Future[UInt256] {.base, gcsafe.} = +method getBlockNumber*( + provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method getBlock*(provider: Provider, tag: BlockTag): Future[?Block] {.base, gcsafe.} = +method getBlock*( + provider: Provider, + tag: BlockTag): Future[?Block] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method call*(provider: Provider, - tx: Transaction, - blockTag = BlockTag.latest): Future[seq[byte]] {.base, gcsafe.} = +method call*( + provider: Provider, + tx: Transaction, + blockTag = BlockTag.latest): Future[seq[byte]] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method getGasPrice*(provider: Provider): Future[UInt256] {.base, gcsafe.} = +method getGasPrice*( + provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method getTransactionCount*(provider: Provider, - address: Address, - blockTag = BlockTag.latest): - Future[UInt256] {.base, gcsafe.} = +method getTransactionCount*( + provider: Provider, + address: Address, + blockTag = BlockTag.latest): Future[UInt256] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method getTransaction*(provider: Provider, - txHash: TransactionHash): - Future[?PastTransaction] {.base, gcsafe.} = +method getTransaction*( + provider: Provider, + txHash: TransactionHash): Future[?PastTransaction] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method getTransactionReceipt*(provider: Provider, - txHash: TransactionHash): - Future[?TransactionReceipt] {.base, gcsafe.} = +method getTransactionReceipt*( + provider: Provider, + txHash: TransactionHash): Future[?TransactionReceipt] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method sendTransaction*(provider: Provider, - rawTransaction: seq[byte]): - Future[TransactionResponse] {.base, gcsafe.} = +method sendTransaction*( + provider: Provider, + rawTransaction: seq[byte]): Future[TransactionResponse] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method getLogs*(provider: Provider, - filter: EventFilter): Future[seq[Log]] {.base, gcsafe.} = +method getLogs*( + provider: Provider, + filter: EventFilter): Future[seq[Log]] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method estimateGas*(provider: Provider, - transaction: Transaction, - blockTag = BlockTag.latest): Future[UInt256] {.base, gcsafe.} = +method estimateGas*( + provider: Provider, + transaction: Transaction, + blockTag = BlockTag.latest): Future[UInt256] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method getChainId*(provider: Provider): Future[UInt256] {.base, gcsafe.} = +method getChainId*( + provider: Provider): Future[UInt256] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method subscribe*(provider: Provider, - filter: EventFilter, - callback: LogHandler): - Future[Subscription] {.base, gcsafe.} = +method subscribe*( + provider: Provider, + filter: EventFilter, + callback: LogHandler): Future[Subscription] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method subscribe*(provider: Provider, - callback: BlockHandler): - Future[Subscription] {.base, gcsafe.} = +method subscribe*( + provider: Provider, + callback: BlockHandler): Future[Subscription] {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -method unsubscribe*(subscription: Subscription) {.base, async.} = +method unsubscribe*( + subscription: Subscription) {.base, async: (raises:[ProviderError]).} = + doAssert false, "not implemented" -proc replay*(provider: Provider, tx: Transaction, blockNumber: UInt256) {.async.} = +proc replay*( + provider: Provider, + tx: Transaction, + blockNumber: UInt256) {.async: (raises:[ProviderError]).} = # Replay transaction at block. Useful for fetching revert reasons, which will # be present in the raised error message. The replayed block number should # include the state of the chain in the block previous to the block in which @@ -172,8 +198,7 @@ proc replay*(provider: Provider, tx: Transaction, blockNumber: UInt256) {.async. method getRevertReason*( provider: Provider, hash: TransactionHash, - blockNumber: UInt256 -): Future[?string] {.base, async.} = + blockNumber: UInt256): Future[?string] {.base, async: (raises: [ProviderError]).} = without pastTx =? await provider.getTransaction(hash): return none string @@ -187,8 +212,7 @@ method getRevertReason*( method getRevertReason*( provider: Provider, - receipt: TransactionReceipt -): Future[?string] {.base, async.} = + receipt: TransactionReceipt): Future[?string] {.base, async: (raises: [ProviderError]).} = if receipt.status != TransactionStatus.Failure: return none string @@ -200,8 +224,7 @@ method getRevertReason*( proc ensureSuccess( provider: Provider, - receipt: TransactionReceipt -) {.async, raises: [ProviderError].} = + receipt: TransactionReceipt) {.async: (raises: [ProviderError]).} = ## If the receipt.status is Failed, the tx is replayed to obtain a revert ## reason, after which a ProviderError with the revert reason is raised. ## If no revert reason was obtained @@ -220,11 +243,10 @@ proc ensureSuccess( trace "transaction replay completed, no revert reason obtained" raiseProviderError("Transaction reverted with unknown reason") -proc confirm*(tx: TransactionResponse, - confirmations = EthersDefaultConfirmations, - timeout = EthersReceiptTimeoutBlks): - Future[TransactionReceipt] - {.async, raises: [ProviderError, EthersError].} = +proc confirm*( + tx: TransactionResponse, + confirmations = EthersDefaultConfirmations, + timeout = EthersReceiptTimeoutBlks): Future[TransactionReceipt] {.async: (raises: [CancelledError, ProviderError, EthersError]).} = ## 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 an error if too many @@ -266,10 +288,10 @@ proc confirm*(tx: TransactionResponse, await tx.provider.ensureSuccess(receipt) return receipt -proc confirm*(tx: Future[TransactionResponse], - confirmations: int = EthersDefaultConfirmations, - timeout: int = EthersReceiptTimeoutBlks): - Future[TransactionReceipt] {.async.} = +proc confirm*( + tx: Future[TransactionResponse], + confirmations: int = EthersDefaultConfirmations, + timeout: int = EthersReceiptTimeoutBlks): Future[TransactionReceipt] {.async.} = ## Convenience method that allows wait to be chained to a sendTransaction ## call, eg: ## `await signer.sendTransaction(populated).confirm(3)` @@ -277,5 +299,5 @@ proc confirm*(tx: Future[TransactionResponse], let txResp = await tx return await txResp.confirm(confirmations, timeout) -method close*(provider: Provider) {.async, base.} = +method close*(provider: Provider) {.base, async: (raises:[ProviderError]).} = discard diff --git a/ethers/providers/jsonrpc.nim b/ethers/providers/jsonrpc.nim index 303e589..a2a8e24 100644 --- a/ethers/providers/jsonrpc.nim +++ b/ethers/providers/jsonrpc.nim @@ -49,9 +49,7 @@ template convertError(body) = body except JsonRpcError as error: raiseJsonRpcProviderError(error.msg) - # Catch all ValueErrors for now, at least until JsonRpcError is actually - # raised. PR created: https://github.com/status-im/nim-json-rpc/pull/151 - except ValueError as error: + except CatchableError as error: raiseJsonRpcProviderError(error.msg) # Provider @@ -62,9 +60,11 @@ const defaultPollingInterval = 4.seconds proc jsonHeaders: seq[(string, string)] = @[("Content-Type", "application/json")] -proc new*(_: type JsonRpcProvider, - url=defaultUrl, - pollingInterval=defaultPollingInterval): JsonRpcProvider = +proc new*( + _: type JsonRpcProvider, + url=defaultUrl, + pollingInterval=defaultPollingInterval): JsonRpcProvider {.raises: [JsonRpcProviderError].} = + var initialized: Future[void] var client: RpcClient var subscriptions: JsonRpcSubscriptions @@ -82,19 +82,21 @@ proc new*(_: type JsonRpcProvider, client = http subscriptions = JsonRpcSubscriptions.new(http, pollingInterval = pollingInterval) + subscriptions.init() - proc awaitClient: Future[RpcClient] {.async.} = + proc awaitClient: Future[RpcClient] {.async:(raises:[JsonRpcProviderError]).} = convertError: await initialized return client - proc awaitSubscriptions: Future[JsonRpcSubscriptions] {.async.} = + proc awaitSubscriptions: Future[JsonRpcSubscriptions] {.async:(raises:[JsonRpcProviderError]).} = convertError: await initialized return subscriptions - initialized = initialize() - JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions()) + convertError: + initialized = initialize() + return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions()) proc callImpl( client: RpcClient, @@ -116,7 +118,6 @@ proc send*( arguments: seq[JsonNode] = @[]): Future[JsonNode] {.async.} = convertError: let client = await provider.client - return await client.call(call, %arguments) return await client.callImpl(call, %arguments) proc listAccounts*(provider: JsonRpcProvider): Future[seq[Address]] {.async.} = @@ -130,54 +131,66 @@ proc getSigner*(provider: JsonRpcProvider): JsonRpcSigner = proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner = JsonRpcSigner(provider: provider, address: some address) -method getBlockNumber*(provider: JsonRpcProvider): Future[UInt256] {.async.} = +method getBlockNumber*( + provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_blockNumber() -method getBlock*(provider: JsonRpcProvider, - tag: BlockTag): Future[?Block] {.async.} = +method getBlock*( + provider: JsonRpcProvider, + tag: BlockTag): Future[?Block] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_getBlockByNumber(tag, false) -method call*(provider: JsonRpcProvider, - tx: Transaction, - blockTag = BlockTag.latest): Future[seq[byte]] {.async.} = +method call*( + provider: JsonRpcProvider, + tx: Transaction, + blockTag = BlockTag.latest): Future[seq[byte]] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_call(tx, blockTag) -method getGasPrice*(provider: JsonRpcProvider): Future[UInt256] {.async.} = +method getGasPrice*( + provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_gasPrice() -method getTransactionCount*(provider: JsonRpcProvider, - address: Address, - blockTag = BlockTag.latest): - Future[UInt256] {.async.} = +method getTransactionCount*( + provider: JsonRpcProvider, + address: Address, + blockTag = BlockTag.latest): Future[UInt256] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_getTransactionCount(address, blockTag) -method getTransaction*(provider: JsonRpcProvider, - txHash: TransactionHash): - Future[?PastTransaction] {.async.} = +method getTransaction*( + provider: JsonRpcProvider, + txHash: TransactionHash): Future[?PastTransaction] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_getTransactionByHash(txHash) -method getTransactionReceipt*(provider: JsonRpcProvider, - txHash: TransactionHash): - Future[?TransactionReceipt] {.async.} = +method getTransactionReceipt*( + provider: JsonRpcProvider, + txHash: TransactionHash): Future[?TransactionReceipt] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_getTransactionReceipt(txHash) -method getLogs*(provider: JsonRpcProvider, - filter: EventFilter): - Future[seq[Log]] {.async.} = +method getLogs*( + provider: JsonRpcProvider, + filter: EventFilter): Future[seq[Log]] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client let logsJson = if filter of Filter: @@ -189,19 +202,23 @@ method getLogs*(provider: JsonRpcProvider, var logs: seq[Log] = @[] for logJson in logsJson.getElems: - if log =? Log.fromJson(logJson).catch: + if log =? Log.fromJson(logJson): logs.add log return logs -method estimateGas*(provider: JsonRpcProvider, - transaction: Transaction, - blockTag = BlockTag.latest): Future[UInt256] {.async.} = +method estimateGas*( + provider: JsonRpcProvider, + transaction: Transaction, + blockTag = BlockTag.latest): Future[UInt256] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client return await client.eth_estimateGas(transaction, blockTag) -method getChainId*(provider: JsonRpcProvider): Future[UInt256] {.async.} = +method getChainId*( + provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client try: @@ -209,7 +226,10 @@ method getChainId*(provider: JsonRpcProvider): Future[UInt256] {.async.} = except CatchableError: return parse(await client.net_version(), UInt256) -method sendTransaction*(provider: JsonRpcProvider, rawTransaction: seq[byte]): Future[TransactionResponse] {.async.} = +method sendTransaction*( + provider: JsonRpcProvider, + rawTransaction: seq[byte]): Future[TransactionResponse] {.async: (raises:[ProviderError]).} = + convertError: let client = await provider.client @@ -217,33 +237,42 @@ method sendTransaction*(provider: JsonRpcProvider, rawTransaction: seq[byte]): F return TransactionResponse(hash: hash, provider: provider) -method subscribe*(provider: JsonRpcProvider, - filter: EventFilter, - onLog: LogHandler): - Future[Subscription] {.async.} = +method subscribe*( + provider: JsonRpcProvider, + filter: EventFilter, + onLog: LogHandler): Future[Subscription] {.async: (raises:[ProviderError]).} = + convertError: let subscriptions = await provider.subscriptions let id = await subscriptions.subscribeLogs(filter, onLog) return JsonRpcSubscription(subscriptions: subscriptions, id: id) -method subscribe*(provider: JsonRpcProvider, - onBlock: BlockHandler): - Future[Subscription] {.async.} = +method subscribe*( + provider: JsonRpcProvider, + onBlock: BlockHandler): Future[Subscription] {.async: (raises:[ProviderError]).} = + convertError: let subscriptions = await provider.subscriptions let id = await subscriptions.subscribeBlocks(onBlock) return JsonRpcSubscription(subscriptions: subscriptions, id: id) -method unsubscribe(subscription: JsonRpcSubscription) {.async.} = +method unsubscribe*( + subscription: JsonRpcSubscription) {.async: (raises:[ProviderError]).} = + convertError: let subscriptions = subscription.subscriptions let id = subscription.id + echo "[jsonrpc.unsubscribe] subscription id: ", $id await subscriptions.unsubscribe(id) -method close*(provider: JsonRpcProvider) {.async.} = +method close*( + provider: JsonRpcProvider) {.async: (raises:[ProviderError]).} = + convertError: + echo "[JsonRpcProvider.close]" let client = await provider.client let subscriptions = await provider.subscriptions + echo "[JsonRpcProvider.closing subscriptions]" await subscriptions.close() await client.close() @@ -252,25 +281,32 @@ method close*(provider: JsonRpcProvider) {.async.} = method provider*(signer: JsonRpcSigner): Provider = signer.provider -method getAddress*(signer: JsonRpcSigner): Future[Address] {.async.} = +method getAddress*( + signer: JsonRpcSigner): Future[Address] {.async: (raises:[ProviderError]).} = + if address =? signer.address: return address - let accounts = await signer.provider.listAccounts() - if accounts.len > 0: - return accounts[0] + convertError: + let accounts = await signer.provider.listAccounts() + if accounts.len > 0: + return accounts[0] raiseJsonRpcProviderError "no address found" -method signMessage*(signer: JsonRpcSigner, - message: seq[byte]): Future[seq[byte]] {.async.} = +method signMessage*( + signer: JsonRpcSigner, + message: seq[byte]): Future[seq[byte]] {.async: (raises:[JsonRpcProviderError]).} = + convertError: let client = await signer.provider.client let address = await signer.getAddress() return await client.eth_sign(address, message) -method sendTransaction*(signer: JsonRpcSigner, - transaction: Transaction): Future[TransactionResponse] {.async.} = +method sendTransaction*( + signer: JsonRpcSigner, + transaction: Transaction): Future[TransactionResponse] {.async: (raises:[JsonRpcProviderError]).} = + convertError: if nonce =? transaction.nonce: signer.updateNonce(nonce) diff --git a/ethers/providers/jsonrpc/looping.nim b/ethers/providers/jsonrpc/looping.nim index f0bb7a8..74ed075 100644 --- a/ethers/providers/jsonrpc/looping.nim +++ b/ethers/providers/jsonrpc/looping.nim @@ -2,5 +2,5 @@ template untilCancelled*(body) = try: while true: body - except CancelledError: - raise + except CancelledError as e: + raise e diff --git a/ethers/signer.nim b/ethers/signer.nim index eaca26c..07ded5b 100644 --- a/ethers/signer.nim +++ b/ethers/signer.nim @@ -3,6 +3,8 @@ import ./provider export basics +{.push raises: [].} + type Signer* = ref object of RootObj lastSeenNonce: ?UInt256 @@ -19,17 +21,18 @@ template raiseSignerError(message: string, parent: ref ProviderError = nil) = proc raiseEstimateGasError( transaction: Transaction, parent: ref ProviderError = nil -) = +) {.raises: [EstimateGasError] .} = let e = (ref EstimateGasError)( msg: "Estimate gas failed", transaction: transaction, parent: parent) raise e -method provider*(signer: Signer): Provider {.base, gcsafe.} = +method provider*(signer: Signer): + Provider {.base, gcsafe, raises: [EthersError].} = doAssert false, "not implemented" -method getAddress*(signer: Signer): Future[Address] {.base, gcsafe.} = +method getAddress*(signer: Signer): Future[Address] {.base, async: (raises:[ProviderError]).} = doAssert false, "not implemented" method signMessage*(signer: Signer, @@ -40,7 +43,8 @@ method sendTransaction*(signer: Signer, transaction: Transaction): Future[TransactionResponse] {.base, async.} = doAssert false, "not implemented" -method getGasPrice*(signer: Signer): Future[UInt256] {.base, gcsafe.} = +method getGasPrice*(signer: Signer): + Future[UInt256] {.base, gcsafe, raises: [EthersError].} = signer.provider.getGasPrice() method getTransactionCount*(signer: Signer, @@ -59,7 +63,8 @@ method estimateGas*(signer: Signer, except ProviderError as e: raiseEstimateGasError transaction, e -method getChainId*(signer: Signer): Future[UInt256] {.base, gcsafe.} = +method getChainId*(signer: Signer): + Future[UInt256] {.base, gcsafe, raises: [EthersError].} = signer.provider.getChainId() method getNonce(signer: Signer): Future[UInt256] {.base, gcsafe, async.} = @@ -91,6 +96,7 @@ method populateTransaction*(signer: Signer, transaction: Transaction): Future[Transaction] {.base, async.} = + echo "[signer.populatetransaction] signer type: ", typeof signer if sender =? transaction.sender and sender != await signer.getAddress(): raiseSignerError("from address mismatch") if chainId =? transaction.chainId and chainId != await signer.getChainId(): @@ -119,8 +125,10 @@ method populateTransaction*(signer: Signer, populated.nonce = some(await signer.getNonce()) try: populated.gasLimit = some(await signer.estimateGas(populated)) - except ProviderError, EstimateGasError: - let e = getCurrentException() + except ProviderError as e: + signer.decreaseNonce() + raise e + except EstimateGasError as e: signer.decreaseNonce() raise e diff --git a/ethers/wallet.nim b/ethers/wallet.nim index 85305f6..83b21fb 100644 --- a/ethers/wallet.nim +++ b/ethers/wallet.nim @@ -54,7 +54,7 @@ proc createRandom*(_: type Wallet, provider: Provider): Wallet = result.address = Address.init(result.publicKey.toCanonicalAddress()) result.provider = some provider -method provider*(wallet: Wallet): Provider = +method provider*(wallet: Wallet): Provider {.raises: [WalletError].} = without provider =? wallet.provider: raiseWalletError "Wallet has no provider" provider diff --git a/ethers/wallet/error.nim b/ethers/wallet/error.nim index dc4ab4e..3ccf94d 100644 --- a/ethers/wallet/error.nim +++ b/ethers/wallet/error.nim @@ -3,5 +3,5 @@ import ../basics type WalletError* = object of EthersError -func raiseWalletError*(message: string) = +func raiseWalletError*(message: string) {.raises: [WalletError].}= raise newException(WalletError, message)