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.
This commit is contained in:
Etan Kissling 2024-03-17 00:04:38 +01:00 committed by GitHub
parent 428c46c94f
commit 80c7aa6de2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 120 additions and 75 deletions

View File

@ -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()

View File

@ -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.}:

View File

@ -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

View File

@ -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.}:

View File

@ -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:

View File

@ -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.} =

View File

@ -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

View File

@ -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)