diff --git a/.gitignore b/.gitignore index 8c9e871..75ad594 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ node_modules nohup.out hardhat.config.js package-lock.json +test_null_conversion diff --git a/README.md b/README.md index a81f7e6..239a05d 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,25 @@ The humble beginnings of a Nim library similar to web3.[js|py] ## Installation You can install the developement version of the library through nimble with the following command + ``` nimble install https://github.com/status-im/nim-web3@#master ``` +## Development + +You should first run `./simulator.sh` which runs `ganache-cli` + +This creates a local simulated Ethereum network on your local machine and the tests will use this for their E2E processing + ## License Licensed and distributed under either of -* MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT +- MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT or -* Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0) +- Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0) at your option. This file may not be copied, modified, or distributed except according to those terms. diff --git a/simulator.sh b/simulator.sh new file mode 100755 index 0000000..32332ec --- /dev/null +++ b/simulator.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# NOTE: Requires nodemon (https://github.com/remy/nodemon) +# npm i -g nodemon + +# Watch all nim files for changes +# When a file change is detected we will restart ganache-cli +# This ensures that our deposit contracts have enough ETH as +# it seems like some of the tests do not properly initialise +# their contracts at this time. (state persists across runs) + +nodemon --ext '.nim' --watch tests --watch web3 --exec "ganache-cli" diff --git a/tests/all_tests.nim b/tests/all_tests.nim index f31c88f..4185832 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -10,6 +10,7 @@ {. warning[UnusedImport]:off .} import + test_null_conversion, test_primitives, test_contracts, test_deposit_contract, diff --git a/tests/test_deposit_contract.nim b/tests/test_deposit_contract.nim index 0d25336..56be541 100644 --- a/tests/test_deposit_contract.nim +++ b/tests/test_deposit_contract.nim @@ -69,4 +69,8 @@ suite "Deposit contract": echo "hash_tree_root: ", await ns.get_deposit_root().call() await web3.close() - waitFor test() + try: + waitFor test() + except CatchableError as err: + echo "Failed to process deposit contract", err.msg + fail() diff --git a/tests/test_null_conversion.nim b/tests/test_null_conversion.nim new file mode 100644 index 0000000..94857f1 --- /dev/null +++ b/tests/test_null_conversion.nim @@ -0,0 +1,109 @@ +import + std/[json, strutils], + 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) + +template should_not_error(input: string, value: untyped): void = + fromJson(%input, "", value) + +suite "Null conversion": + var resAddress: Address + var resDynamicBytes: DynamicBytes[32] + var resFixedBytes: FixedBytes[5] + var resQuantity: Quantity + var resRlpEncodedBytes: RlpEncodedBytes + var resTypedTransaction: TypedTransaction + var resUInt256: UInt256 + 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 + test "passing null values to normal convertors": + should_be_value_error("null", resAddress) + should_be_value_error("null", resDynamicBytes) + should_be_value_error("null", resFixedBytes) + should_be_value_error("null", resQuantity) + should_be_value_error("null", resRlpEncodedBytes) + should_be_value_error("null", resTypedTransaction) + should_be_value_error("null", resUInt256) + should_be_value_error("null", resUInt256Ref) + + test "passing empty values to normal convertors": + should_be_value_error("", resAddress) + should_be_value_error("", resDynamicBytes) + should_be_value_error("", resFixedBytes) + should_be_value_error("", resQuantity) + should_be_value_error("", resRlpEncodedBytes) + should_be_value_error("", resTypedTransaction) + should_be_value_error("", resUInt256) + 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) + + test "passing hex (0x) values to normal convertors": + 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) + + ## Covering the web3/engine_api_types + ## + ## NOTE: These will be transformed by the fromJson imported from + ## nim-json-rpc/json_rpc/jsonmarshal + test "passing nully values to specific convertors": + + let payloadAttributesV1 = """{ "timestamp": {item}, "prevRandao": {item}, "suggestedFeeRecipient": {item} }""" + let forkchoiceStateV1 = """{ "status": {item}, "safeBlockHash": {item}, "finalizedBlockHash": {item} }""" + let forkchoiceUpdatedResponse = """{ "payloadStatus": {item}, "payloadId": {item} }""" + let transitionConfigurationV1 = """{ "terminalTotalDifficulty": {item}, "terminalBlockHash": {item}, "terminalBlockNumber": {item} }""" + + var resPayloadAttributesV1: PayloadAttributesV1 + var resForkchoiceStateV1: ForkchoiceStateV1 + var resForkchoiceUpdatedResponse: ForkchoiceUpdatedResponse + var resTransitionConfigurationV1: TransitionConfigurationV1 + + for item in @["null", "\"\"", "\"0x\"", "\"0x_\"", ""]: + template format(str: string): string = + str.replace("{item}", item) + + should_be_value_error(payloadAttributesV1.format(), resPayloadAttributesV1) + should_be_value_error(forkchoiceStateV1.format(), resForkchoiceStateV1) + 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": + + var resPayloadStatusV1: PayloadStatusV1 + + for status_type in PayloadExecutionStatus: + let payloadStatusV1 = """{ + "status": "status_name", + "latestValidHash": null, + "validationError": null + }""".replace("status_name", $status_type) + + should_be_value_error(payloadStatusV1, resPayloadStatusV1) diff --git a/tests/test_signed_tx.nim b/tests/test_signed_tx.nim index dbc1a0b..14b610b 100644 --- a/tests/test_signed_tx.nim +++ b/tests/test_signed_tx.nim @@ -105,4 +105,8 @@ suite "Signed transactions": assert(n == 5.u256) await web3.close() - waitFor test() + try: + waitFor test() + except CatchableError as err: + echo "Failed to send signed tx", err.msg + fail() diff --git a/web3.nim b/web3.nim index 80e0bd2..67e5056 100644 --- a/web3.nim +++ b/web3.nim @@ -8,15 +8,14 @@ # those terms. import - std/[options, math, json, tables, uri, strformat] + std/[options, json, tables, uri, strformat] from os import DirSep, AltSep from eth/common/eth_types import ChainId import - stint, httputils, chronicles, chronos, nimcrypto/keccak, + stint, httputils, chronos, json_rpc/[rpcclient, jsonmarshal], stew/byteutils, eth/keys, - chronos/apps/http/httpclient, web3/[eth_api_types, conversions, ethhexstrings, transaction_signing, encoding, contract_dsl] diff --git a/web3.nimble b/web3.nimble index 61524ba..719bdec 100644 --- a/web3.nimble +++ b/web3.nimble @@ -7,12 +7,11 @@ # This file may not be copied, modified, or distributed except according to # those terms. -mode = ScriptMode.Verbose - -version = "0.2.2" -author = "Status Research & Development GmbH" -description = "This is the humble begginings of library similar to web3.[js|py]" -license = "MIT or Apache License 2.0" +mode = ScriptMode.Verbose +version = "0.2.4" +author = "Status Research & Development GmbH" +description = "This is the humble begginings of library similar to web3.[js|py]" +license = "MIT or Apache License 2.0" ### Dependencies requires "nim >= 1.6.0" diff --git a/web3/contract_dsl.nim b/web3/contract_dsl.nim index 974bb5d..a64f1c0 100644 --- a/web3/contract_dsl.nim +++ b/web3/contract_dsl.nim @@ -175,12 +175,6 @@ proc parseContract(body: NimNode): seq[InterfaceObject] = macro contract*(cname: untyped, body: untyped): untyped = var objects = parseContract(body) result = newStmtList() - let - address = ident "address" - client = ident "client" - receipt = genSym(nskForVar) - receiver = ident "receiver" - eventListener = ident "eventListener" result.add quote do: type `cname`* = object diff --git a/web3/conversions.nim b/web3/conversions.nim index be2f97f..772d84f 100644 --- a/web3/conversions.nim +++ b/web3/conversions.nim @@ -34,6 +34,7 @@ template invalidQuantityPrefix(s: string): bool = func `%`*(n: Int256|UInt256): JsonNode = %("0x" & n.toHex) + # 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" @@ -43,7 +44,7 @@ func fromJson*(n: JsonNode, argName: string, result: var UInt256) = 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: Handle errors + 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) = @@ -55,13 +56,18 @@ func fromJson*(n: JsonNode, argName: string, result: var ref UInt256) = if hexStr.invalidQuantityPrefix: raise newException(ValueError, "Parameter \"" & argName & "\" value has invalid leading 0") new result - result[] = hexStr.parse(StUint[256], 16) # TODO: Handle errors + result[] = hexStr.parse(StUint[256], 16) # TODO Add error checking func bytesFromJson(n: JsonNode, argName: string, result: var openArray[byte]) = n.kind.expect(JString, argName) let hexStr = n.getStr() + + if not ("0x" in hexStr): + raise newException(ValueError, "Parameter \"" & argName & "\" is not a hexadecimal string") + if hexStr.len != result.len * 2 + 2: # including "0x" raise newException(ValueError, "Parameter \"" & argName & "\" value wrong length: " & $hexStr.len) + hexToByteArray(hexStr, result) func fromJson*[N](n: JsonNode, argName: string, result: var FixedBytes[N]) diff --git a/web3/encoding.nim b/web3/encoding.nim index 7c41ba4..0ddde01 100644 --- a/web3/encoding.nim +++ b/web3/encoding.nim @@ -178,6 +178,5 @@ func encode*(x: tuple): seq[byte] = inc i # Obsolete -from stew/byteutils import hexToSeqByte func decode*(input: string, offset: int, to: var DynamicBytes): int {.inline, deprecated: "Use decode(openarray[byte], ...) instead".} = decode(hexToSeqByte(input), 0, offset div 2, to) * 2 diff --git a/web3/engine_api_types.nim b/web3/engine_api_types.nim index b54d3e1..0c70c05 100644 --- a/web3/engine_api_types.nim +++ b/web3/engine_api_types.nim @@ -172,10 +172,10 @@ type # https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/paris.md#payloadstatusv1 PayloadExecutionStatus* {.pure.} = enum - syncing = "SYNCING" - valid = "VALID" - invalid = "INVALID" - accepted = "ACCEPTED" + syncing = "SYNCING" + valid = "VALID" + invalid = "INVALID" + accepted = "ACCEPTED" invalid_block_hash = "INVALID_BLOCK_HASH" PayloadStatusV1* = object diff --git a/web3/transaction_signing.nim b/web3/transaction_signing.nim index 07f8f6a..bb2fff5 100644 --- a/web3/transaction_signing.nim +++ b/web3/transaction_signing.nim @@ -9,7 +9,7 @@ import options, - eth_api_types, stew/byteutils, stint, + eth_api_types, stint, eth/[common, keys, rlp], eth/common/transaction func signTransaction(tr: var Transaction, pk: PrivateKey) =