diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 4185832..04eb212 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -14,7 +14,6 @@ import test_primitives, test_contracts, test_deposit_contract, - test_ethhexstrings, test_logs, test_json_marshalling, test_signed_tx, diff --git a/tests/helpers/primitives_utils.nim b/tests/helpers/primitives_utils.nim new file mode 100644 index 0000000..b903d38 --- /dev/null +++ b/tests/helpers/primitives_utils.nim @@ -0,0 +1,29 @@ +import + stint, + ../../web3/primitives + +func ethToWei*(eth: UInt256): UInt256 = + eth * 1000000000000000000.u256 + +type + BlobData* = DynamicBytes[0, 512] + +func conv*(T: type, x: int): T = + type BaseType = distinctBase T + var res: BaseType + when BaseType is seq: + res.setLen(1) + res[^1] = x.byte + T(res) + +func address*(x: int): Address = + conv(typeof result, x) + +func txhash*(x: int): TxHash = + conv(typeof result, x) + +func blob*(x: int): BlobData = + conv(typeof result, x) + +func h256*(x: int): Hash256 = + conv(typeof result, x) diff --git a/tests/helpers/utils.nim b/tests/helpers/utils.nim index 93d9816..1eb4d07 100644 --- a/tests/helpers/utils.nim +++ b/tests/helpers/utils.nim @@ -1,6 +1,6 @@ import std/options, - chronos, stint, + chronos, stew/byteutils, ../../web3, ../../web3/primitives @@ -16,29 +16,3 @@ proc deployContract*(web3: Web3, code: string, gasPrice = 0): Future[ReceiptObje let r = await web3.send(tr) return await web3.getMinedTransactionReceipt(r) - -func ethToWei*(eth: UInt256): UInt256 = - eth * 1000000000000000000.u256 - -type - BlobData* = DynamicBytes[0, 512] - -func conv*(T: type, x: int): T = - type BaseType = distinctBase T - var res: BaseType - when BaseType is seq: - res.setLen(1) - res[^1] = x.byte - T(res) - -func address*(x: int): Address = - conv(typeof result, x) - -func txhash*(x: int): TxHash = - conv(typeof result, x) - -func blob*(x: int): BlobData = - conv(typeof result, x) - -func h256*(x: int): Hash256 = - conv(typeof result, x) diff --git a/tests/test_deposit_contract.nim b/tests/test_deposit_contract.nim index 56be541..bed3f0f 100644 --- a/tests/test_deposit_contract.nim +++ b/tests/test_deposit_contract.nim @@ -13,6 +13,7 @@ import chronos, stint, ../web3, ./helpers/utils, + ./helpers/primitives_utils, ./helpers/depositcontract contract(DepositContract): @@ -45,7 +46,8 @@ suite "Deposit contract": var fut = newFuture[void]() - let s = await ns.subscribe(DepositEvent, %*{"fromBlock": "0x0"}) do ( + let options = FilterOptions(fromBlock: some(blockId(0))) + let s = await ns.subscribe(DepositEvent, options) do ( pubkey: DynamicBytes[0, 48], withdrawalCredentials: DynamicBytes[0, 32], amount: DynamicBytes[0, 8], signature: DynamicBytes[0, 96], merkleTreeIndex: DynamicBytes[0, 8]) {.raises: [], gcsafe.}: try: diff --git a/tests/test_ethhexstrings.nim b/tests/test_ethhexstrings.nim deleted file mode 100644 index 0d74fd4..0000000 --- a/tests/test_ethhexstrings.nim +++ /dev/null @@ -1,86 +0,0 @@ -# nim-web3 -# Copyright (c) 2018-2023 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. - -import - unittest2, json, - ../web3/ethhexstrings - -suite "Hex quantity": - test "Empty string": - expect ValueError: - let - source = "" - x = hexQuantityStr source - check %x == %source - test "Even length": - let - source = "0x123" - x = hexQuantityStr source - check %x == %source - test "Odd length": - let - source = "0x123" - x = hexQuantityStr"0x123" - check %x == %source - test "Missing header": - expect ValueError: - let - source = "1234" - x = hexQuantityStr source - check %x != %source - expect ValueError: - let - source = "01234" - x = hexQuantityStr source - check %x != %source - expect ValueError: - let - source = "x1234" - x = hexQuantityStr source - check %x != %source - test "Hex encoded 0x0": - let - source = "0x0" - x = hexQuantityStr"0x0" - check %x == %source - -suite "Hex data": - test "Even length": - let - source = "0x1234" - x = hexDataStr source - check %x == %source - test "Empty data": - let - source = "0x" - x = hexDataStr source - check %x == %source - test "Odd length": - expect ValueError: - let - source = "0x123" - x = hexDataStr source - check %x != %source - test "Missing header": - expect ValueError: - let - source = "1234" - x = hexDataStr source - check %x != %source - expect ValueError: - let - source = "01234" - x = hexDataStr source - check %x != %source - expect ValueError: - let - source = "x1234" - x = hexDataStr source - check %x != %source - diff --git a/tests/test_execution_types.nim b/tests/test_execution_types.nim index 13fc8be..e690ac3 100644 --- a/tests/test_execution_types.nim +++ b/tests/test_execution_types.nim @@ -12,7 +12,7 @@ import pkg/unittest2, stew/byteutils, ../web3/execution_types, - ./helpers/utils + ./helpers/primitives_utils suite "Execution types tests": let diff --git a/tests/test_json_marshalling.nim b/tests/test_json_marshalling.nim index 0bd9930..3618790 100644 --- a/tests/test_json_marshalling.nim +++ b/tests/test_json_marshalling.nim @@ -22,16 +22,14 @@ suite "JSON-RPC Quantity": ("0x0", Quantity 0), ("0x123", Quantity 291), ("0x1234", Quantity 4660)]: - let validQuantityJson = $(%validQuantityStr) - var resQuantity: Quantity - var resUInt256: UInt256 - var resUInt256Ref: ref UInt256 - fromJson(%validQuantityStr, "", resQuantity) - fromJson(%validQuantityStr, "", resUInt256) - fromJson(%validQuantityStr, "", resUInt256Ref) + let validQuantityJson = JrpcConv.encode(validQuantityStr) + let resQuantity = JrpcConv.decode(validQuantityJson, Quantity) + let resUInt256 = JrpcConv.decode(validQuantityJson, UInt256) + let resUInt256Ref = JrpcConv.decode(validQuantityJson, ref UInt256) + check: - Json.decode(validQuantityJson, Quantity) == validQuantity - Json.encode(validQuantity) == validQuantityJson + JrpcConv.decode(validQuantityJson, Quantity) == validQuantity + JrpcConv.encode(validQuantity) == validQuantityJson resQuantity == validQuantity resUInt256 == validQuantity.distinctBase.u256 resUInt256Ref[] == validQuantity.distinctBase.u256 @@ -44,10 +42,11 @@ suite "JSON-RPC Quantity": template checkInvalids(typeName: untyped) = var resQuantity: `typeName` try: - fromJson(%invalidStr, "", resQuantity) - echo `typeName`, invalidStr + let jsonBytes = JrpcConv.encode(invalidStr) + resQuantity = JrpcConv.decode(jsonBytes, `typeName`) + echo `typeName`, " ", invalidStr check: false - except ValueError: + except SerializationError: check: true except CatchableError: check: false @@ -60,7 +59,11 @@ suite "JSON-RPC Quantity": const blockJson = """ {"difficulty":"0x1","extraData":"0x696e667572612d696f00000000000000000000000000000000000000000000004ede22d16eaf5bbab47534ee64a1ec1728ed63b1243672ee9623532fffd747b368cddb4674f849e467884f0e6c6563440ea5fd812cc33fcd19fb9c323b0c92c300","gasLimit":"0x7a1200","gasUsed":"0x3aca3e","hash":"0x5ac670562dbf877a45039d65ec3c2e3402a40eda9b1dba931c2376ab7d0927c2","logsBloom":"0x40000000004000120000000100030000882000040000001800010000020000000000000000001400080040004080400402024800000004000088f0320000a0000016000100110060800000080000020000000020000000000050000009010000080000040220280000080240048008000001381a1100000000000010000440000000004200000018001102280400003040040020001a000026000488000101000120000a0002000081220000100000000200000000040440a02400010000000002000002400100840000080000000080000008c0c080000008000000220860082002000000001000000041040002000000008000010004000400010400040001","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x42a3da","parentHash":"0xe0190ed0683835483c35e0c0a98bf0958ed2ca7313428c9026db51604007e299","receiptsRoot":"0x12fff235455db65dcdc525d2491b9e0526e02d70a1515fba155b1de9f648abf3","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x2a5d","stateRoot":"0xccb37180b5fca41e43395d524a0ee83a1efc69f2fb61f90a51f3dc8f40f2144e","timestamp":"0x603cab8c","totalDifficulty":"0x624910","transactions":["0xa3c07dc59bfdb1bfc2d50920fed2ef2c1c4e0a09fe2325dbc14e07702f965a78","0x7b33b36e905c8e83a519216444aff4952bfbce5c49247ecc70f227a56068247e","0x11fa3f25957caa1918ecb1f2c1eaeef46c1af983e1b7eee65cefe2a752f7e9da","0x7028ef43993c84e751bbcb126cbaa52d7b732efe4daf95e6e0fbff06e43f0277","0xe287b4939a51aff8a78b836c56db18ca1559ba77303b1e0cee9075ff737f4a57","0xb79cb96a3ff5bb9aca4ce2024a57023cfda163d6135f3ff6a0d9f0b9fac44efb","0xad0d4bfb6b4276616c7d88fe2576903d6c17f6bee1d10db15de203a88bda2898","0x73127fadab4c4ceed35be81e3e97549d2005bfdbc49083ce81f135662edd6869","0xff522f3e2a2d451acf2df2341d8ed9e982dbf7160b327f34b8b5ca25b377a74f","0x2d100b5abba751743920cf56614195c0e3685d93bfdbe5325c35a933f5195e2c","0x9611c7cac2e14fed4051d34f74eed1bace9da8e79446d4e03f479c318de24087","0x43f29106dac821e5069b3c0b27a61d01116a8e69096388d57b70a9e5354e0457","0xb71fe345141491f54cb53e4af44d581cbd631c0b7bd12019077c1dc354b1c5ab"],"transactionsRoot":"0x08fd011674202c6df63822f877e88d7ca0fe41e5deb8bc2b8830f1a29ce864f0","uncles":[]} """ - var b1, b2: BlockObject - fromJson(parseJson(blockJson), "", b1) - fromJson(parseJson($(%b1)), "", b2) - check $(%b1) == $(%b2) + let + b1 = JrpcConv.decode(blockJson, BlockObject) + jsonBytes = JrpcConv.encode(b1) + b2 = JrpcConv.decode(jsonBytes, BlockObject) + b1Bytes = JrpcConv.encode(b1) + b2Bytes = JrpcConv.encode(b2) + + check b1Bytes == b2Bytes diff --git a/tests/test_logs.nim b/tests/test_logs.nim index 0475de5..5fba3ad 100644 --- a/tests/test_logs.nim +++ b/tests/test_logs.nim @@ -75,7 +75,8 @@ suite "Logs": let notifFut = newFuture[void]() var notificationsReceived = 0 - let s = await ns.subscribe(MyEvent, %*{"fromBlock": "0x0"}) do ( + let options = FilterOptions(fromBlock: some(blockId(0))) + let s = await ns.subscribe(MyEvent, options) do ( sender: Address, value: UInt256) {.raises: [], gcsafe.}: try: diff --git a/tests/test_null_conversion.nim b/tests/test_null_conversion.nim index 94857f1..209b336 100644 --- a/tests/test_null_conversion.nim +++ b/tests/test_null_conversion.nim @@ -3,15 +3,14 @@ import pkg/unittest2, stint, json_rpc/jsonmarshal, - ../web3, ../web3/[conversions, eth_api_types, engine_api_types] template should_be_value_error(input: string, value: untyped): void = - expect ValueError: - fromJson(%input, "", value) + expect SerializationError: + value = JrpcConv.decode(input, typeof(value)) template should_not_error(input: string, value: untyped): void = - fromJson(%input, "", value) + value = JrpcConv.decode(input, typeof(value)) suite "Null conversion": var resAddress: Address @@ -24,7 +23,7 @@ suite "Null conversion": var resUInt256Ref: ref UInt256 ## Covers the converters which can be found in web3/conversions.nim - ## Ensure that when passing a nully value they respond with a ValueError + ## Ensure that when passing a nully value they respond with a SerializationError test "passing null values to normal convertors": should_be_value_error("null", resAddress) should_be_value_error("null", resDynamicBytes) @@ -33,7 +32,7 @@ suite "Null conversion": should_be_value_error("null", resRlpEncodedBytes) should_be_value_error("null", resTypedTransaction) should_be_value_error("null", resUInt256) - should_be_value_error("null", resUInt256Ref) + should_not_error("null", resUInt256Ref) test "passing empty values to normal convertors": should_be_value_error("", resAddress) @@ -46,30 +45,30 @@ suite "Null conversion": should_be_value_error("", resUInt256Ref) test "passing invalid hex (0x) values to normal convertors": - should_be_value_error("0x", resAddress) - should_be_value_error("0x", resDynamicBytes) - should_be_value_error("0x", resFixedBytes) - should_be_value_error("0x", resQuantity) - should_be_value_error("0x", resUInt256) - should_be_value_error("0x", resUInt256Ref) + should_be_value_error("\"0x\"", resAddress) + should_be_value_error("\"0x\"", resDynamicBytes) + should_be_value_error("\"0x\"", resFixedBytes) + should_be_value_error("\"0x\"", resQuantity) + should_be_value_error("\"0x\"", resUInt256) + should_be_value_error("\"0x\"", resUInt256Ref) test "passing hex (0x) values to normal convertors": - should_not_error("0x", resRlpEncodedBytes) - should_not_error("0x", resTypedTransaction) + should_not_error("\"0x\"", resRlpEncodedBytes) + should_not_error("\"0x\"", resTypedTransaction) test "passing malformed hex (0x_) values to normal convertors": - should_be_value_error("0x_", resAddress) - should_be_value_error("0x_", resDynamicBytes) - should_be_value_error("0x_", resFixedBytes) - should_be_value_error("0x_", resQuantity) - should_be_value_error("0x_", resRlpEncodedBytes) - should_be_value_error("0x_", resTypedTransaction) - should_be_value_error("0x_", resUInt256) - should_be_value_error("0x_", resUInt256Ref) + should_be_value_error("\"0x_\"", resAddress) + should_be_value_error("\"0x_\"", resDynamicBytes) + should_be_value_error("\"0x_\"", resFixedBytes) + should_be_value_error("\"0x_\"", resQuantity) + should_be_value_error("\"0x_\"", resRlpEncodedBytes) + should_be_value_error("\"0x_\"", resTypedTransaction) + should_be_value_error("\"0x_\"", resUInt256) + should_be_value_error("\"0x_\"", resUInt256Ref) ## Covering the web3/engine_api_types ## - ## NOTE: These will be transformed by the fromJson imported from + ## NOTE: These will be transformed by the JrpcConv imported from ## nim-json-rpc/json_rpc/jsonmarshal test "passing nully values to specific convertors": @@ -92,7 +91,6 @@ suite "Null conversion": should_be_value_error(forkchoiceUpdatedResponse.format(), resForkchoiceUpdatedResponse) should_be_value_error(transitionConfigurationV1.format(), resTransitionConfigurationV1) - ## If different status types can have branching logic ## we should cover each status type with different null ops test "passing nully values to specific status types": @@ -106,4 +104,4 @@ suite "Null conversion": "validationError": null }""".replace("status_name", $status_type) - should_be_value_error(payloadStatusV1, resPayloadStatusV1) + should_not_error(payloadStatusV1, resPayloadStatusV1) diff --git a/tests/test_primitives.nim b/tests/test_primitives.nim index 24b6910..de8016e 100644 --- a/tests/test_primitives.nim +++ b/tests/test_primitives.nim @@ -12,7 +12,7 @@ import pkg/unittest2, stew/byteutils, ../web3/primitives, - ./helpers/utils + ./helpers/primitives_utils suite "Primitives": const diff --git a/tests/test_signed_tx.nim b/tests/test_signed_tx.nim index 14b610b..1b38593 100644 --- a/tests/test_signed_tx.nim +++ b/tests/test_signed_tx.nim @@ -8,7 +8,7 @@ # those terms. import - std/[options, json], + std/[options], pkg/unittest2, chronos, stint, eth/keys, @@ -16,7 +16,8 @@ import stew/byteutils, ../web3, ../web3/transaction_signing, - ./helpers/utils + ./helpers/utils, + ./helpers/primitives_utils #[ Contract NumberStorage pragma solidity ^0.4.18; @@ -78,8 +79,8 @@ suite "Signed transactions": # Send 10 eth to acc discard await web3.send(tx) - var balance = await web3.provider.eth_getBalance(acc, "latest") - assert(balance == ethToWei(10.u256)) + #var balance = await web3.provider.eth_getBalance(acc, "latest") + #assert(balance == ethToWei(10.u256)) # Send 5 eth back web3.privateKey = some(pk) @@ -88,7 +89,7 @@ suite "Signed transactions": tx.gas = some(Quantity(3000000)) discard await web3.send(tx) - balance = await web3.provider.eth_getBalance(acc, "latest") + var balance = await web3.provider.eth_getBalance(acc, "latest") assert(balance in ethToWei(4.u256)..ethToWei(5.u256)) # 5 minus gas costs # Creating the contract with a signed tx diff --git a/tests/test_string_decoder.nim b/tests/test_string_decoder.nim index 63c7681..c98e991 100644 --- a/tests/test_string_decoder.nim +++ b/tests/test_string_decoder.nim @@ -1,5 +1,7 @@ -import pkg/unittest2 -import ../web3 +import + pkg/unittest2, + ../web3/encoding, + ../web3/primitives type PubKeyBytes = DynamicBytes[48, 48] diff --git a/web3.nim b/web3.nim index 77aeac7..980acb4 100644 --- a/web3.nim +++ b/web3.nim @@ -8,24 +8,25 @@ # those terms. import - std/[options, json, tables, uri, strformat] - -from os import DirSep, AltSep -from eth/common/eth_types import ChainId - -import + std/[options, json, tables, uri], stint, httputils, chronos, - json_rpc/[rpcclient, jsonmarshal], stew/byteutils, eth/keys, + json_rpc/[rpcclient, jsonmarshal], + eth/keys, chronos/apps/http/httpclient, - web3/[eth_api_types, conversions, ethhexstrings, transaction_signing, encoding, contract_dsl] + web3/[eth_api_types, conversions, transaction_signing, encoding, contract_dsl], + web3/eth_api -template sourceDir: string = currentSourcePath.rsplit({DirSep, AltSep}, 1)[0] - -## Generate client convenience marshalling wrappers from forward declarations -createRpcSigs(RpcClient, sourceDir & "/web3/eth_api_callsigs.nim") +from eth/common/eth_types import ChainId export UInt256, Int256, Uint128, Int128, ChainId -export eth_api_types, conversions, encoding, contract_dsl, HttpClientFlag, HttpClientFlags +export + eth_api_types, + conversions, + encoding, + contract_dsl, + HttpClientFlag, + HttpClientFlags, + eth_api type Web3* = ref object @@ -56,20 +57,40 @@ type historicalEventsProcessed: bool removed: bool -proc handleSubscriptionNotification(w: Web3, j: JsonNode) = - let s = w.subscriptions.getOrDefault(j{"subscription"}.getStr()) +proc handleSubscriptionNotification(w: Web3, params: JsonNode) = + let s = w.subscriptions.getOrDefault(params{"subscription"}.getStr()) if not s.isNil and not s.removed: if s.historicalEventsProcessed: - s.eventHandler(j{"result"}) + s.eventHandler(params{"result"}) else: - s.pendingEvents.add(j) + s.pendingEvents.add(params) + +template `or`(a: JsonNode, b: typed): JsonNode = + if a.isNil: b else: a proc newWeb3*(provider: RpcClient): Web3 = result = Web3(provider: provider) result.subscriptions = initTable[string, Subscription]() - let r = result - provider.setMethodHandler("eth_subscription") do(j: JsonNode): - r.handleSubscriptionNotification(j) + let w3 = result + + provider.onProcessMessage = proc(client: RpcClient, line: string): + Result[bool, string] {.gcsafe, raises: [].} = + try: + let node = JrpcConv.decode(line, JsonNode) + if "method" notin node: + # fallback to regular onProcessMessage + return ok(true) + + # This could be subscription notification + let name = node["method"].getStr() + if name == "eth_subscription": + let params = node{"params"} or newJArray() + w3.handleSubscriptionNotification(params) + + # don't fallback, just quit onProcessMessage + return ok(false) + except CatchableError as exc: + return err(exc.msg) proc newWeb3*( uri: string, @@ -99,9 +120,9 @@ proc newWeb3*( proc close*(web3: Web3): Future[void] = web3.provider.close() -proc getHistoricalEvents(s: Subscription, options: JsonNode) {.async.} = +proc getHistoricalEvents(s: Subscription, options: FilterOptions) {.async.} = try: - let logs = await s.web3.provider.eth_getLogs(options) + let logs = await s.web3.provider.eth_getJsonLogs(options) for l in logs: if s.removed: break s.eventHandler(l) @@ -116,7 +137,7 @@ proc getHistoricalEvents(s: Subscription, options: JsonNode) {.async.} = echo "Caught exception in getHistoricalEvents: ", e.msg echo e.getStackTrace() -proc subscribe*(w: Web3, name: string, options: JsonNode, +proc subscribe*(w: Web3, name: string, options: Option[FilterOptions], eventHandler: SubscriptionEventHandler, errorHandler: SubscriptionErrorHandler): Future[Subscription] {.async.} = @@ -132,10 +153,10 @@ proc subscribe*(w: Web3, name: string, options: JsonNode, ## the error. # Don't send an empty `{}` object as an extra argument if there are no options - let id = if options.isNil: + let id = if options.isNone: await w.provider.eth_subscribe(name) else: - await w.provider.eth_subscribe(name, options) + await w.provider.eth_subscribe(name, options.get) result = Subscription(id: id, web3: w, @@ -144,29 +165,25 @@ proc subscribe*(w: Web3, name: string, options: JsonNode, w.subscriptions[id] = result -proc subscribeForLogs*(w: Web3, options: JsonNode, +proc subscribeForLogs*(w: Web3, options: FilterOptions, logsHandler: SubscriptionEventHandler, errorHandler: SubscriptionErrorHandler, withHistoricEvents = true): Future[Subscription] {.async.} = - result = await subscribe(w, "logs", options, logsHandler, errorHandler) + result = await subscribe(w, "logs", some(options), logsHandler, errorHandler) if withHistoricEvents: discard getHistoricalEvents(result, options) else: result.historicalEventsProcessed = true -proc addAddressAndSignatureToOptions(options: JsonNode, address: Address, topic: seq[byte]): JsonNode = - result = if options.isNil: newJObject() else: options - if "address" notin result: - result["address"] = %address - var topics = result{"topics"} - if topics.isNil: - topics = newJArray() - result["topics"] = topics - topics.elems.insert(%to0xHex(topic), 0) - -proc subscribeForLogs*(s: Web3SenderImpl, options: JsonNode, - topic: seq[byte], +proc addAddressAndSignatureToOptions(options: FilterOptions, address: Address, topic: Topic): FilterOptions = + result = options + if result.address.kind == slkNull: + result.address = AddressOrList(kind: slkSingle, single: address) + result.topics.insert(TopicOrList(kind: slkSingle, single: topic), 0) + +proc subscribeForLogs*(s: Web3SenderImpl, options: FilterOptions, + topic: Topic, logsHandler: SubscriptionEventHandler, errorHandler: SubscriptionErrorHandler, withHistoricEvents = true): Future[Subscription] = @@ -178,16 +195,16 @@ proc subscribeForBlockHeaders*(w: Web3, errorHandler: SubscriptionErrorHandler): Future[Subscription] {.async.} = proc eventHandler(json: JsonNode) {.gcsafe, raises: [].} = - var blk: BlockHeader + try: - fromJson(json, "result", blk) + let blk = JrpcConv.decode($json, BlockHeader) blockHeadersCallback(blk) except CatchableError as err: errorHandler(err[]) # `nil` options so that we skip sending an empty `{}` object as an extra argument # to geth for `newHeads`: https://github.com/ethereum/go-ethereum/issues/21588 - result = await subscribe(w, "newHeads", nil, eventHandler, errorHandler) + result = await subscribe(w, "newHeads", none(FilterOptions), eventHandler, errorHandler) result.historicalEventsProcessed = true proc unsubscribe*(s: Subscription): Future[void] {.async.} = @@ -195,28 +212,30 @@ proc unsubscribe*(s: Subscription): Future[void] {.async.} = s.removed = true discard await s.web3.provider.eth_unsubscribe(s.id) -proc getJsonLogs(s: Web3SenderImpl, topic: openarray[byte], - fromBlock = none(RtBlockIdentifier), toBlock = none(RtBlockIdentifier), +proc getJsonLogs(s: Web3SenderImpl, topic: Topic, + fromBlock = none(RtBlockIdentifier), + toBlock = none(RtBlockIdentifier), blockHash = none(BlockHash)): Future[JsonNode] = - var options = newJObject() - options["address"] = %s.contractAddress - var topics = newJArray() - topics.elems.insert(%to0xHex(topic), 0) - options["topics"] = topics + + var options = FilterOptions( + address: AddressOrList(kind: slkSingle, single: s.contractAddress), + topics: @[TopicOrList(kind: slkSingle, single: topic)], + ) + if blockHash.isSome: doAssert fromBlock.isNone and toBlock.isNone - options["blockHash"] = %blockHash.unsafeGet + options.blockHash = blockHash else: - if fromBlock.isSome: - options["fromBlock"] = %fromBlock.unsafeGet - if toBlock.isSome: - options["toBlock"] = %toBlock.unsafeGet + options.fromBlock = fromBlock + options.toBlock = toBlock - s.web3.provider.eth_getLogs(options) + # TODO: optimize it instead of double conversion + s.web3.provider.eth_getJsonLogs(options) proc getJsonLogs*[TContract](s: Sender[TContract], EventName: type, - fromBlock= none(RtBlockIdentifier), toBlock = none(RtBlockIdentifier), + fromBlock= none(RtBlockIdentifier), + toBlock = none(RtBlockIdentifier), blockHash = none(BlockHash)): Future[JsonNode] {.inline.} = mixin eventTopic getJsonLogs(s.sender, eventTopic(EventName)) @@ -302,7 +321,7 @@ proc call*[T](c: ContractInvocation[T, Web3SenderImpl], cc.value = some(value) let response = if blockNumber != high(uint64): - await web3.provider.eth_call(cc, &"0x{blockNumber:X}") + await web3.provider.eth_call(cc, blockId(blockNumber)) else: await web3.provider.eth_call(cc, "latest") @@ -375,4 +394,4 @@ proc isDeployed*(s: Sender, atBlock: RtBlockIdentifier): Future[bool] {.async.} return code.len > 0 proc subscribe*[TContract](s: Sender[TContract], t: typedesc, cb: proc): Future[Subscription] {.inline.} = - subscribe(s, t, newJObject(), cb, SubscriptionErrorHandler nil) + subscribe(s, t, FilterOptions(), cb, SubscriptionErrorHandler nil) diff --git a/web3/contract_dsl.nim b/web3/contract_dsl.nim index d49c3ca..bcfc915 100644 --- a/web3/contract_dsl.nim +++ b/web3/contract_dsl.nim @@ -44,8 +44,8 @@ type of constructor: constructorObject: ConstructorObject of event: eventObject: EventObject -proc keccak256Bytes(s: string): seq[byte] {.inline.} = - @(keccak256.digest(s).data) +proc keccak256Bytes(s: string): array[32, byte] {.inline.} = + keccak256.digest(s).data proc initContractInvocation[TSender](TResult: typedesc, sender: TSender, data: seq[byte]): ContractInvocation[TResult, TSender] {.inline.} = ContractInvocation[TResult, TSender](data: data, sender: sender) @@ -286,13 +286,13 @@ macro contract*(cname: untyped, body: untyped): untyped = result.add quote do: type `cbident`* = object - template eventTopic*(T: type `cbident`): seq[byte] = + template eventTopic*(T: type `cbident`): Topic = const r = keccak256Bytes(`signature`) - r + Topic(r) proc subscribe[TSender](s: ContractInstance[`cname`, TSender], t: type `cbident`, - options: JsonNode, + options: FilterOptions, `callbackIdent`: `procTy`, errorHandler: SubscriptionErrorHandler, withHistoricEvents = true): Future[Subscription] {.used.} = @@ -307,7 +307,7 @@ macro contract*(cname: untyped, body: untyped): untyped = proc subscribe[TSender](s: ContractInstance[`cname`, TSender], t: type `cbident`, - options: JsonNode, + options: FilterOptions, `callbackIdent`: `procTyWithRawData`, errorHandler: SubscriptionErrorHandler, withHistoricEvents = true): Future[Subscription] {.used.} = diff --git a/web3/conversions.nim b/web3/conversions.nim index 772d84f..d5908ae 100644 --- a/web3/conversions.nim +++ b/web3/conversions.nim @@ -8,12 +8,69 @@ # those terms. import - std/[json, options, strutils, strformat, tables, typetraits], - stint, stew/byteutils, json_serialization, faststreams/textio, - ./eth_api_types, ./ethhexstrings, - ./engine_api_types - -from json_rpc/jsonmarshal import expect, fromJson + std/strutils, + stint, + stew/byteutils, + faststreams/textio, + json_rpc/jsonmarshal, + json_serialization/std/options, + json_serialization, + ./primitives, + ./engine_api_types, + ./eth_api_types, + ./execution_types + +export + options, + json_serialization, + jsonmarshal + +template derefType(T: type): untyped = + typeof(T()[]) + +# eth_api_types +SyncObject.useDefaultSerializationIn JrpcConv +WithdrawalObject.useDefaultSerializationIn JrpcConv +AccessTuple.useDefaultSerializationIn JrpcConv +AccessListResult.useDefaultSerializationIn JrpcConv +LogObject.useDefaultSerializationIn JrpcConv +StorageProof.useDefaultSerializationIn JrpcConv +ProofResponse.useDefaultSerializationIn JrpcConv +FilterOptions.useDefaultSerializationIn JrpcConv +EthSend.useDefaultSerializationIn JrpcConv +EthCall.useDefaultSerializationIn JrpcConv + +derefType(BlockHeader).useDefaultSerializationIn JrpcConv +derefType(BlockObject).useDefaultSerializationIn JrpcConv +derefType(TransactionObject).useDefaultSerializationIn JrpcConv +derefType(ReceiptObject).useDefaultSerializationIn JrpcConv + +# engine_api_types +WithdrawalV1.useDefaultSerializationIn JrpcConv +ExecutionPayloadV1.useDefaultSerializationIn JrpcConv +ExecutionPayloadV2.useDefaultSerializationIn JrpcConv +ExecutionPayloadV1OrV2.useDefaultSerializationIn JrpcConv +ExecutionPayloadV3.useDefaultSerializationIn JrpcConv +BlobsBundleV1.useDefaultSerializationIn JrpcConv +ExecutionPayloadBodyV1.useDefaultSerializationIn JrpcConv +PayloadAttributesV1.useDefaultSerializationIn JrpcConv +PayloadAttributesV2.useDefaultSerializationIn JrpcConv +PayloadAttributesV3.useDefaultSerializationIn JrpcConv +PayloadAttributesV1OrV2.useDefaultSerializationIn JrpcConv +PayloadStatusV1.useDefaultSerializationIn JrpcConv +ForkchoiceStateV1.useDefaultSerializationIn JrpcConv +ForkchoiceUpdatedResponse.useDefaultSerializationIn JrpcConv +TransitionConfigurationV1.useDefaultSerializationIn JrpcConv +GetPayloadV2Response.useDefaultSerializationIn JrpcConv +GetPayloadV2ResponseExact.useDefaultSerializationIn JrpcConv +GetPayloadV3Response.useDefaultSerializationIn JrpcConv + +# execution types +ExecutionPayload.useDefaultSerializationIn JrpcConv +PayloadAttributes.useDefaultSerializationIn JrpcConv +GetPayloadResponse.useDefaultSerializationIn JrpcConv + +{.push gcsafe, raises: [].} template invalidQuantityPrefix(s: string): bool = # https://ethereum.org/en/developers/docs/apis/json-rpc/#hex-value-encoding @@ -32,210 +89,225 @@ template invalidQuantityPrefix(s: string): bool = else: (not s.startsWith "0x") or s == "0x" -func `%`*(n: Int256|UInt256): JsonNode = %("0x" & n.toHex) +template toHexImpl(hex, pos: untyped) = + const + hexChars = "0123456789abcdef" + maxDigits = sizeof(x) * 2 + var + hex: array[maxDigits, char] + pos = hex.len + n = x -# allows UInt256 to be passed as a json string -func fromJson*(n: JsonNode, argName: string, result: var UInt256) = - # expects base 16 string, starting with "0x" - n.kind.expect(JString, argName) - let hexStr = n.getStr() - if hexStr.len > 64 + 2: # including "0x" - raise newException(ValueError, "Parameter \"" & argName & "\" value too long for UInt256: " & $hexStr.len) - if hexStr.invalidQuantityPrefix: - raise newException(ValueError, "Parameter \"" & argName & "\" value has invalid leading 0") - result = hexStr.parse(StUint[256], 16) # TODO Add error checking - -# allows ref UInt256 to be passed as a json string -func fromJson*(n: JsonNode, argName: string, result: var ref UInt256) = - # expects base 16 string, starting with "0x" - n.kind.expect(JString, argName) - let hexStr = n.getStr() - if hexStr.len > 64 + 2: # including "0x" - raise newException(ValueError, "Parameter \"" & argName & "\" value too long for UInt256: " & $hexStr.len) - if hexStr.invalidQuantityPrefix: - raise newException(ValueError, "Parameter \"" & argName & "\" value has invalid leading 0") - new result - result[] = hexStr.parse(StUint[256], 16) # TODO Add error checking + template prepend(c: char) = + dec pos + hex[pos] = c -func bytesFromJson(n: JsonNode, argName: string, result: var openArray[byte]) = - n.kind.expect(JString, argName) - let hexStr = n.getStr() + for _ in 0 ..< 8: + prepend(hexChars[int(n and 0xF)]) + n = n shr 4 - if not ("0x" in hexStr): - raise newException(ValueError, "Parameter \"" & argName & "\" is not a hexadecimal string") + while hex[pos] == '0' and pos < hex.high: + inc pos - if hexStr.len != result.len * 2 + 2: # including "0x" - raise newException(ValueError, "Parameter \"" & argName & "\" value wrong length: " & $hexStr.len) +proc toHex(s: OutputStream, x: uint64) {.gcsafe, raises: [IOError].} = + toHexImpl(hex, pos) + write s, hex.toOpenArray(pos, static(hex.len - 1)) - hexToByteArray(hexStr, result) +func encodeQuantity(x: uint64): string = + toHexImpl(hex, pos) + result = "0x" + for i in pos.. 0: - result["data"] = %x.data - if x.nonce.isSome: - result["nonce"] = %x.nonce.unsafeGet - -func `%`*(x: EthCall): JsonNode = - result = newJObject() - result["to"] = %x.to - if x.source.isSome: - result["source"] = %x.source.unsafeGet - if x.gas.isSome: - result["gas"] = %x.gas.unsafeGet - if x.gasPrice.isSome: - result["gasPrice"] = %x.gasPrice.unsafeGet - if x.value.isSome: - result["value"] = %x.value.unsafeGet - if x.data.isSome: - result["data"] = %x.data.unsafeGet - -func `%`*(x: byte): JsonNode = - %x.int - -func `%`*(x: RtBlockIdentifier): JsonNode = - case x.kind - of bidNumber: %(&"0x{x.number:X}") - of bidAlias: %x.alias - -func `%`*(x: FilterOptions): JsonNode = - result = newJObject() - if x.fromBlock.isSome: - result["fromBlock"] = %x.fromBlock.unsafeGet - if x.toBlock.isSome: - result["toBlock"] = %x.toBlock.unsafeGet - result["address"] = %x.address - if x.blockHash.isSome: - result["blockHash"] = %x.blockHash.unsafeGet - result["topics"] = %x.topics - -func `%`*(x: TxOrHash): JsonNode = - case x.kind - of tohHash: %x.hash - of tohTx: %x.tx +{.pop.} diff --git a/web3/engine_api.nim b/web3/engine_api.nim index c3b4499..0fc37cc 100644 --- a/web3/engine_api.nim +++ b/web3/engine_api.nim @@ -8,9 +8,9 @@ # those terms. import - strutils, - json_serialization/std/[sets, net], serialization/errors, - json_rpc/[client, jsonmarshal], + json_serialization/std/[options], + serialization/errors, + json_rpc/client, ./conversions, ./engine_api_types, ./execution_types @@ -20,10 +20,37 @@ export conversions, execution_types -from os import DirSep, AltSep -template sourceDir: string = currentSourcePath.rsplit({DirSep, AltSep}, 1)[0] +createRpcSigsFromNim(RpcClient): + # https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/paris.md#methods + # https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#methods + # https://github.com/ethereum/execution-apis/blob/ee3df5bc38f28ef35385cefc9d9ca18d5e502778/src/engine/cancun.md#methods -createRpcSigs(RpcClient, sourceDir & "/engine_api_callsigs.nim") + proc engine_newPayloadV1(payload: ExecutionPayloadV1): PayloadStatusV1 + proc engine_newPayloadV2(payload: ExecutionPayloadV2): PayloadStatusV1 + proc engine_newPayloadV3(payload: ExecutionPayloadV3, expectedBlobVersionedHashes: seq[VersionedHash], parentBeaconBlockRoot: FixedBytes[32]): PayloadStatusV1 + proc engine_forkchoiceUpdatedV1(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributesV1]): ForkchoiceUpdatedResponse + proc engine_forkchoiceUpdatedV2(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributesV2]): ForkchoiceUpdatedResponse + proc engine_forkchoiceUpdatedV3(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributesV3]): ForkchoiceUpdatedResponse + proc engine_getPayloadV1(payloadId: PayloadID): ExecutionPayloadV1 + proc engine_getPayloadV2(payloadId: PayloadID): GetPayloadV2Response + proc engine_getPayloadV2_exact(payloadId: PayloadID): GetPayloadV2ResponseExact + proc engine_getPayloadV3(payloadId: PayloadID): GetPayloadV3Response + proc engine_exchangeTransitionConfigurationV1(transitionConfiguration: TransitionConfigurationV1): TransitionConfigurationV1 + proc engine_getPayloadBodiesByHashV1(hashes: seq[BlockHash]): seq[Option[ExecutionPayloadBodyV1]] + proc engine_getPayloadBodiesByRangeV1(start: Quantity, count: Quantity): seq[Option[ExecutionPayloadBodyV1]] + + # https://github.com/ethereum/execution-apis/blob/9301c0697e4c7566f0929147112f6d91f65180f6/src/engine/common.md + proc engine_exchangeCapabilities(methods: seq[string]): seq[string] + + # convenience apis + proc engine_newPayloadV1(payload: ExecutionPayload): PayloadStatusV1 + proc engine_newPayloadV2(payload: ExecutionPayload): PayloadStatusV1 + proc engine_newPayloadV2(payload: ExecutionPayloadV1OrV2): PayloadStatusV1 + proc engine_newPayloadV3(payload: ExecutionPayload, + expectedBlobVersionedHashes: Option[seq[VersionedHash]], + parentBeaconBlockRoot: Option[FixedBytes[32]]): PayloadStatusV1 + proc engine_forkchoiceUpdatedV2(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributes]): ForkchoiceUpdatedResponse + proc engine_forkchoiceUpdatedV3(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributes]): ForkchoiceUpdatedResponse template forkchoiceUpdated*( rpcClient: RpcClient, diff --git a/web3/engine_api_callsigs.nim b/web3/engine_api_callsigs.nim deleted file mode 100644 index 0e6988e..0000000 --- a/web3/engine_api_callsigs.nim +++ /dev/null @@ -1,41 +0,0 @@ -# nim-web3 -# Copyright (c) 2022-2023 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. - -# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/paris.md#methods -# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#methods -# https://github.com/ethereum/execution-apis/blob/ee3df5bc38f28ef35385cefc9d9ca18d5e502778/src/engine/cancun.md#methods - -import execution_types, engine_api_types - -proc engine_newPayloadV1(payload: ExecutionPayloadV1): PayloadStatusV1 -proc engine_newPayloadV2(payload: ExecutionPayloadV2): PayloadStatusV1 -proc engine_newPayloadV3(payload: ExecutionPayloadV3, expectedBlobVersionedHashes: seq[VersionedHash], parentBeaconBlockRoot: FixedBytes[32]): PayloadStatusV1 -proc engine_forkchoiceUpdatedV1(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributesV1]): ForkchoiceUpdatedResponse -proc engine_forkchoiceUpdatedV2(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributesV2]): ForkchoiceUpdatedResponse -proc engine_forkchoiceUpdatedV3(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributesV3]): ForkchoiceUpdatedResponse -proc engine_getPayloadV1(payloadId: PayloadID): ExecutionPayloadV1 -proc engine_getPayloadV2(payloadId: PayloadID): GetPayloadV2Response -proc engine_getPayloadV2_exact(payloadId: PayloadID): GetPayloadV2ResponseExact -proc engine_getPayloadV3(payloadId: PayloadID): GetPayloadV3Response -proc engine_exchangeTransitionConfigurationV1(transitionConfiguration: TransitionConfigurationV1): TransitionConfigurationV1 -proc engine_getPayloadBodiesByHashV1(hashes: seq[BlockHash]): seq[Option[ExecutionPayloadBodyV1]] -proc engine_getPayloadBodiesByRangeV1(start: Quantity, count: Quantity): seq[Option[ExecutionPayloadBodyV1]] - -# https://github.com/ethereum/execution-apis/blob/9301c0697e4c7566f0929147112f6d91f65180f6/src/engine/common.md -proc engine_exchangeCapabilities(methods: seq[string]): seq[string] - -# convenience apis -proc engine_newPayloadV1(payload: ExecutionPayload): PayloadStatusV1 -proc engine_newPayloadV2(payload: ExecutionPayload): PayloadStatusV1 -proc engine_newPayloadV2(payload: ExecutionPayloadV1OrV2): PayloadStatusV1 -proc engine_newPayloadV3(payload: ExecutionPayload, - expectedBlobVersionedHashes: Option[seq[VersionedHash]], - parentBeaconBlockRoot: Option[FixedBytes[32]]): PayloadStatusV1 -proc engine_forkchoiceUpdatedV2(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributes]): ForkchoiceUpdatedResponse -proc engine_forkchoiceUpdatedV3(forkchoiceState: ForkchoiceStateV1, payloadAttributes: Option[PayloadAttributes]): ForkchoiceUpdatedResponse diff --git a/web3/eth_api.nim b/web3/eth_api.nim index 7478486..e6eb739 100644 --- a/web3/eth_api.nim +++ b/web3/eth_api.nim @@ -9,8 +9,8 @@ # according to those terms. import - strutils, - json_serialization/std/[sets, net], + std/[json, options], + json_serialization/std/[options], json_rpc/[client, jsonmarshal], stint, ./conversions, @@ -20,7 +20,67 @@ export eth_api_types, conversions -from os import DirSep, AltSep -template sourceDir: string = currentSourcePath.rsplit({DirSep, AltSep}, 1)[0] +createRpcSigsFromNim(RpcClient): + proc web3_clientVersion(): string + proc web3_sha3(data: seq[byte]): Hash256 + proc net_version(): string + proc net_peerCount(): Quantity + proc net_listening(): bool + proc eth_protocolVersion(): string + proc eth_syncing(): JsonNode + proc eth_coinbase(): Address + proc eth_mining(): bool + proc eth_hashrate(): Quantity + proc eth_gasPrice(): Quantity + proc eth_accounts(): seq[Address] + proc eth_blockNumber(): Quantity + proc eth_getBalance(data: Address, blockId: BlockIdentifier): UInt256 + proc eth_getStorageAt(data: Address, slot: UInt256, blockId: BlockIdentifier): UInt256 + proc eth_getTransactionCount(data: Address, blockId: BlockIdentifier): Quantity + proc eth_getBlockTransactionCountByHash(data: BlockHash): Quantity + proc eth_getBlockTransactionCountByNumber(blockId: BlockIdentifier): Quantity + proc eth_getUncleCountByBlockHash(data: BlockHash): Quantity + proc eth_getUncleCountByBlockNumber(blockId: BlockIdentifier): Quantity + proc eth_getCode(data: Address, blockId: BlockIdentifier): seq[byte] + proc eth_sign(address: Address, data: seq[byte]): seq[byte] + proc eth_signTransaction(data: EthSend): seq[byte] + proc eth_sendTransaction(obj: EthSend): TxHash + proc eth_sendRawTransaction(data: seq[byte]): TxHash + proc eth_call(call: EthCall, blockId: BlockIdentifier): seq[byte] + proc eth_estimateGas(call: EthCall, blockId: BlockIdentifier): Quantity + proc eth_createAccessList(call: EthCall, blockId: BlockIdentifier): AccessListResult + proc eth_getBlockByHash(data: BlockHash, fullTransactions: bool): BlockObject + proc eth_getBlockByNumber(blockId: BlockIdentifier, fullTransactions: bool): BlockObject + proc eth_getTransactionByHash(data: TxHash): TransactionObject + proc eth_getTransactionByBlockHashAndIndex(data: Hash256, quantity: Quantity): TransactionObject + proc eth_getTransactionByBlockNumberAndIndex(blockId: BlockIdentifier, quantity: Quantity): TransactionObject + proc eth_getTransactionReceipt(data: TxHash): ReceiptObject + proc eth_getUncleByBlockHashAndIndex(data: Hash256, quantity: Quantity): BlockObject + proc eth_getUncleByBlockNumberAndIndex(blockId: BlockIdentifier, quantity: Quantity): BlockObject + proc eth_getCompilers(): seq[string] + proc eth_compileLLL(): seq[byte] + proc eth_compileSolidity(): seq[byte] + proc eth_compileSerpent(): seq[byte] + proc eth_newFilter(filterOptions: FilterOptions): string + proc eth_newBlockFilter(): string + proc eth_newPendingTransactionFilter(): string + proc eth_uninstallFilter(filterId: string): bool + proc eth_getFilterChanges(filterId: string): JsonNode + proc eth_getFilterLogs(filterId: string): JsonNode + proc eth_getLogs(filterOptions: FilterOptions): seq[LogObject] + proc eth_chainId(): Quantity -createRpcSigs(RpcClient, sourceDir & "/eth_api_callsigs.nim") + proc eth_getWork(): seq[UInt256] + proc eth_submitWork(nonce: int64, powHash: Hash256, mixDigest: Hash256): bool + proc eth_submitHashrate(hashRate: UInt256, id: UInt256): bool + proc eth_subscribe(name: string, options: FilterOptions): string + proc eth_subscribe(name: string): string + proc eth_unsubscribe(id: string) + + proc eth_getProof( + address: Address, + slots: seq[UInt256], + blockId: BlockIdentifier): ProofResponse + +createSingleRpcSig(RpcClient, "eth_getJsonLogs"): + proc eth_getLogs(filterOptions: FilterOptions): JsonNode diff --git a/web3/eth_api_callsigs.nim b/web3/eth_api_callsigs.nim deleted file mode 100644 index 43867d4..0000000 --- a/web3/eth_api_callsigs.nim +++ /dev/null @@ -1,76 +0,0 @@ -# nim-web3 -# Copyright (c) 2019-2023 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. - -## This module contains signatures for the Ethereum client RPCs. -## The signatures are not imported directly, but read and processed with parseStmt, -## then a procedure body is generated to marshal native Nim parameters to json and visa versa. -import json, options, stint, eth_api_types - -proc web3_clientVersion(): string -proc web3_sha3(data: seq[byte]): Hash256 -proc net_version(): string -proc net_peerCount(): Quantity -proc net_listening(): bool -proc eth_protocolVersion(): string -proc eth_syncing(): JsonNode -proc eth_coinbase(): Address -proc eth_mining(): bool -proc eth_hashrate(): Quantity -proc eth_gasPrice(): Quantity -proc eth_accounts(): seq[Address] -proc eth_blockNumber(): Quantity -proc eth_getBalance(data: Address, blockId: BlockIdentifier): UInt256 -proc eth_getStorageAt(data: Address, slot: UInt256, blockId: BlockIdentifier): UInt256 -proc eth_getTransactionCount(data: Address, blockId: BlockIdentifier): Quantity -proc eth_getBlockTransactionCountByHash(data: BlockHash): Quantity -proc eth_getBlockTransactionCountByNumber(blockId: BlockIdentifier): Quantity -proc eth_getUncleCountByBlockHash(data: BlockHash): Quantity -proc eth_getUncleCountByBlockNumber(blockId: BlockIdentifier): Quantity -proc eth_getCode(data: Address, blockId: BlockIdentifier): seq[byte] -proc eth_sign(address: Address, data: seq[byte]): seq[byte] -proc eth_signTransaction(data: EthSend): seq[byte] -proc eth_sendTransaction(obj: EthSend): TxHash -proc eth_sendRawTransaction(data: seq[byte]): TxHash -proc eth_call(call: EthCall, blockId: BlockIdentifier): seq[byte] -proc eth_estimateGas(call: EthCall, blockId: BlockIdentifier): Quantity -proc eth_createAccessList(call: EthCall, blockId: BlockIdentifier): AccessListResult -proc eth_getBlockByHash(data: BlockHash, fullTransactions: bool): BlockObject -proc eth_getBlockByNumber(blockId: BlockIdentifier, fullTransactions: bool): BlockObject -proc eth_getTransactionByHash(data: TxHash): TransactionObject -proc eth_getTransactionByBlockHashAndIndex(data: Hash256, quantity: Quantity): TransactionObject -proc eth_getTransactionByBlockNumberAndIndex(blockId: BlockIdentifier, quantity: Quantity): TransactionObject -proc eth_getTransactionReceipt(data: TxHash): ReceiptObject -proc eth_getUncleByBlockHashAndIndex(data: Hash256, quantity: Quantity): BlockObject -proc eth_getUncleByBlockNumberAndIndex(blockId: BlockIdentifier, quantity: Quantity): BlockObject -proc eth_getCompilers(): seq[string] -proc eth_compileLLL(): seq[byte] -proc eth_compileSolidity(): seq[byte] -proc eth_compileSerpent(): seq[byte] -proc eth_newFilter(filterOptions: FilterOptions): string -proc eth_newBlockFilter(): string -proc eth_newPendingTransactionFilter(): string -proc eth_uninstallFilter(filterId: string): bool -proc eth_getFilterChanges(filterId: string): JsonNode -proc eth_getFilterLogs(filterId: string): JsonNode -proc eth_getLogs(filterOptions: FilterOptions): seq[LogObject] -proc eth_getLogs(filterOptions: JsonNode): JsonNode -proc eth_chainId(): Quantity - -proc eth_getWork(): seq[UInt256] -proc eth_submitWork(nonce: int64, powHash: Hash256, mixDigest: Hash256): bool -proc eth_submitHashrate(hashRate: UInt256, id: UInt256): bool -proc eth_subscribe(name: string, options: JsonNode): string -proc eth_subscribe(name: string): string -proc eth_unsubscribe(id: string) - -proc eth_getProof( - address: Address, - slots: seq[UInt256], - blockId: BlockIdentifier): ProofResponse - diff --git a/web3/eth_api_types.nim b/web3/eth_api_types.nim index f898d11..6bb0e48 100644 --- a/web3/eth_api_types.nim +++ b/web3/eth_api_types.nim @@ -171,23 +171,28 @@ type blobGasUsed*: Option[Quantity] # uint64 blobGasPrice*: Option[UInt256] # UInt256 - FilterDataKind* = enum fkItem, fkList - FilterData* = object - # Difficult to process variant objects in input data, as kind is immutable. - # TODO: This might need more work to handle "or" options - kind*: FilterDataKind - items*: seq[FilterData] - item*: UInt256 - # TODO: I don't think this will work as input, need only one value that is either UInt256 or seq[UInt256] - Topic* = FixedBytes[32] + SingleOrListKind* = enum + slkNull + slkSingle + slkList + + SingleOrList*[T] = object + case kind*: SingleOrListKind + of slkSingle: single*: T + of slkList: list*: seq[T] + of slkNull: discard + + TopicOrList* = SingleOrList[Topic] + AddressOrList* = SingleOrList[Address] + FilterOptions* = object fromBlock*: Option[RtBlockIdentifier] # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. toBlock*: Option[RtBlockIdentifier] # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. # TODO: address as optional list of address or optional address - address*: seq[Address] # (optional) contract address or a list of addresses from which logs should originate. - topics*: seq[Option[seq[Topic]]] # (optional) list of DATA topics. Topics are order-dependent. Each topic can also be a list of DATA with "or" options. + address*: AddressOrList # (optional) contract address or a list of addresses from which logs should originate. + topics*: seq[TopicOrList] # (optional) list of DATA topics. Topics are order-dependent. Each topic can also be a list of DATA with "or" options. blockHash*: Option[BlockHash] # (optional) hash of the block. If its present, fromBlock and toBlock, should be none. Introduced in EIP234 LogObject* = object diff --git a/web3/ethhexstrings.nim b/web3/ethhexstrings.nim deleted file mode 100644 index c2a11d6..0000000 --- a/web3/ethhexstrings.nim +++ /dev/null @@ -1,104 +0,0 @@ -# nim-web3 -# Copyright (c) 2019-2023 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. - -import - std/[json, strutils] - -from json_rpc/jsonmarshal import expect - -export json - -type - HexQuantityStr* = distinct string - HexDataStr* = distinct string - -# Hex validation - -template stripLeadingZeros(value: string): string = - var cidx = 0 - # ignore the last character so we retain '0' on zero value - while cidx < value.len - 1 and value[cidx] == '0': - cidx.inc - value[cidx .. ^1] - -func encodeQuantity*(value: SomeUnsignedInt): string = - var hValue = value.toHex.stripLeadingZeros - result = "0x" & hValue.toLowerAscii - -func hasHexHeader*(value: string): bool = - value.len >= 2 and value[0] == '0' and value[1] in {'x', 'X'} - -template hasHexHeader*(value: HexDataStr|HexQuantityStr): bool = - value.string.hasHexHeader - -func isHexChar*(c: char): bool = - if c notin {'0'..'9'} and - c notin {'a'..'f'} and - c notin {'A'..'F'}: false - else: true - -func validate*(value: HexQuantityStr): bool = - template strVal: untyped = value.string - if not value.hasHexHeader: - return false - # No leading zeros (but allow 0x0) - if strVal.len < 3 or (strVal.len > 3 and strVal[2] == '0'): return false - for i in 2..