diff --git a/.gitignore b/.gitignore index d2dd12d..8c9e871 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 8d84dbf..a81f7e6 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/ci-test.sh b/ci-test.sh index 736f1aa..c561c0a 100755 --- a/ci-test.sh +++ b/ci-test.sh @@ -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 diff --git a/config.nims b/config.nims new file mode 100644 index 0000000..7c9db32 --- /dev/null +++ b/config.nims @@ -0,0 +1,4 @@ +# begin Nimble config (version 1) +when fileExists("nimble.paths"): + include "nimble.paths" +# end Nimble config diff --git a/nim.cfg b/nim.cfg index 11938a8..c0e6d2a 100644 --- a/nim.cfg +++ b/nim.cfg @@ -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 diff --git a/tests/all_tests.nim b/tests/all_tests.nim index bebe676..b9e224c 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -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 diff --git a/tests/depositcontract.nim b/tests/helpers/depositcontract.nim similarity index 100% rename from tests/depositcontract.nim rename to tests/helpers/depositcontract.nim diff --git a/tests/helpers/utils.nim b/tests/helpers/utils.nim new file mode 100644 index 0000000..5008329 --- /dev/null +++ b/tests/helpers/utils.nim @@ -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) diff --git a/tests/nim.cfg b/tests/nim.cfg index 371f6d7..4d81fa7 100644 --- a/tests/nim.cfg +++ b/tests/nim.cfg @@ -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: diff --git a/tests/test.nim b/tests/test_contracts.nim similarity index 93% rename from tests/test.nim rename to tests/test_contracts.nim index 99d5ff1..3d594e5 100644 --- a/tests/test.nim +++ b/tests/test_contracts.nim @@ -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: diff --git a/tests/test_deposit_contract.nim b/tests/test_deposit_contract.nim index f47739c..0d25336 100644 --- a/tests/test_deposit_contract.nim +++ b/tests/test_deposit_contract.nim @@ -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]) diff --git a/tests/test_ethhexstrings.nim b/tests/test_ethhexstrings.nim index 56bf950..0d74fd4 100644 --- a/tests/test_ethhexstrings.nim +++ b/tests/test_ethhexstrings.nim @@ -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": diff --git a/tests/test_execution_types.nim b/tests/test_execution_types.nim new file mode 100644 index 0000000..13fc8be --- /dev/null +++ b/tests/test_execution_types.nim @@ -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 + diff --git a/tests/test_json_marshalling.nim b/tests/test_json_marshalling.nim index 6184a1a..0bd9930 100644 --- a/tests/test_json_marshalling.nim +++ b/tests/test_json_marshalling.nim @@ -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.} diff --git a/tests/test_logs.nim b/tests/test_logs.nim index 8e79387..97084de 100644 --- a/tests/test_logs.nim +++ b/tests/test_logs.nim @@ -1,9 +1,18 @@ -import pkg/unittest2 -import ../web3 -import chronos, options, json, stint -import test_utils - -import random +# 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, random], + pkg/unittest2, + ../web3, + chronos, stint, + ./helpers/utils #[ Contract LoggerContract pragma solidity >=0.4.25 <0.6.0; diff --git a/tests/test_primitives.nim b/tests/test_primitives.nim new file mode 100644 index 0000000..24b6910 --- /dev/null +++ b/tests/test_primitives.nim @@ -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" diff --git a/tests/test_signed_tx.nim b/tests/test_signed_tx.nim index a2ca6aa..fbc6a44 100644 --- a/tests/test_signed_tx.nim +++ b/tests/test_signed_tx.nim @@ -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) diff --git a/tests/test_utils.nim b/tests/test_utils.nim deleted file mode 100644 index bac0a49..0000000 --- a/tests/test_utils.nim +++ /dev/null @@ -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 diff --git a/web3.nim b/web3.nim index e48dff9..4cc4884 100644 --- a/web3.nim +++ b/web3.nim @@ -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) diff --git a/web3.nimble b/web3.nimble index 59b6d8c..00e63c9 100644 --- a/web3.nimble +++ b/web3.nimble @@ -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" diff --git a/web3/confutils_defs.nim b/web3/confutils_defs.nim index e473d98..dac80ae 100644 --- a/web3/confutils_defs.nim +++ b/web3/confutils_defs.nim @@ -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] = @[] diff --git a/web3/conversions.nim b/web3/conversions.nim index fdddcf6..be2f97f 100644 --- a/web3/conversions.nim +++ b/web3/conversions.nim @@ -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 diff --git a/web3/encoding.nim b/web3/encoding.nim index 84f5958..0339bc7 100644 --- a/web3/encoding.nim +++ b/web3/encoding.nim @@ -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] diff --git a/web3/engine_api.nim b/web3/engine_api.nim index 19b1c4f..c3b4499 100644 --- a/web3/engine_api.nim +++ b/web3/engine_api.nim @@ -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] diff --git a/web3/engine_api_callsigs.nim b/web3/engine_api_callsigs.nim index 01adc89..0e6988e 100644 --- a/web3/engine_api_callsigs.nim +++ b/web3/engine_api_callsigs.nim @@ -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 diff --git a/web3/engine_api_types.nim b/web3/engine_api_types.nim index e8526a9..b54d3e1 100644 --- a/web3/engine_api_types.nim +++ b/web3/engine_api_types.nim @@ -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) diff --git a/web3/eth_api.nim b/web3/eth_api.nim new file mode 100644 index 0000000..7478486 --- /dev/null +++ b/web3/eth_api.nim @@ -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") diff --git a/web3/ethcallsigs.nim b/web3/eth_api_callsigs.nim similarity index 57% rename from web3/ethcallsigs.nim rename to web3/eth_api_callsigs.nim index 7b158c7..43867d4 100644 --- a/web3/ethcallsigs.nim +++ b/web3/eth_api_callsigs.nim @@ -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) diff --git a/web3/eth_api_types.nim b/web3/eth_api_types.nim new file mode 100644 index 0000000..2dc8867 --- /dev/null +++ b/web3/eth_api_types.nim @@ -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) diff --git a/web3/ethhexstrings.nim b/web3/ethhexstrings.nim index 6acb5e7..c2a11d6 100644 --- a/web3/ethhexstrings.nim +++ b/web3/ethhexstrings.nim @@ -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'} diff --git a/web3/ethtypes.nim b/web3/ethtypes.nim deleted file mode 100644 index e1769b2..0000000 --- a/web3/ethtypes.nim +++ /dev/null @@ -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) - ) diff --git a/web3/execution_types.nim b/web3/execution_types.nim new file mode 100644 index 0000000..539533a --- /dev/null +++ b/web3/execution_types.nim @@ -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) + ) diff --git a/web3/primitives.nim b/web3/primitives.nim new file mode 100644 index 0000000..00a4861 --- /dev/null +++ b/web3/primitives.nim @@ -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)) diff --git a/web3/transaction_signing.nim b/web3/transaction_signing.nim index c71c9a0..dbb7b87 100644 --- a/web3/transaction_signing.nim +++ b/web3/transaction_signing.nim @@ -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)