Skip to content

Commit

Permalink
make BlockNumber distinct (#137)
Browse files Browse the repository at this point in the history
The `writeValue` added for `BlockNumber` in #136 interferes with other
`uint64` because `BlockNumber` is not `distinct`. Marking it `distinct`
avoids polluting global serialization logic.
  • Loading branch information
etan-status authored Mar 16, 2024
1 parent 428c46c commit 80c7aa6
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 78 deletions.
21 changes: 13 additions & 8 deletions tests/test_contracts.nim
Original file line number Diff line number Diff line change
@@ -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))
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_deposit_contract.nim
Original file line number Diff line number Diff line change
@@ -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))
Expand Down Expand Up @@ -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.}:
Expand Down
77 changes: 44 additions & 33 deletions tests/test_json_marshalling.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand Down Expand Up @@ -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)

check:
JrpcConv.decode(validQuantityJson, Quantity) == validQuantity
JrpcConv.encode(validQuantity) == validQuantityJson
resQuantity == validQuantity
resUInt256 == validQuantity.distinctBase.u256
resUInt256Ref[] == validQuantity.distinctBase.u256

test "Invalid Quantity/UInt256/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(validJson, typeName) == validValue
JrpcConv.encode(validValue) == validJson
res == validValue
resUInt256 == validValue.distinctBase.u256
resUInt256Ref[] == validValue.distinctBase.u256

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:
Expand All @@ -132,6 +137,7 @@ suite "JSON-RPC Quantity":
check: false

checkInvalids(Quantity)
checkInvalids(BlockNumber)
checkInvalids(UInt256)
checkInvalids(ref UInt256)

Expand Down Expand Up @@ -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
Expand All @@ -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\""

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\""
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\"", 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
Expand Down
4 changes: 2 additions & 2 deletions tests/test_logs.nim
Original file line number Diff line number Diff line change
@@ -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))
Expand Down Expand Up @@ -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.}:
Expand Down
50 changes: 32 additions & 18 deletions web3.nim
Original file line number Diff line number Diff line change
@@ -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))
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -354,29 +354,31 @@ 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)
cc.to = some(contractAddress)
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)
Expand Down Expand Up @@ -436,17 +438,29 @@ 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)
let h = await sendData(sender.web3, sender.contractAddress, sender.defaultAccount, data, sender.value, sender.gas, sender.gasPrice, sender.chainId)
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:
Expand Down
17 changes: 12 additions & 5 deletions web3/conversions.nim
Original file line number Diff line number Diff line change
@@ -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))
Expand Down Expand Up @@ -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 "\""
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.} =
Expand Down
Loading

0 comments on commit 80c7aa6

Please sign in to comment.