From 80c7aa6de2a26c57fa1f06ad47f3ac6058e6545b Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Sun, 17 Mar 2024 00:04:38 +0100 Subject: [PATCH] make `BlockNumber` `distinct` (#137) The `writeValue` added for `BlockNumber` in #136 interferes with other `uint64` because `BlockNumber` is not `distinct`. Marking it `distinct` avoids polluting global serialization logic. --- tests/test_contracts.nim | 21 ++++++---- tests/test_deposit_contract.nim | 4 +- tests/test_json_marshalling.nim | 71 +++++++++++++++++++-------------- tests/test_logs.nim | 4 +- web3.nim | 50 ++++++++++++++--------- web3/conversions.nim | 17 +++++--- web3/eth_api_types.nim | 10 ++--- web3/primitives.nim | 18 ++++++--- 8 files changed, 120 insertions(+), 75 deletions(-) diff --git a/tests/test_contracts.nim b/tests/test_contracts.nim index d1052d2..307b262 100644 --- a/tests/test_contracts.nim +++ b/tests/test_contracts.nim @@ -1,5 +1,5 @@ # nim-web3 -# Copyright (c) 2018-2023 Status Research & Development GmbH +# Copyright (c) 2018-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -213,8 +213,9 @@ suite "Contracts": receipt = await web3.deployContract(MetaCoinCode) cc = receipt.contractAddress.get - let deployedAtBlock = distinctBase(receipt.blockNumber) - echo "Deployed MetaCoin contract: ", cc, " at block ", deployedAtBlock + let deployedAtBlock = receipt.blockNumber + echo "Deployed MetaCoin contract: ", cc, " at block ", + distinctBase(deployedAtBlock) let ns = web3.contractSender(MetaCoin, cc) @@ -225,7 +226,8 @@ suite "Contracts": fromAddr, toAddr: Address, value: UInt256) {.raises: [], gcsafe.}: try: - echo "onTransfer: ", fromAddr, " transferred ", value.toHex, " to ", toAddr + echo "onTransfer: ", fromAddr, " transferred ", value.toHex, + " to ", toAddr inc notificationsReceived assert(fromAddr == web3.defaultAccount) assert((notificationsReceived == 1 and value == 50.u256) or @@ -238,12 +240,15 @@ suite "Contracts": let balNow = await ns.getBalance(web3.defaultAccount).call() echo "getbalance (now): ", balNow.toHex - let balNew = await ns.getBalance(web3.defaultAccount).call(blockNumber = deployedAtBlock) + let balNew = await ns.getBalance(web3.defaultAccount).call( + blockNumber = deployedAtBlock) echo "getbalance (after creation): ", balNew.toHex - # Let's try to get the balance at a point in time where the contract was not deployed yet: + # Let's try to get the balance at a point in time where the contract + # was not deployed yet: try: - let balFirst = await ns.getBalance(web3.defaultAccount).call(blockNumber = 1'u64) + let balFirst = await ns.getBalance(web3.defaultAccount).call( + blockNumber = 1.BlockNumber) echo "getbalance (first block): ", balFirst.toHex except CatchableError as err: echo "getbalance (first block): ", err.msg @@ -261,7 +266,7 @@ suite "Contracts": echo "transfers: ", await ns.getJsonLogs( Transfer, fromBlock = some(blockId(deployedAtBlock)), - toBlock = some(blockId(1000'u64))) + toBlock = some(blockId(1000.BlockNumber))) await notifFut await s.unsubscribe() diff --git a/tests/test_deposit_contract.nim b/tests/test_deposit_contract.nim index fdf7c7d..abd2676 100644 --- a/tests/test_deposit_contract.nim +++ b/tests/test_deposit_contract.nim @@ -1,5 +1,5 @@ # nim-web3 -# Copyright (c) 2018-2023 Status Research & Development GmbH +# Copyright (c) 2018-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -43,7 +43,7 @@ suite "Deposit contract": var fut = newFuture[void]() - let options = FilterOptions(fromBlock: some(blockId(0))) + let options = FilterOptions(fromBlock: some(blockId(0.BlockNumber))) 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.}: diff --git a/tests/test_json_marshalling.nim b/tests/test_json_marshalling.nim index 20f2be9..d9c39a0 100644 --- a/tests/test_json_marshalling.nim +++ b/tests/test_json_marshalling.nim @@ -27,10 +27,10 @@ proc rand[M,N](_: type DynamicBytes[M,N]): DynamicBytes[M,N] = proc rand(_: type Address): Address = discard randomBytes(distinctBase result) -proc rand(_: type Quantity): Quantity = +proc rand[T: Quantity | BlockNumber](_: type T): T = var res: array[8, byte] discard randomBytes(res) - result = Quantity(uint64.fromBytesBE(res)) + result = T(uint64.fromBytesBE(res)) proc rand(_: type RlpEncodedBytes): RlpEncodedBytes = discard randomBytes(distinctBase result) @@ -57,7 +57,7 @@ proc rand(_: type UInt256): UInt256 = result = UInt256.fromBytesBE(x) proc rand(_: type RtBlockIdentifier): RtBlockIdentifier = - RtBlockIdentifier(kind: bidNumber, number: rand(Quantity).uint64) + RtBlockIdentifier(kind: bidNumber, number: rand(BlockNumber)) proc rand(_: type PayloadExecutionStatus): PayloadExecutionStatus = var x: array[1, byte] @@ -98,32 +98,37 @@ template checkRandomObject(T: type) = suite "JSON-RPC Quantity": test "Valid": - for (validQuantityStr, validQuantity) in [ - ("0x0", Quantity 0), - ("0x123", Quantity 291), - ("0x1234", Quantity 4660)]: - let validQuantityJson = JrpcConv.encode(validQuantityStr) - let resQuantity = JrpcConv.decode(validQuantityJson, Quantity) - let resUInt256 = JrpcConv.decode(validQuantityJson, UInt256) - let resUInt256Ref = JrpcConv.decode(validQuantityJson, ref UInt256) + template checkType(typeName: typedesc): untyped = + for (validStr, validValue) in [ + ("0x0", typeName 0), + ("0x123", typeName 291), + ("0x1234", typeName 4660)]: + let + validJson = JrpcConv.encode(validStr) + res = JrpcConv.decode(validJson, typeName) + resUInt256 = JrpcConv.decode(validJson, UInt256) + resUInt256Ref = JrpcConv.decode(validJson, ref UInt256) - check: - JrpcConv.decode(validQuantityJson, Quantity) == validQuantity - JrpcConv.encode(validQuantity) == validQuantityJson - resQuantity == validQuantity - resUInt256 == validQuantity.distinctBase.u256 - resUInt256Ref[] == validQuantity.distinctBase.u256 + check: + JrpcConv.decode(validJson, typeName) == validValue + JrpcConv.encode(validValue) == validJson + res == validValue + resUInt256 == validValue.distinctBase.u256 + resUInt256Ref[] == validValue.distinctBase.u256 - test "Invalid Quantity/UInt256/ref UInt256": + checkType(Quantity) + checkType(BlockNumber) + + test "Invalid Quantity/BlockNumber/UInt256/ref UInt256": # TODO once https://github.com/status-im/nimbus-eth2/pull/3850 addressed, # re-add "0x0400" test case as invalid. for invalidStr in [ "", "1234", "01234", "x1234", "0x", "ff"]: template checkInvalids(typeName: untyped) = - var resQuantity: `typeName` + var res: `typeName` try: let jsonBytes = JrpcConv.encode(invalidStr) - resQuantity = JrpcConv.decode(jsonBytes, `typeName`) + res = JrpcConv.decode(jsonBytes, `typeName`) echo `typeName`, " ", invalidStr check: false except SerializationError: @@ -132,6 +137,7 @@ suite "JSON-RPC Quantity": check: false checkInvalids(Quantity) + checkInvalids(BlockNumber) checkInvalids(UInt256) checkInvalids(ref UInt256) @@ -189,11 +195,11 @@ suite "JSON-RPC Quantity": checkRandomObject(GetPayloadResponse) test "check blockId": - let a = RtBlockIdentifier(kind: bidNumber, number: 77.uint64) + let a = RtBlockIdentifier(kind: bidNumber, number: 77.BlockNumber) let x = JrpcConv.encode(a) let c = JrpcConv.decode(x, RtBlockIdentifier) check c.kind == bidNumber - check c.number == 77 + check c.number == 77.BlockNumber let d = JrpcConv.decode("\"10\"", RtBlockIdentifier) check d.kind == bidAlias @@ -210,15 +216,20 @@ suite "JSON-RPC Quantity": check c.kind == slkNull test "quantity parser and writer": - let a = JrpcConv.decode("\"0x016345785d8a0000\"", Quantity) - check a.uint64 == 100_000_000_000_000_000'u64 - let b = JrpcConv.encode(a) - check b == "\"0x16345785d8a0000\"" + template checkType(typeName: typedesc): untyped = + block: + let a = JrpcConv.decode("\"0x016345785d8a0000\"", typeName) + check a.uint64 == 100_000_000_000_000_000'u64 + let b = JrpcConv.encode(a) + check b == "\"0x16345785d8a0000\"" - let x = JrpcConv.decode("\"0xFFFF_FFFF_FFFF_FFFF\"", Quantity) - check x.uint64 == 0xFFFF_FFFF_FFFF_FFFF_FFFF'u64 - let y = JrpcConv.encode(x) - check y == "\"0xffffffffffffffff\"" + let x = JrpcConv.decode("\"0xFFFF_FFFF_FFFF_FFFF\"", typeName) + check x.uint64 == 0xFFFF_FFFF_FFFF_FFFF_FFFF'u64 + let y = JrpcConv.encode(x) + check y == "\"0xffffffffffffffff\"" + + checkType(Quantity) + checkType(BlockNumber) test "AccessListResult": var z: AccessListResult diff --git a/tests/test_logs.nim b/tests/test_logs.nim index deb693d..3718a26 100644 --- a/tests/test_logs.nim +++ b/tests/test_logs.nim @@ -1,5 +1,5 @@ # nim-web3 -# Copyright (c) 2018-2023 Status Research & Development GmbH +# Copyright (c) 2018-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -74,7 +74,7 @@ suite "Logs": let notifFut = newFuture[void]() var notificationsReceived = 0 - let options = FilterOptions(fromBlock: some(blockId(0))) + let options = FilterOptions(fromBlock: some(blockId(0.BlockNumber))) let s = await ns.subscribe(MyEvent, options) do ( sender: Address, value: UInt256) {.raises: [], gcsafe.}: diff --git a/web3.nim b/web3.nim index 3c07652..06dc341 100644 --- a/web3.nim +++ b/web3.nim @@ -1,5 +1,5 @@ # nim-web3 -# Copyright (c) 2019-2023 Status Research & Development GmbH +# Copyright (c) 2019-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -50,7 +50,7 @@ type gas*: uint64 gasPrice*: int chainId*: Option[ChainId] - blockNumber*: uint64 + blockNumber*: BlockNumber Sender*[T] = ContractInstance[T, Web3SenderImpl] AsyncSender*[T] = ContractInstance[T, Web3AsyncSenderImpl] @@ -354,13 +354,14 @@ proc send*[T](c: ContractInvocation[T, Web3SenderImpl], gasPrice = 0): Future[TxHash] = sendData(c.sender.web3, c.sender.contractAddress, c.sender.web3.defaultAccount, c.data, value, gas, gasPrice, some(chainId)) -proc callAux(web3: Web3, - contractAddress: Address, - defaultAccount: Address, - data: seq[byte], - value = 0.u256, - gas = 3000000'u64, - blockNumber = high(uint64)): Future[seq[byte]] {.async.} = +proc callAux( + web3: Web3, + contractAddress: Address, + defaultAccount: Address, + data: seq[byte], + value = 0.u256, + gas = 3000000'u64, + blockNumber = high(BlockNumber)): Future[seq[byte]] {.async.} = var cc: EthCall cc.data = some(data) cc.source = some(defaultAccount) @@ -368,15 +369,16 @@ proc callAux(web3: Web3, cc.gas = some(Quantity(gas)) cc.value = some(value) result = - if blockNumber != high(uint64): + if blockNumber != high(BlockNumber): await web3.provider.eth_call(cc, blockId(blockNumber)) else: await web3.provider.eth_call(cc, "latest") -proc call*[T](c: ContractInvocation[T, Web3SenderImpl], - value = 0.u256, - gas = 3000000'u64, - blockNumber = high(uint64)): Future[T] {.async.} = +proc call*[T]( + c: ContractInvocation[T, Web3SenderImpl], + value = 0.u256, + gas = 3000000'u64, + blockNumber = high(BlockNumber)): Future[T] {.async.} = let response = await callAux(c.sender.web3, c.sender.contractAddress, c.sender.web3.defaultAccount, c.data, value, gas, blockNumber) if response.len > 0: discard decode(response, 0, 0, result) @@ -436,8 +438,15 @@ proc createMutableContractInvocation*(sender: Web3SenderImpl, ReturnType: typede proc createImmutableContractInvocation*(sender: Web3SenderImpl, ReturnType: typedesc, data: sink seq[byte]): ContractInvocation[ReturnType, Web3SenderImpl] {.inline.} = ContractInvocation[ReturnType, Web3SenderImpl](sender: sender, data: data) -proc contractInstance*(web3: Web3, T: typedesc, toAddress: Address): AsyncSender[T] = - AsyncSender[T](sender: Web3AsyncSenderImpl(web3: web3, contractAddress: toAddress, defaultAccount: web3.defaultAccount, gas: 3000000, blockNumber: uint64.high)) +proc contractInstance*( + web3: Web3, T: typedesc, toAddress: Address): AsyncSender[T] = + AsyncSender[T]( + sender: Web3AsyncSenderImpl( + web3: web3, + contractAddress: toAddress, + defaultAccount: web3.defaultAccount, + gas: 3000000, + blockNumber: BlockNumber.high)) proc createMutableContractInvocation*(sender: Web3AsyncSenderImpl, ReturnType: typedesc, data: sink seq[byte]) {.async.} = assert(sender.gas > 0) @@ -445,8 +454,13 @@ proc createMutableContractInvocation*(sender: Web3AsyncSenderImpl, ReturnType: t let receipt = await sender.web3.getMinedTransactionReceipt(h) discard receipt -proc createImmutableContractInvocation*(sender: Web3AsyncSenderImpl, ReturnType: typedesc, data: sink seq[byte]): Future[ReturnType] {.async.} = - let response = await callAux(sender.web3, sender.contractAddress, sender.defaultAccount, data, sender.value, sender.gas, sender.blockNumber) +proc createImmutableContractInvocation*( + sender: Web3AsyncSenderImpl, + ReturnType: typedesc, + data: sink seq[byte]): Future[ReturnType] {.async.} = + let response = await callAux( + sender.web3, sender.contractAddress, sender.defaultAccount, data, + sender.value, sender.gas, sender.blockNumber) if response.len > 0: discard decode(response, 0, 0, result) else: diff --git a/web3/conversions.nim b/web3/conversions.nim index 5a57794..2ca1161 100644 --- a/web3/conversions.nim +++ b/web3/conversions.nim @@ -1,5 +1,5 @@ # nim-web3 -# Copyright (c) 2019-2023 Status Research & Development GmbH +# Copyright (c) 2019-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -198,8 +198,9 @@ proc writeValue*[F: CommonJsonFlavors](w: var JsonWriter[F], v: RlpEncodedBytes) {.gcsafe, raises: [IOError].} = writeHexValue w, distinctBase(v) -proc writeValue*[F: CommonJsonFlavors](w: var JsonWriter[F], v: Quantity | BlockNumber) - {.gcsafe, raises: [IOError].} = +proc writeValue*[F: CommonJsonFlavors]( + w: var JsonWriter[F], v: Quantity | BlockNumber +) {.gcsafe, raises: [IOError].} = w.stream.write "\"0x" w.stream.toHex(distinctBase v) w.stream.write "\"" @@ -243,6 +244,11 @@ proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var Quantity) wrapValueError: val = Quantity fromHex[uint64](hexStr) +proc readValue*[F: CommonJsonFlavors]( + r: var JsonReader[F], + val: var BlockNumber) {.gcsafe, raises: [IOError, JsonReaderError].} = + r.readValue(distinctBase(val, recursive = false)) + proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var PayloadExecutionStatus) {.gcsafe, raises: [IOError, JsonReaderError].} = const enumStrings = static: getEnumStringTable(PayloadExecutionStatus) @@ -300,7 +306,8 @@ proc readValue*(r: var JsonReader[JrpcConv], val: var RtBlockIdentifier) let hexStr = r.parseString() wrapValueError: if valid(hexStr): - val = RtBlockIdentifier(kind: bidNumber, number: fromHex[uint64](hexStr)) + val = RtBlockIdentifier( + kind: bidNumber, number: BlockNumber fromHex[uint64](hexStr)) else: val = RtBlockIdentifier(kind: bidAlias, alias: hexStr) @@ -385,7 +392,7 @@ proc writeValue*(w: var JsonWriter[JrpcConv], v: Opt[seq[ReceiptObject]]) else: w.writeValue JsonString("null") -func `$`*(v: Quantity): string {.inline.} = +func `$`*(v: Quantity | BlockNumber): string {.inline.} = encodeQuantity(v.uint64) func `$`*(v: TypedTransaction): string {.inline.} = diff --git a/web3/eth_api_types.nim b/web3/eth_api_types.nim index 1b2cdf5..8377331 100644 --- a/web3/eth_api_types.nim +++ b/web3/eth_api_types.nim @@ -86,7 +86,7 @@ type ## A block object, or null when no block was found BlockObject* = ref object - number*: Quantity # the block number. null when its pending block. + number*: BlockNumber # the block number. null when its pending block. hash*: Hash256 # hash of the block. null when its pending block. parentHash*: Hash256 # hash of the parent block. sha3Uncles*: Hash256 # SHA3 of the uncles data in the block. @@ -136,7 +136,7 @@ type hash*: TxHash # hash of the transaction. nonce*: Quantity # TODO: Is int? the number of transactions made by the sender prior to this one. blockHash*: Option[BlockHash] # hash of the block where this transaction was in. null when its pending. - blockNumber*: Option[Quantity] # block number where this transaction was in. null when its pending. + blockNumber*: Option[BlockNumber] # block number where this transaction was in. null when its pending. transactionIndex*: Option[Quantity] # integer of the transactions index position in the block. null when its pending. `from`*: Address # address of the sender. to*: Option[Address] # address of the receiver. null when its a contract creation transaction. @@ -160,7 +160,7 @@ type transactionHash*: TxHash # hash of the transaction. transactionIndex*: Quantity # integer of the transactions index position in the block. blockHash*: BlockHash # hash of the block where this transaction was in. - blockNumber*: Quantity # block number where this transaction was in. + blockNumber*: BlockNumber # block number where this transaction was in. `from`*: Address # address of the sender. to*: Option[Address] # address of the receiver. null when its a contract creation transaction. cumulativeGasUsed*: Quantity # the total amount of gas used when this transaction was executed in the block. @@ -205,7 +205,7 @@ type transactionIndex*: Option[Quantity] # integer of the transactions index position log was created from. null when its pending log. transactionHash*: Option[TxHash] # hash of the transactions this log was created from. null when its pending log. blockHash*: Option[BlockHash] # hash of the block where this log was in. null when its pending. null when its pending log. - blockNumber*: Option[Quantity] # the block number where this log was in. null when its pending. null when its pending log. + blockNumber*: Option[BlockNumber] # the block number where this log was in. null when its pending. null when its pending log. address*: Address # address from which this log originated. data*: seq[byte] # contains one or more 32 Bytes non-indexed arguments of the log. topics*: seq[Topic] # array of 0 to 4 32 Bytes DATA of indexed log arguments. @@ -228,7 +228,7 @@ type storageHash*: StorageHash storageProof*: seq[StorageProof] - BlockIdentifier* = string|BlockNumber|RtBlockIdentifier + BlockIdentifier* = string | BlockNumber | RtBlockIdentifier BlockIdentifierKind* = enum bidNumber diff --git a/web3/primitives.nim b/web3/primitives.nim index 0e9d560..c017399 100644 --- a/web3/primitives.nim +++ b/web3/primitives.nim @@ -1,5 +1,5 @@ # nim-web3 -# Copyright (c) 2023 Status Research & Development GmbH +# Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) @@ -25,8 +25,8 @@ type TxHash* = FixedBytes[32] Hash256* = FixedBytes[32] BlockHash* = FixedBytes[32] - BlockNumber* = uint64 Quantity* = distinct uint64 + BlockNumber* = distinct Quantity CodeHash* = FixedBytes[32] StorageHash* = FixedBytes[32] @@ -37,15 +37,23 @@ type template `==`*[N](a, b: FixedBytes[N]): bool = distinctBase(a) == distinctBase(b) -template `==`*(a, b: Quantity): bool = - distinctBase(a) == distinctBase(b) - template `==`*[minLen, maxLen](a, b: DynamicBytes[minLen, maxLen]): bool = distinctBase(a) == distinctBase(b) func `==`*(a, b: Address): bool {.inline.} = distinctBase(a) == distinctBase(b) +template ethQuantity(typ: type) {.dirty.} = + func `+`*(a: typ, b: distinctBase(typ)): typ {.borrow.} + func `-`*(a: typ, b: distinctBase(typ)): typ {.borrow.} + + func `<`*(a, b: typ): bool {.borrow.} + func `<=`*(a, b: typ): bool {.borrow.} + func `==`*(a, b: typ): bool {.borrow.} + +ethQuantity Quantity +ethQuantity BlockNumber + func hash*[N](bytes: FixedBytes[N]): Hash = hash(distinctBase bytes)