v0.2.0: Revamp data types

This commit is contained in:
jangko 2023-12-06 19:59:24 +07:00
parent 428b931e7c
commit dcabb8f29e
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
34 changed files with 1534 additions and 690 deletions

2
.gitignore vendored
View File

@ -1,4 +1,5 @@
nimcache/
vendor/
# Executables shall be put in an ignored build/ directory
# Ignore dynamic, static libs and libtool archive files
@ -9,6 +10,7 @@ build/
*.la
*.exe
*.dll
nimble.paths
node_modules
nohup.out

View File

@ -3,7 +3,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
![Github action](https://github.com/status-im/nim-web3/workflows/nim-web3%20CI/badge.svg)
![Github action](https://github.com/status-im/nim-web3/workflows/CI/badge.svg)
The humble beginnings of a Nim library similar to web3.[js|py]

View File

@ -6,7 +6,7 @@ touch hardhat.config.js
nohup npx hardhat node &
nimble install -y --depsOnly
# Wait until ganache responds
# Wait until hardhat responds
while ! curl -X POST --data '{"jsonrpc":"2.0","method":"net_version","params":[],"id":67}' localhost:8545 2>/dev/null
do
sleep 1

4
config.nims Normal file
View File

@ -0,0 +1,4 @@
# begin Nimble config (version 1)
when fileExists("nimble.paths"):
include "nimble.paths"
# end Nimble config

View File

@ -1,3 +1,12 @@
# 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.
# nim.cfg
@if nimHasWarningObservableStores:
warning[ObservableStores]: off

View File

@ -1,16 +1,20 @@
# web3
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
# * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) 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.
# 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.
{. warning[UnusedImport]:off .}
import
test,
test_primitives,
test_contracts,
test_deposit_contract,
test_ethhexstrings,
test_logs,
test_json_marshalling,
test_signed_tx
test_signed_tx,
test_execution_types

47
tests/helpers/utils.nim Normal file
View File

@ -0,0 +1,47 @@
import
std/options,
chronos, stint,
stew/byteutils,
../../web3,
../../web3/primitives
proc deployContract*(web3: Web3, code: string, gasPrice = 0): Future[ReceiptObject] {.async.} =
let provider = web3.provider
let accounts = await provider.eth_accounts()
var code = code
var tr: EthSend
tr.`from` = web3.defaultAccount
tr.data = hexToSeqByte(code)
tr.gas = Quantity(3000000).some
if gasPrice != 0:
tr.gasPrice = some(gasPrice.Quantity)
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)

View File

@ -1,3 +1,12 @@
# nim-web3
# Copyright (c) 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.
# Avoid some rare stack corruption while using exceptions with a SEH-enabled
# toolchain: https://github.com/status-im/nimbus-eth2/issues/3121
@if windows and not vcc:

View File

@ -1,8 +1,18 @@
import pkg/unittest2
import ../web3
import chronos, options, json, stint
import test_utils
# 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
std/[options, json],
pkg/unittest2,
chronos, stint,
../web3,
./helpers/utils
contract(EncodingTest):
proc setBool(val: Bool)
@ -80,6 +90,12 @@ contract(MetaCoin):
const MetaCoinCode = "608060405234801561001057600080fd5b5032600090815260208190526040902061271090556101c2806100346000396000f30060806040526004361061004b5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166390b98a118114610050578063f8b2cb4f14610095575b600080fd5b34801561005c57600080fd5b5061008173ffffffffffffffffffffffffffffffffffffffff600435166024356100d5565b604080519115158252519081900360200190f35b3480156100a157600080fd5b506100c373ffffffffffffffffffffffffffffffffffffffff6004351661016e565b60408051918252519081900360200190f35b336000908152602081905260408120548211156100f457506000610168565b336000818152602081815260408083208054879003905573ffffffffffffffffffffffffffffffffffffffff871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060015b92915050565b73ffffffffffffffffffffffffffffffffffffffff16600090815260208190526040902054905600a165627a7a72305820000313ec0ebbff4ffefbe79d615d0ab019d8566100c40eb95a4eee617a87d1090029"
proc `$`(list: seq[Address]): string =
result.add '['
for x in list:
result.add $x
result.add ", "
result.add ']'
suite "Contracts":
setup:

View File

@ -1,8 +1,19 @@
import pkg/unittest2
import ../web3
import chronos, options, json, stint
import test_utils
import ./depositcontract
# 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
std/[options, json],
pkg/unittest2,
chronos, stint,
../web3,
./helpers/utils,
./helpers/depositcontract
contract(DepositContract):
proc deposit(pubkey: DynamicBytes[0, 48], withdrawalCredentials: DynamicBytes[0, 32], signature: DynamicBytes[0, 96], deposit_data_root: FixedBytes[32])

View File

@ -1,5 +1,14 @@
# 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
unittest, json,
unittest2, json,
../web3/ethhexstrings
suite "Hex quantity":

View File

@ -0,0 +1,137 @@
# 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
std/typetraits,
pkg/unittest2,
stew/byteutils,
../web3/execution_types,
./helpers/utils
suite "Execution types tests":
let
wd = WithdrawalV1(
index: 1.Quantity,
validatorIndex: 2.Quantity,
address: address(3),
amount: 4.Quantity,
)
payload = ExecutionPayload(
parentHash: h256(1),
feeRecipient: address(2),
stateRoot: h256(3),
receiptsRoot: h256(4),
logsBloom: FixedBytes[256].conv(5),
prevRandao: h256(6),
blockNumber: 7.Quantity,
gasLimit: 8.Quantity,
gasUsed: 9.Quantity,
timestamp: 10.Quantity,
extraData: DynamicBytes[0, 32].conv(11),
baseFeePerGas: 12.u256,
blockHash: h256(13),
transactions: @[TypedTransaction.conv(14)],
withdrawals: some(@[wd]),
blobGasUsed: some(15.Quantity),
excessBlobGas: some(16.Quantity),
)
attr = PayloadAttributes(
timestamp: 1.Quantity,
prevRandao: h256(2),
suggestedFeeRecipient: address(3),
withdrawals: some(@[wd]),
parentBeaconBlockRoot: some(h256(4)),
)
blobs = BlobsBundleV1(
commitments: @[KZGCommitment.conv(1)],
proofs: @[KZGProof.conv(2)],
blobs: @[Blob.conv(3)],
)
response = GetPayloadResponse(
executionPayload: payload,
blockValue: some(1.u256),
blobsBundle: some(blobs),
shouldOverrideBuilder: some(false),
)
test "payload version":
var badv31 = payload
badv31.excessBlobGas = none(Quantity)
var badv32 = payload
badv32.blobGasUsed = none(Quantity)
var v2 = payload
v2.excessBlobGas = none(Quantity)
v2.blobGasUsed = none(Quantity)
var v1 = v2
v1.withdrawals = none(seq[WithdrawalV1])
check badv31.version == Version.V2
check badv32.version == Version.V2
check v2.version == Version.V2
check v1.version == Version.V1
check payload.version == Version.V3
test "attr version":
var v2 = attr
v2.parentBeaconBlockRoot = none(Hash256)
var v1 = v2
v1.withdrawals = none(seq[WithdrawalV1])
check attr.version == Version.V3
check v2.version == Version.V2
check v1.version == Version.V1
test "response version":
var badv31 = response
badv31.blobsBundle = none(BlobsBundleV1)
var badv32 = response
badv32.shouldOverrideBuilder = none(bool)
var v2 = response
v2.blobsBundle = none(BlobsBundleV1)
v2.shouldOverrideBuilder = none(bool)
var v1 = v2
v1.blockValue = none(UInt256)
check badv31.version == Version.V2
check badv32.version == Version.V2
check v2.version == Version.V2
check v1.version == Version.V1
check response.version == Version.V3
test "ExecutionPayload roundtrip":
let v3 = payload.V3
check v3 == v3.executionPayload.V3
let v2 = payload.V2
check v2 == v2.executionPayload.V2
let v1 = payload.V1
check v1 == v1.executionPayload.V1
test "PayloadAttributes roundtrip":
let v3 = attr.V3
check v3 == v3.payloadAttributes.V3
let v2 = attr.V2
check v2 == v2.payloadAttributes.V2
let v1 = attr.V1
check v1 == v1.payloadAttributes.V1
test "GetPayloadResponse roundtrip":
let v3 = response.V3
check v3 == v3.getPayloadResponse.V3
let v2 = response.V2
check v2 == v2.getPayloadResponse.V2
let v1 = response.V1
check v1 == v1.getPayloadResponse.V1

View File

@ -1,8 +1,18 @@
# 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
std/typetraits,
unittest, std/json, json_rpc/jsonmarshal, json_serialization,
std/[typetraits, json],
stint,
../web3/[conversions, ethtypes]
unittest2,
json_rpc/jsonmarshal, json_serialization,
../web3/[conversions, eth_api_types]
proc `==`(x, y: Quantity): bool {.borrow, noSideEffect.}

View File

@ -1,9 +1,18 @@
import pkg/unittest2
import ../web3
import chronos, options, json, stint
import test_utils
# 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 random
import
std/[options, json, random],
pkg/unittest2,
../web3,
chronos, stint,
./helpers/utils
#[ Contract LoggerContract
pragma solidity >=0.4.25 <0.6.0;

75
tests/test_primitives.nim Normal file
View File

@ -0,0 +1,75 @@
# 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
std/typetraits,
pkg/unittest2,
stew/byteutils,
../web3/primitives,
./helpers/utils
suite "Primitives":
const
addr1 = address(1)
txhash1 = txhash(1)
blob1 = blob(1)
addr2 = address(2)
txhash2 = txhash(2)
blob2 = blob(2)
test "Comparators":
check addr1 == addr1
check addr1 != addr2
check txhash1 == txhash1
check txhash1 != txhash2
check blob1 == blob1
check blob1 != blob2
test "toHex":
check addr1.toHex == "0000000000000000000000000000000000000001"
check addr2.toHex == "0000000000000000000000000000000000000002"
check txhash1.toHex == "0000000000000000000000000000000000000000000000000000000000000001"
check txhash2.toHex == "0000000000000000000000000000000000000000000000000000000000000002"
check blob1.toHex == "01"
check blob2.toHex == "02"
test "fromHex":
let
addr3 = Address.fromHex("0000000000000000000000000000000000000123")
txhash3 = TxHash.fromHex("0000000000000000000000000000000000000000000000000000000000000456")
blob3 = BlobData.fromHex("7890")
check addr3.toHex == "0000000000000000000000000000000000000123"
check txhash3.toHex == "0000000000000000000000000000000000000000000000000000000000000456"
check blob3.toHex == "7890"
test "to bytes":
let
ab2 = addr2.bytes
tb2 = txhash2.bytes
bb2 = blob2.bytes
check ab2.toHex == "0000000000000000000000000000000000000002"
check tb2.toHex == "0000000000000000000000000000000000000000000000000000000000000002"
check bb2.toHex == "02"
test "len":
check addr1.len == 20
check txhash1.len == 32
check blob1.len == 1
test "dollar":
check $addr1 == "0x0000000000000000000000000000000000000001"
check $txhash1 == "0x0000000000000000000000000000000000000000000000000000000000000001"
check $blob1 == "0x01"

View File

@ -1,7 +1,18 @@
import pkg/unittest2
import ../web3
import chronos, options, json, stint, eth/keys
import test_utils
# 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
std/[options, json],
pkg/unittest2,
chronos, stint, eth/keys,
../web3,
./helpers/utils
#[ Contract NumberStorage
pragma solidity ^0.4.18;
@ -39,10 +50,10 @@ suite "Signed transactions":
let acc = Address(toCanonicalAddress(pk.toPublicKey()))
var tx: EthSend
tx.source = accounts[0]
tx.`from` = accounts[0]
tx.value = some(ethToWei(10.u256))
tx.to = some(acc)
tx.gasPrice = some(gasPrice)
tx.gasPrice = some(gasPrice.Quantity)
# Send 10 eth to acc
discard await web3.send(tx)

View File

@ -1,21 +0,0 @@
import ../web3, chronos, options, stint
proc deployContract*(web3: Web3, code: string, gasPrice = 0): Future[ReceiptObject] {.async.} =
let provider = web3.provider
let accounts = await provider.eth_accounts()
var code = code
if code[1] notin {'x', 'X'}:
code = "0x" & code
var tr: EthSend
tr.source = web3.defaultAccount
tr.data = code
tr.gas = Quantity(3000000).some
if gasPrice != 0:
tr.gasPrice = some(gasPrice)
let r = await web3.send(tr)
return await web3.getMinedTransactionReceipt(r)
func ethToWei*(eth: UInt256): UInt256 =
eth * 1000000000000000000.u256

View File

@ -1,5 +1,14 @@
# 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/[macros, strutils, options, math, json, tables, uri, strformat]
std/[macros, strutils, options, json, tables, uri, strformat]
from os import DirSep, AltSep
@ -7,15 +16,15 @@ import
stint, httputils, chronicles, chronos, nimcrypto/keccak,
json_rpc/[rpcclient, jsonmarshal], stew/byteutils, eth/keys,
chronos/apps/http/httpclient,
web3/[ethtypes, conversions, ethhexstrings, transaction_signing, encoding]
web3/[eth_api_types, conversions, ethhexstrings, transaction_signing, encoding]
template sourceDir: string = currentSourcePath.rsplit({DirSep, AltSep}, 1)[0]
## Generate client convenience marshalling wrappers from forward declarations
createRpcSigs(RpcClient, sourceDir & "/web3/ethcallsigs.nim")
createRpcSigs(RpcClient, sourceDir & "/web3/eth_api_callsigs.nim")
export UInt256, Int256, Uint128, Int128
export ethtypes, conversions, encoding, HttpClientFlag, HttpClientFlags
export eth_api_types, conversions, encoding, HttpClientFlag, HttpClientFlags
type
Web3* = ref object
@ -23,7 +32,7 @@ type
subscriptions*: Table[string, Subscription]
defaultAccount*: Address
privateKey*: Option[PrivateKey]
lastKnownNonce*: Option[Nonce]
lastKnownNonce*: Option[Quantity]
onDisconnect*: proc() {.gcsafe, raises: [].}
Sender*[T] = ref object
@ -48,7 +57,7 @@ type
ContractCallBase = ref object of RootObj
web3: Web3
data: string
data: seq[byte]
to: Address
value: UInt256
@ -194,7 +203,10 @@ template typeSignature(T: typedesc): string =
unknownType(T)
proc initContractCall[T](web3: Web3, data: string, to: Address): ContractCall[T] {.inline.} =
ContractCall[T](web3: web3, data: data, to: to)
try:
ContractCall[T](web3: web3, data: hexToSeqByte(data), to: to)
except ValueError as ex:
raise newException(AssertionDefect, ex.msg)
type
InterfaceObjectKind = enum
@ -545,7 +557,7 @@ proc getJsonLogs*(s: Sender,
options["topics"] = topics
if blockHash.isSome:
doAssert fromBlock.isNone and toBlock.isNone
options["blockhash"] = %blockHash.unsafeGet
options["blockHash"] = %blockHash.unsafeGet
else:
if fromBlock.isSome:
options["fromBlock"] = %fromBlock.unsafeGet
@ -554,13 +566,13 @@ proc getJsonLogs*(s: Sender,
s.web3.provider.eth_getLogs(options)
proc nextNonce*(web3: Web3): Future[Nonce] {.async.} =
proc nextNonce*(web3: Web3): Future[Quantity] {.async.} =
if web3.lastKnownNonce.isSome:
inc web3.lastKnownNonce.get
return web3.lastKnownNonce.get
else:
let fromAddress = web3.privateKey.get().toPublicKey().toCanonicalAddress.Address
result = int(await web3.provider.eth_getTransactionCount(fromAddress, "latest"))
result = await web3.provider.eth_getTransactionCount(fromAddress, "latest")
web3.lastKnownNonce = some result
proc send*(web3: Web3, c: EthSend): Future[TxHash] {.async.} =
@ -568,7 +580,7 @@ proc send*(web3: Web3, c: EthSend): Future[TxHash] {.async.} =
var cc = c
if cc.nonce.isNone:
cc.nonce = some(await web3.nextNonce())
let t = "0x" & encodeTransaction(cc, web3.privateKey.get())
let t = encodeTransaction(cc, web3.privateKey.get())
return await web3.provider.eth_sendRawTransaction(t)
else:
return await web3.provider.eth_sendTransaction(c)
@ -579,19 +591,20 @@ proc send*(c: ContractCallBase,
gasPrice = 0): Future[TxHash] {.async.} =
let
web3 = c.web3
gasPrice = if web3.privateKey.isSome() or gasPrice != 0: some(gasPrice)
else: none(int)
gasPrice = if web3.privateKey.isSome() or gasPrice != 0: some(gasPrice.Quantity)
else: none(Quantity)
nonce = if web3.privateKey.isSome(): some(await web3.nextNonce())
else: none(Nonce)
else: none(Quantity)
cc = EthSend(
data: "0x" & c.data,
source: web3.defaultAccount,
data: c.data,
`from`: web3.defaultAccount,
to: some(c.to),
gas: some(Quantity(gas)),
value: some(value),
nonce: nonce,
gasPrice: gasPrice)
gasPrice: gasPrice,
)
return await web3.send(cc)
@ -600,12 +613,12 @@ proc call*[T](c: ContractCall[T],
gas = 3000000'u64,
blockNumber = high(uint64)): Future[T] {.async.} =
var cc: EthCall
cc.data = some("0x" & c.data)
cc.data = some(c.data)
cc.source = some(c.web3.defaultAccount)
cc.to = c.to
cc.to = some(c.to)
cc.gas = some(Quantity(gas))
cc.value = some(value)
let response = strip0xPrefix:
let response =
if blockNumber != high(uint64):
await c.web3.provider.eth_call(cc, &"0x{blockNumber:X}")
else:
@ -613,7 +626,7 @@ proc call*[T](c: ContractCall[T],
if response.len > 0:
var res: T
discard decode(response, 0, res)
discard decode(response.toHex, 0, res)
return res
else:
raise newException(CatchableError, "No response from the Web3 provider")
@ -622,12 +635,12 @@ proc getMinedTransactionReceipt*(web3: Web3, tx: TxHash): Future[ReceiptObject]
## Returns the receipt for the transaction. Waits for it to be mined if necessary.
# TODO: Potentially more optimal solution is to subscribe and wait for appropriate
# notification. Now we're just polling every 500ms which should be ok for most cases.
var r: Option[ReceiptObject]
while r.isNone:
var r: ReceiptObject
while r.isNil:
r = await web3.provider.eth_getTransactionReceipt(tx)
if r.isNone:
if r.isNil:
await sleepAsync(500.milliseconds)
result = r.get
result = r
proc exec*[T](c: ContractCall[T], value = 0.u256, gas = 3000000'u64): Future[T] {.async.} =
let h = await c.send(value, gas)

View File

@ -1,6 +1,15 @@
# 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.
mode = ScriptMode.Verbose
version = "0.0.1"
version = "0.2.0"
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"

View File

@ -1,18 +1,27 @@
# nim-web3
# Copyright (c) 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.
{.push raises: [].}
import
ethtypes
./primitives
func parseCmdArg*(T: type Address, input: TaintedString): T
func parseCmdArg*(T: type Address, input: string): T
{.raises: [ValueError].} =
fromHex(T, string input)
func completeCmdArg*(T: type Address, input: TaintedString): seq[string] =
func completeCmdArg*(T: type Address, input: string): seq[string] =
@[]
func parseCmdArg*(T: type BlockHash, input: TaintedString): T
func parseCmdArg*(T: type BlockHash, input: string): T
{.raises: [ValueError].} =
fromHex(T, string input)
func completeCmdArg*(T: type BlockHash, input: TaintedString): seq[string] =
func completeCmdArg*(T: type BlockHash, input: string): seq[string] =
@[]

View File

@ -1,16 +1,19 @@
# Copyright (c) 2019-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# 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, options, strutils, strformat, tables, typetraits],
stint, stew/byteutils, json_serialization, faststreams/textio,
ethtypes, ethhexstrings,
./eth_api_types, ./ethhexstrings,
./engine_api_types
from json_rpc/jsonmarshal import expect
from json_rpc/jsonmarshal import expect, fromJson
template invalidQuantityPrefix(s: string): bool =
# https://ethereum.org/en/developers/docs/apis/json-rpc/#hex-value-encoding
@ -129,6 +132,37 @@ func fromJson*(
raise newException(
ValueError, "Parameter \"" & argName & "\" value invalid: " & n.getStr)
func fromJson*(n: JsonNode, argName: string, result: var RtBlockIdentifier) =
n.kind.expect(JString, argName)
let hexStr = n.getStr()
if validate(hexStr.HexQuantityStr):
result = RtBlockIdentifier(kind: bidNumber, number: fromHex[uint64](hexStr))
else:
result = RtBlockIdentifier(kind: bidAlias, alias: hexStr)
func fromJson*(n: JsonNode, argName: string, result: var TxOrHash) =
if n.kind == JString:
var hash: TxHash
fromJson(n, argName, hash)
result = TxOrHash(kind: tohHash, hash: hash)
else:
var tx: TransactionObject
fromJson(n, argName, tx)
result = TxOrHash(kind: tohTx, tx: tx)
func fromJson*(n: JsonNode, argName: string, result: var EthSend) =
n.kind.expect(JObject, argName)
for k, v in n:
case k
of "from": fromJson(v, argName, result.`from`)
of "to": fromJson(v, argName, result.to)
of "gas": fromJson(v, argName, result.gas)
of "gasPrice": fromJson(v, argName, result.gasPrice)
of "value": fromJson(v, argName, result.value)
of "data": fromJson(v, argName, result.data)
of "nonce": fromJson(v, argName, result.nonce)
else: discard
func `%`*(v: Quantity): JsonNode =
%encodeQuantity(v.uint64)
@ -147,6 +181,9 @@ func `%`*(v: TypedTransaction): JsonNode =
func `%`*(v: RlpEncodedBytes): JsonNode =
%("0x" & distinctBase(v).toHex)
func `%`*(v: openArray[byte]): JsonNode =
%("0x" & v.toHex)
proc writeHexValue(w: JsonWriter, v: openArray[byte]) =
w.stream.write "\"0x"
w.stream.writeHex v
@ -197,30 +234,21 @@ proc readValue*(r: var JsonReader, T: type Quantity): T =
func `$`*(v: Quantity): string {.inline.} =
encodeQuantity(v.uint64)
func `$`*[N](v: FixedBytes[N]): string {.inline.} =
"0x" & array[N, byte](v).toHex
func `$`*(v: Address): string {.inline.} =
"0x" & array[20, byte](v).toHex
func `$`*(v: TypedTransaction): string {.inline.} =
"0x" & distinctBase(v).toHex
func `$`*(v: RlpEncodedBytes): string {.inline.} =
"0x" & distinctBase(v).toHex
func `$`*(v: DynamicBytes): string {.inline.} =
"0x" & toHex(v)
func `%`*(x: EthSend): JsonNode =
result = newJObject()
result["from"] = %x.source
result["from"] = %x.`from`
if x.to.isSome:
result["to"] = %x.to.unsafeGet
if x.gas.isSome:
result["gas"] = %x.gas.unsafeGet
if x.gasPrice.isSome:
result["gasPrice"] = %Quantity(x.gasPrice.unsafeGet)
result["gasPrice"] = %x.gasPrice.unsafeGet
if x.value.isSome:
result["value"] = %x.value.unsafeGet
if x.data.len > 0:
@ -245,18 +273,23 @@ func `%`*(x: EthCall): JsonNode =
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
if x.address.isSome:
result["address"] = %x.address.unsafeGet
if x.topics.isSome:
result["topics"] = %x.topics.unsafeGet
result["address"] = %x.address
if x.blockHash.isSome:
result["blockHash"] = %x.blockHash.unsafeGet
result["topics"] = %x.topics
func `%`*(x: RtBlockIdentifier): JsonNode =
func `%`*(x: TxOrHash): JsonNode =
case x.kind
of bidNumber: %(&"0x{x.number:X}")
of bidAlias: %x.alias
of tohHash: %x.hash
of tohTx: %x.tx

View File

@ -1,8 +1,17 @@
# nim-web3
# Copyright (c) 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/[typetraits, strutils, macros, math]
import
stint, stew/byteutils, ./ethtypes
stint, stew/byteutils, ./eth_api_types
type
EncodeResult* = tuple[dynamic: bool, data: string]

View File

@ -1,11 +1,24 @@
# 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.
import
strutils,
json_serialization/std/[sets, net], serialization/errors,
json_rpc/[client, jsonmarshal],
conversions, engine_api_types
./conversions,
./engine_api_types,
./execution_types
export
engine_api_types, conversions
engine_api_types,
conversions,
execution_types
from os import DirSep, AltSep
template sourceDir: string = currentSourcePath.rsplit({DirSep, AltSep}, 1)[0]

View File

@ -1,8 +1,17 @@
# 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 ethtypes, engine_api_types
import execution_types, engine_api_types
proc engine_newPayloadV1(payload: ExecutionPayloadV1): PayloadStatusV1
proc engine_newPayloadV2(payload: ExecutionPayloadV2): PayloadStatusV1
@ -20,3 +29,13 @@ proc engine_getPayloadBodiesByRangeV1(start: Quantity, count: Quantity): seq[Opt
# 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

View File

@ -1,12 +1,134 @@
# 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.
import
std/options,
std/[options, typetraits],
stint,
ethtypes
primitives
export
options, stint, ethtypes
options, stint, primitives
const
# https://github.com/ethereum/execution-apis/blob/c4089414bbbe975bbc4bf1ccf0a3d31f76feb3e1/src/engine/cancun.md#blobsbundlev1
fieldElementsPerBlob = 4096
type
TypedTransaction* = distinct seq[byte]
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#withdrawalv1
WithdrawalV1* = object
index*: Quantity
validatorIndex*: Quantity
address*: Address
amount*: Quantity
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/paris.md#executionpayloadv1
ExecutionPayloadV1* = object
parentHash*: Hash256
feeRecipient*: Address
stateRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: Hash256
transactions*: seq[TypedTransaction]
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#executionpayloadv2
ExecutionPayloadV2* = object
parentHash*: Hash256
feeRecipient*: Address
stateRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: Hash256
transactions*: seq[TypedTransaction]
withdrawals*: seq[WithdrawalV1]
# This is ugly, but I don't think the RPC library will handle
# ExecutionPayloadV1 | ExecutionPayloadV2. (Am I wrong?)
# Note that the spec currently says that various V2 methods
# (e.g. engine_newPayloadV2) need to accept *either* V1 or V2
# of the data structure (e.g. either ExecutionPayloadV1 or
# ExecutionPayloadV2); it's not like V2 of the method only
# needs to accept V2 of the structure. Anyway, the best way
# I've found to handle this is to make this structure with an
# Option for the withdrawals field. If you've got a better idea,
# please fix this. (Maybe the RPC library does handle sum types?
# Or maybe we can enhance it to do so?) --Adam
#
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md
ExecutionPayloadV1OrV2* = object
parentHash*: BlockHash
feeRecipient*: Address
stateRoot*: BlockHash
receiptsRoot*: BlockHash
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: BlockHash
transactions*: seq[TypedTransaction]
withdrawals*: Option[seq[WithdrawalV1]]
# https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#executionpayloadv3
ExecutionPayloadV3* = object
parentHash*: Hash256
feeRecipient*: Address
stateRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: Hash256
transactions*: seq[TypedTransaction]
withdrawals*: seq[WithdrawalV1]
blobGasUsed*: Quantity
excessBlobGas*: Quantity
SomeExecutionPayload* =
ExecutionPayloadV1 |
ExecutionPayloadV2 |
ExecutionPayloadV3
KZGCommitment* = FixedBytes[48]
KZGProof* = FixedBytes[48]
Blob* = FixedBytes[fieldElementsPerBlob * 32]
# https://github.com/ethereum/execution-apis/blob/ee3df5bc38f28ef35385cefc9d9ca18d5e502778/src/engine/cancun.md#blobsbundlev1
BlobsBundleV1* = object
commitments*: seq[KZGCommitment]
proofs*: seq[KZGProof]
blobs*: seq[Blob]
# https://github.com/ethereum/execution-apis/blob/d03c193dc317538e2a1a098030c21bacc2fd1333/src/engine/shanghai.md#executionpayloadbodyv1
# For optional withdrawals field, see:
# https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_getpayloadbodiesbyhashv1
@ -43,6 +165,11 @@ type
suggestedFeeRecipient*: Address
withdrawals*: Option[seq[WithdrawalV1]]
SomePayloadAttributes* =
PayloadAttributesV1 |
PayloadAttributesV2 |
PayloadAttributesV3
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/paris.md#payloadstatusv1
PayloadExecutionStatus* {.pure.} = enum
syncing = "SYNCING"
@ -109,3 +236,8 @@ const
engineApiInvalidPayloadAttributes* = -38003
engineApiTooLargeRequest* = -38004
engineApiUnsupportedFork* = -38005
{.push raises: [].}
template `==`*(a, b: TypedTransaction): bool =
distinctBase(a) == distinctBase(b)

26
web3/eth_api.nim Normal file
View File

@ -0,0 +1,26 @@
# nim-web3
# Copyright (c) 2019-2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
import
strutils,
json_serialization/std/[sets, net],
json_rpc/[client, jsonmarshal],
stint,
./conversions,
./eth_api_types
export
eth_api_types,
conversions
from os import DirSep, AltSep
template sourceDir: string = currentSourcePath.rsplit({DirSep, AltSep}, 1)[0]
createRpcSigs(RpcClient, sourceDir & "/eth_api_callsigs.nim")

View File

@ -1,43 +1,53 @@
# 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, ethtypes
import json, options, stint, eth_api_types
proc web3_clientVersion(): string
proc web3_sha3(data: string): string
proc web3_sha3(data: seq[byte]): Hash256
proc net_version(): string
proc net_peerCount(): int
proc net_peerCount(): Quantity
proc net_listening(): bool
proc eth_protocolVersion(): string
proc eth_syncing(): JsonNode
proc eth_coinbase(): string
proc eth_coinbase(): Address
proc eth_mining(): bool
proc eth_hashrate(): int
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, quantity: int, blockId: BlockIdentifier): seq[byte]
proc eth_getStorageAt(data: Address, slot: UInt256, blockId: BlockIdentifier): UInt256
proc eth_getTransactionCount(data: Address, blockId: BlockIdentifier): Quantity
proc eth_getBlockTransactionCountByHash(data: BlockHash)
proc eth_getBlockTransactionCountByNumber(blockId: BlockIdentifier)
proc eth_getUncleCountByBlockHash(data: BlockHash)
proc eth_getUncleCountByBlockNumber(blockId: BlockIdentifier)
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: string): 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: string): TxHash
proc eth_call(call: EthCall, blockId: BlockIdentifier): string #UInt256
proc eth_estimateGas(call: EthCall, blockId: BlockIdentifier): UInt256
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: UInt256, quantity: int): TransactionObject
proc eth_getTransactionByBlockNumberAndIndex(blockId: BlockIdentifier, quantity: int): TransactionObject
proc eth_getTransactionReceipt(data: TxHash): Option[ReceiptObject]
proc eth_getUncleByBlockHashAndIndex(data: UInt256, quantity: int64): BlockObject
proc eth_getUncleByBlockNumberAndIndex(blockId: BlockIdentifier, quantity: int64): BlockObject
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]
@ -53,8 +63,8 @@ proc eth_getLogs(filterOptions: JsonNode): JsonNode
proc eth_chainId(): Quantity
proc eth_getWork(): seq[UInt256]
proc eth_submitWork(nonce: int64, powHash: Uint256, mixDigest: Uint256): bool
proc eth_submitHashrate(hashRate: UInt256, id: Uint256): bool
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)

250
web3/eth_api_types.nim Normal file
View File

@ -0,0 +1,250 @@
# 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
stint,
./primitives
export
primitives
type
SyncObject* = object
startingBlock*: Quantity
currentBlock*: Quantity
highestBlock*: Quantity
HistoricExtraData* = DynamicBytes[0, 4096]
## In the current specs, the maximum is 32, but historically this value was
## used as Clique metadata which is dynamic in lenght and exceeds 32 bytes.
## Since we still need to support syncing old blocks, we use this more relaxed
## setting. Downstream libraries that want to enforce the up-to-date limit are
## expected to do this on their own.
EthSend* = object
`from`*: Address # the address the transaction is sent from.
to*: Option[Address] # (optional when creating new contract) the address the transaction is directed to.
gas*: Option[Quantity] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
gasPrice*: Option[Quantity] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
value*: Option[UInt256] # (optional) integer of the value sent with this transaction.
data*: seq[byte] # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters.
# For details see Ethereum Contract ABI.
nonce*: Option[Quantity] # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
# TODO: Both `EthSend` and `EthCall` are super outdated, according to new spec
# those should be merged into one type `GenericTransaction` with a lot more fields
# see: https://github.com/ethereum/execution-apis/blob/main/src/schemas/transaction.yaml#L244
EthCall* = object
source*: Option[Address] # (optional) The address the transaction is sent from.
to*: Option[Address] # The address the transaction is directed to.
gas*: Option[Quantity] # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
gasPrice*: Option[Quantity] # (optional) Integer of the gasPrice used for each paid gas.
value*: Option[UInt256] # (optional) Integer of the value sent with this transaction.
data*: Option[seq[byte]] # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI.
maxFeePerGas*: Option[Quantity] # (optional) MaxFeePerGas is the maximum fee per gas offered, in wei.
maxPriorityFeePerGas*: Option[Quantity] # (optional) MaxPriorityFeePerGas is the maximum miner tip per gas offered, in wei.
## A block header object
BlockHeader* = ref object
number*: Quantity
hash*: Hash256
parentHash*: Hash256
sha3Uncles*: Hash256
logsBloom*: FixedBytes[256]
transactionsRoot*: Hash256
stateRoot*: Hash256
receiptsRoot*: Hash256
miner*: Address
difficulty*: UInt256
extraData*: HistoricExtraData
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
nonce*: FixedBytes[8]
mixHash*: Hash256
baseFeePerGas*: Option[UInt256] # EIP-1559
withdrawalsRoot*: Option[Hash256] # EIP-4895
blobGasUsed*: Option[Quantity] # EIP-4844
excessBlobGas*: Option[Quantity] # EIP-4844
parentBeaconBlockRoot*: Option[Hash256] # EIP-4788
WithdrawalObject* = object
index*: Quantity
validatorIndex*: Quantity
address*: Address
amount*: Quantity
## A block object, or null when no block was found
BlockObject* = ref object
number*: Quantity # 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.
logsBloom*: FixedBytes[256] # the bloom filter for the logs of the block. null when its pending block.
transactionsRoot*: Hash256 # the root of the transaction trie of the block.
stateRoot*: Hash256 # the root of the final state trie of the block.
receiptsRoot*: Hash256 # the root of the receipts trie of the block.
miner*: Address # the address of the beneficiary to whom the mining rewards were given.
difficulty*: UInt256 # integer of the difficulty for this block.
extraData*: HistoricExtraData # the "extra data" field of this block.
gasLimit*: Quantity # the maximum gas allowed in this block.
gasUsed*: Quantity # the total used gas by all transactions in this block.
timestamp*: Quantity # the unix timestamp for when the block was collated.
nonce*: Option[FixedBytes[8]] # hash of the generated proof-of-work. null when its pending block.
mixHash*: Hash256
size*: Quantity # integer the size of this block in bytes.
totalDifficulty*: UInt256 # integer of the total difficulty of the chain until this block.
transactions*: seq[TxOrHash] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
uncles*: seq[Hash256] # list of uncle hashes.
baseFeePerGas*: Option[UInt256] # EIP-1559
withdrawals*: Option[seq[WithdrawalObject]] # EIP-4895
withdrawalsRoot*: Option[Hash256] # EIP-4895
blobGasUsed*: Option[Quantity] # EIP-4844
excessBlobGas*: Option[Quantity] # EIP-4844
parentBeaconBlockRoot*: Option[Hash256] # EIP-4788
TxOrHashKind* = enum
tohHash
tohTx
TxOrHash* = object
case kind*: TxOrHashKind
of tohHash:
hash*: TxHash
of tohTx:
tx*: TransactionObject
AccessTuple* = object
address*: Address
storageKeys*: seq[FixedBytes[32]]
AccessListResult* = object
accessList*: seq[AccessTuple]
error*: string
gasUsed: Quantity
TransactionObject* = ref object # A transaction object, or null when no transaction was found:
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.
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.
value*: UInt256 # value transferred in Wei.
gasPrice*: Quantity # gas price provided by the sender in Wei.
gas*: Quantity # gas provided by the sender.
input*: seq[byte] # the data send along with the transaction.
v*: Quantity # ECDSA recovery id
r*: UInt256 # ECDSA signature r
s*: UInt256 # ECDSA signature s
`type`*: Option[Quantity] # EIP-2718, with 0x0 for Legacy
chainId*: Option[Quantity] # EIP-159
accessList*: Option[seq[AccessTuple]] # EIP-2930
maxFeePerGas*: Option[Quantity] # EIP-1559
maxPriorityFeePerGas*: Option[Quantity] # EIP-1559
maxFeePerBlobGas*: Option[UInt256] # EIP-4844
versionedHashes*: Option[seq[VersionedHash]] # EIP-4844
ReceiptObject* = ref object # A transaction receipt object, or null when no receipt was found:
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.
`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.
effectiveGasPrice*: Quantity # The sum of the base fee and tip paid per unit of gas.
gasUsed*: Quantity # the amount of gas used by this specific transaction alone.
contractAddress*: Option[Address] # the contract address created, if the transaction was a contract creation, otherwise null.
logs*: seq[LogObject] # TODO: See Wiki for details. list of log objects, which this transaction generated.
logsBloom*: FixedBytes[256] # bloom filter for light clients to quickly retrieve related logs.
`type`*: Option[Quantity] # integer of the transaction type, 0x0 for legacy transactions, 0x1 for access list types, 0x2 for dynamic fees.
root*: Option[Hash256] # 32 bytes of post-transaction stateroot (pre Byzantium)
status*: Option[Quantity] # either 1 (success) or 0 (failure)
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]
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.
blockHash*: Option[BlockHash] # (optional) hash of the block. If its present, fromBlock and toBlock, should be none. Introduced in EIP234
LogObject* = object
removed*: bool # true when the log was removed, due to a chain reorganization. false if its a valid log.
logIndex*: Option[Quantity] # integer of the log index position in the block. null when its pending log.
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.
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.
# (In solidity: The first topic is the hash of the signature of the event.
# (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)
RlpEncodedBytes* = distinct seq[byte]
StorageProof* = object
key*: UInt256
value*: UInt256
proof*: seq[RlpEncodedBytes]
ProofResponse* = object
address*: Address
accountProof*: seq[RlpEncodedBytes]
balance*: UInt256
codeHash*: CodeHash
nonce*: Quantity
storageHash*: StorageHash
storageProof*: seq[StorageProof]
BlockIdentifier* = string|BlockNumber|RtBlockIdentifier
BlockIdentifierKind* = enum
bidNumber
bidAlias
RtBlockIdentifier* = object
case kind*: BlockIdentifierKind
of bidNumber:
number*: BlockNumber
of bidAlias:
alias*: string
{.push raises: [].}
func blockId*(n: BlockNumber): RtBlockIdentifier =
RtBlockIdentifier(kind: bidNumber, number: n)
func blockId*(b: BlockObject): RtBlockIdentifier =
RtBlockIdentifier(kind: bidNumber, number: BlockNumber b.number)
func blockId*(a: string): RtBlockIdentifier =
RtBlockIdentifier(kind: bidAlias, alias: a)
func txOrHash*(hash: TxHash): TxOrHash =
TxOrHash(kind: tohHash, hash: hash)
func txOrHash*(tx: TransactionObject): TxOrHash =
TxOrHash(kind: tohTx, tx: tx)

View File

@ -1,3 +1,12 @@
# 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]
@ -20,7 +29,7 @@ template stripLeadingZeros(value: string): string =
func encodeQuantity*(value: SomeUnsignedInt): string =
var hValue = value.toHex.stripLeadingZeros
result = "0x" & hValue
result = "0x" & hValue.toLowerAscii
func hasHexHeader*(value: string): bool =
value.len >= 2 and value[0] == '0' and value[1] in {'x', 'X'}

View File

@ -1,542 +0,0 @@
import
std/[options, hashes, typetraits],
stint, stew/[byteutils, results]
export
hashes, options, typetraits
const
# https://github.com/ethereum/execution-apis/blob/c4089414bbbe975bbc4bf1ccf0a3d31f76feb3e1/src/engine/cancun.md#blobsbundlev1
fieldElementsPerBlob = 4096
type
SyncObject* = object
startingBlock*: int
currentBlock*: int
highestBlock*: int
FixedBytes*[N: static[int]] = distinct array[N, byte]
DynamicBytes*[
minLen: static[int] = 0,
maxLen: static[int] = high(int)] = distinct seq[byte]
HistoricExtraData = DynamicBytes[0, 4096]
## In the current specs, the maximum is 32, but historically this value was
## used as Clique metadata which is dynamic in lenght and exceeds 32 bytes.
## Since we still need to support syncing old blocks, we use this more relaxed
## setting. Downstream libraries that want to enforce the up-to-date limit are
## expected to do this on their own.
Address* = distinct array[20, byte]
TxHash* = FixedBytes[32]
Hash256* = FixedBytes[32]
BlockHash* = Hash256
BlockNumber* = uint64
BlockIdentifier* = string|BlockNumber|RtBlockIdentifier
Nonce* = int
CodeHash* = FixedBytes[32]
StorageHash* = FixedBytes[32]
VersionedHash* = FixedBytes[32]
BlockIdentifierKind* = enum
bidNumber
bidAlias
RtBlockIdentifier* = object
case kind*: BlockIdentifierKind
of bidNumber:
number*: BlockNumber
of bidAlias:
alias*: string
Quantity* = distinct uint64
KZGCommitment* = FixedBytes[48]
KZGProof* = FixedBytes[48]
Blob* = FixedBytes[fieldElementsPerBlob * 32]
EthSend* = object
source*: Address # the address the transaction is sent from.
to*: Option[Address] # (optional when creating new contract) the address the transaction is directed to.
gas*: Option[Quantity] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
gasPrice*: Option[int] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
value*: Option[UInt256] # (optional) integer of the value sent with this transaction.
data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters.
# For details see Ethereum Contract ABI.
nonce*: Option[Nonce] # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
#EthSend* = object
# source*: Address # the address the transaction is sent from.
# to*: Address # (optional when creating new contract) the address the transaction is directed to.
# gas*: int # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
# gasPrice*: int # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
# value*: int # (optional) integer of the value sent with this transaction.
# data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI.
# nonce*: int # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
# TODO: Both `EthSend` and `EthCall` are super outdated, according to new spec
# those should be merged into one type `GenericTransaction` with a lot more fields
# see: https://github.com/ethereum/execution-apis/blob/main/src/schemas/transaction.yaml#L244
EthCall* = object
source*: Option[Address] # (optional) The address the transaction is sent from.
to*: Address # The address the transaction is directed to.
gas*: Option[Quantity] # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
gasPrice*: Option[int] # (optional) Integer of the gasPrice used for each paid gas.
value*: Option[UInt256] # (optional) Integer of the value sent with this transaction.
data*: Option[string] # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI.
#EthCall* = object
# source*: Address # (optional) The address the transaction is sent from.
# to*: Address # The address the transaction is directed to.
# gas*: int # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
# gasPrice*: int # (optional) Integer of the gasPrice used for each paid gas.
# value*: int # (optional) Integer of the value sent with this transaction.
# data*: int # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI.
## A block header object
BlockHeader* = ref object
number*: Quantity
hash*: Hash256
parentHash*: Hash256
sha3Uncles*: Hash256
logsBloom*: FixedBytes[256]
transactionsRoot*: Hash256
stateRoot*: Hash256
receiptsRoot*: Hash256
miner*: Address
difficulty*: UInt256
extraData*: HistoricExtraData
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
nonce*: FixedBytes[8]
mixHash*: Hash256
baseFeePerGas*: Option[UInt256] # EIP-1559
withdrawalsRoot*: Option[Hash256] # EIP-4895
blobGasUsed*: Option[Quantity] # EIP-4844
excessBlobGas*: Option[Quantity] # EIP-4844
parentBeaconBlockRoot*: Option[Hash256] # EIP-4788
WithdrawalObject* = object
index*: Quantity
validatorIndex*: Quantity
address*: Address
amount*: Quantity
## A block object, or null when no block was found
BlockObject* = ref object
number*: Quantity # 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.
logsBloom*: FixedBytes[256] # the bloom filter for the logs of the block. null when its pending block.
transactionsRoot*: Hash256 # the root of the transaction trie of the block.
stateRoot*: Hash256 # the root of the final state trie of the block.
receiptsRoot*: Hash256 # the root of the receipts trie of the block.
miner*: Address # the address of the beneficiary to whom the mining rewards were given.
difficulty*: UInt256 # integer of the difficulty for this block.
extraData*: HistoricExtraData # the "extra data" field of this block.
gasLimit*: Quantity # the maximum gas allowed in this block.
gasUsed*: Quantity # the total used gas by all transactions in this block.
timestamp*: Quantity # the unix timestamp for when the block was collated.
nonce*: Option[FixedBytes[8]] # hash of the generated proof-of-work. null when its pending block.
mixHash*: Hash256
size*: Quantity # integer the size of this block in bytes.
totalDifficulty*: UInt256 # integer of the total difficulty of the chain until this block.
transactions*: seq[TxHash] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
uncles*: seq[Hash256] # list of uncle hashes.
baseFeePerGas*: Option[UInt256] # EIP-1559
withdrawals*: Option[seq[WithdrawalObject]] # EIP-4895
withdrawalsRoot*: Option[Hash256] # EIP-4895
blobGasUsed*: Option[Quantity] # EIP-4844
excessBlobGas*: Option[Quantity] # EIP-4844
parentBeaconBlockRoot*: Option[Hash256] # EIP-4788
AccessTuple* = object
address*: Address
storageKeys*: seq[Hash256]
TransactionObject* = object # A transaction object, or null when no transaction was found:
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.
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.
value*: UInt256 # value transferred in Wei.
gasPrice*: Quantity # gas price provided by the sender in Wei.
gas*: Quantity # gas provided by the sender.
input*: seq[byte] # the data send along with the transaction.
v*: UInt256 # ECDSA recovery id
r*: UInt256 # ECDSA signature r
s*: UInt256 # ECDSA signature s
`type`*: Option[Quantity] # EIP-2718, with 0x0 for Legacy
chainId*: Option[UInt256] # EIP-159
accessList*: Option[seq[AccessTuple]] # EIP-2930
maxFeePerGas*: Option[Quantity] # EIP-1559
maxPriorityFeePerGas*: Option[Quantity] # EIP-1559
maxFeePerBlobGas*: Option[UInt256] # EIP-4844
blobVersionedHashes*: Option[seq[VersionedHash]] # EIP-4844
ReceiptKind* = enum rkRoot, rkStatus
ReceiptObject* = object
# A transaction receipt object, or null when no receipt was found:
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.
`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.
effectiveGasPrice*: Quantity # The sum of the base fee and tip paid per unit of gas.
gasUsed*: Quantity # the amount of gas used by this specific transaction alone.
contractAddress*: Option[Address] # the contract address created, if the transaction was a contract creation, otherwise null.
logs*: seq[LogObject] # TODO: See Wiki for details. list of log objects, which this transaction generated.
logsBloom*: FixedBytes[256] # bloom filter for light clients to quickly retrieve related logs.
`type`*: Option[Quantity] # integer of the transaction type, 0x0 for legacy transactions, 0x1 for access list types, 0x2 for dynamic fees.
root*: Option[Hash256] # 32 bytes of post-transaction stateroot (pre Byzantium)
status*: Option[Quantity] # either 1 (success) or 0 (failure)
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]
FilterOptions* = object
fromBlock*: Option[string] # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions.
toBlock*: Option[string] # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions.
address*: Option[Address] # (optional) contract address or a list of addresses from which logs should originate.
topics*: Option[seq[string]]#Option[seq[FilterData]] # (optional) list of DATA topics. Topics are order-dependent. Each topic can also be a list of DATA with "or" options.
blockhash*: Option[BlockHash]
LogObject* = object
removed*: bool # true when the log was removed, due to a chain reorganization. false if its a valid log.
logIndex*: Quantity # integer of the log index position in the block. null when its pending log.
transactionIndex*: Quantity # integer of the transactions index position log was created from. null when its pending log.
transactionHash*: TxHash # hash of the transactions this log was created from. null when its pending log.
blockHash*: BlockHash # hash of the block where this log was in. null when its pending. null when its pending log.
blockNumber*: Quantity # 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[FixedBytes[32]] # array of 0 to 4 32 Bytes DATA of indexed log arguments.
# (In solidity: The first topic is the hash of the signature of the event.
# (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)
# EthSend* = object
# source*: Address # the address the transaction is sent from.
# to*: Option[Address] # (optional when creating new contract) the address the transaction is directed to.
# gas*: Option[int] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
# gasPrice*: Option[int] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
# value*: Option[int] # (optional) integer of the value sent with this transaction.
# data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI.
# nonce*: Option[int] # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
# var x: array[20, byte] = [1.byte, 2, 3, 4, 5, 6, 7, 0xab, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
TypedTransaction* = distinct seq[byte]
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#withdrawalv1
WithdrawalV1* = object
index*: Quantity
validatorIndex*: Quantity
address*: Address
amount*: Quantity
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/paris.md#executionpayloadv1
ExecutionPayloadV1* = object
parentHash*: Hash256
feeRecipient*: Address
stateRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: Hash256
transactions*: seq[TypedTransaction]
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#executionpayloadv2
ExecutionPayloadV2* = object
parentHash*: Hash256
feeRecipient*: Address
stateRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: Hash256
transactions*: seq[TypedTransaction]
withdrawals*: seq[WithdrawalV1]
# This is ugly, but I don't think the RPC library will handle
# ExecutionPayloadV1 | ExecutionPayloadV2. (Am I wrong?)
# Note that the spec currently says that various V2 methods
# (e.g. engine_newPayloadV2) need to accept *either* V1 or V2
# of the data structure (e.g. either ExecutionPayloadV1 or
# ExecutionPayloadV2); it's not like V2 of the method only
# needs to accept V2 of the structure. Anyway, the best way
# I've found to handle this is to make this structure with an
# Option for the withdrawals field. If you've got a better idea,
# please fix this. (Maybe the RPC library does handle sum types?
# Or maybe we can enhance it to do so?) --Adam
#
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md
ExecutionPayloadV1OrV2* = object
parentHash*: BlockHash
feeRecipient*: Address
stateRoot*: BlockHash
receiptsRoot*: BlockHash
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: BlockHash
transactions*: seq[TypedTransaction]
withdrawals*: Option[seq[WithdrawalV1]]
# https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#executionpayloadv3
ExecutionPayloadV3* = object
parentHash*: Hash256
feeRecipient*: Address
stateRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: Hash256
transactions*: seq[TypedTransaction]
withdrawals*: seq[WithdrawalV1]
blobGasUsed*: Quantity
excessBlobGas*: Quantity
SomeExecutionPayload* =
ExecutionPayloadV1 |
ExecutionPayloadV2 |
ExecutionPayloadV3
# https://github.com/ethereum/execution-apis/blob/ee3df5bc38f28ef35385cefc9d9ca18d5e502778/src/engine/cancun.md#blobsbundlev1
BlobsBundleV1* = object
commitments*: seq[KZGCommitment]
proofs*: seq[KZGProof]
blobs*: seq[Blob]
RlpEncodedBytes* = distinct seq[byte]
StorageProof* = object
key*: UInt256
value*: UInt256
proof*: seq[RlpEncodedBytes]
ProofResponse* = object
address*: Address
accountProof*: seq[RlpEncodedBytes]
balance*: UInt256
codeHash*: CodeHash
nonce*: Quantity
storageHash*: StorageHash
storageProof*: seq[StorageProof]
AccessListEntry* = object
address*: Address
storageKeys*: seq[FixedBytes[32]]
AccessList* = seq[AccessListEntry]
AccessListResult* = object
accessList*: AccessList
error*: string
gasUsed: Quantity
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.} =
array[20, byte](a) == array[20, byte](b)
func blockId*(n: BlockNumber): RtBlockIdentifier =
RtBlockIdentifier(kind: bidNumber, number: n)
func blockId*(b: BlockObject): RtBlockIdentifier =
RtBlockIdentifier(kind: bidNumber, number: BlockNumber b.number)
func blockId*(a: string): RtBlockIdentifier =
RtBlockIdentifier(kind: bidAlias, alias: a)
func hash*[N](bytes: FixedBytes[N]): Hash =
hash(distinctBase bytes)
template toHex*[N](x: FixedBytes[N]): string =
toHex(distinctBase x)
template toHex*[minLen, maxLen](x: DynamicBytes[minLen, maxLen]): string =
toHex(distinctBase x)
template toHex*(x: Address): string =
toHex(distinctBase x)
template fromHex*(T: type Address, hexStr: string): T =
T fromHex(distinctBase(T), hexStr)
template skip0xPrefix(hexStr: string): int =
## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix
if hexStr.len > 1 and hexStr[0] == '0' and hexStr[1] in {'x', 'X'}: 2
else: 0
func strip0xPrefix*(s: string): string =
let prefixLen = skip0xPrefix(s)
if prefixLen != 0:
s[prefixLen .. ^1]
else:
s
func fromHex*[minLen, maxLen](T: type DynamicBytes[minLen, maxLen], hexStr: string): T =
let prefixLen = skip0xPrefix(hexStr)
let hexDataLen = hexStr.len - prefixLen
if hexDataLen < minLen * 2:
raise newException(ValueError, "hex input too small")
if hexDataLen > maxLen * 2:
raise newException(ValueError, "hex input too large")
T hexToSeqByte(hexStr)
template fromHex*[N](T: type FixedBytes[N], hexStr: string): T =
T fromHex(distinctBase(T), hexStr)
func toArray*[N](data: DynamicBytes[N, N]): array[N, byte] =
copyMem(addr result[0], unsafeAddr distinctBase(data)[0], N)
template bytes*(data: DynamicBytes): seq[byte] =
distinctBase data
template bytes*(data: FixedBytes): auto =
distinctBase data
template len*(data: DynamicBytes): int =
len(distinctBase data)
func `$`*[minLen, maxLen](data: DynamicBytes[minLen, maxLen]): string =
"0x" & byteutils.toHex(distinctBase(data))
# These conversion functions are very ugly, but at least
# they're very straightforward and simple. If anyone has
# a better idea, I'm all ears. (See the above comment on
# ExecutionPayloadV1OrV2.) --Adam
func toExecutionPayloadV1OrExecutionPayloadV2*(p: ExecutionPayloadV1OrV2): Result[ExecutionPayloadV1, ExecutionPayloadV2] =
if p.withdrawals.isNone:
ok(
ExecutionPayloadV1(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions
)
)
else:
err(
ExecutionPayloadV2(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: p.withdrawals.get
)
)
func toExecutionPayloadV1*(p: ExecutionPayloadV1OrV2): ExecutionPayloadV1 =
p.toExecutionPayloadV1OrExecutionPayloadV2.get
func toExecutionPayloadV2*(p: ExecutionPayloadV1OrV2): ExecutionPayloadV2 =
p.toExecutionPayloadV1OrExecutionPayloadV2.error
func toExecutionPayloadV1OrV2*(p: ExecutionPayloadV1): ExecutionPayloadV1OrV2 =
ExecutionPayloadV1OrV2(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: none[seq[WithdrawalV1]]()
)
func toExecutionPayloadV1OrV2*(p: ExecutionPayloadV2): ExecutionPayloadV1OrV2 =
ExecutionPayloadV1OrV2(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: some(p.withdrawals)
)

393
web3/execution_types.nim Normal file
View File

@ -0,0 +1,393 @@
# nim-web3
# Copyright (c) 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
stint,
./engine_api_types
export
stint,
engine_api_types
type
ExecutionPayload* = object
parentHash*: Hash256
feeRecipient*: Address
stateRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: FixedBytes[256]
prevRandao*: FixedBytes[32]
blockNumber*: Quantity
gasLimit*: Quantity
gasUsed*: Quantity
timestamp*: Quantity
extraData*: DynamicBytes[0, 32]
baseFeePerGas*: UInt256
blockHash*: Hash256
transactions*: seq[TypedTransaction]
withdrawals*: Option[seq[WithdrawalV1]]
blobGasUsed*: Option[Quantity]
excessBlobGas*: Option[Quantity]
PayloadAttributes* = object
timestamp*: Quantity
prevRandao*: FixedBytes[32]
suggestedFeeRecipient*: Address
withdrawals*: Option[seq[WithdrawalV1]]
parentBeaconBlockRoot*: Option[FixedBytes[32]]
SomeOptionalPayloadAttributes* =
Option[PayloadAttributesV1] |
Option[PayloadAttributesV2] |
Option[PayloadAttributesV3]
GetPayloadResponse* = object
executionPayload*: ExecutionPayload
blockValue*: Option[UInt256]
blobsBundle*: Option[BlobsBundleV1]
shouldOverrideBuilder*: Option[bool]
Version* {.pure.} = enum
V1
V2
V3
{.push raises: [].}
func version*(payload: ExecutionPayload): Version =
if payload.blobGasUsed.isSome and payload.excessBlobGas.isSome:
Version.V3
elif payload.withdrawals.isSome:
Version.V2
else:
Version.V1
func version*(attr: PayloadAttributes): Version =
if attr.parentBeaconBlockRoot.isSome:
Version.V3
elif attr.withdrawals.isSome:
Version.V2
else:
Version.V1
func version*(res: GetPayloadResponse): Version =
if res.blobsBundle.isSome and res.shouldOverrideBuilder.isSome:
Version.V3
elif res.blockValue.isSome:
Version.V2
else:
Version.V1
func V1V2*(attr: PayloadAttributes): PayloadAttributesV1OrV2 =
PayloadAttributesV1OrV2(
timestamp: attr.timestamp,
prevRandao: attr.prevRandao,
suggestedFeeRecipient: attr.suggestedFeeRecipient,
withdrawals: attr.withdrawals
)
func V1*(attr: PayloadAttributes): PayloadAttributesV1 =
PayloadAttributesV1(
timestamp: attr.timestamp,
prevRandao: attr.prevRandao,
suggestedFeeRecipient: attr.suggestedFeeRecipient
)
func V2*(attr: PayloadAttributes): PayloadAttributesV2 =
PayloadAttributesV2(
timestamp: attr.timestamp,
prevRandao: attr.prevRandao,
suggestedFeeRecipient: attr.suggestedFeeRecipient,
withdrawals: attr.withdrawals.get
)
func V3*(attr: PayloadAttributes): PayloadAttributesV3 =
PayloadAttributesV3(
timestamp: attr.timestamp,
prevRandao: attr.prevRandao,
suggestedFeeRecipient: attr.suggestedFeeRecipient,
withdrawals: attr.withdrawals.get(newSeq[WithdrawalV1]()),
parentBeaconBlockRoot: attr.parentBeaconBlockRoot.get
)
func V1*(attr: Option[PayloadAttributes]): Option[PayloadAttributesV1] =
if attr.isNone:
return none(PayloadAttributesV1)
some(attr.get.V1)
func V2*(attr: Option[PayloadAttributes]): Option[PayloadAttributesV2] =
if attr.isNone:
return none(PayloadAttributesV2)
some(attr.get.V2)
func V3*(attr: Option[PayloadAttributes]): Option[PayloadAttributesV3] =
if attr.isNone:
return none(PayloadAttributesV3)
some(attr.get.V3)
func payloadAttributes*(attr: PayloadAttributesV1): PayloadAttributes =
PayloadAttributes(
timestamp: attr.timestamp,
prevRandao: attr.prevRandao,
suggestedFeeRecipient: attr.suggestedFeeRecipient
)
func payloadAttributes*(attr: PayloadAttributesV2): PayloadAttributes =
PayloadAttributes(
timestamp: attr.timestamp,
prevRandao: attr.prevRandao,
suggestedFeeRecipient: attr.suggestedFeeRecipient,
withdrawals: some(attr.withdrawals)
)
func payloadAttributes*(attr: PayloadAttributesV3): PayloadAttributes =
PayloadAttributes(
timestamp: attr.timestamp,
prevRandao: attr.prevRandao,
suggestedFeeRecipient: attr.suggestedFeeRecipient,
withdrawals: some(attr.withdrawals),
parentBeaconBlockRoot: some(attr.parentBeaconBlockRoot)
)
func payloadAttributes*(x: Option[PayloadAttributesV1]): Option[PayloadAttributes] =
if x.isNone: none(PayloadAttributes)
else: some(payloadAttributes x.get)
func payloadAttributes*(x: Option[PayloadAttributesV2]): Option[PayloadAttributes] =
if x.isNone: none(PayloadAttributes)
else: some(payloadAttributes x.get)
func payloadAttributes*(x: Option[PayloadAttributesV3]): Option[PayloadAttributes] =
if x.isNone: none(PayloadAttributes)
else: some(payloadAttributes x.get)
func V1V2*(p: ExecutionPayload): ExecutionPayloadV1OrV2 =
ExecutionPayloadV1OrV2(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: p.withdrawals
)
func V1*(p: ExecutionPayload): ExecutionPayloadV1 =
ExecutionPayloadV1(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions
)
func V2*(p: ExecutionPayload): ExecutionPayloadV2 =
ExecutionPayloadV2(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: p.withdrawals.get
)
func V3*(p: ExecutionPayload): ExecutionPayloadV3 =
ExecutionPayloadV3(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: p.withdrawals.get,
blobGasUsed: p.blobGasUsed.get,
excessBlobGas: p.excessBlobGas.get
)
func V1*(p: ExecutionPayloadV1OrV2): ExecutionPayloadV1 =
ExecutionPayloadV1(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions
)
func V2*(p: ExecutionPayloadV1OrV2): ExecutionPayloadV2 =
ExecutionPayloadV2(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: p.withdrawals.get
)
func executionPayload*(p: ExecutionPayloadV1): ExecutionPayload =
ExecutionPayload(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions
)
func executionPayload*(p: ExecutionPayloadV2): ExecutionPayload =
ExecutionPayload(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: some(p.withdrawals)
)
func executionPayload*(p: ExecutionPayloadV3): ExecutionPayload =
ExecutionPayload(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: some(p.withdrawals),
blobGasUsed: some(p.blobGasUsed),
excessBlobGas: some(p.excessBlobGas)
)
func executionPayload*(p: ExecutionPayloadV1OrV2): ExecutionPayload =
ExecutionPayload(
parentHash: p.parentHash,
feeRecipient: p.feeRecipient,
stateRoot: p.stateRoot,
receiptsRoot: p.receiptsRoot,
logsBloom: p.logsBloom,
prevRandao: p.prevRandao,
blockNumber: p.blockNumber,
gasLimit: p.gasLimit,
gasUsed: p.gasUsed,
timestamp: p.timestamp,
extraData: p.extraData,
baseFeePerGas: p.baseFeePerGas,
blockHash: p.blockHash,
transactions: p.transactions,
withdrawals: p.withdrawals
)
func V1*(res: GetPayloadResponse): ExecutionPayloadV1 =
res.executionPayload.V1
func V2*(res: GetPayloadResponse): GetPayloadV2Response =
GetPayloadV2Response(
executionPayload: res.executionPayload.V1V2,
blockValue: res.blockValue.get
)
func V3*(res: GetPayloadResponse): GetPayloadV3Response =
GetPayloadV3Response(
executionPayload: res.executionPayload.V3,
blockValue: res.blockValue.get,
blobsBundle: res.blobsBundle.get,
shouldOverrideBuilder: res.shouldOverrideBuilder.get
)
func getPayloadResponse*(x: ExecutionPayloadV1): GetPayloadResponse =
GetPayloadResponse(executionPayload: x.executionPayload)
func getPayloadResponse*(x: GetPayloadV2Response): GetPayloadResponse =
GetPayloadResponse(
executionPayload: x.executionPayload.executionPayload,
blockValue: some(x.blockValue)
)
func getPayloadResponse*(x: GetPayloadV3Response): GetPayloadResponse =
GetPayloadResponse(
executionPayload: x.executionPayload.executionPayload,
blockValue: some(x.blockValue),
blobsBundle: some(x.blobsBundle),
shouldOverrideBuilder: some(x.shouldOverrideBuilder)
)

123
web3/primitives.nim Normal file
View File

@ -0,0 +1,123 @@
# nim-web3
# Copyright (c) 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/[options, hashes, typetraits],
stint, stew/[byteutils, results]
export
hashes, options, typetraits
type
FixedBytes*[N: static[int]] = distinct array[N, byte]
DynamicBytes*[
minLen: static[int] = 0,
maxLen: static[int] = high(int)] = distinct seq[byte]
Address* = distinct array[20, byte]
TxHash* = FixedBytes[32]
Hash256* = FixedBytes[32]
BlockHash* = FixedBytes[32]
BlockNumber* = uint64
Quantity* = distinct uint64
CodeHash* = FixedBytes[32]
StorageHash* = FixedBytes[32]
VersionedHash* = FixedBytes[32]
{.push raises: [].}
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)
func hash*[N](bytes: FixedBytes[N]): Hash =
hash(distinctBase bytes)
func hash*(bytes: Address): Hash =
hash(distinctBase bytes)
template toHex*[N](x: FixedBytes[N]): string =
toHex(distinctBase x)
template toHex*[minLen, maxLen](x: DynamicBytes[minLen, maxLen]): string =
toHex(distinctBase x)
template toHex*(x: Address): string =
toHex(distinctBase x)
template fromHex*(T: type Address, hexStr: string): T =
T fromHex(distinctBase(T), hexStr)
template skip0xPrefix(hexStr: string): int =
## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix
if hexStr.len > 1 and hexStr[0] == '0' and hexStr[1] in {'x', 'X'}: 2
else: 0
func strip0xPrefix*(s: string): string =
let prefixLen = skip0xPrefix(s)
if prefixLen != 0:
s[prefixLen .. ^1]
else:
s
func fromHex*[minLen, maxLen](T: type DynamicBytes[minLen, maxLen], hexStr: string): T {.raises: [ValueError].} =
let prefixLen = skip0xPrefix(hexStr)
let hexDataLen = hexStr.len - prefixLen
if hexDataLen < minLen * 2:
raise newException(ValueError, "hex input too small")
if hexDataLen > maxLen * 2:
raise newException(ValueError, "hex input too large")
T hexToSeqByte(hexStr)
template fromHex*[N](T: type FixedBytes[N], hexStr: string): T =
T fromHex(distinctBase(T), hexStr)
func toArray*[N](data: DynamicBytes[N, N]): array[N, byte] =
copyMem(addr result[0], unsafeAddr distinctBase(data)[0], N)
template bytes*(data: DynamicBytes): seq[byte] =
distinctBase data
template bytes*(data: FixedBytes): auto =
distinctBase data
template bytes*(data: Address): auto =
distinctBase data
template len*(data: DynamicBytes): int =
len(distinctBase data)
template len*(data: FixedBytes): int =
len(distinctBase data)
template len*(data: Address): int =
len(distinctBase data)
func `$`*[minLen, maxLen](data: DynamicBytes[minLen, maxLen]): string =
"0x" & byteutils.toHex(distinctBase(data))
func `$`*[N](data: FixedBytes[N]): string =
"0x" & byteutils.toHex(distinctBase(data))
func `$`*(data: Address): string =
"0x" & byteutils.toHex(distinctBase(data))

View File

@ -1,6 +1,15 @@
# 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
options,
ethtypes, stew/byteutils, stint,
eth_api_types, stew/byteutils, stint,
eth/[common, keys, rlp], eth/common/transaction
func signTransaction(tr: var Transaction, pk: PrivateKey) =
@ -15,19 +24,16 @@ func signTransaction(tr: var Transaction, pk: PrivateKey) =
tr.V = int64(v) + 27 # TODO! Complete this
func encodeTransaction*(s: EthSend, pk: PrivateKey): string =
func encodeTransaction*(s: EthSend, pk: PrivateKey): seq[byte] =
var tr = Transaction(txType: TxLegacy)
tr.gasLimit = GasInt(s.gas.get.uint64)
tr.gasPrice = s.gasPrice.get
tr.gasPrice = s.gasPrice.get.GasInt
if s.to.isSome:
tr.to = some(EthAddress(s.to.get))
if s.value.isSome:
tr.value = s.value.get
tr.nonce = uint64(s.nonce.get)
# TODO: The following is a misdesign indication.
# All the encodings should be done into seq[byte], not a hex string.
if s.data.len != 0:
tr.payload = hexToSeqByte(s.data)
tr.payload = s.data
signTransaction(tr, pk)
return rlp.encode(tr).toHex
return rlp.encode(tr)