From 06e289cec5654e9fd10ecdadaea27ecbfeffd241 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 22 Nov 2024 15:42:16 +0100 Subject: [PATCH 01/67] squashed --- Cargo.toml | 35 +- prdoc/pr_5926.prdoc | 13 + substrate/frame/revive/rpc/codegen/Cargo.toml | 18 + substrate/frame/revive/rpc/codegen/README.md | 5 + .../frame/revive/rpc/codegen/openrpc.json | 2268 +++++++++++++++++ .../frame/revive/rpc/codegen/src/LICENSE.txt | 16 + .../frame/revive/rpc/codegen/src/generator.rs | 740 ++++++ .../frame/revive/rpc/codegen/src/main.rs | 64 + .../frame/revive/rpc/codegen/src/open_rpc.rs | 834 ++++++ .../frame/revive/rpc/codegen/src/printer.rs | 496 ++++ 10 files changed, 4468 insertions(+), 21 deletions(-) create mode 100644 prdoc/pr_5926.prdoc create mode 100644 substrate/frame/revive/rpc/codegen/Cargo.toml create mode 100644 substrate/frame/revive/rpc/codegen/README.md create mode 100644 substrate/frame/revive/rpc/codegen/openrpc.json create mode 100644 substrate/frame/revive/rpc/codegen/src/LICENSE.txt create mode 100644 substrate/frame/revive/rpc/codegen/src/generator.rs create mode 100644 substrate/frame/revive/rpc/codegen/src/main.rs create mode 100644 substrate/frame/revive/rpc/codegen/src/open_rpc.rs create mode 100644 substrate/frame/revive/rpc/codegen/src/printer.rs diff --git a/Cargo.toml b/Cargo.toml index 533ea4c9e878..5402b3e60cc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -403,6 +403,7 @@ members = [ "substrate/frame/revive/mock-network", "substrate/frame/revive/proc-macro", "substrate/frame/revive/rpc", + "substrate/frame/revive/rpc/codegen", "substrate/frame/revive/uapi", "substrate/frame/root-offences", "substrate/frame/root-testing", @@ -556,13 +557,7 @@ default-members = [ [workspace.lints.rust] suspicious_double_ref_op = { level = "allow", priority = 2 } # `substrate_runtime` is a common `cfg` condition name used in the repo. -unexpected_cfgs = { level = "warn", check-cfg = [ - 'cfg(build_opt_level, values("3"))', - 'cfg(build_profile, values("debug", "release"))', - 'cfg(enable_alloc_error_handler)', - 'cfg(fuzzing)', - 'cfg(substrate_runtime)', -] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(build_opt_level, values("3"))', 'cfg(build_profile, values("debug", "release"))', 'cfg(enable_alloc_error_handler)', 'cfg(fuzzing)', 'cfg(substrate_runtime)'] } [workspace.lints.clippy] all = { level = "allow", priority = 0 } @@ -637,7 +632,7 @@ bitvec = { version = "1.0.1", default-features = false } blake2 = { version = "0.10.4", default-features = false } blake2b_simd = { version = "1.0.2", default-features = false } blake3 = { version = "1.5" } -bounded-collections = { version = "0.2.2", default-features = false } +bounded-collections = { version = "0.2.0", default-features = false } bounded-vec = { version = "0.7" } bp-asset-hub-rococo = { path = "bridges/chains/chain-asset-hub-rococo", default-features = false } bp-asset-hub-westend = { path = "bridges/chains/chain-asset-hub-westend", default-features = false } @@ -683,7 +678,6 @@ cid = { version = "0.9.0" } clap = { version = "4.5.13" } clap-num = { version = "1.0.2" } clap_complete = { version = "4.5.13" } -cmd_lib = { version = "1.9.5" } coarsetime = { version = "0.1.22" } codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" } collectives-westend-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend" } @@ -742,7 +736,7 @@ derive_more = { version = "0.99.17", default-features = false } digest = { version = "0.10.3", default-features = false } directories = { version = "5.0.1" } dlmalloc = { version = "0.2.4" } -docify = { version = "0.2.9" } +docify = { version = "0.2.8" } dyn-clonable = { version = "0.9.0" } dyn-clone = { version = "1.0.16" } ed25519-dalek = { version = "2.1", default-features = false } @@ -754,7 +748,7 @@ enumn = { version = "0.1.13" } env_logger = { version = "0.11.2" } environmental = { version = "1.1.4", default-features = false } equivocation-detector = { path = "bridges/relays/equivocation" } -ethabi = { version = "2.0.0", default-features = false, package = "ethabi-decode" } +ethabi = { version = "1.0.0", default-features = false, package = "ethabi-decode" } ethbloom = { version = "0.14.1", default-features = false } ethereum-types = { version = "0.15.1", default-features = false } exit-future = { version = "0.2.0" } @@ -792,7 +786,7 @@ frame-system-rpc-runtime-api = { path = "substrate/frame/system/rpc/runtime-api" frame-try-runtime = { path = "substrate/frame/try-runtime", default-features = false } fs4 = { version = "0.7.0" } fs_extra = { version = "1.3.0" } -futures = { version = "0.3.31" } +futures = { version = "0.3.30" } futures-channel = { version = "0.3.23" } futures-timer = { version = "3.0.2" } futures-util = { version = "0.3.30", default-features = false } @@ -848,7 +842,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.1", features = ["websocket"] } +litep2p = { version = "0.7.0", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } @@ -1094,9 +1088,7 @@ polkavm-derive = "0.9.1" polkavm-linker = "0.9.2" portpicker = { version = "0.1.1" } pretty_assertions = { version = "1.3.0" } -primitive-types = { version = "0.13.1", default-features = false, features = [ - "num-traits", -] } +primitive-types = { version = "0.13.1", default-features = false, features = ["num-traits"] } proc-macro-crate = { version = "3.0.0" } proc-macro-warning = { version = "1.0.0", default-features = false } proc-macro2 = { version = "1.0.86" } @@ -1205,11 +1197,12 @@ seccompiler = { version = "0.4.0" } secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } separator = { version = "0.4.1" } -serde = { version = "1.0.214", default-features = false } +serde = { version = "1.0.210", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } serde_json = { version = "1.0.132", default-features = false } serde_yaml = { version = "0.9" } +serial_test = { version = "2.0.0" } sha1 = { version = "0.10.6" } sha2 = { version = "0.10.7", default-features = false } sha3 = { version = "0.10.0", default-features = false } @@ -1316,9 +1309,9 @@ substrate-test-runtime-client = { path = "substrate/test-utils/runtime/client" } substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime/transaction-pool" } substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } -subxt = { version = "0.38", default-features = false } -subxt-signer = { version = "0.38" } -syn = { version = "2.0.87" } +subxt = { version = "0.37", default-features = false } +subxt-signer = { version = "0.37" } +syn = { version = "2.0.82" } sysinfo = { version = "0.30" } tar = { version = "0.4" } tempfile = { version = "3.8.1" } @@ -1387,7 +1380,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } -zombienet-sdk = { version = "0.2.15" } +zombienet-sdk = { version = "0.2.13" } zstd = { version = "0.12.4", default-features = false } [profile.release] diff --git a/prdoc/pr_5926.prdoc b/prdoc/pr_5926.prdoc new file mode 100644 index 000000000000..f05aeb93eb71 --- /dev/null +++ b/prdoc/pr_5926.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet-revive] add codegen for Ethereum RPC API" + +doc: + - audience: Runtime Dev + description: | + Add codegen crate for generating Ethereum RPC methods and types from the spec. + +crates: + - name: pallet-revive-rpc-codegen + bump: patch diff --git a/substrate/frame/revive/rpc/codegen/Cargo.toml b/substrate/frame/revive/rpc/codegen/Cargo.toml new file mode 100644 index 000000000000..65d8ba501b1c --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pallet-revive-rpc-codegen" +version = "0.1.0" +edition.workspace = true +publish = false + +[dependencies] +Inflector = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +anyhow = { workspace = true } + +[dev-dependencies] +pretty_assertions.workspace = true + +[features] +default = ["std"] +std = ["anyhow/std", "serde/std", "serde_json/std"] diff --git a/substrate/frame/revive/rpc/codegen/README.md b/substrate/frame/revive/rpc/codegen/README.md new file mode 100644 index 000000000000..2ca838f0db23 --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/README.md @@ -0,0 +1,5 @@ +Generates the Ethereum JSON-RPC API from the official specification. + +- See +- See building instructions to re-generate the openrpc.json +- Include fixes from diff --git a/substrate/frame/revive/rpc/codegen/openrpc.json b/substrate/frame/revive/rpc/codegen/openrpc.json new file mode 100644 index 000000000000..7b55131590ee --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/openrpc.json @@ -0,0 +1,2268 @@ +{ + "openrpc": "1.2.4", + "info": { + "title": "Ethereum JSON-RPC Specification", + "description": "A specification of the standard interface for Ethereum clients.", + "license": { + "name": "CC0-1.0", + "url": "https://creativecommons.org/publicdomain/zero/1.0/legalcode" + }, + "version": "0.0.0" + }, + "methods": [ + { + "name": "eth_accounts", + "summary": "Returns a list of addresses owned by client.", + "params": [], + "result": { + "name": "Accounts", + "schema": { + "title": "Accounts", + "type": "array", + "items": { + "$ref": "#/components/schemas/address" + } + } + } + }, + { + "name": "eth_blobBaseFee", + "summary": "Returns the base fee per blob gas in wei.", + "params": [], + "result": { + "name": "Blob gas base fee", + "schema": { + "title": "Blob gas base fee", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_blockNumber", + "summary": "Returns the number of most recent block.", + "params": [], + "result": { + "name": "Block number", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_call", + "summary": "Executes a new message call immediately without creating a transaction on the block chain.", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/GenericTransaction" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Return data", + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + }, + { + "name": "eth_chainId", + "summary": "Returns the chain ID of the current network.", + "params": [], + "result": { + "name": "Chain ID", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_coinbase", + "summary": "Returns the client coinbase address.", + "params": [], + "result": { + "name": "Coinbase address", + "schema": { + "$ref": "#/components/schemas/address" + } + } + }, + { + "name": "eth_createAccessList", + "summary": "Generates an access list for a transaction.", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/GenericTransaction" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Gas used", + "schema": { + "title": "Access list result", + "type": "object", + "additionalProperties": false, + "properties": { + "accessList": { + "title": "accessList", + "$ref": "#/components/schemas/AccessList" + }, + "error": { + "title": "error", + "type": "string" + }, + "gasUsed": { + "title": "Gas used", + "$ref": "#/components/schemas/uint" + } + } + } + } + }, + { + "name": "eth_estimateGas", + "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/GenericTransaction" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Gas used", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_feeHistory", + "summary": "Transaction fee history", + "description": "Returns transaction base fee per gas and effective priority fee per gas for the requested/supported block range.", + "params": [ + { + "name": "blockCount", + "description": "Requested range of blocks. Clients will return less than the requested range if not all blocks are available.", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint" + } + }, + { + "name": "newestBlock", + "description": "Highest block of the requested range.", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + }, + { + "name": "rewardPercentiles", + "description": "A monotonically increasing list of percentile values. For each block in the requested range, the transactions will be sorted in ascending order by effective tip per gas and the coresponding effective tip for the percentile will be determined, accounting for gas consumed.", + "required": true, + "schema": { + "title": "rewardPercentiles", + "type": "array", + "items": { + "title": "rewardPercentile", + "description": "Floating point value between 0 and 100.", + "type": "number" + } + } + } + ], + "result": { + "name": "feeHistoryResult", + "description": "Fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available.", + "schema": { + "title": "feeHistoryResults", + "description": "Fee history results.", + "type": "object", + "required": [ + "oldestBlock", + "baseFeePerGas", + "gasUsedRatio" + ], + "additionalProperties": false, + "properties": { + "oldestBlock": { + "title": "oldestBlock", + "description": "Lowest number block of returned range.", + "$ref": "#/components/schemas/uint" + }, + "baseFeePerGas": { + "title": "baseFeePerGasArray", + "description": "An array of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks.", + "type": "array", + "items": { + "$ref": "#/components/schemas/uint" + } + }, + "baseFeePerBlobGas": { + "title": "baseFeePerBlobGasArray", + "description": "An array of block base fees per blob gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-4844 blocks.", + "type": "array", + "items": { + "$ref": "#/components/schemas/uint" + } + }, + "gasUsedRatio": { + "title": "gasUsedRatio", + "description": "An array of block gas used ratios. These are calculated as the ratio of gasUsed and gasLimit.", + "type": "array", + "items": { + "$ref": "#/components/schemas/ratio" + } + }, + "blobGasUsedRatio": { + "title": "blobGasUsedRatio", + "description": "An array of block blob gas used ratios. These are calculated as the ratio of blobGasUsed and the max blob gas per block.", + "type": "array", + "items": { + "$ref": "#/components/schemas/ratio" + } + }, + "reward": { + "title": "rewardArray", + "description": "A two-dimensional array of effective priority fees per gas at the requested block percentiles.", + "type": "array", + "items": { + "title": "rewardPercentile", + "description": "An array of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty.", + "type": "array", + "items": { + "title": "rewardPercentile", + "description": "A given percentile sample of effective priority fees per gas from a single block in ascending order, weighted by gas used. Zeroes are returned if the block is empty.", + "$ref": "#/components/schemas/uint" + } + } + } + } + } + } + }, + { + "name": "eth_gasPrice", + "summary": "Returns the current price per gas in wei.", + "params": [], + "result": { + "name": "Gas price", + "schema": { + "title": "Gas price", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getBalance", + "summary": "Returns the balance of the account of given address.", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Balance", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getBlockByHash", + "summary": "Returns information about a block by hash.", + "params": [ + { + "name": "Block hash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "name": "Hydrated transactions", + "required": true, + "schema": { + "title": "hydrated", + "type": "boolean" + } + } + ], + "result": { + "name": "Block information", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "$ref": "#/components/schemas/Block" + } + ] + } + } + }, + { + "name": "eth_getBlockByNumber", + "summary": "Returns information about a block by number.", + "params": [ + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + }, + { + "name": "Hydrated transactions", + "required": true, + "schema": { + "title": "hydrated", + "type": "boolean" + } + } + ], + "result": { + "name": "Block information", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "$ref": "#/components/schemas/Block" + } + ] + } + } + }, + { + "name": "eth_getBlockReceipts", + "summary": "Returns the receipts of a block by number or hash.", + "params": [ + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Receipts information", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "title": "Receipts information", + "type": "array", + "items": { + "$ref": "#/components/schemas/ReceiptInfo" + } + } + ] + } + } + }, + { + "name": "eth_getBlockTransactionCountByHash", + "summary": "Returns the number of transactions in a block from a block matching the given block hash.", + "params": [ + { + "name": "Block hash", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + ], + "result": { + "name": "Transaction count", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "title": "Transaction count", + "$ref": "#/components/schemas/uint" + } + ] + } + } + }, + { + "name": "eth_getBlockTransactionCountByNumber", + "summary": "Returns the number of transactions in a block matching the given block number.", + "params": [ + { + "name": "Block", + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Transaction count", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "title": "Transaction count", + "$ref": "#/components/schemas/uint" + } + ] + } + } + }, + { + "name": "eth_getCode", + "summary": "Returns code at a given address.", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Bytecode", + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + }, + { + "name": "eth_getFilterChanges", + "summary": "Polling method for a filter, which returns an array of logs which occurred since last poll.", + "params": [ + { + "name": "Filter Identifier", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + ], + "result": { + "name": "Log objects", + "schema": { + "$ref": "#/components/schemas/FilterResults" + } + } + }, + { + "name": "eth_getFilterLogs", + "summary": "Returns an array of all logs matching filter with given id.", + "params": [ + { + "name": "Filter Identifier", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + ], + "result": { + "name": "Log objects", + "schema": { + "$ref": "#/components/schemas/FilterResults" + } + } + }, + { + "name": "eth_getLogs", + "summary": "Returns an array of all logs matching filter with given id.", + "params": [ + { + "name": "Filter", + "schema": { + "$ref": "#/components/schemas/Filter" + } + } + ], + "result": { + "name": "Log objects", + "schema": { + "$ref": "#/components/schemas/FilterResults" + } + } + }, + { + "name": "eth_getProof", + "summary": "Returns the merkle proof for a given account and optionally some storage keys.", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "StorageKeys", + "required": true, + "schema": { + "title": "Storage keys", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytesMax32" + } + } + }, + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Account", + "schema": { + "$ref": "#/components/schemas/AccountProof" + } + } + }, + { + "name": "eth_getStorageAt", + "summary": "Returns the value from a storage position at a given address.", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Storage slot", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint256" + } + }, + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Value", + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + }, + { + "name": "eth_getTransactionByBlockHashAndIndex", + "summary": "Returns information about a transaction by block hash and transaction index position.", + "params": [ + { + "name": "Block hash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "name": "Transaction index", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint" + } + } + ], + "result": { + "name": "Transaction information", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "$ref": "#/components/schemas/TransactionInfo" + } + ] + } + } + }, + { + "name": "eth_getTransactionByBlockNumberAndIndex", + "summary": "Returns information about a transaction by block number and transaction index position.", + "params": [ + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + }, + { + "name": "Transaction index", + "required": true, + "schema": { + "$ref": "#/components/schemas/uint" + } + } + ], + "result": { + "name": "Transaction information", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "$ref": "#/components/schemas/TransactionInfo" + } + ] + } + } + }, + { + "name": "eth_getTransactionByHash", + "summary": "Returns the information about a transaction requested by transaction hash.", + "params": [ + { + "name": "Transaction hash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + ], + "result": { + "name": "Transaction information", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "$ref": "#/components/schemas/TransactionInfo" + } + ] + } + } + }, + { + "name": "eth_getTransactionCount", + "summary": "Returns the number of transactions sent from an address.", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Block", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTagOrHash" + } + } + ], + "result": { + "name": "Transaction count", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_getTransactionReceipt", + "summary": "Returns the receipt of a transaction by transaction hash.", + "params": [ + { + "name": "Transaction hash", + "required": true, + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + ], + "result": { + "name": "Receipt information", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "$ref": "#/components/schemas/ReceiptInfo" + } + ] + } + } + }, + { + "name": "eth_getUncleCountByBlockHash", + "summary": "Returns the number of uncles in a block from a block matching the given block hash.", + "params": [ + { + "name": "Block hash", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + ], + "result": { + "name": "Uncle count", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "title": "Uncle count", + "$ref": "#/components/schemas/uint" + } + ] + } + } + }, + { + "name": "eth_getUncleCountByBlockNumber", + "summary": "Returns the number of transactions in a block matching the given block number.", + "params": [ + { + "name": "Block", + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Uncle count", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/notFound" + }, + { + "title": "Uncle count", + "$ref": "#/components/schemas/uint" + } + ] + } + } + }, + { + "name": "eth_maxPriorityFeePerGas", + "summary": "Returns the current maxPriorityFeePerGas per gas in wei.", + "params": [], + "result": { + "name": "Max priority fee per gas", + "schema": { + "title": "Max priority fee per gas", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_newBlockFilter", + "summary": "Creates a filter in the node, to notify when a new block arrives.", + "params": [], + "result": { + "name": "Filter Identifier", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_newFilter", + "summary": "Creates a filter object, based on filter options, to notify when the state changes (logs).", + "params": [ + { + "name": "Filter", + "schema": { + "$ref": "#/components/schemas/Filter" + } + } + ], + "result": { + "name": "Filter Identifier", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_newPendingTransactionFilter", + "summary": "Creates a filter in the node, to notify when new pending transactions arrive.", + "params": [], + "result": { + "name": "Filter Identifier", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + }, + { + "name": "eth_sendRawTransaction", + "summary": "Submits a raw transaction. For EIP-4844 transactions, the raw form must be the network form. This means it includes the blobs, KZG commitments, and KZG proofs.", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + ], + "result": { + "name": "Transaction hash", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + }, + { + "name": "eth_sendTransaction", + "summary": "Signs and submits a transaction.", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/GenericTransaction" + } + } + ], + "result": { + "name": "Transaction hash", + "schema": { + "$ref": "#/components/schemas/hash32" + } + } + }, + { + "name": "eth_sign", + "summary": "Returns an EIP-191 signature over the provided data.", + "params": [ + { + "name": "Address", + "required": true, + "schema": { + "$ref": "#/components/schemas/address" + } + }, + { + "name": "Message", + "required": true, + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + ], + "result": { + "name": "Signature", + "schema": { + "$ref": "#/components/schemas/bytes65" + } + } + }, + { + "name": "eth_signTransaction", + "summary": "Returns an RLP encoded transaction signed by the specified account.", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/GenericTransaction" + } + } + ], + "result": { + "name": "Encoded transaction", + "schema": { + "$ref": "#/components/schemas/bytes" + } + } + }, + { + "name": "eth_syncing", + "summary": "Returns an object with data about the sync status or false.", + "params": [], + "result": { + "name": "Syncing status", + "schema": { + "$ref": "#/components/schemas/SyncingStatus" + } + } + }, + { + "name": "eth_uninstallFilter", + "summary": "Uninstalls a filter with given id.", + "params": [ + { + "name": "Filter Identifier", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + ], + "result": { + "name": "Success", + "schema": { + "type": "boolean" + } + } + } + ], + "components": { + "schemas": { + "address": { + "title": "hex encoded address", + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" + }, + "addresses": { + "title": "hex encoded address", + "type": "array", + "items": { + "$ref": "#/components/schemas/address" + } + }, + "byte": { + "title": "hex encoded byte", + "type": "string", + "pattern": "^0x([0-9a-fA-F]?){1,2}$" + }, + "bytes": { + "title": "hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]*$" + }, + "bytesMax32": { + "title": "32 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{0,64}$" + }, + "bytes8": { + "title": "8 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{16}$" + }, + "bytes32": { + "title": "32 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "bytes48": { + "title": "48 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{96}$" + }, + "bytes96": { + "title": "96 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{192}$" + }, + "bytes256": { + "title": "256 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{512}$" + }, + "bytes65": { + "title": "65 hex encoded bytes", + "type": "string", + "pattern": "^0x[0-9a-f]{130}$" + }, + "ratio": { + "title": "normalized ratio", + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "uint": { + "title": "hex encoded unsigned integer", + "type": "string", + "pattern": "^0x([1-9a-f]+[0-9a-f]*|0)$" + }, + "uint64": { + "title": "hex encoded 64 bit unsigned integer", + "type": "string", + "pattern": "^0x([1-9a-f]+[0-9a-f]{0,15})|0$" + }, + "uint256": { + "title": "hex encoded 256 bit unsigned integer", + "type": "string", + "pattern": "^0x([1-9a-f]+[0-9a-f]{0,31})|0$" + }, + "hash32": { + "title": "32 byte hex value", + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "notFound": { + "title": "Not Found (null)", + "type": "null" + }, + "Block": { + "title": "Block object", + "type": "object", + "required": [ + "hash", + "parentHash", + "sha3Uncles", + "miner", + "stateRoot", + "transactionsRoot", + "receiptsRoot", + "logsBloom", + "number", + "gasLimit", + "gasUsed", + "timestamp", + "extraData", + "mixHash", + "nonce", + "size", + "transactions", + "uncles" + ], + "additionalProperties": false, + "properties": { + "hash": { + "title": "Hash", + "$ref": "#/components/schemas/hash32" + }, + "parentHash": { + "title": "Parent block hash", + "$ref": "#/components/schemas/hash32" + }, + "sha3Uncles": { + "title": "Ommers hash", + "$ref": "#/components/schemas/hash32" + }, + "miner": { + "title": "Coinbase", + "$ref": "#/components/schemas/address" + }, + "stateRoot": { + "title": "State root", + "$ref": "#/components/schemas/hash32" + }, + "transactionsRoot": { + "title": "Transactions root", + "$ref": "#/components/schemas/hash32" + }, + "receiptsRoot": { + "title": "Receipts root", + "$ref": "#/components/schemas/hash32" + }, + "logsBloom": { + "title": "Bloom filter", + "$ref": "#/components/schemas/bytes256" + }, + "difficulty": { + "title": "Difficulty", + "$ref": "#/components/schemas/uint" + }, + "number": { + "title": "Number", + "$ref": "#/components/schemas/uint" + }, + "gasLimit": { + "title": "Gas limit", + "$ref": "#/components/schemas/uint" + }, + "gasUsed": { + "title": "Gas used", + "$ref": "#/components/schemas/uint" + }, + "timestamp": { + "title": "Timestamp", + "$ref": "#/components/schemas/uint" + }, + "extraData": { + "title": "Extra data", + "$ref": "#/components/schemas/bytes" + }, + "mixHash": { + "title": "Mix hash", + "$ref": "#/components/schemas/hash32" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/bytes8" + }, + "totalDifficulty": { + "title": "Total difficulty", + "$ref": "#/components/schemas/uint" + }, + "baseFeePerGas": { + "title": "Base fee per gas", + "$ref": "#/components/schemas/uint" + }, + "withdrawalsRoot": { + "title": "Withdrawals root", + "$ref": "#/components/schemas/hash32" + }, + "blobGasUsed": { + "title": "Blob gas used", + "$ref": "#/components/schemas/uint" + }, + "excessBlobGas": { + "title": "Excess blob gas", + "$ref": "#/components/schemas/uint" + }, + "parentBeaconBlockRoot": { + "title": "Parent Beacon Block Root", + "$ref": "#/components/schemas/hash32" + }, + "size": { + "title": "Block size", + "$ref": "#/components/schemas/uint" + }, + "transactions": { + "anyOf": [ + { + "title": "Transaction hashes", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "title": "Full transactions", + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionInfo" + } + } + ] + }, + "withdrawals": { + "title": "Withdrawals", + "type": "array", + "items": { + "$ref": "#/components/schemas/Withdrawal" + } + }, + "uncles": { + "title": "Uncles", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + } + } + }, + "BlockTag": { + "title": "Block tag", + "type": "string", + "enum": [ + "earliest", + "finalized", + "safe", + "latest", + "pending" + ], + "description": "`earliest`: The lowest numbered block the client has available; `finalized`: The most recent crypto-economically secure block, cannot be re-orged outside of manual intervention driven by community coordination; `safe`: The most recent block that is safe from re-orgs under honest majority and certain synchronicity assumptions; `latest`: The most recent block in the canonical chain observed by the client, this block may be re-orged out of the canonical chain even under healthy/normal conditions; `pending`: A sample next block built by the client on top of `latest` and containing the set of transactions usually taken from local mempool. Before the merge transition is finalized, any call querying for `finalized` or `safe` block MUST be responded to with `-39001: Unknown block` error" + }, + "BlockNumberOrTag": { + "title": "Block number or tag", + "oneOf": [ + { + "title": "Block number", + "$ref": "#/components/schemas/uint" + }, + { + "title": "Block tag", + "$ref": "#/components/schemas/BlockTag" + } + ] + }, + "BlockNumberOrTagOrHash": { + "title": "Block number, tag, or block hash", + "anyOf": [ + { + "title": "Block number", + "$ref": "#/components/schemas/uint" + }, + { + "title": "Block tag", + "$ref": "#/components/schemas/BlockTag" + }, + { + "title": "Block hash", + "$ref": "#/components/schemas/hash32" + } + ] + }, + "BadBlock": { + "title": "Bad block", + "type": "object", + "required": [ + "block", + "hash", + "rlp" + ], + "additionalProperties": false, + "properties": { + "block": { + "title": "Block", + "$ref": "#/components/schemas/Block" + }, + "hash": { + "title": "Hash", + "$ref": "#/components/schemas/hash32" + }, + "rlp": { + "title": "RLP", + "$ref": "#/components/schemas/bytes" + } + } + }, + "SyncingStatus": { + "title": "Syncing status", + "oneOf": [ + { + "title": "Syncing progress", + "type": "object", + "additionalProperties": false, + "properties": { + "startingBlock": { + "title": "Starting block", + "$ref": "#/components/schemas/uint" + }, + "currentBlock": { + "title": "Current block", + "$ref": "#/components/schemas/uint" + }, + "highestBlock": { + "title": "Highest block", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "title": "Not syncing", + "description": "Should always return false if not syncing.", + "type": "boolean" + } + ] + }, + "FilterResults": { + "title": "Filter results", + "oneOf": [ + { + "title": "new block or transaction hashes", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + { + "title": "new logs", + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + } + } + ] + }, + "Filter": { + "title": "filter", + "type": "object", + "additionalProperties": false, + "properties": { + "fromBlock": { + "title": "from block", + "$ref": "#/components/schemas/uint" + }, + "toBlock": { + "title": "to block", + "$ref": "#/components/schemas/uint" + }, + "address": { + "title": "Address(es)", + "oneOf": [ + { + "title": "Any Address", + "type": "null" + }, + { + "title": "Address", + "$ref": "#/components/schemas/address" + }, + { + "title": "Addresses", + "$ref": "#/components/schemas/addresses" + } + ] + }, + "topics": { + "title": "Topics", + "$ref": "#/components/schemas/FilterTopics" + } + } + }, + "FilterTopics": { + "title": "Filter Topics", + "oneOf": [ + { + "title": "Any Topic Match", + "type": "null" + }, + { + "title": "Specified Filter Topics", + "type": "array", + "items": { + "$ref": "#/components/schemas/FilterTopic" + } + } + ] + }, + "FilterTopic": { + "title": "Filter Topic List Entry", + "oneOf": [ + { + "title": "Single Topic Match", + "$ref": "#/components/schemas/bytes32" + }, + { + "title": "Multiple Topic Match", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytes32" + } + } + ] + }, + "Log": { + "title": "log", + "type": "object", + "required": [ + "transactionHash" + ], + "additionalProperties": false, + "properties": { + "removed": { + "title": "removed", + "type": "boolean" + }, + "logIndex": { + "title": "log index", + "$ref": "#/components/schemas/uint" + }, + "transactionIndex": { + "title": "transaction index", + "$ref": "#/components/schemas/uint" + }, + "transactionHash": { + "title": "transaction hash", + "$ref": "#/components/schemas/hash32" + }, + "blockHash": { + "title": "block hash", + "$ref": "#/components/schemas/hash32" + }, + "blockNumber": { + "title": "block number", + "$ref": "#/components/schemas/uint" + }, + "address": { + "title": "address", + "$ref": "#/components/schemas/address" + }, + "data": { + "title": "data", + "$ref": "#/components/schemas/bytes" + }, + "topics": { + "title": "topics", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytes32" + } + } + } + }, + "ReceiptInfo": { + "type": "object", + "title": "Receipt information", + "required": [ + "blockHash", + "blockNumber", + "from", + "cumulativeGasUsed", + "gasUsed", + "logs", + "logsBloom", + "transactionHash", + "transactionIndex", + "effectiveGasPrice" + ], + "additionalProperties": false, + "properties": { + "type": { + "title": "type", + "$ref": "#/components/schemas/byte" + }, + "transactionHash": { + "title": "transaction hash", + "$ref": "#/components/schemas/hash32" + }, + "transactionIndex": { + "title": "transaction index", + "$ref": "#/components/schemas/uint" + }, + "blockHash": { + "title": "block hash", + "$ref": "#/components/schemas/hash32" + }, + "blockNumber": { + "title": "block number", + "$ref": "#/components/schemas/uint" + }, + "from": { + "title": "from", + "$ref": "#/components/schemas/address" + }, + "to": { + "title": "to", + "description": "Address of the receiver or null in a contract creation transaction.", + "oneOf": [ + { + "title": "Contract Creation (null)", + "type": "null" + }, + { + "title": "Recipient Address", + "$ref": "#/components/schemas/address" + } + ] + }, + "cumulativeGasUsed": { + "title": "cumulative gas used", + "description": "The sum of gas used by this transaction and all preceding transactions in the same block.", + "$ref": "#/components/schemas/uint" + }, + "gasUsed": { + "title": "gas used", + "description": "The amount of gas used for this specific transaction alone.", + "$ref": "#/components/schemas/uint" + }, + "blobGasUsed": { + "title": "blob gas used", + "description": "The amount of blob gas used for this specific transaction. Only specified for blob transactions as defined by EIP-4844.", + "$ref": "#/components/schemas/uint" + }, + "contractAddress": { + "title": "contract address", + "description": "The contract address created, if the transaction was a contract creation, otherwise null.", + "oneOf": [ + { + "$ref": "#/components/schemas/address" + }, + { + "title": "Null", + "type": "null" + } + ] + }, + "logs": { + "title": "logs", + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + } + }, + "logsBloom": { + "title": "logs bloom", + "$ref": "#/components/schemas/bytes256" + }, + "root": { + "title": "state root", + "description": "The post-transaction state root. Only specified for transactions included before the Byzantium upgrade.", + "$ref": "#/components/schemas/hash32" + }, + "status": { + "title": "status", + "description": "Either 1 (success) or 0 (failure). Only specified for transactions included after the Byzantium upgrade.", + "$ref": "#/components/schemas/uint" + }, + "effectiveGasPrice": { + "title": "effective gas price", + "description": "The actual value per gas deducted from the sender's account. Before EIP-1559, this is equal to the transaction's gas price. After, it is equal to baseFeePerGas + min(maxFeePerGas - baseFeePerGas, maxPriorityFeePerGas).", + "$ref": "#/components/schemas/uint" + }, + "blobGasPrice": { + "title": "blob gas price", + "description": "The actual value per gas deducted from the sender's account for blob gas. Only specified for blob transactions as defined by EIP-4844.", + "$ref": "#/components/schemas/uint" + } + } + }, + "AccountProof": { + "title": "Account proof", + "type": "object", + "required": [ + "address", + "accountProof", + "balance", + "codeHash", + "nonce", + "storageHash", + "storageProof" + ], + "additionalProperties": false, + "properties": { + "address": { + "title": "address", + "$ref": "#/components/schemas/address" + }, + "accountProof": { + "title": "accountProof", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytes" + } + }, + "balance": { + "title": "balance", + "$ref": "#/components/schemas/uint256" + }, + "codeHash": { + "title": "codeHash", + "$ref": "#/components/schemas/hash32" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint64" + }, + "storageHash": { + "title": "storageHash", + "$ref": "#/components/schemas/hash32" + }, + "storageProof": { + "title": "Storage proofs", + "type": "array", + "items": { + "$ref": "#/components/schemas/StorageProof" + } + } + } + }, + "StorageProof": { + "title": "Storage proof", + "type": "object", + "required": [ + "key", + "value", + "proof" + ], + "additionalProperties": false, + "properties": { + "key": { + "title": "key", + "$ref": "#/components/schemas/bytesMax32" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint256" + }, + "proof": { + "title": "proof", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytes" + } + } + } + }, + "Transaction4844Unsigned": { + "type": "object", + "title": "EIP-4844 transaction.", + "required": [ + "type", + "nonce", + "to", + "gas", + "value", + "input", + "maxPriorityFeePerGas", + "maxFeePerGas", + "maxFeePerBlobGas", + "accessList", + "blobVersionedHashes", + "chainId" + ], + "properties": { + "type": { + "title": "type", + "type": "string", + "pattern": "^0x3$" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint" + }, + "to": { + "title": "to address", + "$ref": "#/components/schemas/address" + }, + "gas": { + "title": "gas limit", + "$ref": "#/components/schemas/uint" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint" + }, + "input": { + "title": "input data", + "$ref": "#/components/schemas/bytes" + }, + "maxPriorityFeePerGas": { + "title": "max priority fee per gas", + "description": "Maximum fee per gas the sender is willing to pay to miners in wei", + "$ref": "#/components/schemas/uint" + }, + "maxFeePerGas": { + "title": "max fee per gas", + "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", + "$ref": "#/components/schemas/uint" + }, + "maxFeePerBlobGas": { + "title": "max fee per blob gas", + "description": "The maximum total fee per gas the sender is willing to pay for blob gas in wei", + "$ref": "#/components/schemas/uint" + }, + "accessList": { + "title": "accessList", + "description": "EIP-2930 access list", + "$ref": "#/components/schemas/AccessList" + }, + "blobVersionedHashes": { + "title": "blobVersionedHashes", + "description": "List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + "chainId": { + "title": "chainId", + "description": "Chain ID that this transaction is valid on.", + "$ref": "#/components/schemas/uint" + } + } + }, + "AccessListEntry": { + "title": "Access list entry", + "type": "object", + "additionalProperties": false, + "required": [ "address", "storageKeys" ], + "properties": { + "address": { + "$ref": "#/components/schemas/address" + }, + "storageKeys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + } + } + }, + "AccessList": { + "title": "Access list", + "type": "array", + "items": { + "$ref": "#/components/schemas/AccessListEntry" + } + }, + "Transaction1559Unsigned": { + "type": "object", + "title": "EIP-1559 transaction.", + "required": [ + "type", + "nonce", + "gas", + "value", + "input", + "maxFeePerGas", + "maxPriorityFeePerGas", + "gasPrice", + "chainId", + "accessList" + ], + "properties": { + "type": { + "title": "type", + "type": "string", + "pattern": "^0x2$" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint" + }, + "to": { + "title": "to address", + "oneOf": [ + { + "title": "Contract Creation (null)", + "type": "null" + }, + { + "title": "Address", + "$ref": "#/components/schemas/address" + } + ] + }, + "gas": { + "title": "gas limit", + "$ref": "#/components/schemas/uint" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint" + }, + "input": { + "title": "input data", + "$ref": "#/components/schemas/bytes" + }, + "maxPriorityFeePerGas": { + "title": "max priority fee per gas", + "description": "Maximum fee per gas the sender is willing to pay to miners in wei", + "$ref": "#/components/schemas/uint" + }, + "maxFeePerGas": { + "title": "max fee per gas", + "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", + "$ref": "#/components/schemas/uint" + }, + "gasPrice": { + "title": "gas price", + "description": "The effective gas price paid by the sender in wei. For transactions not yet included in a block, this value should be set equal to the max fee per gas. This field is DEPRECATED, please transition to using effectiveGasPrice in the receipt object going forward.", + "$ref": "#/components/schemas/uint" + }, + "accessList": { + "title": "accessList", + "description": "EIP-2930 access list", + "$ref": "#/components/schemas/AccessList" + }, + "chainId": { + "title": "chainId", + "description": "Chain ID that this transaction is valid on.", + "$ref": "#/components/schemas/uint" + } + } + }, + "Transaction2930Unsigned": { + "type": "object", + "title": "EIP-2930 transaction.", + "required": [ + "type", + "nonce", + "gas", + "value", + "input", + "gasPrice", + "chainId", + "accessList" + ], + "properties": { + "type": { + "title": "type", + "type": "string", + "pattern": "^0x1$" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint" + }, + "to": { + "title": "to address", + "oneOf": [ + { + "title": "Contract Creation (null)", + "type": "null" + }, + { + "title": "Address", + "$ref": "#/components/schemas/address" + } + ] + }, + "gas": { + "title": "gas limit", + "$ref": "#/components/schemas/uint" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint" + }, + "input": { + "title": "input data", + "$ref": "#/components/schemas/bytes" + }, + "gasPrice": { + "title": "gas price", + "description": "The gas price willing to be paid by the sender in wei", + "$ref": "#/components/schemas/uint" + }, + "accessList": { + "title": "accessList", + "description": "EIP-2930 access list", + "$ref": "#/components/schemas/AccessList" + }, + "chainId": { + "title": "chainId", + "description": "Chain ID that this transaction is valid on.", + "$ref": "#/components/schemas/uint" + } + } + }, + "TransactionLegacyUnsigned": { + "type": "object", + "title": "Legacy transaction.", + "required": [ + "type", + "nonce", + "gas", + "value", + "input", + "gasPrice" + ], + "properties": { + "type": { + "title": "type", + "type": "string", + "pattern": "^0x0$" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint" + }, + "to": { + "title": "to address", + "oneOf": [ + { + "title": "Contract Creation (null)", + "type": "null" + }, + { + "title": "Address", + "$ref": "#/components/schemas/address" + } + ] + }, + "gas": { + "title": "gas limit", + "$ref": "#/components/schemas/uint" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint" + }, + "input": { + "title": "input data", + "$ref": "#/components/schemas/bytes" + }, + "gasPrice": { + "title": "gas price", + "description": "The gas price willing to be paid by the sender in wei", + "$ref": "#/components/schemas/uint" + }, + "chainId": { + "title": "chainId", + "description": "Chain ID that this transaction is valid on.", + "$ref": "#/components/schemas/uint" + } + } + }, + "TransactionUnsigned": { + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction4844Unsigned" + }, + { + "$ref": "#/components/schemas/Transaction1559Unsigned" + }, + { + "$ref": "#/components/schemas/Transaction2930Unsigned" + }, + { + "$ref": "#/components/schemas/TransactionLegacyUnsigned" + } + ] + }, + "Transaction4844Signed": { + "title": "Signed 4844 Transaction", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/Transaction4844Unsigned" + }, + { + "title": "EIP-4844 transaction signature properties.", + "required": [ + "r", + "s" + ], + "properties": { + "yParity": { + "title": "yParity", + "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", + "$ref": "#/components/schemas/uint" + }, + "r": { + "title": "r", + "$ref": "#/components/schemas/uint" + }, + "s": { + "title": "s", + "$ref": "#/components/schemas/uint" + } + } + } + ] + }, + "Transaction1559Signed": { + "title": "Signed 1559 Transaction", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/Transaction1559Unsigned" + }, + { + "title": "EIP-1559 transaction signature properties.", + "required": [ + "r", + "s" + ], + "properties": { + "yParity": { + "title": "yParity", + "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", + "$ref": "#/components/schemas/uint" + }, + "v": { + "title": "v", + "description": "For backwards compatibility, `v` is optionally provided as an alternative to `yParity`. This field is DEPRECATED and all use of it should migrate to `yParity`.", + "$ref": "#/components/schemas/uint" + }, + "r": { + "title": "r", + "$ref": "#/components/schemas/uint" + }, + "s": { + "title": "s", + "$ref": "#/components/schemas/uint" + } + } + } + ] + }, + "Transaction2930Signed": { + "title": "Signed 2930 Transaction", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/Transaction2930Unsigned" + }, + { + "title": "EIP-2930 transaction signature properties.", + "required": [ + "yParity", + "r", + "s" + ], + "properties": { + "yParity": { + "title": "yParity", + "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", + "$ref": "#/components/schemas/uint" + }, + "v": { + "title": "v", + "description": "For backwards compatibility, `v` is optionally provided as an alternative to `yParity`. This field is DEPRECATED and all use of it should migrate to `yParity`.", + "$ref": "#/components/schemas/uint" + }, + "r": { + "title": "r", + "$ref": "#/components/schemas/uint" + }, + "s": { + "title": "s", + "$ref": "#/components/schemas/uint" + } + } + } + ] + }, + "TransactionLegacySigned": { + "title": "Signed Legacy Transaction", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/TransactionLegacyUnsigned" + }, + { + "title": "Legacy transaction signature properties.", + "required": [ + "v", + "r", + "s" + ], + "properties": { + "v": { + "title": "v", + "$ref": "#/components/schemas/uint" + }, + "r": { + "title": "r", + "$ref": "#/components/schemas/uint" + }, + "s": { + "title": "s", + "$ref": "#/components/schemas/uint" + } + } + } + ] + }, + "TransactionSigned": { + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction4844Signed" + }, + { + "$ref": "#/components/schemas/Transaction1559Signed" + }, + { + "$ref": "#/components/schemas/Transaction2930Signed" + }, + { + "$ref": "#/components/schemas/TransactionLegacySigned" + } + ] + }, + "TransactionInfo": { + "type": "object", + "title": "Transaction information", + "allOf": [ + { + "title": "Contextual information", + "required": [ + "blockHash", + "blockNumber", + "from", + "hash", + "transactionIndex" + ], + "unevaluatedProperties": false, + "properties": { + "blockHash": { + "title": "block hash", + "$ref": "#/components/schemas/hash32" + }, + "blockNumber": { + "title": "block number", + "$ref": "#/components/schemas/uint" + }, + "from": { + "title": "from address", + "$ref": "#/components/schemas/address" + }, + "hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/hash32" + }, + "transactionIndex": { + "title": "transaction index", + "$ref": "#/components/schemas/uint" + } + } + }, + { + "$ref": "#/components/schemas/TransactionSigned" + } + ] + }, + "GenericTransaction": { + "type": "object", + "title": "Transaction object generic to all types", + "additionalProperties": false, + "properties": { + "type": { + "title": "type", + "$ref": "#/components/schemas/byte" + }, + "nonce": { + "title": "nonce", + "$ref": "#/components/schemas/uint" + }, + "to": { + "title": "to address", + "oneOf": [ + { + "title": "Contract Creation (null)", + "type": "null" + }, + { + "title": "Address", + "$ref": "#/components/schemas/address" + } + ] + }, + "from": { + "title": "from address", + "$ref": "#/components/schemas/address" + }, + "gas": { + "title": "gas limit", + "$ref": "#/components/schemas/uint" + }, + "value": { + "title": "value", + "$ref": "#/components/schemas/uint" + }, + "input": { + "title": "input data", + "$ref": "#/components/schemas/bytes" + }, + "gasPrice": { + "title": "gas price", + "description": "The gas price willing to be paid by the sender in wei", + "$ref": "#/components/schemas/uint" + }, + "maxPriorityFeePerGas": { + "title": "max priority fee per gas", + "description": "Maximum fee per gas the sender is willing to pay to miners in wei", + "$ref": "#/components/schemas/uint" + }, + "maxFeePerGas": { + "title": "max fee per gas", + "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", + "$ref": "#/components/schemas/uint" + }, + "maxFeePerBlobGas": { + "title": "max fee per blob gas", + "description": "The maximum total fee per gas the sender is willing to pay for blob gas in wei", + "$ref": "#/components/schemas/uint" + }, + "accessList": { + "title": "accessList", + "description": "EIP-2930 access list", + "$ref": "#/components/schemas/AccessList" + }, + "blobVersionedHashes": { + "title": "blobVersionedHashes", + "description": "List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.", + "type": "array", + "items": { + "$ref": "#/components/schemas/hash32" + } + }, + "blobs": { + "title": "blobs", + "description": "Raw blob data.", + "type": "array", + "items": { + "$ref": "#/components/schemas/bytes" + } + }, + "chainId": { + "title": "chainId", + "description": "Chain ID that this transaction is valid on.", + "$ref": "#/components/schemas/uint" + } + } + }, + "Withdrawal": { + "type": "object", + "title": "Validator withdrawal", + "required": [ + "index", + "validatorIndex", + "address", + "amount" + ], + "additionalProperties": false, + "properties": { + "index": { + "title": "index of withdrawal", + "$ref": "#/components/schemas/uint64" + }, + "validatorIndex": { + "title": "index of validator that generated withdrawal", + "$ref": "#/components/schemas/uint64" + }, + "address": { + "title": "recipient address for withdrawal value", + "$ref": "#/components/schemas/address" + }, + "amount": { + "title": "value contained in withdrawal", + "$ref": "#/components/schemas/uint256" + } + } + } + } + } +} diff --git a/substrate/frame/revive/rpc/codegen/src/LICENSE.txt b/substrate/frame/revive/rpc/codegen/src/LICENSE.txt new file mode 100644 index 000000000000..ecd364a6d62e --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/src/LICENSE.txt @@ -0,0 +1,16 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/substrate/frame/revive/rpc/codegen/src/generator.rs b/substrate/frame/revive/rpc/codegen/src/generator.rs new file mode 100644 index 000000000000..6419a994b6d3 --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/src/generator.rs @@ -0,0 +1,740 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use inflector::Inflector; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + mem, + sync::LazyLock, +}; + +use crate::{ + open_rpc::*, + printer::{ + doc_str_from_schema, Fields, Required, TypeContent, TypeInfo, TypeNameProvider, + TypePrinter, Variants, + }, + writeln, +}; + +pub const LICENSE: &str = include_str!("LICENSE.txt"); + +/// List of supported Ethereum RPC methods we want to generate. +pub static SUPPORTED_ETH_METHODS: LazyLock> = LazyLock::new(|| { + vec![ + "eth_accounts", + "eth_blockNumber", + "eth_call", + "eth_chainId", + "eth_estimateGas", + "eth_gasPrice", + "eth_getBalance", + "eth_getBlockByHash", + "eth_getBlockByNumber", + "eth_getBlockTransactionCountByHash", + "eth_getBlockTransactionCountByNumber", + "eth_getCode", + "eth_getStorageAt", + "eth_getTransactionByBlockHashAndIndex", + "eth_getTransactionByBlockNumberAndIndex", + "eth_getTransactionByHash", + "eth_getTransactionCount", + "eth_getTransactionReceipt", + "eth_sendRawTransaction", + "eth_sendTransaction", + "eth_syncing", + "net_version", + ] +}); + +/// Mapping of primitive schema types to their Rust counterparts. +pub static PRIMITIVE_MAPPINGS: LazyLock> = + LazyLock::new(|| { + HashMap::from([ + ("#/components/schemas/address", "Address"), + ("#/components/schemas/byte", "Byte"), + ("#/components/schemas/bytes", "Bytes"), + ("#/components/schemas/bytes256", "Bytes256"), + ("#/components/schemas/hash32", "H256"), + ("#/components/schemas/bytes32", "H256"), + ("#/components/schemas/bytes8", "Bytes8"), + ("#/components/schemas/uint", "U256"), + ("#/components/schemas/uint256", "U256"), + ("#/components/schemas/uint64", "U256"), + ]) + }); + +/// Mapping of legacy aliases to their new names. +pub static LEGACY_ALIASES: LazyLock>> = + LazyLock::new(|| { + HashMap::from([ + // We accept "data" and "input" for backwards-compatibility reasons. + // Issue detail: https://github.com/ethereum/go-ethereum/issues/15628 + ("#/components/schemas/GenericTransaction", HashMap::from([("input", "data")])), + ]) + }); + +/// Read the OpenRPC specs, and inject extra methods and legacy aliases. +pub fn read_specs() -> anyhow::Result { + let content = include_str!("../openrpc.json"); + let mut specs: OpenRpc = serde_json::from_str(content)?; + + // Inject legacy aliases. + inject_legacy_aliases(&mut specs); + + // Inject extra methods. + specs.methods.push(RefOr::Inline(Method { + name: "net_version".to_string(), + summary: Some("The string value of current network id".to_string()), + result: Some(RefOr::Reference { reference: "String".to_string() }), + ..Default::default() + })); + + Ok(specs) +} + +// Inject legacy aliases declared by [`LEGACY_ALIASES`]. +pub fn inject_legacy_aliases(specs: &mut OpenRpc) { + for (alias, mapping) in LEGACY_ALIASES.iter() { + let schema = specs.get_schema_mut(alias).unwrap(); + match &mut schema.contents { + SchemaContents::Object(o) | SchemaContents::Literal(Literal::Object(o)) => { + o.legacy_aliases = + mapping.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(); + }, + _ => { + panic!("Alias should be an object got {:?} instead", schema.contents); + }, + } + } +} + +/// Format the given code using rustfmt. +pub fn format_code(code: &str) -> anyhow::Result { + use std::{io::Write, process::*}; + let mut rustfmt = Command::new("rustup") + .args(["run", "nightly", "rustfmt"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + + let stdin = rustfmt.stdin.as_mut().expect("Failed to open stdin"); + stdin.write_all(code.as_bytes())?; + + let output = rustfmt.wait_with_output()?; + if !output.status.success() { + anyhow::bail!("rustfmt failed: {}", String::from_utf8_lossy(&output.stderr)); + } + + let formatted_code = String::from_utf8_lossy(&output.stdout).to_string(); + Ok(formatted_code) +} + +/// Type generator for generating RPC methods and types. +#[derive(Default)] +pub struct TypeGenerator { + /// List of collected types, that are not yet generated. + collected: BTreeMap, + /// List of already generated types. + generated: HashSet, + /// List of filtered method names, we want to generate. + filtered_method_names: HashSet, + /// Stripped prefix for the generated method names. + prefix: String, +} + +/// Reference or schema +pub enum ReferenceOrSchema { + // A reference to a schema such as `#/components/schemas/Foo`. + Reference(String), + // A schema definition. + Schema(Schema), +} + +impl ReferenceOrSchema { + /// Return the schema for the reference or the schema itself. + fn schema<'a>(&'a self, specs: &'a OpenRpc) -> &'a Schema { + match self { + Self::Schema(schema) => schema, + Self::Reference(reference) => specs.get_schema(reference).unwrap(), + } + } +} + +impl TypeGenerator { + /// Create a new type generator. + pub fn new() -> Self { + let mut generated = + HashSet::from_iter(["notFound"].into_iter().map(|name| name.to_pascal_case())); + + generated.extend(PRIMITIVE_MAPPINGS.keys().map(|name| reference_to_name(name))); + generated.extend(PRIMITIVE_MAPPINGS.values().map(|name| name.to_string())); + let filtered_method_names = + SUPPORTED_ETH_METHODS.iter().map(|name| name.to_string()).collect(); + + Self { + collected: Default::default(), + filtered_method_names, + generated, + prefix: "eth".to_string(), + } + } + + /// Generate the RPC method, and add the collected types. + pub fn generate_rpc_methods(&mut self, specs: &OpenRpc) -> String { + let methods = specs + .methods + .iter() + .map(RefOr::unwrap_inline) + .filter(|method| self.filtered_method_names.contains(&method.name)) + .collect::>(); + + if methods.len() != self.filtered_method_names.len() { + let available = + methods.iter().map(|method| method.name.clone()).collect::>(); + let missing = self.filtered_method_names.difference(&available).collect::>(); + panic!("Missing methods: {missing:?}"); + } + + let mut code = LICENSE.to_string(); + code.push_str( + r#" + //! Generated JSON-RPC methods. + #![allow(missing_docs)] + + use super::*; + use jsonrpsee::core::RpcResult; + use jsonrpsee::proc_macros::rpc; + + #[rpc(server, client)] + pub trait EthRpc { + "#, + ); + + for method in methods { + self.generate_rpc_method(&mut code, method); + code.push('\n'); + } + code.push('}'); + code.push('\n'); + code + } + + pub fn collect_extra_type(&mut self, type_name: &str) { + self.collect( + type_name, + ReferenceOrSchema::Reference(format!("#/components/schemas/{}", type_name)), + ); + } + + /// Recursively collect the types and generate them. + /// + /// Note: This should be called after [`TypeGenerator::generate_rpc_methods`] to collect the + /// types used in the RPC methods. + pub fn generate_types(&mut self, specs: &OpenRpc) -> String { + let mut code = LICENSE.to_string(); + code.push_str( + r#" + //! Generated JSON-RPC types. + #![allow(missing_docs)] + + use super::{byte::*, Type0, Type1, Type2, Type3}; + use alloc::vec::Vec; + use codec::{Decode, Encode}; + use derive_more::{From, TryInto}; + pub use ethereum_types::*; + use scale_info::TypeInfo; + use serde::{Deserialize, Serialize}; + + "#, + ); + loop { + let collected = mem::take(&mut self.collected); + self.generated.extend(collected.keys().cloned()); + + if collected.is_empty() { + break; + } + + for (name, ref_or_schema) in collected { + let r#type = self.generate_type(name, ref_or_schema.schema(specs)); + r#type.print(&mut code); + code.push('\n'); + } + } + + code + } + + /// Return the type printer for the given schema. + fn generate_type(&mut self, name: String, schema: &Schema) -> TypePrinter { + let doc = doc_str_from_schema(schema); + + let content = match &schema.contents { + &SchemaContents::Literal(Literal::Object(ref o)) | &SchemaContents::Object(ref o) => + TypeContent::Struct(Fields::from(o, self)), + SchemaContents::AllOf { all_of } => + TypeContent::Struct(Fields::from_all_of(all_of, self)), + &SchemaContents::AnyOf { any_of: ref items } | + &SchemaContents::OneOf { one_of: ref items } => + TypeContent::Enum(Variants::from_one_of(items, self)), + &SchemaContents::Literal(Literal::Array(ArrayLiteral { items: Some(ref schema) })) => { + let mut type_info = + self.type_info(schema).expect("Anonymous array type not supported"); + type_info.array = true; + + TypeContent::TypeAlias(type_info) + }, + &SchemaContents::Literal(Literal::String(StringLiteral { + min_length: None, + max_length: None, + pattern: None, + format: None, + enumeration: Some(ref enumeration), + })) => TypeContent::UntaggedEnum(enumeration.clone()), + v => { + panic!("Unsupported type {name} {v:#?}") + }, + }; + + TypePrinter { name, doc, content } + } + + fn generate_rpc_method(&mut self, buffer: &mut String, method: &Method) { + let Method { ref summary, ref name, ref params, ref result, .. } = method; + writeln!(@doc buffer, summary); + + let result = result + .as_ref() + .map(|content| match content { + RefOr::Inline(descriptor) => self + .type_info(&descriptor.schema) + .expect("Result type should be defined") + .get_type(), + RefOr::Reference { reference } => reference.clone(), + }) + .unwrap_or("()".to_string()); + + let parameters = params + .iter() + .map(RefOr::unwrap_inline) + .map(|ContentDescriptor { name, required, schema, .. }| { + let name_arg = name.to_snake_case().replace(' ', "_"); + let name_type = self + .type_info(schema) + .expect("Parameter type should be defined") + .set_required(*required) + .get_type(); + format!("{name_arg}: {name_type}") + }) + .collect::>() + .join(", "); + + writeln!(buffer, "#[method(name = \"{name}\")]"); + let method_name = name.trim_start_matches(&self.prefix).to_snake_case(); + writeln!(buffer, "async fn {method_name}(&self, {parameters}) -> RpcResult<{result}>;"); + } + + /// Collect the type if it's not yet generated or collected. + fn collect(&mut self, type_name: &str, ref_or_schema: ReferenceOrSchema) { + if !self.generated.contains(type_name) && !self.collected.contains_key(type_name) { + self.collected.insert(type_name.to_string(), ref_or_schema); + } + } +} + +/// Convert a reference to a type name. +fn reference_to_name(reference: &str) -> String { + if PRIMITIVE_MAPPINGS.contains_key(reference) { + return PRIMITIVE_MAPPINGS[reference].to_string(); + } + reference.split('/').last().unwrap().to_pascal_case() +} + +impl TypeNameProvider for TypeGenerator { + fn record_inline_type(&mut self, type_name: String, schema: &Schema) -> TypeInfo { + self.collect(&type_name, ReferenceOrSchema::Schema(schema.clone())); + TypeInfo { name: type_name, required: Required::Yes, array: false } + } + + fn type_info(&mut self, schema: &Schema) -> Option { + match &schema.contents { + SchemaContents::Reference { reference } => { + let type_name = reference_to_name(reference); + self.collect(&type_name, ReferenceOrSchema::Reference(reference.to_string())); + Some(type_name.into()) + }, + SchemaContents::Literal(Literal::Array(ArrayLiteral { items: Some(ref schema) })) => { + let mut type_info = + self.type_info(schema).expect("Anonymous array type not supported"); + type_info.array = true; + Some(type_info) + }, + SchemaContents::AllOf { all_of } => Some( + all_of + .iter() + .map(|s| self.type_info(s).expect("Anonymous all_of type not supported").name) + .collect::>() + .join("And") + .into(), + ), + SchemaContents::AnyOf { any_of: ref items } | + SchemaContents::OneOf { one_of: ref items } => { + let mut required = Required::Yes; + let items = items + .iter() + .filter_map(|s| { + let info = self.type_info(s).expect("Anonymous any_of type not supported"); + let name = info.name; + + if name == "Null" || name == "NotFound" { + required = Required::No { skip_if_null: false }; + None + } else { + Some(name) + } + }) + .collect::>(); + + let name = items.join("Or"); + if items.len() > 1 { + self.collect(&name, ReferenceOrSchema::Schema(schema.clone())); + } + + Some(TypeInfo { name, required, array: false }) + }, + SchemaContents::Literal(Literal::Null) => Some("Null".into()), + + // Use Type0, Type1, Type2, ... for String that have a single digit pattern. + SchemaContents::Literal(Literal::String(StringLiteral { + min_length: None, + max_length: None, + pattern: Some(ref pattern), + format: None, + enumeration: None, + })) if ["^0x0$", "^0x1$", "^0x2$", "^0x3$"].contains(&pattern.as_str()) => { + let type_id = format!("Type{}", &pattern[3..4]); + + Some(type_id.into()) + }, + + SchemaContents::Literal(Literal::Boolean) => Some("bool".into()), + SchemaContents::Object(_) => None, + SchemaContents::Literal(Literal::Object(_)) => None, + v => { + panic!("No type name for {v:#?}"); + }, + } + } +} + +#[cfg(test)] +pub fn assert_code_match(expected: &str, actual: &str) { + assert_eq!(format_code(expected).unwrap().trim(), format_code(actual).unwrap().trim()); +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn generate_works() { + let specs = read_specs().unwrap(); + + let mut generator = TypeGenerator::new(); + SUPPORTED_ETH_METHODS.iter().for_each(|name| { + generator.filtered_method_names.insert(name.to_string()); + }); + + let buffer = generator.generate_rpc_methods(&specs); + println!("{}", buffer); + } + + #[test] + fn generate_rpc_works() { + let method = serde_json::from_str::( + r###" + { + "name": "eth_estimateGas", + "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", + "params": [ + { + "name": "Transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/GenericTransaction" + } + }, + { + "name": "Block", + "required": false, + "schema": { + "$ref": "#/components/schemas/BlockNumberOrTag" + } + } + ], + "result": { + "name": "Gas used", + "schema": { + "$ref": "#/components/schemas/uint" + } + } + } + "###, + ) + .unwrap(); + + let mut buffer = String::new(); + let mut generator = TypeGenerator::new(); + + generator.generate_rpc_method(&mut buffer, &method); + assert_code_match( + &buffer, + r#" + /// Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. + #[method(name = "eth_estimateGas")] + async fn estimate_gas(&self, transaction: GenericTransaction, block: Option) -> RpcResult; + "#, + ); + } + + #[test] + fn generate_type_name_works() { + let mut generator = TypeGenerator::new(); + + let schema: Schema = serde_json::from_str( + r###" + { + "title": "to address", + "oneOf": [ + { "title": "Contract Creation (null)", "type": "null" }, + { "title": "Address", "$ref": "#/components/schemas/address" } + ] + } + "###, + ) + .unwrap(); + + assert_eq!(&generator.type_info(&schema).unwrap().get_type(), "Option
"); + } + + #[test] + fn generate_all_off_type_works() { + let specs = read_specs().unwrap(); + let mut generator = TypeGenerator::new(); + let res = generator.generate_type( + "Transaction4844Signed".to_string(), + specs.get_schema("#/components/schemas/Transaction4844Signed").unwrap(), + ); + let mut buffer = String::new(); + res.print(&mut buffer); + assert_code_match( + &buffer, + r#" + /// Signed 4844 Transaction + #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] + pub struct Transaction4844Signed { + #[serde(flatten)] + pub transaction_4844_unsigned: Transaction4844Unsigned, + /// r + pub r: U256, + /// s + pub s: U256, + /// yParity + /// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature. + #[serde(rename = "yParity", skip_serializing_if = "Option::is_none")] + pub y_parity: Option, + } + "#, + ); + } + + #[test] + fn generate_one_of_type_works() { + let specs = read_specs().unwrap(); + let mut generator = TypeGenerator::new(); + let res = generator.generate_type( + "TransactionUnsigned".to_string(), + specs.get_schema("#/components/schemas/TransactionUnsigned").unwrap(), + ); + let mut buffer = String::new(); + res.print(&mut buffer); + assert_code_match( + &buffer, + r#" + #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)] + #[serde(untagged)] + pub enum TransactionUnsigned { + Transaction4844Unsigned(Transaction4844Unsigned), + Transaction1559Unsigned(Transaction1559Unsigned), + Transaction2930Unsigned(Transaction2930Unsigned), + TransactionLegacyUnsigned(TransactionLegacyUnsigned), + } + impl Default for TransactionUnsigned { + fn default() -> Self { + TransactionUnsigned::Transaction4844Unsigned(Default::default()) + } + } + "#, + ); + } + + #[test] + fn generate_type_with_inline_variant_works() { + let specs = read_specs().unwrap(); + let mut generator = TypeGenerator::new(); + let res = generator.generate_type( + "SyncingStatus".to_string(), + specs.get_schema("#/components/schemas/SyncingStatus").unwrap(), + ); + let mut buffer = String::new(); + res.print(&mut buffer); + + assert_code_match( + &buffer, + r#" + /// Syncing status + #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)] + #[serde(untagged)] + pub enum SyncingStatus { + /// Syncing progress + SyncingProgress(SyncingProgress), + /// Not syncing + /// Should always return false if not syncing. + Bool(bool), + } + impl Default for SyncingStatus { + fn default() -> Self { + SyncingStatus::SyncingProgress(Default::default()) + } + } + "#, + ); + } + + #[test] + fn generate_array_type_works() { + let specs = read_specs().unwrap(); + let mut generator = TypeGenerator::new(); + let res = generator.generate_type( + "AccessList".to_string(), + specs.get_schema("#/components/schemas/AccessList").unwrap(), + ); + let mut buffer = String::new(); + res.print(&mut buffer); + assert_code_match( + &buffer, + r#" + /// Access list + pub type AccessList = Vec; + "#, + ); + } + + #[test] + fn generate_one_of_with_null_variant_works() { + let specs = read_specs().unwrap(); + let mut generator = TypeGenerator::new(); + let res = generator.generate_type( + "FilterTopics".to_string(), + specs.get_schema("#/components/schemas/FilterTopics").unwrap(), + ); + let mut buffer = String::new(); + res.print(&mut buffer); + assert_code_match( + &buffer, + r#" + /// Filter Topics + pub type FilterTopics = Vec; + "#, + ); + } + + #[test] + fn generate_object_type_works() { + let specs = read_specs().unwrap(); + let mut generator = TypeGenerator::new(); + let res = generator.generate_type( + "Transaction".to_string(), + specs.get_schema("#/components/schemas/GenericTransaction").unwrap(), + ); + + let mut buffer = String::new(); + res.print(&mut buffer); + assert_code_match( + &buffer, + r#" + /// Transaction object generic to all types + #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] + pub struct Transaction { + /// accessList + /// EIP-2930 access list + #[serde(rename = "accessList", skip_serializing_if = "Option::is_none")] + pub access_list: Option, + /// blobVersionedHashes + /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. + #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Option::is_none")] + pub blob_versioned_hashes: Option>, + /// blobs + /// Raw blob data. + #[serde(skip_serializing_if = "Option::is_none")] + pub blobs: Option>, + /// chainId + /// Chain ID that this transaction is valid on. + #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] + pub chain_id: Option, + /// from address + #[serde(skip_serializing_if = "Option::is_none")] + pub from: Option
, + /// gas limit + #[serde(skip_serializing_if = "Option::is_none")] + pub gas: Option, + /// gas price + /// The gas price willing to be paid by the sender in wei + #[serde(rename = "gasPrice", skip_serializing_if = "Option::is_none")] + pub gas_price: Option, + /// input data + #[serde(alias = "data", skip_serializing_if = "Option::is_none")] + pub input: Option, + /// max fee per blob gas + /// The maximum total fee per gas the sender is willing to pay for blob gas in wei + #[serde(rename = "maxFeePerBlobGas", skip_serializing_if = "Option::is_none")] + pub max_fee_per_blob_gas: Option, + /// max fee per gas + /// The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei + #[serde(rename = "maxFeePerGas", skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, + /// max priority fee per gas + /// Maximum fee per gas the sender is willing to pay to miners in wei + #[serde(rename = "maxPriorityFeePerGas", skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, + /// nonce + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + /// to address + pub to: Option
, + /// type + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, + /// value + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + } + "#, + ); + } +} diff --git a/substrate/frame/revive/rpc/codegen/src/main.rs b/substrate/frame/revive/rpc/codegen/src/main.rs new file mode 100644 index 000000000000..e0b660ea84e5 --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/src/main.rs @@ -0,0 +1,64 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::generator::{format_code, TypeGenerator}; +use anyhow::Context; +use std::path::Path; + +mod generator; +mod open_rpc; +mod printer; + +fn main() -> anyhow::Result<()> { + let specs = generator::read_specs()?; + + let mut generator = TypeGenerator::new(); + generator.collect_extra_type("TransactionUnsigned"); + + let out_dir = if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { + Path::new(&dir).join("../src") + } else { + "../src".into() + } + .canonicalize() + .with_context(|| "Failed to find the api directory")?; + + let out = out_dir.join("rpc_methods_gen.rs"); + println!("Generating rpc_methods at {out:?}"); + format_and_write_file(&out, &generator.generate_rpc_methods(&specs)) + .with_context(|| format!("Failed to generate code to {out:?}"))?; + + let out_dir = if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { + Path::new(&dir).join("../../src/evm/api") + } else { + "../../src/evm/api".into() + } + .canonicalize() + .with_context(|| "Failed to find the api directory")?; + + let out = std::fs::canonicalize(out_dir.join("rpc_types_gen.rs"))?; + println!("Generating rpc_types at {out:?}"); + format_and_write_file(&out, &generator.generate_types(&specs)) + .with_context(|| format!("Failed to generate code to {out:?}"))?; + + Ok(()) +} + +fn format_and_write_file(path: &Path, content: &str) -> anyhow::Result<()> { + let code = format_code(content)?; + std::fs::write(path, code).expect("Unable to write file"); + Ok(()) +} diff --git a/substrate/frame/revive/rpc/codegen/src/open_rpc.rs b/substrate/frame/revive/rpc/codegen/src/open_rpc.rs new file mode 100644 index 000000000000..fa7510a50561 --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/src/open_rpc.rs @@ -0,0 +1,834 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! Defines the types defined by the [`OpenRPC`](https://spec.open-rpc.org) specification. + +#![warn(missing_docs, missing_debug_implementations)] + +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; + +/// Represents an OpenRPC document. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct OpenRpc { + /// The semantic version number of the OpenRPC Specification version that the OpenRPC document + /// uses. + /// + /// This field should be used by tooling specifications and clients to interpret the OpenRPC + /// document. + pub openrpc: String, + /// Provides metadata about the API. + /// + /// This metadata may be used by tooling as required. + pub info: Info, + /// An array of [`Server`] objects, which provide connectivity information to a target server. + /// + /// If the `servers` property is not provided, or is an empty array, the default value would + /// be a [`Server`] with a `url` value of `localhost`. This is taken care of by the + /// [`open-rpc`](crate) crate. + #[serde(default = "serde_fns::servers")] + pub servers: Vec, + /// The available methods for the API. While this field is required, it is legal to leave it + /// empty. + pub methods: Vec>, + /// Holds various schemas for the specification. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub components: Option, + /// Contains additional documentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_docs: Option, +} + +impl OpenRpc { + /// Returns the [`Method`] with the given path reference. + /// + /// # Examples + /// + /// ```no_run + /// let path = "#/components/schemas/MY_SCHEMA"; + /// let schema = openrpc.get_schema(path).unwrap(); + /// ``` + pub fn get_schema(&self, reference: &str) -> Option<&Schema> { + let mut components = reference.split('/'); + + if !matches!(components.next(), Some("#")) { + return None; + } + + if !matches!(components.next(), Some("components")) { + return None; + } + + if !matches!(components.next(), Some("schemas")) { + return None; + } + + let name = components.next()?; + self.components.as_ref()?.schemas.get(name) + } + + /// Same as [`OpenRpc::get_schema`] but returns a &mut reference + pub fn get_schema_mut(&mut self, reference: &str) -> Option<&mut Schema> { + let mut components = reference.split('/'); + + if !matches!(components.next(), Some("#")) { + return None; + } + + if !matches!(components.next(), Some("components")) { + return None; + } + + if !matches!(components.next(), Some("schemas")) { + return None; + } + + let name = components.next()?; + self.components.as_mut()?.schemas.get_mut(name) + } +} + +/// Provides metadata about the API. +/// +/// The metadata may be used by clients if needed, and may be presented in editing or +/// documentation generation tools for convenience. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Info { + /// The title of the application. + #[serde(default)] + pub title: String, + /// A verbose description of the application. + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// A URL to the Terms of Service for the API. + /// + /// This must contain an URL. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub terms_of_service: Option, + /// contact information for the exposed API. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub contact: Option, + /// License information for the exposed API. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub license: Option, + /// The version of the OpenRPC document. + /// + /// Note that this is distinct from the `openrpc` field of [`OpenRpc`] which specifies the + /// version of the OpenRPC Specification used. + #[serde(default)] + pub version: String, +} + +/// Contact information for the exposed API. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Contact { + /// The identifying name of the contact person/organization. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + /// The URL pointing to the contact information. + /// + /// This must contain an URL. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + /// The email address of the contact person/organization. + /// + /// This must contain an email address. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, +} + +/// License information for the exposed API. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct License { + /// The name of the license used for the API. + #[serde(default)] + pub name: String, + /// The URL pointing to the license used for the API. + /// + /// This must contain an URL. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +/// A server. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Server { + /// A name to be used as the canonical name for the server. + #[serde(default)] + pub name: String, + /// A URL to the target host. + /// + /// This URL supports Server Variables and may be relative to indicate that the host location + /// is relative to the location where the OpenRPC document is being served. + /// + /// Server Variables are passed into the Runtime Expression to produce a server URL. + pub url: RuntimeExpression, + /// A short description of what the server is. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// Describes the host designated by the URL. + /// + /// GitHub Flavored Markdown may be used for rich text presentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// The values of this object are passed to the [`RuntimeExpression`] to produce an actual + /// URL. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub variables: BTreeMap, +} + +/// An object representing a Server Variable for server URL template substitution. +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[serde(rename_all = "camelCase")] +pub struct ServerVariable { + /// An enumeration of string values to be used if the substitution options are from a limited + /// set. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub enum_: Vec, + /// The default value to use for substitution, which shall be sent if an alternate value is + /// not supplied. + /// + /// Note this behavior is different than the Schema Object's treatment of default values, + /// because in those cases parameter values are optional. + #[serde(default)] + pub default: String, + /// An optional description for the server variable. + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +/// Describes the interface for the given method name. +/// +/// The method name is used as the `method` field of the JSON-RPC body. It therefore must be +/// unique. +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Method { + /// The canonical name of the method. + /// + /// This name must be unique within the methods array. + #[serde(default)] + pub name: String, + /// A list of tags for API documentation control. Tags can be used for logical grouping + /// of methods by resources or any other qualifier. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub tags: Vec>, + /// A short summary of what the method does. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// A verbose explanation of the method behavior. + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Additional external documentation for this method. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_docs: Option, + /// A list of parameters that are applicable for this method. + /// + /// The list must not include duplicated parameters and therefore require `name` to be + /// unique. + /// + /// All required parameters must be listed *before* any optional parameters. + #[serde(default)] + pub params: Vec>, + /// The description of the result returned by the method. + /// + /// If defined, it must be a [`ContentDescriptor`] or a Reference. + /// + /// If undefined, the method must only be used as a *notification*. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub result: Option>, + /// Declares this method as deprecated. + /// + /// Consumers should refrain from usage of the declared method. + /// + /// The default value is `false`. + #[serde(default, skip_serializing_if = "serde_fns::is_false")] + pub deprecated: bool, + /// An alternative `servers` array to service this method. + /// + /// If specified, it overrides the `servers` array defined at the root level. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub servers: Option>, + /// A list of custom application-defined errors that may be returned. + /// + /// The errors must have unique error codes. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub errors: Vec>, + /// A list of possible links from this method call. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub links: Vec>, + /// The expected format of the parameters. + /// + /// The parameters of a method may be an array, an object, or either. When a method + /// has a `param_structure` value of [`ByName`], callers of the method must pass an + /// object as the parameters. When a method has a `param_structure` value of [`ByPosition`], + /// callers of the method must pass an array as the parameters. Otherwise, callers may + /// pass either an array or an object as the parameters. + /// + /// The default value is [`Either`]. + /// + /// [`ByName`]: ParamStructure::ByName + /// [`ByPosition`]: ParamStructure::ByPosition + /// [`Either`]: ParamStructure::Either + #[serde(default, skip_serializing_if = "serde_fns::is_default")] + pub param_structure: ParamStructure, + /// An array of [`ExamplePairing`] objects, where each example includes a valid + /// params-to-result [`ContentDescriptor`] pairing. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub examples: Vec>, +} + +/// A possible value for the `param_structure` field of [`Method`]. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)] +#[serde(rename_all = "kebab-case")] +pub enum ParamStructure { + /// Parameters must be passed as a JSON object. + ByName, + /// Parameters must be passed as a JSON array. + ByPosition, + /// Parameters may be passed as either a JSON object or a JSON array. + #[default] + Either, +} + +/// Content descriptors are that do just as they suggest - describe content. They are reusable +/// ways of describing either parameters or results. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ContentDescriptor { + /// The name of the content being described. + /// + /// If the content described is a method parameter assignable + /// [`ByName`](ParamStructure::ByName), this field must be the name of the parameter. + #[serde(default)] + pub name: String, + /// A short summary of the content that is being described. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// A verbose explanation of the content being described. + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Determines if the content is a required field. + /// + /// Default is `false`. + #[serde(default, skip_serializing_if = "serde_fns::is_false")] + pub required: bool, + /// A [`Schema`] that describes what is allowed in the content. + #[serde(default)] + pub schema: Schema, + /// Whether the content is deprecated. + /// + /// Default is `false`. + #[serde(default, skip_serializing_if = "serde_fns::is_false")] + pub deprecated: bool, +} + +/// Allows the definition of input and output data types. +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[serde(rename_all = "camelCase")] +pub struct Schema { + /// The title of the schema. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub title: Option, + /// The description of the schema. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// The contents of the schema. + #[serde(flatten)] + pub contents: SchemaContents, +} + +/// The content of a schema. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum SchemaContents { + /// The schema contains a reference to another schema. + Reference { + /// The reference string. + #[serde(rename = "$ref")] + reference: String, + }, + /// The schema is made of a combination of other schemas. + /// + /// The final object must match *all* of the schemas. + AllOf { + /// The schemas that the final object must match. + #[serde(rename = "allOf")] + all_of: Vec, + }, + /// The schema is made of a combination of other schemas. + /// + /// The final object must match *any* of the schemas. + AnyOf { + /// The schemas that the final object must match. + #[serde(rename = "anyOf")] + any_of: Vec, + }, + /// The schema is made of a combination of other schemas. + /// + /// The final object must match exactly *one* of the schemas. + OneOf { + /// The schemas that the final object must match. + #[serde(rename = "oneOf")] + one_of: Vec, + }, + /// The schema contains a literal value. + Literal(Literal), + /// The schema contains an Object. + /// + /// Note this is a workaround to parse Literal(Literal::ObjectLiteral), that don't havethe + /// type: "object" field. + Object(ObjectLiteral), +} + +impl Default for SchemaContents { + #[inline] + fn default() -> Self { + Self::Literal(Literal::Null) + } +} + +/// A literal value. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum Literal { + /// The literal is a boolean. + Boolean, + /// The literal is an integer. + Integer(IntegerLiteral), + /// The literal is a number. + Number(NumberLiteral), + /// The literal is a string. + String(StringLiteral), + // The literal is an object. + Object(ObjectLiteral), + /// The literal is an array. + Array(ArrayLiteral), + /// The literal is a null value. + Null, +} + +/// The constraints that may be applied to an integer literal schema. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone)] +pub struct IntegerLiteral { + /// The integer must be a multiple of this value. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub multiple_of: Option, + /// The minimum value of the integer. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub minimum: Option, + /// The maximum value of the integer. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub maximum: Option, + /// Whether the minimum value is exclusive. + /// + /// Default is `false`. + #[serde(default, skip_serializing_if = "serde_fns::is_false")] + pub exclusive_minimum: bool, + /// Whether the maximum value is exclusive. + /// + /// Default is `false`. + #[serde(default, skip_serializing_if = "serde_fns::is_false")] + pub exclusive_maximum: bool, +} + +/// The constraints that may be applied to a number literal schema. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct NumberLiteral { + /// The number must be a multiple of this value. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub multiple_of: Option, + /// The minimum value of the number. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub minimum: Option, + /// The maximum value of the number. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub maximum: Option, + /// Whether the minimum value is exclusive. + /// + /// Default is `false`. + #[serde(default, skip_serializing_if = "serde_fns::is_false")] + pub exclusive_minimum: bool, + /// Whether the maximum value is exclusive. + /// + /// Default is `false`. + #[serde(default, skip_serializing_if = "serde_fns::is_false")] + pub exclusive_maximum: bool, +} + +/// The constraints that may be applied to an array literal schema. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ArrayLiteral { + /// The schema that the items in the array must match. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub items: Option>, +} + +/// The constraints that may be applied to an string literal schema. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct StringLiteral { + /// The minimum length of the string. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub min_length: Option, + /// The maximum length of the string.s + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_length: Option, + /// The pattern that the string must match. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pattern: Option, + /// The format that the string must be in. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub format: Option, + /// A list of possible values for the string. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "enum")] + pub enumeration: Option>, +} + +/// A string format. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[serde(rename_all = "kebab-case")] +pub enum StringFormat { + /// Date and time together, for example, `2018-11-13T20:20:39+00:00`. + DateTime, + /// Time, for example, `20:20:39+00:00`. + Time, + /// Date, for example, `2018-11-13`. + Date, + /// A duration as defined by the [ISO 8601 ABNF](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A). + Duration, + /// An email. See [RFC 5321](http://tools.ietf.org/html/rfc5321#section-4.1.2). + Email, + /// The internationalized version of an email. See [RFC 6531](https://tools.ietf.org/html/rfc6531). + IdnEmail, + /// A host name. See [RFC 1123](https://datatracker.ietf.org/doc/html/rfc1123#section-2.1). + Hostname, + /// The internationalized version of a host name. See [RFC 5890](https://tools.ietf.org/html/rfc5890#section-2.3.2.3). + IdnHostname, + /// An IP v4. See [RFC 2673](http://tools.ietf.org/html/rfc2673#section-3.2). + #[serde(rename = "ipv4")] + IpV4, + /// An IP v6. See [RFC 2373](http://tools.ietf.org/html/rfc2373#section-2.2). + #[serde(rename = "ipv6")] + IpV6, + /// A universally unique identifier. See [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122). + Uuid, + /// A universal resource identifier . See [RFC 3986](http://tools.ietf.org/html/rfc3986). + Uri, + /// A URI reference. See (RFC 3986)[]. + UriReference, + /// The internationalized version of a URI. See [RFC 3987](https://tools.ietf.org/html/rfc3987). + Iri, + /// The internationalized version of a URI reference. See [RFC 3987](https://tools.ietf.org/html/rfc3987). + IriReference, + /// A URI template. See [RFC 6570](https://tools.ietf.org/html/rfc6570). + UriTemplate, + /// A JSON pointer. See [RFC 6901](https://tools.ietf.org/html/rfc6901). + JsonPointer, + /// A relative JSON pointer. See [Relative JSON Pointer](https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01). + RelativeJsonPointer, + /// A regular expression. See [ECMA 262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). + Regex, +} + +/// The constraints that may be applied to an object literal schema. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ObjectLiteral { + /// The properties that the object might have. + pub properties: BTreeMap, + + /// List of legacy aliases for properties. + #[serde(skip)] + pub legacy_aliases: HashMap, + + /// A list of properties that the object must have. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub required: Vec, +} + +/// A set of example parameters and a result. +/// +/// This result is what you'd expect from the JSON-RPC service given the exact params. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ExamplePairing { + /// The name for the example pairing. + #[serde(default)] + pub name: String, + /// A verbose description of the example pairing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// A short summary of the example pairing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// Example parameters. + #[serde(default)] + pub params: Vec>, + /// Example result. + /// + /// When undefined, shows the usage of the method as a notification. + #[serde(default)] + pub result: RefOr, +} + +/// Defines an example that is intended to match a [`Schema`] of a given [`ContentDescriptor`]. +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ExampleObject { + /// Canonical name of the example. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + /// A verbose description of the example + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// A short summary of the example. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// The value of the example. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +/// The example value of an [`ExampleObject`]. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ExampleValue { + /// The value is a JSON object embedded in the document. + /// A link to an external document containing the value. + #[serde(rename = "externalValue")] + External(String), +} + +/// Represents a possible design-time link for a result. +/// +/// The presence of a link does not guarantee the caller's ability to successfully invoke it, +/// rather it provides a known relationship and traversal mechanism between results and other +/// methods. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone)] +pub struct Link { + /// Canonical name for the link. + #[serde(default)] + pub name: String, + /// A description of the link. + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Short description for the link. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// The name of an *existing*, resolvable OpenRPC method, as defined with a unique + /// `method`. This field must resolve to a unique [`Method`] object. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub method: Option, + /// The parameters to pass to a method as specified with `method`. The key is the parameter + /// name to be used, whereas the value can be a constant or a [`RuntimeExpression`] to be + /// evaluated and passed to the linked method. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub params: Option, + /// A server object to be used by the target method. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server: Option, +} + +/// The content of the `params` field of a [`Link`]. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum LinkParams { + /// A [`RuntimeExpression`] that evaluates to the parameters. + Dynamic(RuntimeExpression), +} + +/// Runtime expressions allow the user to define an expression which will evaluate to a +/// string once the desired value(s) are known. +/// +/// They are used when the desired value of a link or server can only be constructed at +/// run time. This mechanism is used by [`Link`] objects and [`ServerVariable`]s. +/// +/// This runtime expression makes use of JSON template strings. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(transparent)] +pub struct RuntimeExpression(pub String); + +/// An application-level error. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Error { + /// An application-defined error code. + #[serde(default)] + pub code: i64, + /// A string providing a short description of the error. + /// + /// The message should be limited to a concise single sentence. + #[serde(default)] + pub message: String, +} + +/// Holds a set of reusable objects for different aspects of the OpenRPC document. +/// +/// All objects defined within the [`Components`] object will have no effect on the API +/// unless they are explicitly referenced from properties outside of the [`Components`] +/// object. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Components { + /// A list of reusable [`ContentDescriptor`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub content_descriptors: BTreeMap, + /// A list of reusable [`Schema`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub schemas: BTreeMap, + /// A list of reusable [`ExampleObject`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub examples: BTreeMap, + /// A list of reusable [`Link`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub links: BTreeMap, + /// A list of reusable [`Error`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub errors: BTreeMap, + /// A list of reusable [`ExamplePairing`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "examplePairingObjects")] + pub example_pairings: BTreeMap, + /// A list of reusable [`Tag`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub tags: BTreeMap, +} + +/// Adds metadata to a single tag that is used by the [`Method`] Object. +/// +/// It is not mandatory to have a [`Tag`] Object per tag defined in the [`Method`] +/// Object instances. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Tag { + /// The name of the tag. + #[serde(default)] + pub name: String, + /// A short summary of the tag. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// A verbose explanation of the tag. + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Additional external documentation for this tag. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_docs: Option, +} + +/// Allows referencing an external resource for extended documentation. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ExternalDocumentation { + /// A verbose explanation of the target documentation. + /// + /// GitHub Flavored Markdown syntax may be used for rich text representation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// A URL for the target documentation. + /// + /// This must contain an URL. + #[serde(default)] + pub url: String, +} + +/// Either a reference or an inline object. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum RefOr { + /// A reference to an object defined elsewhere. + Reference { + /// The reference string. + #[serde(rename = "$ref")] + reference: String, + }, + /// An inline object. + Inline(T), +} + +impl RefOr { + /// Unwraps the inlined object. + pub fn unwrap_inline(&self) -> &T { + match self { + RefOr::Reference { reference } => panic!("Unexpected reference: {reference}"), + RefOr::Inline(v) => v, + } + } +} + +impl Default for RefOr { + #[inline] + fn default() -> Self { + RefOr::Inline(T::default()) + } +} + +/// Functions used by `serde`, such as predicates and default values. +mod serde_fns { + use std::collections::BTreeMap; + + use super::{RuntimeExpression, Server}; + + /// Returns the default value of the `servers` field. + pub fn servers() -> Vec { + vec![Server { + name: "default".into(), + url: RuntimeExpression("localhost".into()), + summary: None, + description: None, + variables: BTreeMap::new(), + }] + } + + /// Returns whether `b` is `false`. + pub fn is_false(b: &bool) -> bool { + !*b + } + + /// Returns whether the given value is the default value of its type. + pub fn is_default(t: &T) -> bool { + *t == T::default() + } +} + +#[test] +fn parsing_works() { + let content = include_str!("../openrpc.json"); + let _: OpenRpc = dbg!(serde_json::from_str(content).unwrap()); +} diff --git a/substrate/frame/revive/rpc/codegen/src/printer.rs b/substrate/frame/revive/rpc/codegen/src/printer.rs new file mode 100644 index 000000000000..4d1bb620c2e4 --- /dev/null +++ b/substrate/frame/revive/rpc/codegen/src/printer.rs @@ -0,0 +1,496 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::open_rpc::*; +use inflector::Inflector; + +/// Type information used for generating the type. +#[derive(Debug, Clone)] +pub struct TypeInfo { + /// The type name. + pub name: String, + /// Whether the type is an array. + pub array: bool, + /// Whether the type is required. + pub required: Required, +} + +impl TypeInfo { + pub fn set_required(mut self, required: bool) -> Self { + if required { + self.required = Required::Yes; + } else { + self.required = Required::No { skip_if_null: true }; + } + self + } + + /// Return Whether the type is optional. + pub fn is_optional(&self) -> bool { + matches!(self.required, Required::No { .. }) + } +} + +/// A trait to provide type names. +pub trait TypeNameProvider { + /// Returns type information for a schema. + fn type_info(&mut self, schema: &Schema) -> Option; + + /// Record an inline type. + fn record_inline_type(&mut self, name: String, schema: &Schema) -> TypeInfo; +} + +/// Describes whether the type is required or not. +#[derive(Debug, Clone)] +pub enum Required { + /// The type is required. + Yes, + /// The type is not required, and may be skipped when serializing if it's None and skip_if_null + /// is true. + No { skip_if_null: bool }, +} + +impl TypeInfo { + //// Convert the type info to a string we can use in the generated code. + pub fn get_type(&self) -> String { + let mut type_name = self.name.clone(); + if self.array { + type_name = format!("Vec<{}>", type_name) + } + if self.is_optional() { + type_name = format!("Option<{}>", type_name) + } + type_name + } +} + +impl From for TypeInfo +where + T: Into, +{ + fn from(name: T) -> Self { + Self { name: name.into(), required: Required::Yes, array: false } + } +} +/// Represents a field in a struct. +#[derive(Debug)] +pub struct Field { + /// The documentation for the field. + doc: Option, + /// The name of the field. + name: String, + /// the type information for the field. + type_info: TypeInfo, + /// Whether to flatten the field, when serializing. + flatten: bool, + /// Legacy alias for the field. + alias: Option, +} + +/// Represents a collection of fields. +#[derive(Debug)] +pub struct Fields(Vec); + +impl From> for Fields { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl IntoIterator for Fields { + type Item = Field; + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Fields { + /// Creates a collection of fields from an [`ObjectLiteral]. + /// + /// The methods also takes a [`TypeNameProvider`] to resolve the types of the fields, and to + /// collect child types. + pub fn from(value: &ObjectLiteral, provider: &mut impl TypeNameProvider) -> Self { + let ObjectLiteral { properties, legacy_aliases, required } = value; + + properties + .iter() + .map(|(name, schema)| { + let mut type_info = provider.type_info(schema).expect("Type should be defined"); + if matches!(type_info.required, Required::Yes) && !required.contains(name) { + type_info.required = Required::No { skip_if_null: true }; + } + + let doc = doc_str_from_schema(schema); + Field { + doc, + name: name.clone(), + type_info, + alias: legacy_aliases.get(name).cloned(), + flatten: false, + } + }) + .collect::>() + .into() + } + + /// Creates a collection of fields from the items of a [`SchemaContents::AllOf`] schema. + pub fn from_all_of(all_of: &[Schema], provider: &mut impl TypeNameProvider) -> Fields { + all_of + .iter() + .flat_map(|schema| { + let doc = doc_str_from_schema(schema); + if let Some(type_info) = provider.type_info(schema) { + vec![Field { + doc, + name: type_info.name.clone(), + type_info, + alias: None, + flatten: true, + }] + } else { + let object = match &schema.contents { + SchemaContents::Object(object) => object, + SchemaContents::Literal(Literal::Object(object)) => object, + v => panic!("Unsupported anonymous all_of type {:?}", v), + }; + + Fields::from(object, provider).0 + } + }) + .collect::>() + .into() + } +} + +/// The variant of an enum. +#[derive(Debug)] +pub struct Variant { + /// The documentation for the variant. + doc: Option, + /// The type information for the variant. + type_info: TypeInfo, +} + +impl Variant { + pub fn name(&self) -> String { + let name = self.type_info.name.to_pascal_case(); + if self.type_info.array { + format!("{}s", name) + } else { + name + } + } +} + +pub fn doc_str_from_schema(schema: &Schema) -> Option { + let mut doc = schema.title.clone(); + + if let Some(description) = &schema.description { + doc = Some(doc.map_or_else(|| description.clone(), |doc| format!("{doc}\n{description}"))); + } + + doc +} + +#[derive(Debug)] +pub struct Variants(Vec); +impl Variants { + /// Creates a collection of variants from the items of a [`SchemaContents::OneOf`] schema. + pub(crate) fn from_one_of(one_of: &[Schema], provider: &mut impl TypeNameProvider) -> Variants { + one_of + .iter() + .filter_map(|schema| { + let doc = doc_str_from_schema(schema); + if let Some(type_info) = provider.type_info(schema) { + if type_info.name == "Null" || type_info.name == "NotFound" { + return None; + } + + Some(Variant { doc, type_info }) + } else { + let name = schema + .title + .clone() + .expect("Title should be defined for inline variant") + .to_pascal_case(); + + let type_info = provider.record_inline_type(name.clone(), schema); + Some(Variant { doc, type_info }) + } + }) + .collect::>() + .into() + } +} + +impl From> for Variants { + fn from(value: Vec) -> Self { + Self(value) + } +} + +/// The content of a type. +#[derive(Debug)] +pub enum TypeContent { + /// A struct type. + Struct(Fields), + /// A unit struct type. + TypeAlias(TypeInfo), + /// An enum type. + Enum(Variants), + /// A serde untagged enum type. + UntaggedEnum(Vec), +} + +/// A type printer. +#[derive(Debug)] +pub struct TypePrinter { + pub doc: Option, + pub name: String, + pub content: TypeContent, +} + +/// A macro to write a formatted line to a buffer. +#[macro_export] +macro_rules! writeln { + (@doc $s: ident, $doc: ident) => { + $crate::writeln!(@doc $s, $doc, 0) + }; + (@doc $s: ident, $doc: ident, $indent: literal) => { + if let Some(doc) = $doc { + for line in doc.lines() { + writeln!($s, "{:indent$}/// {}", "", line, indent = $indent); + } + } + }; + ($s: ident, $($arg: tt)*) => { + $s.push_str(&format!($($arg)*)); + $s.push_str("\n"); + }; + + + +} + +impl TypePrinter { + /// Prints the type to a buffer. + pub fn print(self, buffer: &mut String) { + let Self { doc, name, content, .. } = self; + + writeln!(@doc buffer, doc); + match content { + TypeContent::Enum(variants) if variants.0.len() == 1 => { + let type_info = &variants.0[0].type_info; + writeln!(buffer, "pub type {name} = {};", type_info.get_type()); + }, + TypeContent::TypeAlias(type_info) => { + writeln!(buffer, "pub type {name} = {};", type_info.get_type()); + }, + TypeContent::Enum(variants) => { + writeln!( + buffer, + "#[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)]" + ); + writeln!(buffer, "#[serde(untagged)]"); + writeln!(buffer, "pub enum {name} {{"); + for variant in variants.0.iter() { + let doc = &variant.doc; + writeln!(@doc buffer, doc, 2); + writeln!(buffer, " {}({}),", variant.name(), variant.type_info.get_type()); + } + writeln!(buffer, "}}"); + + // Implement Default trait + let variant = variants.0[0].name(); + writeln!(buffer, "impl Default for {name} {{"); + writeln!(buffer, " fn default() -> Self {{"); + writeln!(buffer, " {name}::{variant}(Default::default())"); + writeln!(buffer, " }}"); + writeln!(buffer, "}}"); + }, + TypeContent::UntaggedEnum(variants) => { + writeln!( + buffer, + "#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)]" + ); + writeln!(buffer, "pub enum {name} {{"); + for (i, name) in variants.iter().enumerate() { + writeln!(buffer, " #[serde(rename = \"{name}\")]"); + if i == 0 { + writeln!(buffer, " #[default]"); + } + let pascal_name = name.to_pascal_case(); + writeln!(buffer, " {pascal_name},"); + } + writeln!(buffer, "}}"); + }, + TypeContent::Struct(fields) => { + writeln!( + buffer, + "#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)]" + ); + + writeln!(buffer, "pub struct {name} {{"); + for Field { doc, name, type_info, alias, flatten } in fields { + writeln!(@doc buffer, doc, 2); + let mut snake_name = name.to_snake_case(); + let mut serde_params = vec![]; + + if flatten { + serde_params.push("flatten".to_string()); + } else if snake_name != name { + serde_params.push(format!("rename = \"{}\"", name)); + } + + if let Some(alias) = alias { + serde_params.push(format!("alias = \"{}\"", alias)); + } + + if matches!(type_info.required, Required::No { skip_if_null: true }) { + serde_params.push("skip_serializing_if = \"Option::is_none\"".to_string()); + } + + if !serde_params.is_empty() { + writeln!(buffer, " #[serde({})]", serde_params.join(", ")); + } + + let type_name = type_info.get_type(); + + if snake_name == "type" { + snake_name = "r#type".to_string() + } + writeln!(buffer, " pub {snake_name}: {type_name},"); + } + writeln!(buffer, "}}"); + }, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::generator::assert_code_match; + + #[test] + fn print_struct_works() { + let gen = TypePrinter { + doc: Some("A simple struct".to_string()), + name: "SimpleStruct".to_string(), + content: TypeContent::Struct( + vec![ + Field { + doc: Some("The first field".to_string()), + name: "firstField".to_string(), + type_info: "u32".into(), + flatten: false, + alias: None, + }, + Field { + doc: None, + name: "second".to_string(), + type_info: TypeInfo { + name: "String".to_string(), + required: Required::No { skip_if_null: true }, + array: false, + }, + flatten: true, + alias: None, + }, + ] + .into(), + ), + }; + let mut buffer = String::new(); + gen.print(&mut buffer); + assert_code_match( + &buffer, + r#" + /// A simple struct + #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] + pub struct SimpleStruct { + /// The first field + #[serde(rename = "firstField")] + pub first_field: u32, + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub second: Option, + } + "#, + ); + } + + #[test] + fn print_untagged_enum_works() { + let gen = TypePrinter { + doc: Some("A simple untagged enum".to_string()), + name: "SimpleUntaggedEnum".to_string(), + content: TypeContent::UntaggedEnum(vec!["first".to_string(), "second".to_string()]), + }; + let mut buffer = String::new(); + gen.print(&mut buffer); + assert_code_match( + &buffer, + r#" + /// A simple untagged enum + #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] + pub enum SimpleUntaggedEnum { + #[serde(rename = "first")] + #[default] + First, + #[serde(rename = "second")] + Second, + } + "#, + ); + } + + #[test] + fn print_enum_works() { + let gen = TypePrinter { + doc: Some("A simple enum".to_string()), + name: "SimpleEnum".to_string(), + content: TypeContent::Enum( + vec![ + Variant { doc: Some("The Foo variant".to_string()), type_info: "Foo".into() }, + Variant { doc: Some("The Bar variant".to_string()), type_info: "Bar".into() }, + ] + .into(), + ), + }; + let mut buffer = String::new(); + gen.print(&mut buffer); + assert_code_match( + &buffer, + r#" + /// A simple enum + #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)] + #[serde(untagged)] + pub enum SimpleEnum { + /// The Foo variant + Foo(Foo), + /// The Bar variant + Bar(Bar), + } + impl Default for SimpleEnum { + fn default() -> Self { + SimpleEnum::Foo(Default::default()) + } + } + "#, + ); + } +} From 3529fbbc82756272677e1ab5c116cba7f6979ef4 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 22 Nov 2024 15:46:37 +0100 Subject: [PATCH 02/67] Add geth-diff-tests --- .../rpc/examples/js/abi/errorTester.json | 106 +++++ .../revive/rpc/examples/js/abi/errorTester.ts | 106 +++++ .../revive/rpc/examples/js/abi/event.json | 66 +-- .../frame/revive/rpc/examples/js/abi/event.ts | 34 ++ .../revive/rpc/examples/js/abi/piggyBank.json | 128 +++--- .../revive/rpc/examples/js/abi/piggyBank.ts | 65 +++ .../frame/revive/rpc/examples/js/bun.lockb | Bin 45391 -> 33662 bytes .../rpc/examples/js/contracts/ErrorTester.sol | 52 +++ .../frame/revive/rpc/examples/js/package.json | 8 +- .../rpc/examples/js/pvm/errorTester.polkavm | Bin 0 -> 12890 bytes .../revive/rpc/examples/js/src/balance.ts | 9 + .../rpc/examples/js/src/build-contracts.ts | 26 +- .../frame/revive/rpc/examples/js/src/event.ts | 40 +- .../rpc/examples/js/src/geth-diff.test.ts | 398 ++++++++++++++++++ .../frame/revive/rpc/examples/js/src/lib.ts | 133 +++--- .../revive/rpc/examples/js/src/piggy-bank.ts | 81 +++- .../revive/rpc/examples/js/src/revert.ts | 10 - .../revive/rpc/examples/js/src/transfer.ts | 15 +- substrate/frame/revive/rpc/src/client.rs | 67 +-- substrate/frame/revive/rpc/src/lib.rs | 37 +- substrate/frame/revive/rpc/src/tests.rs | 1 + .../frame/revive/src/evm/api/rpc_types_gen.rs | 21 +- 22 files changed, 1148 insertions(+), 255 deletions(-) create mode 100644 substrate/frame/revive/rpc/examples/js/abi/errorTester.json create mode 100644 substrate/frame/revive/rpc/examples/js/abi/errorTester.ts create mode 100644 substrate/frame/revive/rpc/examples/js/abi/event.ts create mode 100644 substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts create mode 100644 substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm create mode 100644 substrate/frame/revive/rpc/examples/js/src/balance.ts create mode 100644 substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/src/revert.ts diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.json b/substrate/frame/revive/rpc/examples/js/abi/errorTester.json new file mode 100644 index 000000000000..2d8dccc771e8 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/errorTester.json @@ -0,0 +1,106 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "CustomError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "newState", + "type": "bool" + } + ], + "name": "setState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "state", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "triggerAssertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerCustomError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerDivisionByZero", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerOutOfBoundsError", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRequireError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRevertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "valueMatch", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts new file mode 100644 index 000000000000..d1ad60c1f55a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts @@ -0,0 +1,106 @@ +export const abi = [ + { + inputs: [ + { + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "CustomError", + type: "error", + }, + { + inputs: [ + { + internalType: "bool", + name: "newState", + type: "bool", + }, + ], + name: "setState", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "state", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "triggerAssertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerCustomError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerDivisionByZero", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerOutOfBoundsError", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRequireError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRevertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "valueMatch", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.json b/substrate/frame/revive/rpc/examples/js/abi/event.json index d36089fbc84e..a64c920c4068 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/event.json +++ b/substrate/frame/revive/rpc/examples/js/abi/event.json @@ -1,34 +1,34 @@ [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "message", - "type": "string" - } - ], - "name": "ExampleEvent", - "type": "event" - }, - { - "inputs": [], - "name": "triggerEvent", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "ExampleEvent", + "type": "event" + }, + { + "inputs": [], + "name": "triggerEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.ts b/substrate/frame/revive/rpc/examples/js/abi/event.ts new file mode 100644 index 000000000000..317ed00b92f9 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/event.ts @@ -0,0 +1,34 @@ +export const abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + indexed: false, + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "ExampleEvent", + type: "event", + }, + { + inputs: [], + name: "triggerEvent", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json index 2c2cfd5f7533..e6655889e21a 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json +++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json @@ -1,65 +1,65 @@ [ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "deposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "getDeposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "withdrawAmount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [ - { - "internalType": "uint256", - "name": "remainingBal", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "remainingBal", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts new file mode 100644 index 000000000000..bc685e0b827a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts @@ -0,0 +1,65 @@ +export const abi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "deposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "getDeposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "withdrawAmount", + type: "uint256", + }, + ], + name: "withdraw", + outputs: [ + { + internalType: "uint256", + name: "remainingBal", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 700dca51da2ad3f843e890258b59c16fd4df6457..0ff3d54157db21a636e30076634343a24c812dca 100755 GIT binary patch delta 9083 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 delta 15944 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 diff --git a/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol new file mode 100644 index 000000000000..d32cc7e11a9a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ErrorTester { + + // Payable function that can be used to test unsifficient funds errors + function valueMatch(uint value) public payable { + require(msg.value == value , "msg.value does not match value"); + } + + bool public state; + + function setState(bool newState) public { + state = newState; + } + + // Trigger a require statement failure with a custom error message + function triggerRequireError() public pure { + require(false, "This is a require error"); + } + + // Trigger an assert statement failure + function triggerAssertError() public pure { + assert(false); + } + + // Trigger a revert statement with a custom error message + function triggerRevertError() public pure { + revert("This is a revert error"); + } + + // Trigger a division by zero error + function triggerDivisionByZero() public pure returns (uint256) { + uint256 a = 1; + uint256 b = 0; + return a / b; + } + + // Trigger an out-of-bounds array access + function triggerOutOfBoundsError() public pure returns (uint256) { + uint256[] memory arr = new uint256[](1); + return arr[2]; + } + + // Trigger a custom error + error CustomError(string message); + + function triggerCustomError() public pure { + revert CustomError("This is a custom error"); + } +} + diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 3ae1f0fbd799..fa101dcad1d9 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -6,16 +6,16 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview", - "generate-types": "typechain --target=ethers-v6 'abi/*.json'" + "preview": "vite preview" }, "dependencies": { - "@typechain/ethers-v6": "^0.5.1", "ethers": "^6.13.4", + "prettier": "^3.3.3", "solc": "^0.8.28", - "typechain": "^8.3.2" + "viem": "^2.21.47" }, "devDependencies": { + "@types/bun": "^1.1.13", "typescript": "^5.5.3", "vite": "^5.4.8" } diff --git a/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..aebe24c4c0f597fb3d0171a9f527bc86d2d77b93 GIT binary patch literal 12890 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 P|DsnIq4bp_3F&_TlmMih literal 0 HcmV?d00001 diff --git a/substrate/frame/revive/rpc/examples/js/src/balance.ts b/substrate/frame/revive/rpc/examples/js/src/balance.ts new file mode 100644 index 000000000000..0c5e59d07849 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/balance.ts @@ -0,0 +1,9 @@ +import { walletClient } from './lib.ts' + +const recipient = '0x8D97689C9818892B700e27F316cc3E41e17fBeb9' +try { + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) +} catch (err) { + console.error(err) +} + diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index c6b7700d1ccf..3bafdd30a34d 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -1,11 +1,23 @@ import { compile } from '@parity/revive' +import { format } from 'prettier' +import { parseArgs } from 'node:util' import solc from 'solc' import { readFileSync, writeFileSync } from 'fs' import { join } from 'path' type CompileInput = Parameters[0] -type CompileOutput = Awaited> -type Abi = CompileOutput['contracts'][string][string]['abi'] + +const { + values: { filter }, +} = parseArgs({ + args: process.argv.slice(2), + options: { + filter: { + type: 'string', + short: 'f', + }, + }, +}) function evmCompile(sources: CompileInput) { const input = { @@ -27,9 +39,9 @@ console.log('Compiling contracts...') const input = [ { file: 'Event.sol', contract: 'EventExample', keypath: 'event' }, - { file: 'Revert.sol', contract: 'RevertExample', keypath: 'revert' }, { file: 'PiggyBank.sol', contract: 'PiggyBank', keypath: 'piggyBank' }, -] + { file: 'ErrorTester.sol', contract: 'ErrorTester', keypath: 'errorTester' }, +].filter(({ keypath }) => !filter || keypath.includes(filter)) for (const { keypath, contract, file } of input) { const input = { @@ -42,6 +54,12 @@ for (const { keypath, contract, file } of input) { const entry = out.contracts[file][contract] writeFileSync(join('evm', `${keypath}.bin`), Buffer.from(entry.evm.bytecode.object, 'hex')) writeFileSync(join('abi', `${keypath}.json`), JSON.stringify(entry.abi, null, 2)) + writeFileSync( + join('abi', `${keypath}.ts`), + await format(`export const abi = ${JSON.stringify(entry.abi, null, 2)} as const`, { + parser: 'typescript', + }) + ) } { diff --git a/substrate/frame/revive/rpc/examples/js/src/event.ts b/substrate/frame/revive/rpc/examples/js/src/event.ts index 94cc2560272e..2e672a9772ff 100644 --- a/substrate/frame/revive/rpc/examples/js/src/event.ts +++ b/substrate/frame/revive/rpc/examples/js/src/event.ts @@ -1,15 +1,29 @@ //! Run with bun run script-event.ts -import { call, getContract, deploy } from './lib.ts' - -try { - const { abi, bytecode } = getContract('event') - const contract = await deploy(bytecode, abi) - const receipt = await call('triggerEvent', await contract.getAddress(), abi) - if (receipt) { - for (const log of receipt.logs) { - console.log('Event log:', JSON.stringify(log, null, 2)) - } - } -} catch (err) { - console.error(err) + +import { abi } from '../abi/event.ts' +import { assert, getByteCode, walletClient } from './lib.ts' + +const deployHash = await walletClient.deployContract({ + abi, + bytecode: getByteCode('event'), +}) +const deployReceipt = await walletClient.waitForTransactionReceipt({ hash: deployHash }) +const contractAddress = deployReceipt.contractAddress +console.log('Contract deployed:', contractAddress) +assert(contractAddress, 'Contract address should be set') + +const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'triggerEvent', +}) + +const hash = await walletClient.writeContract(request) +const receipt = await walletClient.waitForTransactionReceipt({ hash }) +console.log(`Receipt: ${receipt.status}`) +console.log(`Logs receipt: ${receipt.status}`) + +for (const log of receipt.logs) { + console.log('Event log:', log) } diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts new file mode 100644 index 000000000000..ef4cde1824e4 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -0,0 +1,398 @@ +import { spawn, spawnSync, Subprocess } from 'bun' +import { join } from 'path' +import { readFileSync } from 'fs' +import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' +import { + createWalletClient, + defineChain, + encodeFunctionData, + Hex, + http, + parseEther, + publicActions, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { abi } from '../abi/errorTester' + +export function getByteCode(name: string, evm: boolean): Hex { + const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) + return `0x${Buffer.from(bytecode).toString('hex')}` +} + +type JsonRpcError = { + code: number + message: string + data: Hex +} + +function killProcessOnPort(port: number) { + // Check which process is using the specified port + const result = spawnSync(['lsof', '-ti', `:${port}`]) + const output = result.stdout.toString().trim() + + if (output) { + console.log(`Port ${port} is in use. Killing process...`) + const pids = output.split('\n') + + // Kill each process using the port + for (const pid of pids) { + spawnSync(['kill', '-9', pid]) + console.log(`Killed process with PID: ${pid}`) + } + } +} + +let jsonRpcErrors: JsonRpcError[] = [] +async function createEnv(name: 'geth' | 'kitchensink') { + const gethPort = process.env.GETH_PORT || '8546' + const kitchensinkPort = process.env.KITCHENSINK_PORT || '8545' + const url = `http://localhost:${name == 'geth' ? gethPort : kitchensinkPort}` + const chain = defineChain({ + id: name == 'geth' ? 1337 : 420420420, + name, + nativeCurrency: { + name: 'Westie', + symbol: 'WST', + decimals: 18, + }, + rpcUrls: { + default: { + http: [url], + }, + }, + testnet: true, + }) + + const transport = http(url, { + onFetchResponse: async (response) => { + const raw = await response.clone().json() + if (raw.error) { + jsonRpcErrors.push(raw.error as JsonRpcError) + } + }, + }) + + const wallet = createWalletClient({ + transport, + chain, + }) + + const [account] = await wallet.getAddresses() + const serverWallet = createWalletClient({ + account, + transport, + chain, + }).extend(publicActions) + + const accountWallet = createWalletClient({ + account: privateKeyToAccount( + '0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f' + ), + transport, + chain, + }).extend(publicActions) + + return { serverWallet, accountWallet, evm: name == 'geth' } +} + +// wait for http request to return 200 +export function waitForHealth(url: string) { + return new Promise((resolve, reject) => { + const start = Date.now() + const interval = setInterval(() => { + fetch(url) + .then((res) => { + if (res.status === 200) { + clearInterval(interval) + resolve() + } + }) + .catch(() => { + const elapsed = Date.now() - start + if (elapsed > 30_000) { + clearInterval(interval) + reject(new Error('hit timeout')) + } + }) + }, 1000) + }) +} + +const procs: Subprocess[] = [] +if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + // + (() => { + killProcessOnPort(8546) + return spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: join(process.env.HOME!, 'polkadot-sdk'), + } + ) + })() + , + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: join(process.env.HOME!, 'polkadot-sdk'), + } + ) + await waitForHealth('http://localhost:8545/health').catch() + return proc + })() + ) +} + +afterEach(() => { + jsonRpcErrors = [] +}) + +afterAll(async () => { + procs.forEach((proc) => proc.kill()) +}) + +const envs = await Promise.all([createEnv('geth'), createEnv('kitchensink')]) + +for (const env of envs) { + describe(env.serverWallet.chain.name, () => { + let errorTesterAddr: Hex = '0x' + beforeAll(async () => { + const hash = await env.serverWallet.deployContract({ + abi, + bytecode: getByteCode('errorTester', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') + errorTesterAddr = deployReceipt.contractAddress + }) + + test('triggerAssertError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerAssertError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000001' + ) + expect(lastJsonRpcError?.message).toBe('execution reverted: assert(false)') + } + }) + + test('triggerRevertError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerRevertError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.message).toBe('execution reverted: This is a revert error') + expect(lastJsonRpcError?.data).toBe( + '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120726576657274206572726f7200000000000000000000' + ) + } + }) + + test('triggerDivisionByZero', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerDivisionByZero', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000012' + ) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: division or modulo by zero' + ) + } + }) + + test('triggerOutOfBoundsError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerOutOfBoundsError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000032' + ) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: out-of-bounds access of an array or bytesN' + ) + } + }) + + test('triggerCustomError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerCustomError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x8d6ea8be0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120637573746f6d206572726f7200000000000000000000' + ) + expect(lastJsonRpcError?.message).toBe('execution reverted') + } + }) + + test('eth_call (not enough funds)', async () => { + expect.assertions(3) + try { + await env.accountWallet.simulateContract({ + address: errorTesterAddr, + abi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (not enough funds)', async () => { + expect.assertions(3) + try { + await env.accountWallet.estimateContractGas({ + address: errorTesterAddr, + abi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (revert)', async () => { + expect.assertions(3) + try { + await env.serverWallet.estimateContractGas({ + address: errorTesterAddr, + abi, + functionName: 'valueMatch', + value: parseEther('11'), + args: [parseEther('10')], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: msg.value does not match value' + ) + expect(lastJsonRpcError?.data).toBe( + '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e6d73672e76616c756520646f6573206e6f74206d617463682076616c75650000' + ) + } + }) + + test('eth_get_balance (no account)', async () => { + const balance = await env.serverWallet.getBalance({ + address: '0x0000000000000000000000000000000000000123', + }) + expect(balance).toBe(0n) + }) + + test('eth_estimate (not enough funds to cover gas specified)', async () => { + expect.assertions(4) + try { + let balance = await env.serverWallet.getBalance(env.accountWallet.account) + expect(balance).toBe(0n) + + await env.accountWallet.estimateContractGas({ + address: errorTesterAddr, + abi, + functionName: 'setState', + args: [true], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (no gas specified)', async () => { + let balance = await env.serverWallet.getBalance(env.accountWallet.account) + expect(balance).toBe(0n) + + const data = encodeFunctionData({ + abi, + functionName: 'setState', + args: [true], + }) + + await env.accountWallet.request({ + method: 'eth_estimateGas', + params: [ + { + data, + from: env.accountWallet.account.address, + to: errorTesterAddr, + }, + ], + }) + }) + }) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts index 975d8faf15b3..d1f14bbc064e 100644 --- a/substrate/frame/revive/rpc/examples/js/src/lib.ts +++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts @@ -1,22 +1,18 @@ -import { - Contract, - ContractFactory, - JsonRpcProvider, - TransactionReceipt, - TransactionResponse, - Wallet, -} from 'ethers' import { readFileSync } from 'node:fs' -import type { compile } from '@parity/revive' import { spawn } from 'node:child_process' import { parseArgs } from 'node:util' -import { BaseContract } from 'ethers' - -type CompileOutput = Awaited> -type Abi = CompileOutput['contracts'][string][string]['abi'] +import { + createWalletClient, + defineChain, + Hex, + http, + parseEther, + publicActions, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' const { - values: { geth, westend, ['private-key']: privateKey }, + values: { geth, proxy, westend, endowment, ['private-key']: privateKey }, } = parseArgs({ args: process.argv.slice(2), options: { @@ -24,6 +20,13 @@ const { type: 'string', short: 'k', }, + endowment: { + type: 'string', + short: 'e', + }, + proxy: { + type: 'boolean', + }, geth: { type: 'boolean', }, @@ -42,7 +45,7 @@ if (geth) { '--http.api', 'web3,eth,debug,personal,net', '--http.port', - '8546', + process.env.GETH_PORT ?? '8546', '--dev', '--verbosity', '0', @@ -55,56 +58,78 @@ if (geth) { await new Promise((resolve) => setTimeout(resolve, 500)) } -export const provider = new JsonRpcProvider( - westend +const rpcUrl = proxy + ? 'http://localhost:8080' + : westend ? 'https://westend-asset-hub-eth-rpc.polkadot.io' : geth ? 'http://localhost:8546' : 'http://localhost:8545' -) -export const signer = privateKey ? new Wallet(privateKey, provider) : await provider.getSigner() -console.log(`Signer address: ${await signer.getAddress()}, Nonce: ${await signer.getNonce()}`) +export const chain = defineChain({ + id: geth ? 1337 : 420420420, + name: 'Asset Hub Westend', + network: 'asset-hub', + nativeCurrency: { + name: 'Westie', + symbol: 'WST', + decimals: 18, + }, + rpcUrls: { + default: { + http: [rpcUrl], + }, + }, + testnet: true, +}) + +const wallet = createWalletClient({ + transport: http(), + chain, +}) +const [account] = await wallet.getAddresses() +export const serverWalletClient = createWalletClient({ + account, + transport: http(), + chain, +}) + +export const walletClient = await (async () => { + if (privateKey) { + const account = privateKeyToAccount(`0x${privateKey}`) + console.log(`Wallet address ${account.address}`) + + const wallet = createWalletClient({ + account, + transport: http(), + chain, + }) + + if (endowment) { + await serverWalletClient.sendTransaction({ + to: account.address, + value: parseEther(endowment), + }) + console.log(`Endowed address ${account.address} with: ${endowment}`) + } + + return wallet.extend(publicActions) + } else { + return serverWalletClient.extend(publicActions) + } +})() /** * Get one of the pre-built contracts * @param name - the contract name */ -export function getContract(name: string): { abi: Abi; bytecode: string } { +export function getByteCode(name: string): Hex { const bytecode = geth ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) - const abi = JSON.parse(readFileSync(`abi/${name}.json`, 'utf8')) as Abi - return { abi, bytecode: Buffer.from(bytecode).toString('hex') } + return `0x${Buffer.from(bytecode).toString('hex')}` } -/** - * Deploy a contract - * @returns the contract address - **/ -export async function deploy(bytecode: string, abi: Abi, args: any[] = []): Promise { - console.log('Deploying contract with', args) - const contractFactory = new ContractFactory(abi, bytecode, signer) - - const contract = await contractFactory.deploy(args) - await contract.waitForDeployment() - const address = await contract.getAddress() - console.log(`Contract deployed: ${address}`) - - return contract -} - -/** - * Call a contract - **/ -export async function call( - method: string, - address: string, - abi: Abi, - args: any[] = [], - opts: { value?: bigint } = {} -): Promise { - console.log(`Calling ${method} at ${address} with`, args, opts) - const contract = new Contract(address, abi, signer) - const tx = (await contract[method](...args, opts)) as TransactionResponse - console.log('Call transaction hash:', tx.hash) - return tx.wait() +export function assert(condition: any, message: string): asserts condition { + if (!condition) { + throw new Error(message) + } } diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts index 7a8edbde3662..0040b0c78dc4 100644 --- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts +++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts @@ -1,24 +1,69 @@ -import { provider, call, getContract, deploy } from './lib.ts' -import { parseEther } from 'ethers' -import { PiggyBank } from '../types/ethers-contracts/PiggyBank' +import { assert, getByteCode, walletClient } from './lib.ts' +import { abi } from '../abi/piggyBank.ts' +import { parseEther } from 'viem' -try { - const { abi, bytecode } = getContract('piggyBank') - const contract = (await deploy(bytecode, abi)) as PiggyBank - const address = await contract.getAddress() +const hash = await walletClient.deployContract({ + abi, + bytecode: getByteCode('piggyBank'), +}) +const deployReceipt = await walletClient.waitForTransactionReceipt({ hash }) +const contractAddress = deployReceipt.contractAddress +console.log('Contract deployed:', contractAddress) +assert(contractAddress, 'Contract address should be set') - let receipt = await call('deposit', address, abi, [], { - value: parseEther('10.0'), +// Deposit 10 WST +{ + const result = await walletClient.estimateContractGas({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'deposit', + value: parseEther('10'), }) - console.log('Deposit receipt:', receipt?.status) - console.log(`Contract balance: ${await provider.getBalance(address)}`) - console.log('deposit: ', await contract.getDeposit()) + console.log(`Gas estimate: ${result}`) - receipt = await call('withdraw', address, abi, [parseEther('5.0')]) - console.log('Withdraw receipt:', receipt?.status) - console.log(`Contract balance: ${await provider.getBalance(address)}`) - console.log('deposit: ', await contract.getDeposit()) -} catch (err) { - console.error(err) + const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'deposit', + value: parseEther('10'), + }) + + request.nonce = 0 + const hash = await walletClient.writeContract(request) + + const receipt = await walletClient.waitForTransactionReceipt({ hash }) + console.log(`Deposit receipt: ${receipt.status}`) + if (process.env.STOP) { + process.exit(0) + } +} + +// Withdraw 5 WST +{ + const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'withdraw', + args: [parseEther('5')], + }) + + const hash = await walletClient.writeContract(request) + const receipt = await walletClient.waitForTransactionReceipt({ hash }) + console.log(`Withdraw receipt: ${receipt.status}`) + + // Check remaining balance + const balance = await walletClient.readContract({ + address: contractAddress, + abi, + functionName: 'getDeposit', + }) + + console.log(`Get deposit: ${balance}`) + console.log( + `Get contract balance: ${await walletClient.getBalance({ address: contractAddress })}` + ) } diff --git a/substrate/frame/revive/rpc/examples/js/src/revert.ts b/substrate/frame/revive/rpc/examples/js/src/revert.ts deleted file mode 100644 index ea1bf4eceeb9..000000000000 --- a/substrate/frame/revive/rpc/examples/js/src/revert.ts +++ /dev/null @@ -1,10 +0,0 @@ -//! Run with bun run script-revert.ts -import { call, getContract, deploy } from './lib.ts' - -try { - const { abi, bytecode } = getContract('revert') - const contract = await deploy(bytecode, abi) - await call('doRevert', await contract.getAddress(), abi) -} catch (err) { - console.error(err) -} diff --git a/substrate/frame/revive/rpc/examples/js/src/transfer.ts b/substrate/frame/revive/rpc/examples/js/src/transfer.ts index ae2dd50f2af8..aef9a487b0c0 100644 --- a/substrate/frame/revive/rpc/examples/js/src/transfer.ts +++ b/substrate/frame/revive/rpc/examples/js/src/transfer.ts @@ -1,17 +1,18 @@ -import { parseEther } from 'ethers' -import { provider, signer } from './lib.ts' +import { parseEther } from 'viem' +import { walletClient } from './lib.ts' const recipient = '0x75E480dB528101a381Ce68544611C169Ad7EB342' try { - console.log(`Signer balance: ${await provider.getBalance(signer.address)}`) - console.log(`Recipient balance: ${await provider.getBalance(recipient)}`) - await signer.sendTransaction({ + console.log(`Signer balance: ${await walletClient.getBalance(walletClient.account)}`) + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) + + await walletClient.sendTransaction({ to: recipient, value: parseEther('1.0'), }) console.log(`Sent: ${parseEther('1.0')}`) - console.log(`Signer balance: ${await provider.getBalance(signer.address)}`) - console.log(`Recipient balance: ${await provider.getBalance(recipient)}`) + console.log(`Signer balance: ${await walletClient.getBalance(walletClient.account)}`) + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) } catch (err) { console.error(err) } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index d37f1d760065..d7cbca520113 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -118,16 +118,40 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { fn extract_revert_message(exec_data: &[u8]) -> Option { let function_selector = exec_data.get(0..4)?; - // keccak256("Error(string)") - let expected_selector = [0x08, 0xC3, 0x79, 0xA0]; - if function_selector != expected_selector { - return None; - } + match function_selector { + // assert(false) + [0x4E, 0x48, 0x7B, 0x71] => { + let panic_code: u32 = U256::from_big_endian(&exec_data.get(4..36)?).try_into().ok()?; + + // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require + let msg = match panic_code { + 0x00 => "generic panic", + 0x01 => "assert(false)", + 0x11 => "arithmetic underflow or overflow", + 0x12 => "division or modulo by zero", + 0x21 => "enum overflow", + 0x22 => "invalid encoded storage byte array accessed", + 0x31 => "out-of-bounds array access; popping on an empty array", + 0x32 => "out-of-bounds access of an array or bytesN", + 0x41 => "out of memory", + 0x51 => "uninitialized function", + code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), + }; - let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; - match decoded.first()? { - ethabi::Token::String(msg) => Some(msg.to_string()), - _ => None, + Some(format!("execution reverted: {msg}")) + }, + // revert(string) + [0x08, 0xC3, 0x79, 0xA0] => { + let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; + if let Some(ethabi::Token::String(msg)) = decoded.first() { + return Some(format!("execution reverted: {msg}")) + } + Some("execution reverted".to_string()) + }, + _ => { + log::debug!(target: LOG_TARGET, "Unknown revert function selector: {function_selector:?}"); + Some("execution reverted".to_string()) + }, } } @@ -147,25 +171,26 @@ pub enum ClientError { #[error(transparent)] CodecError(#[from] codec::Error), /// The dry run failed. - #[error("Dry run failed: {0}")] + #[error("dry run failed: {0}")] DryRunFailed(String), /// Contract reverted - #[error("Execution reverted: {}", extract_revert_message(.0).unwrap_or_default())] + #[error("{}", extract_revert_message(.0).unwrap_or_default())] Reverted(Vec), /// A decimal conversion failed. - #[error("Conversion failed")] + #[error("conversion failed")] ConversionFailed, /// The block hash was not found. - #[error("Hash not found")] + #[error("hash not found")] BlockNotFound, /// The transaction fee could not be found - #[error("TransactionFeePaid event not found")] + #[error("transactionFeePaid event not found")] TxFeeNotFound, /// The cache is empty. - #[error("Cache is empty")] + #[error("cache is empty")] CacheEmpty, } +const REVERT_CODE: i32 = 3; // TODO convert error code to https://eips.ethereum.org/EIPS/eip-1474#error-codes impl From for ErrorObjectOwned { fn from(err: ClientError) -> Self { @@ -179,7 +204,7 @@ impl From for ErrorObjectOwned { }, ClientError::Reverted(data) => { let data = format!("0x{}", hex::encode(data)); - ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, Some(data)) + ErrorObjectOwned::owned::(REVERT_CODE, msg, Some(data)) }, _ => ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, None), } @@ -672,16 +697,6 @@ impl Client { } } - /// Dry run a transaction and returns the gas estimate for the transaction. - pub async fn estimate_gas( - &self, - tx: &GenericTransaction, - block: BlockNumberOrTagOrHash, - ) -> Result { - let dry_run = self.dry_run(tx, block).await?; - Ok(U256::from(dry_run.fee / GAS_PRICE as u128) + GAS_PRICE) - } - /// Get the nonce of the given address. pub async fn nonce( &self, diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 6a324e63a857..b35497e34bd7 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -128,10 +128,24 @@ impl EthRpcServer for EthRpcServerImpl { async fn estimate_gas( &self, transaction: GenericTransaction, - _block: Option, + block: Option, ) -> RpcResult { - let result = self.client.estimate_gas(&transaction, BlockTag::Latest.into()).await?; - Ok(result) + // estimate_gas only fails returns even if the contract traps + let dry_run = self.client.dry_run(&transaction, block.unwrap_or_default().into()).await?; + + Ok(U256::from(dry_run.fee / GAS_PRICE as u128) + GAS_PRICE) + } + + async fn call( + &self, + transaction: GenericTransaction, + block: Option, + ) -> RpcResult { + let dry_run = self + .client + .dry_run(&transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) + .await?; + Ok(dry_run.result.into()) } async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult { @@ -158,7 +172,10 @@ impl EthRpcServer for EthRpcServerImpl { gas_required.into(), storage_deposit, ); - self.client.submit(call).await?; + self.client.submit(call).await.map_err(|err| { + log::debug!(target: LOG_TARGET, "submit call failed: {err:?}"); + err + })?; log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}"); Ok(hash) } @@ -234,18 +251,6 @@ impl EthRpcServer for EthRpcServerImpl { Ok(self.accounts.iter().map(|account| account.address()).collect()) } - async fn call( - &self, - transaction: GenericTransaction, - block: Option, - ) -> RpcResult { - let dry_run = self - .client - .dry_run(&transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) - .await?; - Ok(dry_run.result.into()) - } - async fn get_block_by_number( &self, block: BlockNumberOrTag, diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 7734c8c57209..686cebcf657d 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -237,6 +237,7 @@ async fn revert_call() -> anyhow::Result<()> { let call_err = unwrap_call_err!(err.source().unwrap()); assert_eq!(call_err.message(), "Execution reverted: revert message"); + assert_eq!(call_err.code(), 3); Ok(()) } diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 5037ec05d881..1370ea2b7612 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -114,7 +114,7 @@ pub enum BlockNumberOrTag { } impl Default for BlockNumberOrTag { fn default() -> Self { - BlockNumberOrTag::U256(Default::default()) + BlockTag::Latest.into() } } @@ -133,7 +133,16 @@ pub enum BlockNumberOrTagOrHash { } impl Default for BlockNumberOrTagOrHash { fn default() -> Self { - BlockNumberOrTagOrHash::U256(Default::default()) + BlockTag::Latest.into() + } +} + +impl From for BlockNumberOrTagOrHash { + fn from(b: BlockNumberOrTag) -> Self { + match b { + BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::U256(n), + BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t), + } } } @@ -281,7 +290,7 @@ pub enum SyncingStatus { } impl Default for SyncingStatus { fn default() -> Self { - SyncingStatus::SyncingProgress(Default::default()) + SyncingStatus::Bool(false) } } @@ -319,7 +328,7 @@ pub enum TransactionUnsigned { } impl Default for TransactionUnsigned { fn default() -> Self { - TransactionUnsigned::Transaction4844Unsigned(Default::default()) + TransactionUnsigned::TransactionLegacyUnsigned(Default::default()) } } @@ -341,13 +350,13 @@ pub type AccessList = Vec; )] pub enum BlockTag { #[serde(rename = "earliest")] - #[default] Earliest, #[serde(rename = "finalized")] Finalized, #[serde(rename = "safe")] Safe, #[serde(rename = "latest")] + #[default] Latest, #[serde(rename = "pending")] Pending, @@ -574,7 +583,7 @@ pub enum TransactionSigned { } impl Default for TransactionSigned { fn default() -> Self { - TransactionSigned::Transaction4844Signed(Default::default()) + TransactionSigned::TransactionLegacySigned(Default::default()) } } From 87fd63fa25b1343e51be3527d05aacaffd58831f Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 22 Nov 2024 15:51:20 +0100 Subject: [PATCH 03/67] fixup cargo.toml --- Cargo.toml | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5402b3e60cc4..5ece645f82c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -557,7 +557,13 @@ default-members = [ [workspace.lints.rust] suspicious_double_ref_op = { level = "allow", priority = 2 } # `substrate_runtime` is a common `cfg` condition name used in the repo. -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(build_opt_level, values("3"))', 'cfg(build_profile, values("debug", "release"))', 'cfg(enable_alloc_error_handler)', 'cfg(fuzzing)', 'cfg(substrate_runtime)'] } +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(build_opt_level, values("3"))', + 'cfg(build_profile, values("debug", "release"))', + 'cfg(enable_alloc_error_handler)', + 'cfg(fuzzing)', + 'cfg(substrate_runtime)', +] } [workspace.lints.clippy] all = { level = "allow", priority = 0 } @@ -632,7 +638,7 @@ bitvec = { version = "1.0.1", default-features = false } blake2 = { version = "0.10.4", default-features = false } blake2b_simd = { version = "1.0.2", default-features = false } blake3 = { version = "1.5" } -bounded-collections = { version = "0.2.0", default-features = false } +bounded-collections = { version = "0.2.2", default-features = false } bounded-vec = { version = "0.7" } bp-asset-hub-rococo = { path = "bridges/chains/chain-asset-hub-rococo", default-features = false } bp-asset-hub-westend = { path = "bridges/chains/chain-asset-hub-westend", default-features = false } @@ -678,6 +684,7 @@ cid = { version = "0.9.0" } clap = { version = "4.5.13" } clap-num = { version = "1.0.2" } clap_complete = { version = "4.5.13" } +cmd_lib = { version = "1.9.5" } coarsetime = { version = "0.1.22" } codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" } collectives-westend-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend" } @@ -736,7 +743,7 @@ derive_more = { version = "0.99.17", default-features = false } digest = { version = "0.10.3", default-features = false } directories = { version = "5.0.1" } dlmalloc = { version = "0.2.4" } -docify = { version = "0.2.8" } +docify = { version = "0.2.9" } dyn-clonable = { version = "0.9.0" } dyn-clone = { version = "1.0.16" } ed25519-dalek = { version = "2.1", default-features = false } @@ -748,7 +755,7 @@ enumn = { version = "0.1.13" } env_logger = { version = "0.11.2" } environmental = { version = "1.1.4", default-features = false } equivocation-detector = { path = "bridges/relays/equivocation" } -ethabi = { version = "1.0.0", default-features = false, package = "ethabi-decode" } +ethabi = { version = "2.0.0", default-features = false, package = "ethabi-decode" } ethbloom = { version = "0.14.1", default-features = false } ethereum-types = { version = "0.15.1", default-features = false } exit-future = { version = "0.2.0" } @@ -786,7 +793,7 @@ frame-system-rpc-runtime-api = { path = "substrate/frame/system/rpc/runtime-api" frame-try-runtime = { path = "substrate/frame/try-runtime", default-features = false } fs4 = { version = "0.7.0" } fs_extra = { version = "1.3.0" } -futures = { version = "0.3.30" } +futures = { version = "0.3.31" } futures-channel = { version = "0.3.23" } futures-timer = { version = "3.0.2" } futures-util = { version = "0.3.30", default-features = false } @@ -842,7 +849,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.7.0", features = ["websocket"] } +litep2p = { version = "0.8.1", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } @@ -1088,7 +1095,9 @@ polkavm-derive = "0.9.1" polkavm-linker = "0.9.2" portpicker = { version = "0.1.1" } pretty_assertions = { version = "1.3.0" } -primitive-types = { version = "0.13.1", default-features = false, features = ["num-traits"] } +primitive-types = { version = "0.13.1", default-features = false, features = [ + "num-traits", +] } proc-macro-crate = { version = "3.0.0" } proc-macro-warning = { version = "1.0.0", default-features = false } proc-macro2 = { version = "1.0.86" } @@ -1197,12 +1206,11 @@ seccompiler = { version = "0.4.0" } secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } separator = { version = "0.4.1" } -serde = { version = "1.0.210", default-features = false } +serde = { version = "1.0.214", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } serde_json = { version = "1.0.132", default-features = false } serde_yaml = { version = "0.9" } -serial_test = { version = "2.0.0" } sha1 = { version = "0.10.6" } sha2 = { version = "0.10.7", default-features = false } sha3 = { version = "0.10.0", default-features = false } @@ -1309,9 +1317,9 @@ substrate-test-runtime-client = { path = "substrate/test-utils/runtime/client" } substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime/transaction-pool" } substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } -subxt = { version = "0.37", default-features = false } -subxt-signer = { version = "0.37" } -syn = { version = "2.0.82" } +subxt = { version = "0.38", default-features = false } +subxt-signer = { version = "0.38" } +syn = { version = "2.0.87" } sysinfo = { version = "0.30" } tar = { version = "0.4" } tempfile = { version = "3.8.1" } @@ -1380,7 +1388,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } -zombienet-sdk = { version = "0.2.13" } +zombienet-sdk = { version = "0.2.15" } zstd = { version = "0.12.4", default-features = false } [profile.release] From aa1f1be50a56f02e55a49c19b2363152959712a6 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 22 Nov 2024 16:25:14 +0100 Subject: [PATCH 04/67] gen fixes --- Cargo.lock | 11 +++++++++++ .../frame/revive/rpc/codegen/openrpc.json | 6 ++++-- .../frame/revive/rpc/codegen/src/generator.rs | 18 ++++++++++-------- .../frame/revive/rpc/src/rpc_methods_gen.rs | 1 + 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 330c2563d976..4eb40bc9761f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14835,6 +14835,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "pallet-revive-rpc-codegen" +version = "0.1.0" +dependencies = [ + "Inflector", + "anyhow", + "pretty_assertions", + "serde", + "serde_json", +] + [[package]] name = "pallet-revive-uapi" version = "0.1.0" diff --git a/substrate/frame/revive/rpc/codegen/openrpc.json b/substrate/frame/revive/rpc/codegen/openrpc.json index 7b55131590ee..44e590cd3e7d 100644 --- a/substrate/frame/revive/rpc/codegen/openrpc.json +++ b/substrate/frame/revive/rpc/codegen/openrpc.json @@ -1962,7 +1962,8 @@ "title": "EIP-4844 transaction signature properties.", "required": [ "r", - "s" + "s", + "yParity" ], "properties": { "yParity": { @@ -1993,7 +1994,8 @@ "title": "EIP-1559 transaction signature properties.", "required": [ "r", - "s" + "s", + "yParity" ], "properties": { "yParity": { diff --git a/substrate/frame/revive/rpc/codegen/src/generator.rs b/substrate/frame/revive/rpc/codegen/src/generator.rs index 6419a994b6d3..bf18efaa17f8 100644 --- a/substrate/frame/revive/rpc/codegen/src/generator.rs +++ b/substrate/frame/revive/rpc/codegen/src/generator.rs @@ -247,11 +247,10 @@ impl TypeGenerator { pub fn generate_types(&mut self, specs: &OpenRpc) -> String { let mut code = LICENSE.to_string(); code.push_str( - r#" - //! Generated JSON-RPC types. + r#"//! Generated JSON-RPC types. #![allow(missing_docs)] - use super::{byte::*, Type0, Type1, Type2, Type3}; + use super::{byte::*, TypeEip1559, TypeEip2930, TypeEip4844, TypeLegacy}; use alloc::vec::Vec; use codec::{Decode, Encode}; use derive_more::{From, TryInto}; @@ -425,11 +424,14 @@ impl TypeNameProvider for TypeGenerator { pattern: Some(ref pattern), format: None, enumeration: None, - })) if ["^0x0$", "^0x1$", "^0x2$", "^0x3$"].contains(&pattern.as_str()) => { - let type_id = format!("Type{}", &pattern[3..4]); - - Some(type_id.into()) - }, + })) if ["^0x0$", "^0x1$", "^0x2$", "^0x3$"].contains(&pattern.as_str()) => + match pattern.as_str() { + "^0x0$" => Some("TypeLegacy".into()), + "^0x1$" => Some("TypeEip2930".into()), + "^0x2$" => Some("TypeEip1559".into()), + "^0x3$" => Some("TypeEip4844".into()), + _ => unreachable!(), + }, SchemaContents::Literal(Literal::Boolean) => Some("bool".into()), SchemaContents::Object(_) => None, diff --git a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs index 339080368969..ad34dbfdfb49 100644 --- a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs +++ b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs @@ -14,6 +14,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + //! Generated JSON-RPC methods. #![allow(missing_docs)] From e9823a0f8f28c485498b0070d61bec13cc31b992 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 22 Nov 2024 17:21:27 +0100 Subject: [PATCH 05/67] fix generator --- .../frame/revive/rpc/codegen/openrpc.json | 3 +- .../frame/revive/rpc/codegen/src/generator.rs | 34 +++++++++++---- .../frame/revive/rpc/codegen/src/printer.rs | 42 ++++++++++++++++--- .../frame/revive/src/evm/api/rpc_types.rs | 9 ++++ .../frame/revive/src/evm/api/rpc_types_gen.rs | 27 ++++-------- 5 files changed, 81 insertions(+), 34 deletions(-) diff --git a/substrate/frame/revive/rpc/codegen/openrpc.json b/substrate/frame/revive/rpc/codegen/openrpc.json index 44e590cd3e7d..4a91ee18177c 100644 --- a/substrate/frame/revive/rpc/codegen/openrpc.json +++ b/substrate/frame/revive/rpc/codegen/openrpc.json @@ -1395,7 +1395,8 @@ "title": "log", "type": "object", "required": [ - "transactionHash" + "transactionHash", + "address" ], "additionalProperties": false, "properties": { diff --git a/substrate/frame/revive/rpc/codegen/src/generator.rs b/substrate/frame/revive/rpc/codegen/src/generator.rs index bf18efaa17f8..c4881a186c50 100644 --- a/substrate/frame/revive/rpc/codegen/src/generator.rs +++ b/substrate/frame/revive/rpc/codegen/src/generator.rs @@ -87,6 +87,18 @@ pub static LEGACY_ALIASES: LazyLock> = + LazyLock::new(|| { + HashMap::from([ + ("TransactionUnsigned", "TransactionLegacyUnsigned"), + ("TransactionSigned", "TransactionLegacySigned"), + ("BlockNumberOrTagOrHash", "BlockTag"), + ("BlockNumberOrTag", "BlockTag"), + ("BlockTag", "Latest"), + ]) + }); + /// Read the OpenRPC specs, and inject extra methods and legacy aliases. pub fn read_specs() -> anyhow::Result { let content = include_str!("../openrpc.json"); @@ -309,7 +321,8 @@ impl TypeGenerator { }, }; - TypePrinter { name, doc, content } + let default_variant = CUSTOM_DEFAULT_VARIANTS.get(&name.as_str()).map(|v| v.to_string()); + TypePrinter::new(doc, name, content, default_variant) } fn generate_rpc_method(&mut self, buffer: &mut String, method: &Method) { @@ -445,7 +458,10 @@ impl TypeNameProvider for TypeGenerator { #[cfg(test)] pub fn assert_code_match(expected: &str, actual: &str) { - assert_eq!(format_code(expected).unwrap().trim(), format_code(actual).unwrap().trim()); + pretty_assertions::assert_eq!( + format_code(expected).unwrap().trim(), + format_code(actual).unwrap().trim() + ); } #[cfg(test)] @@ -558,8 +574,8 @@ mod test { pub s: U256, /// yParity /// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature. - #[serde(rename = "yParity", skip_serializing_if = "Option::is_none")] - pub y_parity: Option, + #[serde(rename = "yParity")] + pub y_parity: U256, } "#, ); @@ -588,7 +604,7 @@ mod test { } impl Default for TransactionUnsigned { fn default() -> Self { - TransactionUnsigned::Transaction4844Unsigned(Default::default()) + TransactionUnsigned::TransactionLegacyUnsigned(Default::default()) } } "#, @@ -689,12 +705,12 @@ mod test { pub access_list: Option, /// blobVersionedHashes /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. - #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Option::is_none")] - pub blob_versioned_hashes: Option>, + #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Option::is_none")] - pub blobs: Option>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub blobs: Vec, /// chainId /// Chain ID that this transaction is valid on. #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] diff --git a/substrate/frame/revive/rpc/codegen/src/printer.rs b/substrate/frame/revive/rpc/codegen/src/printer.rs index 4d1bb620c2e4..0b13500b85c5 100644 --- a/substrate/frame/revive/rpc/codegen/src/printer.rs +++ b/substrate/frame/revive/rpc/codegen/src/printer.rs @@ -69,8 +69,7 @@ impl TypeInfo { let mut type_name = self.name.clone(); if self.array { type_name = format!("Vec<{}>", type_name) - } - if self.is_optional() { + } else if self.is_optional() { type_name = format!("Option<{}>", type_name) } type_name @@ -262,6 +261,7 @@ pub struct TypePrinter { pub doc: Option, pub name: String, pub content: TypeContent, + custom_default_variant: Option, } /// A macro to write a formatted line to a buffer. @@ -287,6 +287,15 @@ macro_rules! writeln { } impl TypePrinter { + pub fn new( + doc: Option, + name: String, + content: TypeContent, + custom_default_variant: Option, + ) -> Self { + Self { doc, name, content, custom_default_variant } + } + /// Prints the type to a buffer. pub fn print(self, buffer: &mut String) { let Self { doc, name, content, .. } = self; @@ -315,10 +324,14 @@ impl TypePrinter { writeln!(buffer, "}}"); // Implement Default trait - let variant = variants.0[0].name(); + let default_variant = self + .custom_default_variant + .map(|s| s.to_string()) + .unwrap_or_else(|| variants.0[0].name()); + writeln!(buffer, "impl Default for {name} {{"); writeln!(buffer, " fn default() -> Self {{"); - writeln!(buffer, " {name}::{variant}(Default::default())"); + writeln!(buffer, " {name}::{default_variant}(Default::default())"); writeln!(buffer, " }}"); writeln!(buffer, "}}"); }, @@ -328,9 +341,17 @@ impl TypePrinter { "#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)]" ); writeln!(buffer, "pub enum {name} {{"); + + let default_variant_index = self.custom_default_variant.map_or(0, |v| { + variants + .iter() + .position(|x| x.eq_ignore_ascii_case(&v)) + .expect("Default variant not found") + }); + for (i, name) in variants.iter().enumerate() { writeln!(buffer, " #[serde(rename = \"{name}\")]"); - if i == 0 { + if i == default_variant_index { writeln!(buffer, " #[default]"); } let pascal_name = name.to_pascal_case(); @@ -361,7 +382,13 @@ impl TypePrinter { } if matches!(type_info.required, Required::No { skip_if_null: true }) { - serde_params.push("skip_serializing_if = \"Option::is_none\"".to_string()); + if type_info.array { + serde_params + .push("skip_serializing_if = \"Vec::is_empty\"".to_string()); + } else { + serde_params + .push("skip_serializing_if = \"Option::is_none\"".to_string()); + } } if !serde_params.is_empty() { @@ -414,6 +441,7 @@ mod test { ] .into(), ), + custom_default_variant: None, }; let mut buffer = String::new(); gen.print(&mut buffer); @@ -439,6 +467,7 @@ mod test { doc: Some("A simple untagged enum".to_string()), name: "SimpleUntaggedEnum".to_string(), content: TypeContent::UntaggedEnum(vec!["first".to_string(), "second".to_string()]), + custom_default_variant: None, }; let mut buffer = String::new(); gen.print(&mut buffer); @@ -470,6 +499,7 @@ mod test { ] .into(), ), + custom_default_variant: None, }; let mut buffer = String::new(); gen.print(&mut buffer); diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 1cf8d984b68b..287d84fc186f 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -19,6 +19,15 @@ use super::*; use alloc::vec::Vec; use sp_core::{H160, U256}; +impl From for BlockNumberOrTagOrHash { + fn from(b: BlockNumberOrTag) -> Self { + match b { + BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::U256(n), + BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t), + } + } +} + impl TransactionInfo { /// Create a new [`TransactionInfo`] from a receipt and a signed transaction. pub fn new(receipt: ReceiptInfo, transaction_signed: TransactionSigned) -> Self { diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 1370ea2b7612..a9206e38fcc8 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -94,8 +94,8 @@ pub struct Block { /// Uncles pub uncles: Vec, /// Withdrawals - #[serde(skip_serializing_if = "Option::is_none")] - pub withdrawals: Option>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub withdrawals: Vec, /// Withdrawals root #[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")] pub withdrawals_root: Option, @@ -114,7 +114,7 @@ pub enum BlockNumberOrTag { } impl Default for BlockNumberOrTag { fn default() -> Self { - BlockTag::Latest.into() + BlockNumberOrTag::BlockTag(Default::default()) } } @@ -133,16 +133,7 @@ pub enum BlockNumberOrTagOrHash { } impl Default for BlockNumberOrTagOrHash { fn default() -> Self { - BlockTag::Latest.into() - } -} - -impl From for BlockNumberOrTagOrHash { - fn from(b: BlockNumberOrTag) -> Self { - match b { - BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::U256(n), - BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t), - } + BlockNumberOrTagOrHash::BlockTag(Default::default()) } } @@ -157,12 +148,12 @@ pub struct GenericTransaction { pub access_list: Option, /// blobVersionedHashes /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. - #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Option::is_none")] - pub blob_versioned_hashes: Option>, + #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Option::is_none")] - pub blobs: Option>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub blobs: Vec, /// chainId /// Chain ID that this transaction is valid on. #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] @@ -290,7 +281,7 @@ pub enum SyncingStatus { } impl Default for SyncingStatus { fn default() -> Self { - SyncingStatus::Bool(false) + SyncingStatus::SyncingProgress(Default::default()) } } From 61619bdb1482b0f7f5ec67955df1543b4347b45e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 22 Nov 2024 17:24:11 +0100 Subject: [PATCH 06/67] type generator update fixes --- substrate/frame/revive/src/evm/api/rpc_types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 287d84fc186f..338be88e69b5 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -182,7 +182,7 @@ impl GenericTransaction { gas: Some(tx.gas), gas_price: Some(tx.max_fee_per_blob_gas), access_list: Some(tx.access_list), - blob_versioned_hashes: Some(tx.blob_versioned_hashes), + blob_versioned_hashes: tx.blob_versioned_hashes, max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), max_fee_per_gas: Some(tx.max_fee_per_gas), max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), @@ -278,7 +278,7 @@ impl GenericTransaction { max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(), max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(), access_list: self.access_list.unwrap_or_default(), - blob_versioned_hashes: self.blob_versioned_hashes.unwrap_or_default(), + blob_versioned_hashes: self.blob_versioned_hashes, } .into()), _ => Err(()), From 716f40a4b0e0284e0620854674e5037787ad9ec5 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 14:09:45 +0100 Subject: [PATCH 07/67] wip --- Cargo.lock | 1 + .../assets/asset-hub-westend/src/lib.rs | 30 +-- substrate/bin/node/runtime/src/lib.rs | 25 +-- substrate/frame/revive/Cargo.toml | 1 + .../frame/revive/rpc/codegen/src/printer.rs | 5 +- .../rpc/examples/js/src/geth-diff.test.ts | 30 +-- .../frame/revive/rpc/revive_chain.metadata | Bin 658056 -> 659977 bytes substrate/frame/revive/rpc/src/client.rs | 57 ++--- substrate/frame/revive/rpc/src/lib.rs | 19 +- .../frame/revive/rpc/src/subxt_client.rs | 12 +- .../frame/revive/src/evm/api/rlp_codec.rs | 18 +- .../frame/revive/src/evm/api/rpc_types.rs | 137 ++++++------ .../frame/revive/src/evm/api/rpc_types_gen.rs | 8 +- substrate/frame/revive/src/evm/runtime.rs | 93 +++++--- substrate/frame/revive/src/exec.rs | 71 +++++- substrate/frame/revive/src/lib.rs | 211 ++++++++++++------ substrate/frame/revive/src/primitives.rs | 42 +++- substrate/frame/revive/src/storage/meter.rs | 52 +++-- .../frame/revive/src/test_utils/builder.rs | 11 +- substrate/frame/revive/src/tests.rs | 12 +- .../frame/revive/src/tests/test_debug.rs | 5 +- substrate/frame/revive/src/wasm/mod.rs | 11 +- 22 files changed, 521 insertions(+), 330 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4eb40bc9761f..65580fe0239c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14602,6 +14602,7 @@ dependencies = [ "assert_matches", "bitflags 1.3.2", "derive_more 0.99.17", + "env_logger 0.11.3", "environmental", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index cafea3b6ff8b..5807d80ab542 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_016_006, + spec_version: 1_016_008, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -2088,18 +2088,10 @@ impl_runtime_apis! { let account = ::AddressMapper::to_account_id(&address); System::account_nonce(account) } - fn eth_transact( - from: H160, - dest: Option, - value: U256, - input: Vec, - gas_limit: Option, - storage_deposit_limit: Option, - ) -> pallet_revive::EthContractResult + + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result, pallet_revive::EthTransactError> { - use pallet_revive::AddressMapper; - let blockweights = ::BlockWeights::get(); - let origin = ::AddressMapper::to_account_id(&from); + let blockweights: BlockWeights = ::BlockWeights::get(); let encoded_size = |pallet_call| { let call = RuntimeCall::Revive(pallet_call); @@ -2108,15 +2100,9 @@ impl_runtime_apis! { }; Revive::bare_eth_transact( - origin, - dest, - value, - input, - gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + tx, + blockweights.max_block, encoded_size, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -2134,7 +2120,7 @@ impl_runtime_apis! { dest, value, gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, pallet_revive::DebugInfo::UnsafeDebug, pallet_revive::CollectEvents::UnsafeCollect, @@ -2156,7 +2142,7 @@ impl_runtime_apis! { RuntimeOrigin::signed(origin), value, gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), code, data, salt, diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index bff263548087..faffcd23fbcf 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3218,18 +3218,9 @@ impl_runtime_apis! { System::account_nonce(account) } - fn eth_transact( - from: H160, - dest: Option, - value: U256, - input: Vec, - gas_limit: Option, - storage_deposit_limit: Option, - ) -> pallet_revive::EthContractResult + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result, pallet_revive::EthTransactError> { - use pallet_revive::AddressMapper; let blockweights: BlockWeights = ::BlockWeights::get(); - let origin = ::AddressMapper::to_account_id(&from); let encoded_size = |pallet_call| { let call = RuntimeCall::Revive(pallet_call); @@ -3238,15 +3229,9 @@ impl_runtime_apis! { }; Revive::bare_eth_transact( - origin, - dest, - value, - input, - gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + tx, + blockweights.max_block, encoded_size, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -3263,7 +3248,7 @@ impl_runtime_apis! { dest, value, gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, pallet_revive::DebugInfo::UnsafeDebug, pallet_revive::CollectEvents::UnsafeCollect, @@ -3284,7 +3269,7 @@ impl_runtime_apis! { RuntimeOrigin::signed(origin), value, gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), code, data, salt, diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 81fbbc8cf38e..d2026d2ce9eb 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -65,6 +65,7 @@ pallet-revive-fixtures = { workspace = true, default-features = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } hex-literal = { workspace = true } +env_logger = { workspace = true } # Polkadot SDK Dependencies pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/rpc/codegen/src/printer.rs b/substrate/frame/revive/rpc/codegen/src/printer.rs index 0b13500b85c5..8a8933b4f432 100644 --- a/substrate/frame/revive/rpc/codegen/src/printer.rs +++ b/substrate/frame/revive/rpc/codegen/src/printer.rs @@ -383,8 +383,9 @@ impl TypePrinter { if matches!(type_info.required, Required::No { skip_if_null: true }) { if type_info.array { - serde_params - .push("skip_serializing_if = \"Vec::is_empty\"".to_string()); + serde_params.push( + "default, skip_serializing_if = \"Vec::is_empty\"".to_string(), + ); } else { serde_params .push("skip_serializing_if = \"Option::is_none\"".to_string()); diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index ef4cde1824e4..c5cb64e63afd 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -134,21 +134,20 @@ if (!process.env.USE_LIVE_SERVERS) { })(), //Run the substate node (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: join(process.env.HOME!, 'polkadot-sdk'), - } - ) - })() - , + killProcessOnPort(9944) + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: join(process.env.HOME!, 'polkadot-sdk'), + } + ) + })(), // Run eth-rpc on 8545 await (async () => { killProcessOnPort(8545) @@ -301,6 +300,7 @@ for (const env of envs) { } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) + console.log(lastJsonRpcError?.message) expect(lastJsonRpcError?.message).toInclude('insufficient funds') expect(lastJsonRpcError?.data).toBeUndefined() } diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 3560b3b90407acce7f602ce91ac089843be8dea8..64b1f2014dd06815fcea6a87bc96306eb00eda8b 100644 GIT binary patch delta 13838 zcmbt*4OmrG*6`Wq-h1}h_a6lX0llcGC?F^(D41xZSR|-ezKM8+i{6BL;r^&hkul{Y zjWikIip-KJGG}s(96MPtMrM;GCY6m;_%o$xrD~dt*uWbXI?LQ8 z&VbiW$n z&*w@|q3Uby7`ZDetvteL0ZAYGR)C2W!Y z!^31Gd>LnW{EkAhV1>(3;qsKYJbGN*rq!&&YPdIAn_sUv{j%3WNjj`E;CfLe37 zDud24hZj4%J{JNwJ&vW>Yux^T%M)-r%Wia4c>V6c(!_iMdCLu#>fd70#+Mud`(76bGLv zR6K2nQQ&DqWH<>`cD-tdB*wffkHcA9jFX10*9;3Ci?bZ7ya8A5^;Yg(s$!zs?o}aU&D(C+KB+0}~pUd5piJ$RBX7L|sLBOG{mzVpqmo zt)1^$g`)?m5L$SXMCG}B%iLvUg3!p|_--;(x!WxbXVF`bn%Kky{2^7LT_-fFPup3V zupM6YN#n#F44euXto-VeZYE+2t8coLG;uf6&*zl)FcQAdRh+S4njq}vm}YybC}1~| z#rIZ~SQ`r$+EhF!dWj4q`=Mr)6anU-G@TrP+@R#fM(sKrl%5D{XC!Tbv#hKtV}`TL ziBm$_?fiQ&>EO*k_-U1N3poPWtEF4TlMD)T=uq(#gX$bQ0*Z^;tAoLhSJ7_i`sYVKL~|J zMKUS-s-)Wu;w5dqo_spcd|3q+x*7hUs8hN_Bt|%}R@$lTyifW{gyD}#Nx&hE7Ne?$ z^+Jg9!hO;ik%eK07-1A5py>}%6^Z6e6ij|VS}MjE>&MR@skA;I4b_uah&ios^0V!VQ8uUzA2d#zRslNmue8lDb8b33E3}wIoZW zQ$P38K#~nZACbZN%)QlNBil131+h@;IUh2VNj zdYm|4YLr3VIOTQNSNA(v)x@Pe@_V{kSxbRA~cO!@(y|E-!4B29p|id$Tkw zYMqfR$np3CzMyJ!x4GQQR|Nd(LaouDhdm)ZPuB70nWPr%Tcl)C2Q#)v@nj=-w@9N& z1Hcw(n!eFUK7_~dGZ>$g>|_f>J}D)VCYbXi!qmasr?3%V*i+It5)La!sUADj5&IMNJvVIyug!h2K12w+cBlXCDWDK>=cfNx)r%3=PC(o`{>z{4*} zgAM6K*ki1Z@J7Ps36#O7Ez)Yzs)-df86VbWyk>=Me8qI=g#GZ>7bSn_0aa#;NSe^D zNzX2HaLiNty>OBbgRiI5 zp766q;jBhhweWN5^R$aSBrf+NfqmgxeAVk0kV{4)+i&%HTqqj*d~dl+FI+;3Xs?%g zIP>k6R2 zt~3lAJ{8T$xZ%ZjRe&GfmC~#(6N$RX=fsU=&&S*1$#{;$hkHc zgev&zuryYzHq{r_Mkq-g(f|>Smk*J-u15lOz0k0)*TSjoua#P)i(09LmJg)Mtd5Ve z&LnK)uLI$&52bvu!Bl_j!AK?Lh_slHM%eg~^dp*c?Z2ctH0LK|8UtxZrIGktcoc2A z8P*+@Qip8Ek>U$HK1_66nAMcXBs}0QUg-+#fDeyK(?|;p?3Ct^?XaX%T0C(Fm!&Lh zHwi6$_}$T;Uk-}=?$&g%M@jxzdQ3F6ss!3h7&28^gzfK<#erV3*bhUxq>Td)@Hy@` z3GFIQ2jX;SI7fPLI(y+90sS%Q9%CoRIbssJOpx?}7^cOn=Z{HaMB_=740_#DaO{Ni z9BG3GPD*3QS>=V3(h@y62e#7~S1!UoPD^p*qH^XmCIsY?%D#BnR3G#B5ctbJ$*eqe zMtV;q-QfL7`a&e;`e{!NQaoQv-Gmr`T#%xa_2(rA8NkdWKFjZS1?C5wfgmm%#=j1N z!{10r#KNB|BSXw2B}+}ku0WA+pUdg@dcvUYf^-83(QrPyfZjVydmeXD+H8uz^^C~~ zF8OHaxG0sOm8!%Sd@GID$C`yuXtL4hVIF6>D;CF?fyo{wneziaOr-=8?yo9e<}Hhb zec!5<-6qjwnEN0dK;o3l@1%tiN$6>lfq!d=qVk|r^EJ~>#o@(Hose#Z{FilMyXVqF zqL^uh7g|XuOw>^xXcl-SaY2rnhTm zk!CJexojhLp|aSz1gP9oH)phL%XqM50w)qGb#O@BJCFo_*J1|vnm zjsFW>Jv>Cv4sud^<`NG-Wk!f3l9Go>xhrs!3$4|M0s9mODG<+^>s#I%q)dyXLrmx+ zLPpbJux1o}3_S!sPvagUZw!5(T!s;G)H$@evBe~zPD zL}F30#?p~G(O{|H`dv7zO`v5~X5nJ9h#{h2v6${~|!+;NUbm0#o)2)95HMM}vl> z8|W+q+j;{XJvQIcJJJ?d`sF_bC?H%g1uAX?{QCyFXyT$iNQM283N<9&pu?*sf>YI7 z-1Quoi)+Ab5%7n=jilTnl=m=tVmduNveH6AX9j(%Tmp$NcYBKb!HNp6FL13}sO$k3 zXV5pM2Q9>LlY5P;Bxh2JKpbm|aIcJU=QrI|*Xzd3Y|v64^GHkt$3;7+vJkVIKUN`c z2D4CYseky_NToGMcj?gMS!U4}&FfUQg+?OOTA<@C{SY`ci_XWrLi%i)joxbQY&t=# z>-p5q`!>SB96B5Q+~OQ`rA=^m4jqY|d|{7i=vml}y%u(ZI)Xh_5*qPI#Mdo7NM+w#T19Y6!ui!U7Ht-# zPCRSr@1@UL`g&=5&t@|L^B>`y1uDlIBH*(^IvjHyX$jgzH{7}eC-z+tj`sKxIul*m zu_e?YUdAltUs%&pMjE0ZsfY&g!?Q&+7Jc2pBDCplmDW%gxRm}uBvzU0u%eKm`)J}=?GCIca`~JVxZp&EvL57l%jgVZfwroi zNzP?f&Fh6&`_D7P+JBzRifcH`iWBAki4j%-f7~GPL$uWlP03iEyZ0A8RrjRi(QR{! zDJ3yV-ksE?pAkQw7X%l0y|;K*3o-iVcm=IbjSvfV0k79l=3R{~FAq8^D$3lhlEjRx zGR)gbs$ig(j)$*3G>MFWXfIueg~x!GF4e{K8TB617D4Aib z)c&xZ=ES?Kz5Te`+Ru->ky+%~eHGhQOLO(*S6>6Q^lse7U9818R;f-$AGDgak(8m7R^|eI0-YaM zRoH=^nYH(t*#Qk~_G7ewwey)BunHZ@vyagmjhMrI^=EpQ*lC5B6ls+5(9?7bAzi8h zVB$6!0;XpAI60*}+f41e&Zay|)5ux%J7!=h+vqa%T(Aut*x#Z3De1DZ>p40Pgs%YkcMjvz0nqY;<*suFxbtcddxHUbm};2V)9 zMqc7F8+qB$Zegaaw{fN0 z`WaU`qM>nRA>2HdKUfx!G1tBy4+ydmR-U_3FWisB1)Q-Db3qL_8xjxG;qcCTbQGp; z7vH19BXezJc7bQ52lp?%6^4{^6a>~2bcooFfQ23OR?P64J5Uw{@NNekn_OV)8vttb z80pe*T8nTv^*^!DCg2YPK#e{PGqO?d(|H5)Z6qnjv%=-WBb*zZ0aS=f<7>zJbW)PL z&xFcty=PR;>GqvcxrUnh0eu1Xeu$H!AJXKcU>_n?oJjQ*u~hXUQjO0z)hc-XLmC6M zAJR#rnm;eWRBPA~bd2jD=Ln6BtL>wWn*Q3T(FoN69HAd$X65^c9*S$|gVop{t5L&h z1oyva2Ifu&kJ6#Ap$naG6Kw6Gi*aw*-9DtOVqRsS(hI-~xOszX1ql-ok>9P^W9l=Q{QynRVxSvHh zogVR$@I&=Boe+nN{65?Rn>47K01alwXU8y)317}8Ht6S9+il8#tQ z?7iJbckl7K`;BL?ho)4hoAe@|Yn6MI%j{ZJZgy64Ift-epC2wCKlp>MAM1v-=e;e~f{Q z9t_Fc51eDLvG#FpyX7TvLR{%0+lCTcpn8 zy0kNTfvqjNF33_2g2Q|H3F}po*98src<~6cKbLaAd%X!`d2<9;RGcQGwJ2p(fLy8= zyo3EI2@IErHd9{BvDo`XA`6y5(TVa#46zF#|F>15>V2)I_-qXPc#{k zlq8TOKi=%Rt`q8~+ z*4f|Zb)pI2%wp^SC+Vosv3(>F(O(kC2*N}0DLSt3e=rr5A3NuVvm+BZh&e!VA1h3? zqi0G->S`2EMVeP)Ub;$ONVS9gI2{t5ksVlZC1~e(O1=81o)c}{RolVxZ<-n#<-&a) zE*zYkog0Hd?08+q*;@?h8nw04dr`xr!Rr%H(>y-+m6{Nhi>8C>&>Jr=u}R$^V>Ah%4x$v_<`uwn(G7h@(ERxUdhnvp={~1Luui z4i6VxeGuIJ5!@Pr+b%;3FV8=HijKvN-632gx^k^^ARc|-jPb4b+{cv5A#^ zq>zH*3|?6i`~bKo4GL=1gK*+BeQj-3ADpD#I7$6*s`~)faD`nRp$5tRW(eM;m)F_r z?7}*H6l?85tzEtmuZWbF`?$9%cf&WQsR0~kFzVHDhC*S{8O(^|;O;XtD{*6=@z(bq zZhgPe8sMulG-qHV`l9`4KSSJdG3MjP+Q*fzWShSp5c!<>|2DJROgV{I&B~bS;4M z=jlY8%J6S!0*;dR4KlO`s=vW+A+Ylsge-*PyeS9U1#GI}h6}jL8lZwVjnKq9_dv%5 zMDK!Md2<#LF5I3VBBG&p||52zCOGrA|3^(_vU4}ouSHCMt@ zylH@UzQvu`S@;3LG5s0;9i58Bb}0J}`Lw{+?`W1j+CrX#*OO5QzkY|M$OIVnJ?=F# z;g#>{WaRMM?~$h(7=DRP5_enbo2=2W`Vx&oHXpr2CyQ;CtMUT#54bz+g6JRUWPP-i zJOR^hmm;9@2c((|8-Jh+u{KhlWRHed&rx&zBKsh$h16Htqu{0=@jg$XwSI>^9ParM z!GrMRk2D2q4Ili7ldlH-Pgv@!ff+yHTL<`mq7!h-{OnIS?N0dmCpra>9!6iL8(7X8 zTfZn3OoC(1_6{=e*PegFod+TWVtUyN+Y-sZQtjWf#3VfSqbk!-RCdtEf+M{WT95TXC$MnJ&C8kiW;Jfnpm9k?{?|W&E;KT_I zH=Z-DftDrm$RVFP)a^LG9jV?etKdnSH|Q^`f^U|{L)QxV#DPK9%}W>jK2*HJ6lD|0OU%~i0aj@2vN#VaBRUaG^f{CuzCSb?QFKff!c z-nPTrb9h}%y-4O5hL`yQj^*BfdOhtLt5NGe};}e#ARdO4;4=CL$V)a3LQpHR2vQMr20FxCe#}#yb!p#M&`F6y*2U@H2Xs}a;T__Hr~%ik5Q!i9(klZLwP@Zp;hv^MY>@h>ki)@_o^A`CAf|fA#DY*$Pq3bDmA6i1wR(UuUg5KOJ zpQU(->2J@;r%?e7+vQU9JHKp~XObPt^uNffMX7}oKMa*_IaKL*UjCV&$+rJZoA|G{5?6Ml)We)Be*lU`S0>U+!clI zmQU)tjd&ZUd$+t0FXAW#FUj{2JiY5wMo@PMjd*{dUDBSUH{20ZK=gS|< z>GpQxZbTJGw1LzS0p&O36I5v{q<68+3Qmz-# z<2HXLuSAiBd@fJLg`NAkJQH z`YsT?Xg8SQx$kkxco_4&e5)?P91WkIlP~LHur8bb3k_4wotGamU`V+4dwG=je}Nt; zD!2V4dvxgQ-}+hR&iVAuayhQ<1;5A{23*w}2@4QiruhYKHt~?6V_DbUp3$+Fq%1Rv z^m*tAkt6F9f1&VuJ&VL8|D>Kx;WhP61H*Gp<-a6`J^=Mf@oRzdHDw#aQLpzn*espP zyp>ZQ0~Z`DgSa6fhLs}9qcQ9v*XAEavl7(iSEHE&ZJ;udHK0=Icvc0GaqL+`H6I_Q zCb3lbIgW*4YzP_4(y=&{l*~p^vnYzS<~mrC%u-O(JI1o7br=it6WK6Sv_?!pV&l?7F*IMJcRYy=K^-H#VGxnb2H{CP5*?>IU~car2=&Q0%_DFqnSDgMl;#O+v;ohi z+9$JQRQ#`#*>v=5)6Z%_@>~j7GyGq^3N$OR^;*ed`{1x5L& zVkT{7HPyyAkVHwLcth3ZwG{+C2#cVU#uDnpp8bq=iZYo6y?tz+8)Ilq> zl(KtpB7GhD&2F{|W&ehoJ%nB=|4#NhUAv_N4&BL4(Kd{`vWvjnKcR;?ypoklM>vmf z^Nuhjzl=SN<#XjyIXl1=v)9kIp(!p8u)FY3PWe253dRGumsYW5I$UzaRqRF`O6Vk_Fxz9_n$EW-z3~ zclR-m84rh$(L%y+oj@?hnm7{em-GKHnpq_1w;l4_r!;6L;VND^zrt0#P^(XM zTkUCT--0zgJ;Y{DQhRs_%ol>ta~2ewEEbSN!R#Xzi&G_8Qxz zPq*neKu9Ya1pj`GjU$~i5BDO~RlJmMYh zfTI|*PCWF*^b*CX{yj#NR`pZZd+bR)uIlGLU^)?3)$>Q#TKz_w{!OU-h>d`GAE6O$ zgz}Hr!x)4A^AStZ=^8cuy&`E-nm%P42nMLcW9$W7g~yMv?Y2FdfYR)KXIUVv6<+>~ z&4H80S(bRf1|6TXyJ71ICXo(h+X)_bIwAVs>?GPhN48(;ja-8<7_o1Y^(bP4J=qQg zpR?bgzdQUnyI;(=at- z$}bT=Upf6HE8t!t;~e{gs*nBW*qvf{xjh3*Wkvi-8sC_Ey``utnyPXL=6V}{V$B#5CtqfhFcx|)vpX@t>AcL|vaYj-szmGT z8||UrL)I^Bi1N?>hbC$2^+n121+5dq;1Atwh8}n8FT=h6VbQSYKWqd_xcWcrHqxvd o`43yCM>D@eXKW&Sly05zBHr^*zA+dv9<(WQnQ;=)?Y9g64VE!@asU7T delta 11763 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 MsTJ4Mlv;)V0}5B>2><{9 diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index d7cbca520113..5f664be115e2 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -32,7 +32,7 @@ use pallet_revive::{ Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, Log, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, }, - EthContractResult, + EthTransactError, EthTransactInfo, }; use sp_core::keccak_256; use sp_weights::Weight; @@ -170,12 +170,9 @@ pub enum ClientError { /// A [`codec::Error`] wrapper error. #[error(transparent)] CodecError(#[from] codec::Error), - /// The dry run failed. - #[error("dry run failed: {0}")] - DryRunFailed(String), /// Contract reverted - #[error("{}", extract_revert_message(.0).unwrap_or_default())] - Reverted(Vec), + #[error("Contract reverted")] + Reverted(EthTransactError), /// A decimal conversion failed. #[error("conversion failed")] ConversionFailed, @@ -194,19 +191,26 @@ const REVERT_CODE: i32 = 3; // TODO convert error code to https://eips.ethereum.org/EIPS/eip-1474#error-codes impl From for ErrorObjectOwned { fn from(err: ClientError) -> Self { - let msg = err.to_string(); match err { ClientError::SubxtError(subxt::Error::Rpc(err)) | ClientError::RpcError(err) => { if let Some(err) = unwrap_call_err(&err) { return err; } - ErrorObjectOwned::owned::>(CALL_EXECUTION_FAILED_CODE, msg, None) + ErrorObjectOwned::owned::>( + CALL_EXECUTION_FAILED_CODE, + err.to_string(), + None, + ) }, - ClientError::Reverted(data) => { + ClientError::Reverted(EthTransactError::Data(data)) => { + let msg = extract_revert_message(&data).unwrap_or_default(); let data = format!("0x{}", hex::encode(data)); ErrorObjectOwned::owned::(REVERT_CODE, msg, Some(data)) }, - _ => ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, None), + ClientError::Reverted(EthTransactError::Message(msg)) => + ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, None), + _ => + ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, err.to_string(), None), } } } @@ -659,41 +663,22 @@ impl Client { Ok(result) } - /// Dry run a transaction and returns the [`EthContractResult`] for the transaction. + /// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction. pub async fn dry_run( &self, - tx: &GenericTransaction, + tx: GenericTransaction, block: BlockNumberOrTagOrHash, - ) -> Result>, ClientError> { + ) -> Result, ClientError> { let runtime_api = self.runtime_api(&block).await?; + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); - // TODO: remove once subxt is updated - let value = subxt::utils::Static(tx.value.unwrap_or_default()); - let from = tx.from.map(|v| v.0.into()); - let to = tx.to.map(|v| v.0.into()); - - let payload = subxt_client::apis().revive_api().eth_transact( - from.unwrap_or_default(), - to, - value, - tx.input.clone().unwrap_or_default().0, - None, - None, - ); - - let EthContractResult { fee, gas_required, storage_deposit, result } = - runtime_api.call(payload).await?.0; + let result = runtime_api.call(payload).await?; match result { Err(err) => { log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); - Err(ClientError::DryRunFailed(format!("{err:?}"))) - }, - Ok(result) if result.did_revert() => { - log::debug!(target: LOG_TARGET, "Dry run reverted"); - Err(ClientError::Reverted(result.0.data)) + Err(ClientError::Reverted(err.0)) }, - Ok(result) => - Ok(EthContractResult { fee, gas_required, storage_deposit, result: result.0.data }), + Ok(result) => Ok(result.0), } } diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index b35497e34bd7..8072de4d3ce3 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -23,7 +23,7 @@ use jsonrpsee::{ core::{async_trait, RpcResult}, types::{ErrorCode, ErrorObjectOwned}, }; -use pallet_revive::{evm::*, EthContractResult}; +use pallet_revive::evm::*; use sp_core::{keccak_256, H160, H256, U256}; use thiserror::Error; @@ -130,10 +130,8 @@ impl EthRpcServer for EthRpcServerImpl { transaction: GenericTransaction, block: Option, ) -> RpcResult { - // estimate_gas only fails returns even if the contract traps - let dry_run = self.client.dry_run(&transaction, block.unwrap_or_default().into()).await?; - - Ok(U256::from(dry_run.fee / GAS_PRICE as u128) + GAS_PRICE) + let dry_run = self.client.dry_run(transaction, block.unwrap_or_default().into()).await?; + Ok(U256::from(dry_run.eth_gas)) } async fn call( @@ -143,9 +141,9 @@ impl EthRpcServer for EthRpcServerImpl { ) -> RpcResult { let dry_run = self .client - .dry_run(&transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) + .dry_run(transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) .await?; - Ok(dry_run.result.into()) + Ok(dry_run.data.into()) } async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult { @@ -164,13 +162,12 @@ impl EthRpcServer for EthRpcServerImpl { let tx = GenericTransaction::from_signed(tx, Some(eth_addr)); // Dry run the transaction to get the weight limit and storage deposit limit - let dry_run = self.client.dry_run(&tx, BlockTag::Latest.into()).await?; + let dry_run = self.client.dry_run(tx, BlockTag::Latest.into()).await?; - let EthContractResult { gas_required, storage_deposit, .. } = dry_run; let call = subxt_client::tx().revive().eth_transact( transaction.0, - gas_required.into(), - storage_deposit, + dry_run.gas_required.into(), + dry_run.storage_deposit, ); self.client.submit(call).await.map_err(|err| { log::debug!(target: LOG_TARGET, "submit call failed: {err:?}"); diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index a232b231bc7c..1e1c395028a4 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -27,8 +27,16 @@ use subxt::config::{signed_extensions, Config, PolkadotConfig}; with = "::subxt::utils::Static<::sp_core::U256>" ), substitute_type( - path = "pallet_revive::primitives::EthContractResult", - with = "::subxt::utils::Static<::pallet_revive::EthContractResult>" + path = "pallet_revive::evm::api::rpc_types_gen::GenericTransaction", + with = "::subxt::utils::Static<::pallet_revive::evm::GenericTransaction>" + ), + substitute_type( + path = "pallet_revive::primitives::EthTransactInfo", + with = "::subxt::utils::Static<::pallet_revive::EthTransactInfo>" + ), + substitute_type( + path = "pallet_revive::primitives::EthTransactError", + with = "::subxt::utils::Static<::pallet_revive::EthTransactError>" ), substitute_type( path = "pallet_revive::primitives::ExecReturnValue", diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 3442ed73acca..18b7e7c17e09 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -88,14 +88,14 @@ impl TransactionSigned { } } -impl TransactionLegacyUnsigned { - /// Get the rlp encoded bytes of a signed transaction with a dummy 65 bytes signature. +impl TransactionUnsigned { + /// Get a signed transaction with a dummy 65 bytes signature. pub fn dummy_signed_payload(&self) -> Vec { - let mut s = rlp::RlpStream::new(); - s.append(self); const DUMMY_SIGNATURE: [u8; 65] = [0u8; 65]; - s.append_raw(&DUMMY_SIGNATURE.as_ref(), 1); - s.out().to_vec() + self.unsigned_payload() + .into_iter() + .chain(DUMMY_SIGNATURE.iter().copied()) + .collect::>() } } @@ -567,7 +567,7 @@ mod test { #[test] fn dummy_signed_payload_works() { - let tx = TransactionLegacyUnsigned { + let tx: TransactionUnsigned = TransactionLegacyUnsigned { chain_id: Some(596.into()), gas: U256::from(21000), nonce: U256::from(1), @@ -576,10 +576,10 @@ mod test { value: U256::from(123123), input: Bytes(vec![]), r#type: TypeLegacy, - }; + } + .into(); let dummy_signed_payload = tx.dummy_signed_payload(); - let tx: TransactionUnsigned = tx.into(); let payload = Account::default().sign_transaction(tx).signed_payload(); assert_eq!(dummy_signed_payload.len(), payload.len()); } diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 338be88e69b5..ed046cb4da44 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -28,6 +28,18 @@ impl From for BlockNumberOrTagOrHash { } } +impl From for TransactionUnsigned { + fn from(tx: TransactionSigned) -> Self { + use TransactionSigned::*; + match tx { + Transaction4844Signed(tx) => tx.transaction_4844_unsigned.into(), + Transaction1559Signed(tx) => tx.transaction_1559_unsigned.into(), + Transaction2930Signed(tx) => tx.transaction_2930_unsigned.into(), + TransactionLegacySigned(tx) => tx.transaction_legacy_unsigned.into(), + } + } +} + impl TransactionInfo { /// Create a new [`TransactionInfo`] from a receipt and a signed transaction. pub fn new(receipt: ReceiptInfo, transaction_signed: TransactionSigned) -> Self { @@ -152,76 +164,69 @@ fn logs_bloom_works() { impl GenericTransaction { /// Create a new [`GenericTransaction`] from a signed transaction. pub fn from_signed(tx: TransactionSigned, from: Option) -> Self { - use TransactionSigned::*; + Self::from_unsigned(tx.into(), from) + } + + /// Create a new [`GenericTransaction`] from a unsigned transaction. + pub fn from_unsigned(tx: TransactionUnsigned, from: Option) -> Self { + use TransactionUnsigned::*; match tx { - TransactionLegacySigned(tx) => { - let tx = tx.transaction_legacy_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: tx.chain_id, - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - ..Default::default() - } + TransactionLegacyUnsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: tx.chain_id, + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + ..Default::default() }, - Transaction4844Signed(tx) => { - let tx = tx.transaction_4844_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: Some(tx.to), - gas: Some(tx.gas), - gas_price: Some(tx.max_fee_per_blob_gas), - access_list: Some(tx.access_list), - blob_versioned_hashes: tx.blob_versioned_hashes, - max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - ..Default::default() - } + Transaction4844Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: Some(tx.to), + gas: Some(tx.gas), + gas_price: Some(tx.max_fee_per_blob_gas), + access_list: Some(tx.access_list), + blob_versioned_hashes: tx.blob_versioned_hashes, + max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() }, - Transaction1559Signed(tx) => { - let tx = tx.transaction_1559_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - access_list: Some(tx.access_list), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - ..Default::default() - } + Transaction1559Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + access_list: Some(tx.access_list), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() }, - Transaction2930Signed(tx) => { - let tx = tx.transaction_2930_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - access_list: Some(tx.access_list), - ..Default::default() - } + Transaction2930Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + access_list: Some(tx.access_list), + ..Default::default() }, } } diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index a9206e38fcc8..1d65fdefdde6 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -94,7 +94,7 @@ pub struct Block { /// Uncles pub uncles: Vec, /// Withdrawals - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub withdrawals: Vec, /// Withdrawals root #[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")] @@ -148,11 +148,11 @@ pub struct GenericTransaction { pub access_list: Option, /// blobVersionedHashes /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. - #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Vec::is_empty")] + #[serde(rename = "blobVersionedHashes", default, skip_serializing_if = "Vec::is_empty")] pub blob_versioned_hashes: Vec, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub blobs: Vec, /// chainId /// Chain ID that this transaction is valid on. @@ -392,7 +392,7 @@ pub struct Log { #[serde(skip_serializing_if = "Option::is_none")] pub removed: Option, /// topics - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub topics: Vec, /// transaction hash #[serde(rename = "transactionHash")] diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 40c210304ca2..e25069b77121 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -48,7 +48,7 @@ type CallOf = ::RuntimeCall; /// We use a fixed value for the gas price. /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. -pub const GAS_PRICE: u32 = 1u32; +pub const GAS_PRICE: u32 = 1_000u32; /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. @@ -451,7 +451,7 @@ mod test { /// A builder for creating an unchecked extrinsic, and test that the check function works. #[derive(Clone)] struct UncheckedExtrinsicBuilder { - tx: TransactionLegacyUnsigned, + tx: GenericTransaction, gas_limit: Weight, storage_deposit_limit: BalanceOf, } @@ -460,9 +460,10 @@ mod test { /// Create a new builder with default values. fn new() -> Self { Self { - tx: TransactionLegacyUnsigned { + tx: GenericTransaction { + from: Some(Account::default().address()), chain_id: Some(::ChainId::get().into()), - gas_price: U256::from(GAS_PRICE), + gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, gas_limit: Weight::zero(), @@ -471,22 +472,22 @@ mod test { } fn estimate_gas(&mut self) { - let dry_run = crate::Pallet::::bare_eth_transact( - Account::default().substrate_account(), - self.tx.to, - self.tx.value.try_into().unwrap(), - self.tx.input.clone().0, - Weight::MAX, - u64::MAX, - |call| { + let dry_run = + crate::Pallet::::bare_eth_transact(self.tx.clone(), Weight::MAX, |call| { let call = RuntimeCall::Contracts(call); let uxt: Ex = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into(); uxt.encoded_size() as u32 + }); + + match dry_run { + Ok(dry_run) => { + log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); + self.tx.gas = Some(dry_run.eth_gas); }, - crate::DebugInfo::Skip, - crate::CollectEvents::Skip, - ); - self.tx.gas = ((dry_run.fee + GAS_PRICE as u64) / (GAS_PRICE as u64)).into(); + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err); + }, + } } /// Create a new builder with a call to the given address. @@ -500,13 +501,13 @@ mod test { /// Create a new builder with an instantiate call. fn instantiate_with(code: Vec, data: Vec) -> Self { let mut builder = Self::new(); - builder.tx.input = Bytes(code.into_iter().chain(data.into_iter()).collect()); + builder.tx.input = Some(Bytes(code.into_iter().chain(data.into_iter()).collect())); builder.estimate_gas(); builder } /// Update the transaction with the given function. - fn update(mut self, f: impl FnOnce(&mut TransactionLegacyUnsigned) -> ()) -> Self { + fn update(mut self, f: impl FnOnce(&mut GenericTransaction) -> ()) -> Self { f(&mut self.tx); self } @@ -522,7 +523,8 @@ mod test { 100_000_000_000_000, ); - let payload = account.sign_transaction(tx.into()).signed_payload(); + let payload = + account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload(); let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload, gas_limit, @@ -557,10 +559,10 @@ mod test { builder.check().unwrap().0, crate::Call::call:: { dest: builder.tx.to.unwrap(), - value: builder.tx.value.as_u64(), + value: builder.tx.value.unwrap_or_default().as_u64(), gas_limit: builder.gas_limit, storage_deposit_limit: builder.storage_deposit_limit, - data: builder.tx.input.0 + data: builder.tx.input.unwrap_or_default().0 } .into() ); @@ -577,7 +579,7 @@ mod test { assert_eq!( builder.check().unwrap().0, crate::Call::instantiate_with_code:: { - value: builder.tx.value.as_u64(), + value: builder.tx.value.unwrap_or_default().as_u64(), gas_limit: builder.gas_limit, storage_deposit_limit: builder.storage_deposit_limit, code, @@ -593,7 +595,7 @@ mod test { fn check_eth_transact_nonce_works() { ExtBuilder::default().build().execute_with(|| { let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.nonce = 1u32.into()); + .update(|tx| tx.nonce = Some(1u32.into())); assert_eq!( builder.check(), @@ -632,7 +634,7 @@ mod test { // Fail because the tx input fail to get the blob length assert_eq!( - builder.clone().update(|tx| tx.input = Bytes(vec![1, 2, 3])).check(), + builder.clone().update(|tx| tx.input = Some(Bytes(vec![1, 2, 3]))).check(), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) ); }); @@ -641,20 +643,40 @@ mod test { #[test] fn check_transaction_fees() { ExtBuilder::default().build().execute_with(|| { - let scenarios: [(_, Box, _); 5] = [ - ("Eth fees too low", Box::new(|tx| tx.gas_price /= 2), InvalidTransaction::Payment), - ("Gas fees too high", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call), - ("Gas fees too low", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call), + let scenarios: [(_, Box, _); 5] = [ + ( + "Eth fees too low", + Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() / 2); + }), + InvalidTransaction::Payment, + ), + ( + "Gas fees too high", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 2); + }), + InvalidTransaction::Call, + ), + ( + "Gas fees too low", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 2); + }), + InvalidTransaction::Call, + ), ( "Diff > 10%", - Box::new(|tx| tx.gas = tx.gas * 111 / 100), + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 111 / 100); + }), InvalidTransaction::Call, ), ( "Diff < 10%", Box::new(|tx| { - tx.gas_price *= 2; - tx.gas = tx.gas * 89 / 100 + tx.gas_price = Some(tx.gas_price.unwrap() * 2); + tx.gas = Some(tx.gas.unwrap() * 89 / 100); }), InvalidTransaction::Call, ), @@ -671,14 +693,19 @@ mod test { #[test] fn check_transaction_tip() { + let _ = env_logger::builder().is_test(true).try_init(); ExtBuilder::default().build().execute_with(|| { let (code, _) = compile_module("dummy").unwrap(); let data = vec![]; let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) - .update(|tx| tx.gas_price = tx.gas_price * 103 / 100); + .update(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); + log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); + }); let tx = &builder.tx; - let expected_tip = tx.gas_price * tx.gas - U256::from(GAS_PRICE) * tx.gas; + let expected_tip = + tx.gas_price.unwrap() * tx.gas.unwrap() - U256::from(GAS_PRICE) * tx.gas.unwrap(); let (_, extra) = builder.check().unwrap(); assert_eq!(U256::from(extra.1.tip()), expected_tip); }); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 49c08166483e..3f88b3087e9c 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -562,6 +562,8 @@ pub struct Stack<'a, T: Config, E> { debug_message: Option<&'a mut DebugBuffer>, /// Transient storage used to store data, which is kept for the duration of a transaction. transient_storage: TransientStorage, + /// Whether or not actual transfer of funds should be performed. + unchecked: bool, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData, } @@ -777,6 +779,7 @@ where storage_meter: &'a mut storage::meter::Meter, value: U256, input_data: Vec, + unchecked: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); @@ -786,6 +789,7 @@ where gas_meter, storage_meter, value, + unchecked, debug_message, )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) @@ -812,6 +816,7 @@ where value: U256, input_data: Vec, salt: Option<&[u8; 32]>, + unchecked: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result<(H160, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( @@ -825,6 +830,7 @@ where gas_meter, storage_meter, value, + unchecked, debug_message, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); @@ -869,6 +875,7 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: U256, + unchecked: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result, ExecError> { origin.ensure_mapped()?; @@ -896,6 +903,7 @@ where frames: Default::default(), debug_message, transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), + unchecked, _phantom: Default::default(), }; @@ -1073,6 +1081,7 @@ where &frame.account_id, frame.contract_info.get(&frame.account_id), executable.code_info(), + self.unchecked, )?; // Needs to be incremented before calling into the code so that it is visible // in case of recursion. @@ -2101,6 +2110,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, ), Ok(_) @@ -2193,6 +2203,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, ) .unwrap(); @@ -2233,6 +2244,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, )); @@ -2269,6 +2281,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ), ExecError { @@ -2286,6 +2299,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -2314,6 +2328,7 @@ mod tests { &mut storage_meter, 55u64.into(), vec![], + false, None, ) .unwrap(); @@ -2363,6 +2378,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2392,6 +2408,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2421,6 +2438,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![1, 2, 3, 4], + false, None, ); assert_matches!(result, Ok(_)); @@ -2457,6 +2475,7 @@ mod tests { min_balance.into(), vec![1, 2, 3, 4], Some(&[0; 32]), + false, None, ); assert_matches!(result, Ok(_)); @@ -2511,6 +2530,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, ); @@ -2575,6 +2595,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2640,6 +2661,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2672,6 +2694,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); assert_matches!(result, Ok(_)); @@ -2709,6 +2732,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2735,6 +2759,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2779,6 +2804,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2805,6 +2831,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2831,6 +2858,7 @@ mod tests { &mut storage_meter, 1u64.into(), vec![0], + false, None, ); assert_matches!(result, Err(_)); @@ -2875,6 +2903,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2920,6 +2949,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2946,6 +2976,7 @@ mod tests { U256::zero(), // <- zero value vec![], Some(&[0; 32]), + false, None, ), Err(_) @@ -2981,6 +3012,7 @@ mod tests { min_balance.into(), vec![], Some(&[0 ;32]), + false, None, ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address @@ -3032,10 +3064,10 @@ mod tests { executable, &mut gas_meter, &mut storage_meter, - min_balance.into(), vec![], Some(&[0; 32]), + false, None, ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address @@ -3100,6 +3132,7 @@ mod tests { &mut storage_meter, (min_balance * 10).into(), vec![], + false, None, ), Ok(_) @@ -3180,6 +3213,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ), Ok(_) @@ -3223,6 +3257,7 @@ mod tests { 100u64.into(), vec![], Some(&[0; 32]), + false, None, ), Err(Error::::TerminatedInConstructor.into()) @@ -3287,6 +3322,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -3349,6 +3385,7 @@ mod tests { 10u64.into(), vec![], Some(&[0; 32]), + false, None, ); assert_matches!(result, Ok(_)); @@ -3395,6 +3432,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap(); @@ -3426,6 +3464,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, Some(&mut debug_buffer), ) .unwrap(); @@ -3459,6 +3498,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, Some(&mut debug_buffer), ); assert!(result.is_err()); @@ -3492,6 +3532,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, Some(&mut debug_buf_after), ) .unwrap(); @@ -3525,6 +3566,7 @@ mod tests { &mut storage_meter, U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), + false, None, )); @@ -3537,6 +3579,7 @@ mod tests { &mut storage_meter, U256::zero(), BOB_ADDR.as_bytes().to_vec(), + false, None, ) .map_err(|e| e.error), @@ -3587,6 +3630,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ) .map_err(|e| e.error), @@ -3621,6 +3665,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap(); @@ -3705,6 +3750,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap(); @@ -3831,6 +3877,7 @@ mod tests { (min_balance * 100).into(), vec![], Some(&[0; 32]), + false, None, ) .ok(); @@ -3844,6 +3891,7 @@ mod tests { (min_balance * 100).into(), vec![], Some(&[0; 32]), + false, None, )); assert_eq!(System::account_nonce(&ALICE), 1); @@ -3856,6 +3904,7 @@ mod tests { (min_balance * 200).into(), vec![], Some(&[0; 32]), + false, None, )); assert_eq!(System::account_nonce(&ALICE), 2); @@ -3868,6 +3917,7 @@ mod tests { (min_balance * 200).into(), vec![], Some(&[0; 32]), + false, None, )); assert_eq!(System::account_nonce(&ALICE), 3); @@ -3936,6 +3986,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4047,6 +4098,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4086,6 +4138,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4125,6 +4178,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4178,6 +4232,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4234,6 +4289,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4309,6 +4365,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4379,6 +4436,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -4417,6 +4475,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4479,6 +4538,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -4512,6 +4572,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); assert_matches!(result, Ok(_)); @@ -4595,6 +4656,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4663,6 +4725,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -4734,6 +4797,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); assert_matches!(result, Ok(_)); @@ -4785,6 +4849,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4854,6 +4919,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4900,6 +4966,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4944,6 +5011,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4999,6 +5067,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ), Ok(_) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b55854e2eec5..12a55004600c 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,13 +41,13 @@ pub mod test_utils; pub mod weights; use crate::{ - evm::{runtime::GAS_PRICE, TransactionLegacyUnsigned}, + evm::{runtime::GAS_PRICE, GenericTransaction}, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, wasm::{CodeInfo, RuntimeCosts, WasmBlob}, }; -use alloc::boxed::Box; +use alloc::{boxed::Box, format, vec}; use codec::{Codec, Decode, Encode}; use environmental::*; use frame_support::{ @@ -74,7 +74,7 @@ use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; use sp_core::{H160, H256, U256}; use sp_runtime::{ - traits::{BadOrigin, Convert, Dispatchable, Saturating, Zero}, + traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero}, DispatchError, }; @@ -573,6 +573,8 @@ pub mod pallet { AccountUnmapped, /// Tried to map an account that is already mapped. AccountAlreadyMapped, + /// The transaction used to dry-run a contract is invalid. + InvalidGenericTransaction, } /// A reason for the pallet contracts placing a hold on funds. @@ -823,7 +825,7 @@ pub mod pallet { dest, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), data, DebugInfo::Skip, CollectEvents::Skip, @@ -859,7 +861,7 @@ pub mod pallet { origin, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), Code::Existing(code_hash), data, salt, @@ -925,7 +927,7 @@ pub mod pallet { origin, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), Code::Upload(code), data, salt, @@ -1083,7 +1085,7 @@ fn dispatch_result( impl Pallet where - BalanceOf: Into + TryFrom, + BalanceOf: Into + TryFrom + Bounded, MomentOf: Into, T::Hash: frame_support::traits::IsType, { @@ -1098,7 +1100,7 @@ where dest: H160, value: BalanceOf, gas_limit: Weight, - storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, data: Vec, debug: DebugInfo, collect_events: CollectEvents, @@ -1112,7 +1114,10 @@ where }; let try_call = || { let origin = Origin::from_runtime_origin(origin)?; - let mut storage_meter = StorageMeter::new(&origin, storage_deposit_limit, value)?; + let mut storage_meter = match storage_deposit_limit { + DepositLimit::Balance(limit) => StorageMeter::new(&origin, limit, value)?, + DepositLimit::Unchecked => StorageMeter::new_unchecked(BalanceOf::::max_value()), + }; let result = ExecStack::>::run_call( origin.clone(), dest, @@ -1120,9 +1125,14 @@ where &mut storage_meter, Self::convert_native_to_evm(value), data, + storage_deposit_limit.is_unchecked(), debug_message.as_mut(), )?; - storage_deposit = storage_meter.try_into_deposit(&origin)?; + storage_deposit = storage_meter + .try_into_deposit(&origin, storage_deposit_limit.is_unchecked()) + .inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); + })?; Ok(result) }; let result = Self::run_guarded(try_call); @@ -1151,7 +1161,7 @@ where origin: OriginFor, value: BalanceOf, gas_limit: Weight, - mut storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, code: Code, data: Vec, salt: Option<[u8; 32]>, @@ -1162,13 +1172,24 @@ where let mut storage_deposit = Default::default(); let mut debug_message = if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; + + let unchecked_deposit_limit = storage_deposit_limit.is_unchecked(); + let mut storage_deposit_limit = match storage_deposit_limit { + DepositLimit::Balance(limit) => limit, + DepositLimit::Unchecked => BalanceOf::::max_value(), + }; + let try_instantiate = || { let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; let (executable, upload_deposit) = match code { Code::Upload(code) => { let upload_account = T::UploadOrigin::ensure_origin(origin)?; - let (executable, upload_deposit) = - Self::try_upload_code(upload_account, code, storage_deposit_limit)?; + let (executable, upload_deposit) = Self::try_upload_code( + upload_account, + code, + storage_deposit_limit, + unchecked_deposit_limit, + )?; storage_deposit_limit.saturating_reduce(upload_deposit); (executable, upload_deposit) }, @@ -1176,8 +1197,12 @@ where (WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), }; let instantiate_origin = Origin::from_account_id(instantiate_account.clone()); - let mut storage_meter = - StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)?; + let mut storage_meter = if unchecked_deposit_limit { + StorageMeter::new_unchecked(storage_deposit_limit) + } else { + StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)? + }; + let result = ExecStack::>::run_instantiate( instantiate_account, executable, @@ -1186,10 +1211,11 @@ where Self::convert_native_to_evm(value), data, salt.as_ref(), + unchecked_deposit_limit, debug_message.as_mut(), ); storage_deposit = storage_meter - .try_into_deposit(&instantiate_origin)? + .try_into_deposit(&instantiate_origin, unchecked_deposit_limit)? .saturating_add(&StorageDeposit::Charge(upload_deposit)); result }; @@ -1227,16 +1253,10 @@ where /// - `debug`: Debugging configuration. /// - `collect_events`: Event collection configuration. pub fn bare_eth_transact( - origin: T::AccountId, - dest: Option, - value: U256, - input: Vec, + mut tx: GenericTransaction, gas_limit: Weight, - storage_deposit_limit: BalanceOf, utx_encoded_size: impl Fn(Call) -> u32, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> EthContractResult> + ) -> Result>, EthTransactError> where T: pallet_transaction_payment::Config, ::RuntimeCall: @@ -1247,26 +1267,61 @@ where T::Nonce: Into, T::Hash: frame_support::traits::IsType, { - log::debug!(target: LOG_TARGET, "bare_eth_transact: dest: {dest:?} value: {value:?} - gas_limit: {gas_limit:?} storage_deposit_limit: {storage_deposit_limit:?}"); + log::debug!(target: LOG_TARGET, "bare_eth_transact: tx: {tx:?} gas_limit: {gas_limit:?}"); + + let Some(from) = tx.from else { + return Err(EthTransactError::Message("Missing from address".into())); + }; + + let origin = T::AddressMapper::to_account_id(&from); - // Get the nonce to encode in the tx. - let nonce: T::Nonce = >::account_nonce(&origin); + let storage_deposit_limit = if tx.gas.is_some() { + DepositLimit::Balance(BalanceOf::::max_value()) + } else { + DepositLimit::Unchecked + }; + + // TODO remove once we have revisited how we encode the gas limit. + if tx.nonce.is_none() { + tx.nonce = Some(>::account_nonce(&origin).into()); + } + if tx.gas_price.is_none() { + tx.gas_price = Some(GAS_PRICE.into()); + } + if tx.chain_id.is_none() { + tx.chain_id = Some(T::ChainId::get().into()); + } // Convert the value to the native balance type. - let native_value = match Self::convert_evm_to_native(value) { + let evm_value = tx.value.unwrap_or_default(); + let native_value = match Self::convert_evm_to_native(evm_value) { Ok(v) => v, - Err(err) => - return EthContractResult { - gas_required: Default::default(), - storage_deposit: Default::default(), - fee: Default::default(), - result: Err(err.into()), - }, + Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())), + }; + + let input = tx.input.clone().unwrap_or_default().0; + let debug = DebugInfo::Skip; + let collect_events = CollectEvents::Skip; + + let extract_error = |err| { + if err == Error::::TransferFailed.into() || + err == Error::::StorageDepositNotEnoughFunds.into() || + err == Error::::StorageDepositLimitExhausted.into() + { + let balance = Self::evm_balance(&from); + return Err(EthTransactError::Message( + format!("insufficient funds for gas * price + value: address {from:?} have {balance} want {evm_value} (supplied gas {})", + tx.gas.unwrap_or_default())) + ); + } + + return Err(EthTransactError::Message(format!( + "Failed to instantiate contract: {err:?}" + ))); }; // Dry run the call - let (mut result, dispatch_info) = match dest { + let (mut result, dispatch_info) = match tx.to { // A contract call. Some(dest) => { // Dry run the call. @@ -1281,11 +1336,24 @@ where collect_events, ); - let result = EthContractResult { + let data = match result.result { + Ok(return_value) => { + if return_value.did_revert() { + return Err(EthTransactError::Data(return_value.data)); + } + return_value.data + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to execute call: {err:?}"); + return extract_error(err) + }, + }; + + let result = EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), - result: result.result, - fee: Default::default(), + data, + eth_gas: Default::default(), }; // Get the dispatch info of the call. let dispatch_call: ::RuntimeCall = crate::Call::::call { @@ -1326,11 +1394,24 @@ where collect_events, ); - let result = EthContractResult { + let returned_data = match result.result { + Ok(return_value) => { + if return_value.result.did_revert() { + return Err(EthTransactError::Data(return_value.result.data)); + } + return_value.result.data + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to instantiate: {err:?}"); + return extract_error(err) + }, + }; + + let result = EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), - result: result.result.map(|v| v.result), - fee: Default::default(), + data: returned_data, + eth_gas: Default::default(), }; // Get the dispatch info of the call. @@ -1348,23 +1429,18 @@ where }, }; - let mut tx = TransactionLegacyUnsigned { - value, - input: input.into(), - nonce: nonce.into(), - chain_id: Some(T::ChainId::get().into()), - gas_price: GAS_PRICE.into(), - to: dest, - ..Default::default() - }; - // The transaction fees depend on the extrinsic's length, which in turn is influenced by // the encoded length of the gas limit specified in the transaction (tx.gas). // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. // with a maximum of 3 iterations to avoid an infinite loop. for _ in 0..3 { + let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { + log::debug!(target: LOG_TARGET, "Failed to convert to unsigned"); + return Err(EthTransactError::Message("Invalid transaction".into())); + }; + let eth_dispatch_call = crate::Call::::eth_transact { - payload: tx.dummy_signed_payload(), + payload: unsigned_tx.dummy_signed_payload(), gas_limit: result.gas_required, storage_deposit_limit: result.storage_deposit, }; @@ -1375,17 +1451,18 @@ where 0u32.into(), ) .into(); + let eth_gas: U256 = (fee / GAS_PRICE.into()).into(); - if fee == result.fee { - log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} fee: {fee:?}"); + if eth_gas == result.eth_gas { + log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); break; } - result.fee = fee; - tx.gas = (fee / GAS_PRICE.into()).into(); - log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {:?}", tx.gas); + result.eth_gas = eth_gas; + tx.gas = Some(eth_gas.into()); + log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {eth_gas:?}"); } - result + Ok(result) } /// Get the balance with EVM decimals of the given `address`. @@ -1403,7 +1480,7 @@ where storage_deposit_limit: BalanceOf, ) -> CodeUploadResult> { let origin = T::UploadOrigin::ensure_origin(origin)?; - let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit)?; + let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, false)?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) } @@ -1421,9 +1498,10 @@ where origin: T::AccountId, code: Vec, storage_deposit_limit: BalanceOf, + unchecked: bool, ) -> Result<(WasmBlob, BalanceOf), DispatchError> { let mut module = WasmBlob::from_code(code, origin)?; - let deposit = module.store_code()?; + let deposit = module.store_code(unchecked)?; ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); Ok((module, deposit)) } @@ -1527,14 +1605,7 @@ sp_api::decl_runtime_apis! { /// Perform an Ethereum call. /// /// See [`crate::Pallet::bare_eth_transact`] - fn eth_transact( - origin: H160, - dest: Option, - value: U256, - input: Vec, - gas_limit: Option, - storage_deposit_limit: Option, - ) -> EthContractResult; + fn eth_transact(tx: GenericTransaction) -> Result, EthTransactError>; /// Upload new code without instantiating a contract from it. /// diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 024b1f3448e1..c091ecf288c5 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -17,8 +17,8 @@ //! A crate that hosts a common definitions that are relevant for the pallet-revive. -use crate::H160; -use alloc::vec::Vec; +use crate::{H160, U256}; +use alloc::{string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::weights::Weight; use pallet_revive_uapi::ReturnFlags; @@ -28,6 +28,27 @@ use sp_runtime::{ DispatchError, RuntimeDebug, }; +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum DepositLimit { + Unchecked, + Balance(T), +} + +impl DepositLimit { + pub fn is_unchecked(&self) -> bool { + match self { + Self::Unchecked => true, + _ => false, + } + } +} + +impl From for DepositLimit { + fn from(value: T) -> Self { + Self::Balance(value) + } +} + /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and /// `ContractsApi::instantiate`. /// @@ -84,15 +105,22 @@ pub struct ContractResult { /// The result of the execution of a `eth_transact` call. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct EthContractResult> { - /// The fee charged for the execution. - pub fee: Balance, +pub struct EthTransactInfo { /// The amount of gas that was necessary to execute the transaction. pub gas_required: Weight, /// Storage deposit charged. pub storage_deposit: Balance, - /// The execution result. - pub result: R, + /// The weight and deposit equivalent in EVM Gas. + pub eth_gas: U256, + /// The execution return value. + pub data: Vec, +} + +/// Error type of a `eth_transact` call. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum EthTransactError { + Data(Vec), + Message(String), } /// Result type of a `bare_code_upload` call. diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 712010bc8257..f068509c34f1 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -373,24 +373,36 @@ where } } + /// Create new storage meter without checking the limit. + pub fn new_unchecked(limit: BalanceOf) -> Self { + return Self { limit, ..Default::default() } + } + /// The total amount of deposit that should change hands as result of the execution /// that this meter was passed into. This will also perform all the charges accumulated /// in the whole contract stack. /// /// This drops the root meter in order to make sure it is only called when the whole /// execution did finish. - pub fn try_into_deposit(self, origin: &Origin) -> Result, DispatchError> { - // Only refund or charge deposit if the origin is not root. - let origin = match origin { - Origin::Root => return Ok(Deposit::Charge(Zero::zero())), - Origin::Signed(o) => o, - }; - for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { - E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; - } - for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { - E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + pub fn try_into_deposit( + self, + origin: &Origin, + unchecked: bool, + ) -> Result, DispatchError> { + if !unchecked { + // Only refund or charge deposit if the origin is not root. + let origin = match origin { + Origin::Root => return Ok(Deposit::Charge(Zero::zero())), + Origin::Signed(o) => o, + }; + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } } + Ok(self.total_deposit) } } @@ -425,13 +437,18 @@ impl> RawMeter { contract: &T::AccountId, contract_info: &mut ContractInfo, code_info: &CodeInfo, + unchecked: bool, ) -> Result<(), DispatchError> { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); // We need to make sure that the contract's account exists. let ed = Pallet::::min_balance(); self.total_deposit = Deposit::Charge(ed); - T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?; + if unchecked { + T::Currency::set_balance(contract, ed); + } else { + T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?; + } // A consumer is added at account creation and removed it on termination, otherwise the // runtime could remove the account. As long as a contract exists its account must exist. @@ -479,6 +496,7 @@ impl> RawMeter { } if let Deposit::Charge(amount) = total_deposit { if amount > self.limit { + log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit); return Err(>::StorageDepositLimitExhausted.into()) } } @@ -811,7 +829,10 @@ mod tests { nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); meter.absorb(nested0, &BOB, Some(&mut nested0_info)); - assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + assert_eq!( + meter.try_into_deposit(&test_case.origin, false).unwrap(), + test_case.deposit + ); assert_eq!(nested0_info.extra_deposit(), 112); assert_eq!(nested1_info.extra_deposit(), 110); @@ -882,7 +903,10 @@ mod tests { nested0.absorb(nested1, &CHARLIE, None); meter.absorb(nested0, &BOB, None); - assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + assert_eq!( + meter.try_into_deposit(&test_case.origin, false).unwrap(), + test_case.deposit + ); assert_eq!(TestExtTestValue::get(), test_case.expected) } } diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index e64f58894432..8ba5e7384070 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -18,7 +18,8 @@ use super::{deposit_limit, GAS_LIMIT}; use crate::{ address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult, - DebugInfo, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, + DebugInfo, DepositLimit, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, + Pallet, Weight, }; use frame_support::pallet_prelude::DispatchResultWithPostInfo; use paste::paste; @@ -133,7 +134,7 @@ builder!( origin: OriginFor, value: BalanceOf, gas_limit: Weight, - storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, code: Code, data: Vec, salt: Option<[u8; 32]>, @@ -159,7 +160,7 @@ builder!( origin, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), code, data: vec![], salt: Some([0; 32]), @@ -198,7 +199,7 @@ builder!( dest: H160, value: BalanceOf, gas_limit: Weight, - storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, data: Vec, debug: DebugInfo, collect_events: CollectEvents, @@ -216,7 +217,7 @@ builder!( dest, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), data: vec![], debug: DebugInfo::UnsafeDebug, collect_events: CollectEvents::Skip, diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 34afe8aabfe6..1df300f031a7 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -1249,7 +1249,7 @@ fn transfer_expendable_cannot_kill_account() { test_utils::contract_info_storage_deposit(&addr) ); - // Some ot the total balance is held, so it can't be transferred. + // Some or the total balance is held, so it can't be transferred. assert_err!( <::Currency as Mutate>::transfer( &account, @@ -2290,7 +2290,7 @@ fn gas_estimation_for_subcalls() { // Make the same call using the estimated gas. Should succeed. let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_ok!(&result.result); @@ -2298,7 +2298,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little ref_time let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required.sub_ref_time(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_err!(result.result, error); @@ -2306,7 +2306,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little proof_size let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required.sub_proof_size(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_err!(result.result, error); @@ -3592,7 +3592,7 @@ fn deposit_limit_in_nested_instantiate() { // Set enough deposit limit for the child instantiate. This should succeed. let result = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 4 + 2) + .storage_deposit_limit((callee_info_len + 2 + ED + 4 + 2).into()) .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)).encode()) .build(); @@ -3879,7 +3879,7 @@ fn locking_delegate_dependency_works() { // Locking a dependency with a storage limit too low should fail. assert_err!( builder::bare_call(addr_caller) - .storage_deposit_limit(dependency_deposit - 1) + .storage_deposit_limit((dependency_deposit - 1).into()) .data((1u32, hash2addr(&callee_hashes[0]), callee_hashes[0]).encode()) .build() .result, diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs index 7c4fbba71f65..c9e19e52ace1 100644 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -21,6 +21,7 @@ use crate::{ debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, primitives::ExecReturnValue, test_utils::*, + DepositLimit, }; use frame_support::traits::Currency; use pretty_assertions::assert_eq; @@ -114,7 +115,7 @@ fn debugging_works() { RuntimeOrigin::signed(ALICE), 0, GAS_LIMIT, - deposit_limit::(), + DepositLimit::Balance(deposit_limit::()), Code::Upload(wasm), vec![], Some([0u8; 32]), @@ -198,7 +199,7 @@ fn call_interception_works() { RuntimeOrigin::signed(ALICE), 0, GAS_LIMIT, - deposit_limit::(), + deposit_limit::().into(), Code::Upload(wasm), vec![], // some salt to ensure that the address of this contract is unique among all tests diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index f10c4f5fddf8..82aa67a1d678 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -183,7 +183,7 @@ where } /// Puts the module blob into storage, and returns the deposit collected for the storage. - pub fn store_code(&mut self) -> Result, Error> { + pub fn store_code(&mut self, unchecked: bool) -> Result, Error> { let code_hash = *self.code_hash(); >::mutate(code_hash, |stored_code_info| { match stored_code_info { @@ -195,15 +195,16 @@ where // the `owner` is always the origin of the current transaction. None => { let deposit = self.code_info.deposit; - T::Currency::hold( + + if !unchecked { + T::Currency::hold( &HoldReason::CodeUploadDepositReserve.into(), &self.code_info.owner, deposit, - ) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner); + ) .map_err(|err| { log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner); >::StorageDepositNotEnoughFunds })?; + } self.code_info.refcount = 0; >::insert(code_hash, &self.code); From 3a10a37f0b80ceb46835c26814fbde8dc85d7556 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 15:22:37 +0100 Subject: [PATCH 08/67] fixes --- substrate/frame/revive/src/evm/runtime.rs | 334 +++++++++++----------- 1 file changed, 168 insertions(+), 166 deletions(-) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index e25069b77121..a25ea53d276c 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -48,7 +48,7 @@ type CallOf = ::RuntimeCall; /// We use a fixed value for the gas price. /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. -pub const GAS_PRICE: u32 = 1_000u32; +pub const GAS_PRICE: u32 = 1u32; /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. @@ -454,6 +454,7 @@ mod test { tx: GenericTransaction, gas_limit: Weight, storage_deposit_limit: BalanceOf, + before_validate: Option>, } impl UncheckedExtrinsicBuilder { @@ -468,6 +469,7 @@ mod test { }, gas_limit: Weight::zero(), storage_deposit_limit: 0, + before_validate: None, } } @@ -494,7 +496,7 @@ mod test { fn call_with(dest: H160) -> Self { let mut builder = Self::new(); builder.tx.to = Some(dest); - builder.estimate_gas(); + ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } @@ -502,7 +504,7 @@ mod test { fn instantiate_with(code: Vec, data: Vec) -> Self { let mut builder = Self::new(); builder.tx.input = Some(Bytes(code.into_iter().chain(data.into_iter()).collect())); - builder.estimate_gas(); + ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } @@ -511,203 +513,203 @@ mod test { f(&mut self.tx); self } + /// Set before_validate function. + fn before_validate(mut self, f: impl Fn() + Send + Sync + 'static) -> Self { + self.before_validate = Some(std::sync::Arc::new(f)); + self + } /// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension. fn check(&self) -> Result<(RuntimeCall, SignedExtra), TransactionValidityError> { - let UncheckedExtrinsicBuilder { tx, gas_limit, storage_deposit_limit } = self.clone(); - - // Fund the account. - let account = Account::default(); - let _ = ::Currency::set_balance( - &account.substrate_account(), - 100_000_000_000_000, - ); - - let payload = - account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload(); - let call = RuntimeCall::Contracts(crate::Call::eth_transact { - payload, - gas_limit, - storage_deposit_limit, - }); - - let encoded_len = call.encoded_size(); - let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); - let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; - let (account_id, extra): (AccountId32, SignedExtra) = match result.format { - ExtrinsicFormat::Signed(signer, extra) => (signer, extra), - _ => unreachable!(), - }; + ExtBuilder::default().build().execute_with(|| { + let UncheckedExtrinsicBuilder { + tx, + gas_limit, + storage_deposit_limit, + before_validate, + } = self.clone(); + + // Fund the account. + let account = Account::default(); + let _ = ::Currency::set_balance( + &account.substrate_account(), + 100_000_000_000_000, + ); + + let payload = + account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload(); + let call = RuntimeCall::Contracts(crate::Call::eth_transact { + payload, + gas_limit, + storage_deposit_limit, + }); - extra.clone().validate_and_prepare( - RuntimeOrigin::signed(account_id), - &result.function, - &result.function.get_dispatch_info(), - encoded_len, - 0, - )?; + let encoded_len = call.encoded_size(); + let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); + let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; + let (account_id, extra): (AccountId32, SignedExtra) = match result.format { + ExtrinsicFormat::Signed(signer, extra) => (signer, extra), + _ => unreachable!(), + }; - Ok((result.function, extra)) + before_validate.map(|f| f()); + extra.clone().validate_and_prepare( + RuntimeOrigin::signed(account_id), + &result.function, + &result.function.get_dispatch_info(), + encoded_len, + 0, + )?; + + Ok((result.function, extra)) + }) } } #[test] fn check_eth_transact_call_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check().unwrap().0, - crate::Call::call:: { - dest: builder.tx.to.unwrap(), - value: builder.tx.value.unwrap_or_default().as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - data: builder.tx.input.unwrap_or_default().0 - } - .into() - ); - }); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); + assert_eq!( + builder.check().unwrap().0, + crate::Call::call:: { + dest: builder.tx.to.unwrap(), + value: builder.tx.value.unwrap_or_default().as_u64(), + gas_limit: builder.gas_limit, + storage_deposit_limit: builder.storage_deposit_limit, + data: builder.tx.input.unwrap_or_default().0 + } + .into() + ); } #[test] fn check_eth_transact_instantiate_works() { - ExtBuilder::default().build().execute_with(|| { - let (code, _) = compile_module("dummy").unwrap(); - let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); - - assert_eq!( - builder.check().unwrap().0, - crate::Call::instantiate_with_code:: { - value: builder.tx.value.unwrap_or_default().as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - code, - data, - salt: None - } - .into() - ); - }); + let (code, _) = compile_module("dummy").unwrap(); + let data = vec![]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + + assert_eq!( + builder.check().unwrap().0, + crate::Call::instantiate_with_code:: { + value: builder.tx.value.unwrap_or_default().as_u64(), + gas_limit: builder.gas_limit, + storage_deposit_limit: builder.storage_deposit_limit, + code, + data, + salt: None + } + .into() + ); } #[test] fn check_eth_transact_nonce_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.nonce = Some(1u32.into())); - - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) - ); - - >::inc_account_nonce(Account::default().substrate_account()); - - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)) - ); - }); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) + .update(|tx| tx.nonce = Some(1u32.into())); + + assert_eq!( + builder.check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) + ); + + let builder = + UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).before_validate(|| { + >::inc_account_nonce(Account::default().substrate_account()); + }); + + assert_eq!( + builder.check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)) + ); } #[test] fn check_eth_transact_chain_id_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.chain_id = Some(42.into())); - - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); - }); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) + .update(|tx| tx.chain_id = Some(42.into())); + + assert_eq!( + builder.check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); } #[test] fn check_instantiate_data() { - ExtBuilder::default().build().execute_with(|| { - let code = b"invalid code".to_vec(); - let data = vec![1]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); - - // Fail because the tx input fail to get the blob length - assert_eq!( - builder.clone().update(|tx| tx.input = Some(Bytes(vec![1, 2, 3]))).check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); - }); + let code = b"invalid code".to_vec(); + let data = vec![1]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + + // Fail because the tx input fail to get the blob length + assert_eq!( + builder.clone().update(|tx| tx.input = Some(Bytes(vec![1, 2, 3]))).check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); } #[test] fn check_transaction_fees() { - ExtBuilder::default().build().execute_with(|| { - let scenarios: [(_, Box, _); 5] = [ - ( - "Eth fees too low", - Box::new(|tx| { - tx.gas_price = Some(tx.gas_price.unwrap() / 2); - }), - InvalidTransaction::Payment, - ), - ( - "Gas fees too high", - Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 2); - }), - InvalidTransaction::Call, - ), - ( - "Gas fees too low", - Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 2); - }), - InvalidTransaction::Call, - ), - ( - "Diff > 10%", - Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 111 / 100); - }), - InvalidTransaction::Call, - ), - ( - "Diff < 10%", - Box::new(|tx| { - tx.gas_price = Some(tx.gas_price.unwrap() * 2); - tx.gas = Some(tx.gas.unwrap() * 89 / 100); - }), - InvalidTransaction::Call, - ), - ]; - - for (msg, update_tx, err) in scenarios { - let builder = - UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx); - - assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg); - } - }); + let scenarios: [(_, Box, _); 5] = [ + ( + "Eth fees too low", + Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() / 2); + }), + InvalidTransaction::Payment, + ), + ( + "Gas fees too high", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 2); + }), + InvalidTransaction::Call, + ), + ( + "Gas fees too low", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 2); + }), + InvalidTransaction::Call, + ), + ( + "Diff > 10%", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 111 / 100); + }), + InvalidTransaction::Call, + ), + ( + "Diff < 10%", + Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 2); + tx.gas = Some(tx.gas.unwrap() * 89 / 100); + }), + InvalidTransaction::Call, + ), + ]; + + for (msg, update_tx, err) in scenarios { + let builder = + UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx); + + assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg); + } } #[test] fn check_transaction_tip() { - let _ = env_logger::builder().is_test(true).try_init(); - ExtBuilder::default().build().execute_with(|| { - let (code, _) = compile_module("dummy").unwrap(); - let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) - .update(|tx| { - tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); - log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); - }); + let (code, _) = compile_module("dummy").unwrap(); + let data = vec![]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) + .update(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); + log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); + }); - let tx = &builder.tx; - let expected_tip = - tx.gas_price.unwrap() * tx.gas.unwrap() - U256::from(GAS_PRICE) * tx.gas.unwrap(); - let (_, extra) = builder.check().unwrap(); - assert_eq!(U256::from(extra.1.tip()), expected_tip); - }); + let tx = &builder.tx; + let expected_tip = + tx.gas_price.unwrap() * tx.gas.unwrap() - U256::from(GAS_PRICE) * tx.gas.unwrap(); + let (_, extra) = builder.check().unwrap(); + assert_eq!(U256::from(extra.1.tip()), expected_tip); } } From f8fc0fc0bf50d8848a5721716bc956b4987882e6 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 15:24:43 +0100 Subject: [PATCH 09/67] rm codegen files --- Cargo.toml | 1 - substrate/frame/revive/rpc/codegen/Cargo.toml | 18 - substrate/frame/revive/rpc/codegen/README.md | 5 - .../frame/revive/rpc/codegen/openrpc.json | 2271 ----------------- .../frame/revive/rpc/codegen/src/LICENSE.txt | 16 - .../frame/revive/rpc/codegen/src/generator.rs | 758 ------ .../frame/revive/rpc/codegen/src/main.rs | 64 - .../frame/revive/rpc/codegen/src/open_rpc.rs | 834 ------ .../frame/revive/rpc/codegen/src/printer.rs | 527 ---- 9 files changed, 4494 deletions(-) delete mode 100644 substrate/frame/revive/rpc/codegen/Cargo.toml delete mode 100644 substrate/frame/revive/rpc/codegen/README.md delete mode 100644 substrate/frame/revive/rpc/codegen/openrpc.json delete mode 100644 substrate/frame/revive/rpc/codegen/src/LICENSE.txt delete mode 100644 substrate/frame/revive/rpc/codegen/src/generator.rs delete mode 100644 substrate/frame/revive/rpc/codegen/src/main.rs delete mode 100644 substrate/frame/revive/rpc/codegen/src/open_rpc.rs delete mode 100644 substrate/frame/revive/rpc/codegen/src/printer.rs diff --git a/Cargo.toml b/Cargo.toml index 5ece645f82c4..533ea4c9e878 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -403,7 +403,6 @@ members = [ "substrate/frame/revive/mock-network", "substrate/frame/revive/proc-macro", "substrate/frame/revive/rpc", - "substrate/frame/revive/rpc/codegen", "substrate/frame/revive/uapi", "substrate/frame/root-offences", "substrate/frame/root-testing", diff --git a/substrate/frame/revive/rpc/codegen/Cargo.toml b/substrate/frame/revive/rpc/codegen/Cargo.toml deleted file mode 100644 index 65d8ba501b1c..000000000000 --- a/substrate/frame/revive/rpc/codegen/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "pallet-revive-rpc-codegen" -version = "0.1.0" -edition.workspace = true -publish = false - -[dependencies] -Inflector = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -anyhow = { workspace = true } - -[dev-dependencies] -pretty_assertions.workspace = true - -[features] -default = ["std"] -std = ["anyhow/std", "serde/std", "serde_json/std"] diff --git a/substrate/frame/revive/rpc/codegen/README.md b/substrate/frame/revive/rpc/codegen/README.md deleted file mode 100644 index 2ca838f0db23..000000000000 --- a/substrate/frame/revive/rpc/codegen/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Generates the Ethereum JSON-RPC API from the official specification. - -- See -- See building instructions to re-generate the openrpc.json -- Include fixes from diff --git a/substrate/frame/revive/rpc/codegen/openrpc.json b/substrate/frame/revive/rpc/codegen/openrpc.json deleted file mode 100644 index 4a91ee18177c..000000000000 --- a/substrate/frame/revive/rpc/codegen/openrpc.json +++ /dev/null @@ -1,2271 +0,0 @@ -{ - "openrpc": "1.2.4", - "info": { - "title": "Ethereum JSON-RPC Specification", - "description": "A specification of the standard interface for Ethereum clients.", - "license": { - "name": "CC0-1.0", - "url": "https://creativecommons.org/publicdomain/zero/1.0/legalcode" - }, - "version": "0.0.0" - }, - "methods": [ - { - "name": "eth_accounts", - "summary": "Returns a list of addresses owned by client.", - "params": [], - "result": { - "name": "Accounts", - "schema": { - "title": "Accounts", - "type": "array", - "items": { - "$ref": "#/components/schemas/address" - } - } - } - }, - { - "name": "eth_blobBaseFee", - "summary": "Returns the base fee per blob gas in wei.", - "params": [], - "result": { - "name": "Blob gas base fee", - "schema": { - "title": "Blob gas base fee", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_blockNumber", - "summary": "Returns the number of most recent block.", - "params": [], - "result": { - "name": "Block number", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_call", - "summary": "Executes a new message call immediately without creating a transaction on the block chain.", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/GenericTransaction" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Return data", - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - }, - { - "name": "eth_chainId", - "summary": "Returns the chain ID of the current network.", - "params": [], - "result": { - "name": "Chain ID", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_coinbase", - "summary": "Returns the client coinbase address.", - "params": [], - "result": { - "name": "Coinbase address", - "schema": { - "$ref": "#/components/schemas/address" - } - } - }, - { - "name": "eth_createAccessList", - "summary": "Generates an access list for a transaction.", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/GenericTransaction" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Gas used", - "schema": { - "title": "Access list result", - "type": "object", - "additionalProperties": false, - "properties": { - "accessList": { - "title": "accessList", - "$ref": "#/components/schemas/AccessList" - }, - "error": { - "title": "error", - "type": "string" - }, - "gasUsed": { - "title": "Gas used", - "$ref": "#/components/schemas/uint" - } - } - } - } - }, - { - "name": "eth_estimateGas", - "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/GenericTransaction" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Gas used", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_feeHistory", - "summary": "Transaction fee history", - "description": "Returns transaction base fee per gas and effective priority fee per gas for the requested/supported block range.", - "params": [ - { - "name": "blockCount", - "description": "Requested range of blocks. Clients will return less than the requested range if not all blocks are available.", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint" - } - }, - { - "name": "newestBlock", - "description": "Highest block of the requested range.", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - }, - { - "name": "rewardPercentiles", - "description": "A monotonically increasing list of percentile values. For each block in the requested range, the transactions will be sorted in ascending order by effective tip per gas and the coresponding effective tip for the percentile will be determined, accounting for gas consumed.", - "required": true, - "schema": { - "title": "rewardPercentiles", - "type": "array", - "items": { - "title": "rewardPercentile", - "description": "Floating point value between 0 and 100.", - "type": "number" - } - } - } - ], - "result": { - "name": "feeHistoryResult", - "description": "Fee history for the returned block range. This can be a subsection of the requested range if not all blocks are available.", - "schema": { - "title": "feeHistoryResults", - "description": "Fee history results.", - "type": "object", - "required": [ - "oldestBlock", - "baseFeePerGas", - "gasUsedRatio" - ], - "additionalProperties": false, - "properties": { - "oldestBlock": { - "title": "oldestBlock", - "description": "Lowest number block of returned range.", - "$ref": "#/components/schemas/uint" - }, - "baseFeePerGas": { - "title": "baseFeePerGasArray", - "description": "An array of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks.", - "type": "array", - "items": { - "$ref": "#/components/schemas/uint" - } - }, - "baseFeePerBlobGas": { - "title": "baseFeePerBlobGasArray", - "description": "An array of block base fees per blob gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-4844 blocks.", - "type": "array", - "items": { - "$ref": "#/components/schemas/uint" - } - }, - "gasUsedRatio": { - "title": "gasUsedRatio", - "description": "An array of block gas used ratios. These are calculated as the ratio of gasUsed and gasLimit.", - "type": "array", - "items": { - "$ref": "#/components/schemas/ratio" - } - }, - "blobGasUsedRatio": { - "title": "blobGasUsedRatio", - "description": "An array of block blob gas used ratios. These are calculated as the ratio of blobGasUsed and the max blob gas per block.", - "type": "array", - "items": { - "$ref": "#/components/schemas/ratio" - } - }, - "reward": { - "title": "rewardArray", - "description": "A two-dimensional array of effective priority fees per gas at the requested block percentiles.", - "type": "array", - "items": { - "title": "rewardPercentile", - "description": "An array of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty.", - "type": "array", - "items": { - "title": "rewardPercentile", - "description": "A given percentile sample of effective priority fees per gas from a single block in ascending order, weighted by gas used. Zeroes are returned if the block is empty.", - "$ref": "#/components/schemas/uint" - } - } - } - } - } - } - }, - { - "name": "eth_gasPrice", - "summary": "Returns the current price per gas in wei.", - "params": [], - "result": { - "name": "Gas price", - "schema": { - "title": "Gas price", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getBalance", - "summary": "Returns the balance of the account of given address.", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Balance", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getBlockByHash", - "summary": "Returns information about a block by hash.", - "params": [ - { - "name": "Block hash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "name": "Hydrated transactions", - "required": true, - "schema": { - "title": "hydrated", - "type": "boolean" - } - } - ], - "result": { - "name": "Block information", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "$ref": "#/components/schemas/Block" - } - ] - } - } - }, - { - "name": "eth_getBlockByNumber", - "summary": "Returns information about a block by number.", - "params": [ - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - }, - { - "name": "Hydrated transactions", - "required": true, - "schema": { - "title": "hydrated", - "type": "boolean" - } - } - ], - "result": { - "name": "Block information", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "$ref": "#/components/schemas/Block" - } - ] - } - } - }, - { - "name": "eth_getBlockReceipts", - "summary": "Returns the receipts of a block by number or hash.", - "params": [ - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Receipts information", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "title": "Receipts information", - "type": "array", - "items": { - "$ref": "#/components/schemas/ReceiptInfo" - } - } - ] - } - } - }, - { - "name": "eth_getBlockTransactionCountByHash", - "summary": "Returns the number of transactions in a block from a block matching the given block hash.", - "params": [ - { - "name": "Block hash", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - ], - "result": { - "name": "Transaction count", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "title": "Transaction count", - "$ref": "#/components/schemas/uint" - } - ] - } - } - }, - { - "name": "eth_getBlockTransactionCountByNumber", - "summary": "Returns the number of transactions in a block matching the given block number.", - "params": [ - { - "name": "Block", - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Transaction count", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "title": "Transaction count", - "$ref": "#/components/schemas/uint" - } - ] - } - } - }, - { - "name": "eth_getCode", - "summary": "Returns code at a given address.", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Bytecode", - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - }, - { - "name": "eth_getFilterChanges", - "summary": "Polling method for a filter, which returns an array of logs which occurred since last poll.", - "params": [ - { - "name": "Filter Identifier", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - ], - "result": { - "name": "Log objects", - "schema": { - "$ref": "#/components/schemas/FilterResults" - } - } - }, - { - "name": "eth_getFilterLogs", - "summary": "Returns an array of all logs matching filter with given id.", - "params": [ - { - "name": "Filter Identifier", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - ], - "result": { - "name": "Log objects", - "schema": { - "$ref": "#/components/schemas/FilterResults" - } - } - }, - { - "name": "eth_getLogs", - "summary": "Returns an array of all logs matching filter with given id.", - "params": [ - { - "name": "Filter", - "schema": { - "$ref": "#/components/schemas/Filter" - } - } - ], - "result": { - "name": "Log objects", - "schema": { - "$ref": "#/components/schemas/FilterResults" - } - } - }, - { - "name": "eth_getProof", - "summary": "Returns the merkle proof for a given account and optionally some storage keys.", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "StorageKeys", - "required": true, - "schema": { - "title": "Storage keys", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytesMax32" - } - } - }, - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Account", - "schema": { - "$ref": "#/components/schemas/AccountProof" - } - } - }, - { - "name": "eth_getStorageAt", - "summary": "Returns the value from a storage position at a given address.", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Storage slot", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint256" - } - }, - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Value", - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - }, - { - "name": "eth_getTransactionByBlockHashAndIndex", - "summary": "Returns information about a transaction by block hash and transaction index position.", - "params": [ - { - "name": "Block hash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "name": "Transaction index", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint" - } - } - ], - "result": { - "name": "Transaction information", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "$ref": "#/components/schemas/TransactionInfo" - } - ] - } - } - }, - { - "name": "eth_getTransactionByBlockNumberAndIndex", - "summary": "Returns information about a transaction by block number and transaction index position.", - "params": [ - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - }, - { - "name": "Transaction index", - "required": true, - "schema": { - "$ref": "#/components/schemas/uint" - } - } - ], - "result": { - "name": "Transaction information", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "$ref": "#/components/schemas/TransactionInfo" - } - ] - } - } - }, - { - "name": "eth_getTransactionByHash", - "summary": "Returns the information about a transaction requested by transaction hash.", - "params": [ - { - "name": "Transaction hash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - ], - "result": { - "name": "Transaction information", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "$ref": "#/components/schemas/TransactionInfo" - } - ] - } - } - }, - { - "name": "eth_getTransactionCount", - "summary": "Returns the number of transactions sent from an address.", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Block", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTagOrHash" - } - } - ], - "result": { - "name": "Transaction count", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_getTransactionReceipt", - "summary": "Returns the receipt of a transaction by transaction hash.", - "params": [ - { - "name": "Transaction hash", - "required": true, - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - ], - "result": { - "name": "Receipt information", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "$ref": "#/components/schemas/ReceiptInfo" - } - ] - } - } - }, - { - "name": "eth_getUncleCountByBlockHash", - "summary": "Returns the number of uncles in a block from a block matching the given block hash.", - "params": [ - { - "name": "Block hash", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - ], - "result": { - "name": "Uncle count", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "title": "Uncle count", - "$ref": "#/components/schemas/uint" - } - ] - } - } - }, - { - "name": "eth_getUncleCountByBlockNumber", - "summary": "Returns the number of transactions in a block matching the given block number.", - "params": [ - { - "name": "Block", - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Uncle count", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/notFound" - }, - { - "title": "Uncle count", - "$ref": "#/components/schemas/uint" - } - ] - } - } - }, - { - "name": "eth_maxPriorityFeePerGas", - "summary": "Returns the current maxPriorityFeePerGas per gas in wei.", - "params": [], - "result": { - "name": "Max priority fee per gas", - "schema": { - "title": "Max priority fee per gas", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_newBlockFilter", - "summary": "Creates a filter in the node, to notify when a new block arrives.", - "params": [], - "result": { - "name": "Filter Identifier", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_newFilter", - "summary": "Creates a filter object, based on filter options, to notify when the state changes (logs).", - "params": [ - { - "name": "Filter", - "schema": { - "$ref": "#/components/schemas/Filter" - } - } - ], - "result": { - "name": "Filter Identifier", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_newPendingTransactionFilter", - "summary": "Creates a filter in the node, to notify when new pending transactions arrive.", - "params": [], - "result": { - "name": "Filter Identifier", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - }, - { - "name": "eth_sendRawTransaction", - "summary": "Submits a raw transaction. For EIP-4844 transactions, the raw form must be the network form. This means it includes the blobs, KZG commitments, and KZG proofs.", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - ], - "result": { - "name": "Transaction hash", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - }, - { - "name": "eth_sendTransaction", - "summary": "Signs and submits a transaction.", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/GenericTransaction" - } - } - ], - "result": { - "name": "Transaction hash", - "schema": { - "$ref": "#/components/schemas/hash32" - } - } - }, - { - "name": "eth_sign", - "summary": "Returns an EIP-191 signature over the provided data.", - "params": [ - { - "name": "Address", - "required": true, - "schema": { - "$ref": "#/components/schemas/address" - } - }, - { - "name": "Message", - "required": true, - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - ], - "result": { - "name": "Signature", - "schema": { - "$ref": "#/components/schemas/bytes65" - } - } - }, - { - "name": "eth_signTransaction", - "summary": "Returns an RLP encoded transaction signed by the specified account.", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/GenericTransaction" - } - } - ], - "result": { - "name": "Encoded transaction", - "schema": { - "$ref": "#/components/schemas/bytes" - } - } - }, - { - "name": "eth_syncing", - "summary": "Returns an object with data about the sync status or false.", - "params": [], - "result": { - "name": "Syncing status", - "schema": { - "$ref": "#/components/schemas/SyncingStatus" - } - } - }, - { - "name": "eth_uninstallFilter", - "summary": "Uninstalls a filter with given id.", - "params": [ - { - "name": "Filter Identifier", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - ], - "result": { - "name": "Success", - "schema": { - "type": "boolean" - } - } - } - ], - "components": { - "schemas": { - "address": { - "title": "hex encoded address", - "type": "string", - "pattern": "^0x[0-9a-fA-F]{40}$" - }, - "addresses": { - "title": "hex encoded address", - "type": "array", - "items": { - "$ref": "#/components/schemas/address" - } - }, - "byte": { - "title": "hex encoded byte", - "type": "string", - "pattern": "^0x([0-9a-fA-F]?){1,2}$" - }, - "bytes": { - "title": "hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]*$" - }, - "bytesMax32": { - "title": "32 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{0,64}$" - }, - "bytes8": { - "title": "8 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{16}$" - }, - "bytes32": { - "title": "32 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{64}$" - }, - "bytes48": { - "title": "48 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{96}$" - }, - "bytes96": { - "title": "96 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{192}$" - }, - "bytes256": { - "title": "256 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{512}$" - }, - "bytes65": { - "title": "65 hex encoded bytes", - "type": "string", - "pattern": "^0x[0-9a-f]{130}$" - }, - "ratio": { - "title": "normalized ratio", - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "uint": { - "title": "hex encoded unsigned integer", - "type": "string", - "pattern": "^0x([1-9a-f]+[0-9a-f]*|0)$" - }, - "uint64": { - "title": "hex encoded 64 bit unsigned integer", - "type": "string", - "pattern": "^0x([1-9a-f]+[0-9a-f]{0,15})|0$" - }, - "uint256": { - "title": "hex encoded 256 bit unsigned integer", - "type": "string", - "pattern": "^0x([1-9a-f]+[0-9a-f]{0,31})|0$" - }, - "hash32": { - "title": "32 byte hex value", - "type": "string", - "pattern": "^0x[0-9a-f]{64}$" - }, - "notFound": { - "title": "Not Found (null)", - "type": "null" - }, - "Block": { - "title": "Block object", - "type": "object", - "required": [ - "hash", - "parentHash", - "sha3Uncles", - "miner", - "stateRoot", - "transactionsRoot", - "receiptsRoot", - "logsBloom", - "number", - "gasLimit", - "gasUsed", - "timestamp", - "extraData", - "mixHash", - "nonce", - "size", - "transactions", - "uncles" - ], - "additionalProperties": false, - "properties": { - "hash": { - "title": "Hash", - "$ref": "#/components/schemas/hash32" - }, - "parentHash": { - "title": "Parent block hash", - "$ref": "#/components/schemas/hash32" - }, - "sha3Uncles": { - "title": "Ommers hash", - "$ref": "#/components/schemas/hash32" - }, - "miner": { - "title": "Coinbase", - "$ref": "#/components/schemas/address" - }, - "stateRoot": { - "title": "State root", - "$ref": "#/components/schemas/hash32" - }, - "transactionsRoot": { - "title": "Transactions root", - "$ref": "#/components/schemas/hash32" - }, - "receiptsRoot": { - "title": "Receipts root", - "$ref": "#/components/schemas/hash32" - }, - "logsBloom": { - "title": "Bloom filter", - "$ref": "#/components/schemas/bytes256" - }, - "difficulty": { - "title": "Difficulty", - "$ref": "#/components/schemas/uint" - }, - "number": { - "title": "Number", - "$ref": "#/components/schemas/uint" - }, - "gasLimit": { - "title": "Gas limit", - "$ref": "#/components/schemas/uint" - }, - "gasUsed": { - "title": "Gas used", - "$ref": "#/components/schemas/uint" - }, - "timestamp": { - "title": "Timestamp", - "$ref": "#/components/schemas/uint" - }, - "extraData": { - "title": "Extra data", - "$ref": "#/components/schemas/bytes" - }, - "mixHash": { - "title": "Mix hash", - "$ref": "#/components/schemas/hash32" - }, - "nonce": { - "title": "Nonce", - "$ref": "#/components/schemas/bytes8" - }, - "totalDifficulty": { - "title": "Total difficulty", - "$ref": "#/components/schemas/uint" - }, - "baseFeePerGas": { - "title": "Base fee per gas", - "$ref": "#/components/schemas/uint" - }, - "withdrawalsRoot": { - "title": "Withdrawals root", - "$ref": "#/components/schemas/hash32" - }, - "blobGasUsed": { - "title": "Blob gas used", - "$ref": "#/components/schemas/uint" - }, - "excessBlobGas": { - "title": "Excess blob gas", - "$ref": "#/components/schemas/uint" - }, - "parentBeaconBlockRoot": { - "title": "Parent Beacon Block Root", - "$ref": "#/components/schemas/hash32" - }, - "size": { - "title": "Block size", - "$ref": "#/components/schemas/uint" - }, - "transactions": { - "anyOf": [ - { - "title": "Transaction hashes", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "title": "Full transactions", - "type": "array", - "items": { - "$ref": "#/components/schemas/TransactionInfo" - } - } - ] - }, - "withdrawals": { - "title": "Withdrawals", - "type": "array", - "items": { - "$ref": "#/components/schemas/Withdrawal" - } - }, - "uncles": { - "title": "Uncles", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - } - } - }, - "BlockTag": { - "title": "Block tag", - "type": "string", - "enum": [ - "earliest", - "finalized", - "safe", - "latest", - "pending" - ], - "description": "`earliest`: The lowest numbered block the client has available; `finalized`: The most recent crypto-economically secure block, cannot be re-orged outside of manual intervention driven by community coordination; `safe`: The most recent block that is safe from re-orgs under honest majority and certain synchronicity assumptions; `latest`: The most recent block in the canonical chain observed by the client, this block may be re-orged out of the canonical chain even under healthy/normal conditions; `pending`: A sample next block built by the client on top of `latest` and containing the set of transactions usually taken from local mempool. Before the merge transition is finalized, any call querying for `finalized` or `safe` block MUST be responded to with `-39001: Unknown block` error" - }, - "BlockNumberOrTag": { - "title": "Block number or tag", - "oneOf": [ - { - "title": "Block number", - "$ref": "#/components/schemas/uint" - }, - { - "title": "Block tag", - "$ref": "#/components/schemas/BlockTag" - } - ] - }, - "BlockNumberOrTagOrHash": { - "title": "Block number, tag, or block hash", - "anyOf": [ - { - "title": "Block number", - "$ref": "#/components/schemas/uint" - }, - { - "title": "Block tag", - "$ref": "#/components/schemas/BlockTag" - }, - { - "title": "Block hash", - "$ref": "#/components/schemas/hash32" - } - ] - }, - "BadBlock": { - "title": "Bad block", - "type": "object", - "required": [ - "block", - "hash", - "rlp" - ], - "additionalProperties": false, - "properties": { - "block": { - "title": "Block", - "$ref": "#/components/schemas/Block" - }, - "hash": { - "title": "Hash", - "$ref": "#/components/schemas/hash32" - }, - "rlp": { - "title": "RLP", - "$ref": "#/components/schemas/bytes" - } - } - }, - "SyncingStatus": { - "title": "Syncing status", - "oneOf": [ - { - "title": "Syncing progress", - "type": "object", - "additionalProperties": false, - "properties": { - "startingBlock": { - "title": "Starting block", - "$ref": "#/components/schemas/uint" - }, - "currentBlock": { - "title": "Current block", - "$ref": "#/components/schemas/uint" - }, - "highestBlock": { - "title": "Highest block", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "title": "Not syncing", - "description": "Should always return false if not syncing.", - "type": "boolean" - } - ] - }, - "FilterResults": { - "title": "Filter results", - "oneOf": [ - { - "title": "new block or transaction hashes", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - { - "title": "new logs", - "type": "array", - "items": { - "$ref": "#/components/schemas/Log" - } - } - ] - }, - "Filter": { - "title": "filter", - "type": "object", - "additionalProperties": false, - "properties": { - "fromBlock": { - "title": "from block", - "$ref": "#/components/schemas/uint" - }, - "toBlock": { - "title": "to block", - "$ref": "#/components/schemas/uint" - }, - "address": { - "title": "Address(es)", - "oneOf": [ - { - "title": "Any Address", - "type": "null" - }, - { - "title": "Address", - "$ref": "#/components/schemas/address" - }, - { - "title": "Addresses", - "$ref": "#/components/schemas/addresses" - } - ] - }, - "topics": { - "title": "Topics", - "$ref": "#/components/schemas/FilterTopics" - } - } - }, - "FilterTopics": { - "title": "Filter Topics", - "oneOf": [ - { - "title": "Any Topic Match", - "type": "null" - }, - { - "title": "Specified Filter Topics", - "type": "array", - "items": { - "$ref": "#/components/schemas/FilterTopic" - } - } - ] - }, - "FilterTopic": { - "title": "Filter Topic List Entry", - "oneOf": [ - { - "title": "Single Topic Match", - "$ref": "#/components/schemas/bytes32" - }, - { - "title": "Multiple Topic Match", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytes32" - } - } - ] - }, - "Log": { - "title": "log", - "type": "object", - "required": [ - "transactionHash", - "address" - ], - "additionalProperties": false, - "properties": { - "removed": { - "title": "removed", - "type": "boolean" - }, - "logIndex": { - "title": "log index", - "$ref": "#/components/schemas/uint" - }, - "transactionIndex": { - "title": "transaction index", - "$ref": "#/components/schemas/uint" - }, - "transactionHash": { - "title": "transaction hash", - "$ref": "#/components/schemas/hash32" - }, - "blockHash": { - "title": "block hash", - "$ref": "#/components/schemas/hash32" - }, - "blockNumber": { - "title": "block number", - "$ref": "#/components/schemas/uint" - }, - "address": { - "title": "address", - "$ref": "#/components/schemas/address" - }, - "data": { - "title": "data", - "$ref": "#/components/schemas/bytes" - }, - "topics": { - "title": "topics", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytes32" - } - } - } - }, - "ReceiptInfo": { - "type": "object", - "title": "Receipt information", - "required": [ - "blockHash", - "blockNumber", - "from", - "cumulativeGasUsed", - "gasUsed", - "logs", - "logsBloom", - "transactionHash", - "transactionIndex", - "effectiveGasPrice" - ], - "additionalProperties": false, - "properties": { - "type": { - "title": "type", - "$ref": "#/components/schemas/byte" - }, - "transactionHash": { - "title": "transaction hash", - "$ref": "#/components/schemas/hash32" - }, - "transactionIndex": { - "title": "transaction index", - "$ref": "#/components/schemas/uint" - }, - "blockHash": { - "title": "block hash", - "$ref": "#/components/schemas/hash32" - }, - "blockNumber": { - "title": "block number", - "$ref": "#/components/schemas/uint" - }, - "from": { - "title": "from", - "$ref": "#/components/schemas/address" - }, - "to": { - "title": "to", - "description": "Address of the receiver or null in a contract creation transaction.", - "oneOf": [ - { - "title": "Contract Creation (null)", - "type": "null" - }, - { - "title": "Recipient Address", - "$ref": "#/components/schemas/address" - } - ] - }, - "cumulativeGasUsed": { - "title": "cumulative gas used", - "description": "The sum of gas used by this transaction and all preceding transactions in the same block.", - "$ref": "#/components/schemas/uint" - }, - "gasUsed": { - "title": "gas used", - "description": "The amount of gas used for this specific transaction alone.", - "$ref": "#/components/schemas/uint" - }, - "blobGasUsed": { - "title": "blob gas used", - "description": "The amount of blob gas used for this specific transaction. Only specified for blob transactions as defined by EIP-4844.", - "$ref": "#/components/schemas/uint" - }, - "contractAddress": { - "title": "contract address", - "description": "The contract address created, if the transaction was a contract creation, otherwise null.", - "oneOf": [ - { - "$ref": "#/components/schemas/address" - }, - { - "title": "Null", - "type": "null" - } - ] - }, - "logs": { - "title": "logs", - "type": "array", - "items": { - "$ref": "#/components/schemas/Log" - } - }, - "logsBloom": { - "title": "logs bloom", - "$ref": "#/components/schemas/bytes256" - }, - "root": { - "title": "state root", - "description": "The post-transaction state root. Only specified for transactions included before the Byzantium upgrade.", - "$ref": "#/components/schemas/hash32" - }, - "status": { - "title": "status", - "description": "Either 1 (success) or 0 (failure). Only specified for transactions included after the Byzantium upgrade.", - "$ref": "#/components/schemas/uint" - }, - "effectiveGasPrice": { - "title": "effective gas price", - "description": "The actual value per gas deducted from the sender's account. Before EIP-1559, this is equal to the transaction's gas price. After, it is equal to baseFeePerGas + min(maxFeePerGas - baseFeePerGas, maxPriorityFeePerGas).", - "$ref": "#/components/schemas/uint" - }, - "blobGasPrice": { - "title": "blob gas price", - "description": "The actual value per gas deducted from the sender's account for blob gas. Only specified for blob transactions as defined by EIP-4844.", - "$ref": "#/components/schemas/uint" - } - } - }, - "AccountProof": { - "title": "Account proof", - "type": "object", - "required": [ - "address", - "accountProof", - "balance", - "codeHash", - "nonce", - "storageHash", - "storageProof" - ], - "additionalProperties": false, - "properties": { - "address": { - "title": "address", - "$ref": "#/components/schemas/address" - }, - "accountProof": { - "title": "accountProof", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytes" - } - }, - "balance": { - "title": "balance", - "$ref": "#/components/schemas/uint256" - }, - "codeHash": { - "title": "codeHash", - "$ref": "#/components/schemas/hash32" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint64" - }, - "storageHash": { - "title": "storageHash", - "$ref": "#/components/schemas/hash32" - }, - "storageProof": { - "title": "Storage proofs", - "type": "array", - "items": { - "$ref": "#/components/schemas/StorageProof" - } - } - } - }, - "StorageProof": { - "title": "Storage proof", - "type": "object", - "required": [ - "key", - "value", - "proof" - ], - "additionalProperties": false, - "properties": { - "key": { - "title": "key", - "$ref": "#/components/schemas/bytesMax32" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint256" - }, - "proof": { - "title": "proof", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytes" - } - } - } - }, - "Transaction4844Unsigned": { - "type": "object", - "title": "EIP-4844 transaction.", - "required": [ - "type", - "nonce", - "to", - "gas", - "value", - "input", - "maxPriorityFeePerGas", - "maxFeePerGas", - "maxFeePerBlobGas", - "accessList", - "blobVersionedHashes", - "chainId" - ], - "properties": { - "type": { - "title": "type", - "type": "string", - "pattern": "^0x3$" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint" - }, - "to": { - "title": "to address", - "$ref": "#/components/schemas/address" - }, - "gas": { - "title": "gas limit", - "$ref": "#/components/schemas/uint" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint" - }, - "input": { - "title": "input data", - "$ref": "#/components/schemas/bytes" - }, - "maxPriorityFeePerGas": { - "title": "max priority fee per gas", - "description": "Maximum fee per gas the sender is willing to pay to miners in wei", - "$ref": "#/components/schemas/uint" - }, - "maxFeePerGas": { - "title": "max fee per gas", - "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", - "$ref": "#/components/schemas/uint" - }, - "maxFeePerBlobGas": { - "title": "max fee per blob gas", - "description": "The maximum total fee per gas the sender is willing to pay for blob gas in wei", - "$ref": "#/components/schemas/uint" - }, - "accessList": { - "title": "accessList", - "description": "EIP-2930 access list", - "$ref": "#/components/schemas/AccessList" - }, - "blobVersionedHashes": { - "title": "blobVersionedHashes", - "description": "List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - "chainId": { - "title": "chainId", - "description": "Chain ID that this transaction is valid on.", - "$ref": "#/components/schemas/uint" - } - } - }, - "AccessListEntry": { - "title": "Access list entry", - "type": "object", - "additionalProperties": false, - "required": [ "address", "storageKeys" ], - "properties": { - "address": { - "$ref": "#/components/schemas/address" - }, - "storageKeys": { - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - } - } - }, - "AccessList": { - "title": "Access list", - "type": "array", - "items": { - "$ref": "#/components/schemas/AccessListEntry" - } - }, - "Transaction1559Unsigned": { - "type": "object", - "title": "EIP-1559 transaction.", - "required": [ - "type", - "nonce", - "gas", - "value", - "input", - "maxFeePerGas", - "maxPriorityFeePerGas", - "gasPrice", - "chainId", - "accessList" - ], - "properties": { - "type": { - "title": "type", - "type": "string", - "pattern": "^0x2$" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint" - }, - "to": { - "title": "to address", - "oneOf": [ - { - "title": "Contract Creation (null)", - "type": "null" - }, - { - "title": "Address", - "$ref": "#/components/schemas/address" - } - ] - }, - "gas": { - "title": "gas limit", - "$ref": "#/components/schemas/uint" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint" - }, - "input": { - "title": "input data", - "$ref": "#/components/schemas/bytes" - }, - "maxPriorityFeePerGas": { - "title": "max priority fee per gas", - "description": "Maximum fee per gas the sender is willing to pay to miners in wei", - "$ref": "#/components/schemas/uint" - }, - "maxFeePerGas": { - "title": "max fee per gas", - "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", - "$ref": "#/components/schemas/uint" - }, - "gasPrice": { - "title": "gas price", - "description": "The effective gas price paid by the sender in wei. For transactions not yet included in a block, this value should be set equal to the max fee per gas. This field is DEPRECATED, please transition to using effectiveGasPrice in the receipt object going forward.", - "$ref": "#/components/schemas/uint" - }, - "accessList": { - "title": "accessList", - "description": "EIP-2930 access list", - "$ref": "#/components/schemas/AccessList" - }, - "chainId": { - "title": "chainId", - "description": "Chain ID that this transaction is valid on.", - "$ref": "#/components/schemas/uint" - } - } - }, - "Transaction2930Unsigned": { - "type": "object", - "title": "EIP-2930 transaction.", - "required": [ - "type", - "nonce", - "gas", - "value", - "input", - "gasPrice", - "chainId", - "accessList" - ], - "properties": { - "type": { - "title": "type", - "type": "string", - "pattern": "^0x1$" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint" - }, - "to": { - "title": "to address", - "oneOf": [ - { - "title": "Contract Creation (null)", - "type": "null" - }, - { - "title": "Address", - "$ref": "#/components/schemas/address" - } - ] - }, - "gas": { - "title": "gas limit", - "$ref": "#/components/schemas/uint" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint" - }, - "input": { - "title": "input data", - "$ref": "#/components/schemas/bytes" - }, - "gasPrice": { - "title": "gas price", - "description": "The gas price willing to be paid by the sender in wei", - "$ref": "#/components/schemas/uint" - }, - "accessList": { - "title": "accessList", - "description": "EIP-2930 access list", - "$ref": "#/components/schemas/AccessList" - }, - "chainId": { - "title": "chainId", - "description": "Chain ID that this transaction is valid on.", - "$ref": "#/components/schemas/uint" - } - } - }, - "TransactionLegacyUnsigned": { - "type": "object", - "title": "Legacy transaction.", - "required": [ - "type", - "nonce", - "gas", - "value", - "input", - "gasPrice" - ], - "properties": { - "type": { - "title": "type", - "type": "string", - "pattern": "^0x0$" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint" - }, - "to": { - "title": "to address", - "oneOf": [ - { - "title": "Contract Creation (null)", - "type": "null" - }, - { - "title": "Address", - "$ref": "#/components/schemas/address" - } - ] - }, - "gas": { - "title": "gas limit", - "$ref": "#/components/schemas/uint" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint" - }, - "input": { - "title": "input data", - "$ref": "#/components/schemas/bytes" - }, - "gasPrice": { - "title": "gas price", - "description": "The gas price willing to be paid by the sender in wei", - "$ref": "#/components/schemas/uint" - }, - "chainId": { - "title": "chainId", - "description": "Chain ID that this transaction is valid on.", - "$ref": "#/components/schemas/uint" - } - } - }, - "TransactionUnsigned": { - "oneOf": [ - { - "$ref": "#/components/schemas/Transaction4844Unsigned" - }, - { - "$ref": "#/components/schemas/Transaction1559Unsigned" - }, - { - "$ref": "#/components/schemas/Transaction2930Unsigned" - }, - { - "$ref": "#/components/schemas/TransactionLegacyUnsigned" - } - ] - }, - "Transaction4844Signed": { - "title": "Signed 4844 Transaction", - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/Transaction4844Unsigned" - }, - { - "title": "EIP-4844 transaction signature properties.", - "required": [ - "r", - "s", - "yParity" - ], - "properties": { - "yParity": { - "title": "yParity", - "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", - "$ref": "#/components/schemas/uint" - }, - "r": { - "title": "r", - "$ref": "#/components/schemas/uint" - }, - "s": { - "title": "s", - "$ref": "#/components/schemas/uint" - } - } - } - ] - }, - "Transaction1559Signed": { - "title": "Signed 1559 Transaction", - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/Transaction1559Unsigned" - }, - { - "title": "EIP-1559 transaction signature properties.", - "required": [ - "r", - "s", - "yParity" - ], - "properties": { - "yParity": { - "title": "yParity", - "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", - "$ref": "#/components/schemas/uint" - }, - "v": { - "title": "v", - "description": "For backwards compatibility, `v` is optionally provided as an alternative to `yParity`. This field is DEPRECATED and all use of it should migrate to `yParity`.", - "$ref": "#/components/schemas/uint" - }, - "r": { - "title": "r", - "$ref": "#/components/schemas/uint" - }, - "s": { - "title": "s", - "$ref": "#/components/schemas/uint" - } - } - } - ] - }, - "Transaction2930Signed": { - "title": "Signed 2930 Transaction", - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/Transaction2930Unsigned" - }, - { - "title": "EIP-2930 transaction signature properties.", - "required": [ - "yParity", - "r", - "s" - ], - "properties": { - "yParity": { - "title": "yParity", - "description": "The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature.", - "$ref": "#/components/schemas/uint" - }, - "v": { - "title": "v", - "description": "For backwards compatibility, `v` is optionally provided as an alternative to `yParity`. This field is DEPRECATED and all use of it should migrate to `yParity`.", - "$ref": "#/components/schemas/uint" - }, - "r": { - "title": "r", - "$ref": "#/components/schemas/uint" - }, - "s": { - "title": "s", - "$ref": "#/components/schemas/uint" - } - } - } - ] - }, - "TransactionLegacySigned": { - "title": "Signed Legacy Transaction", - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TransactionLegacyUnsigned" - }, - { - "title": "Legacy transaction signature properties.", - "required": [ - "v", - "r", - "s" - ], - "properties": { - "v": { - "title": "v", - "$ref": "#/components/schemas/uint" - }, - "r": { - "title": "r", - "$ref": "#/components/schemas/uint" - }, - "s": { - "title": "s", - "$ref": "#/components/schemas/uint" - } - } - } - ] - }, - "TransactionSigned": { - "oneOf": [ - { - "$ref": "#/components/schemas/Transaction4844Signed" - }, - { - "$ref": "#/components/schemas/Transaction1559Signed" - }, - { - "$ref": "#/components/schemas/Transaction2930Signed" - }, - { - "$ref": "#/components/schemas/TransactionLegacySigned" - } - ] - }, - "TransactionInfo": { - "type": "object", - "title": "Transaction information", - "allOf": [ - { - "title": "Contextual information", - "required": [ - "blockHash", - "blockNumber", - "from", - "hash", - "transactionIndex" - ], - "unevaluatedProperties": false, - "properties": { - "blockHash": { - "title": "block hash", - "$ref": "#/components/schemas/hash32" - }, - "blockNumber": { - "title": "block number", - "$ref": "#/components/schemas/uint" - }, - "from": { - "title": "from address", - "$ref": "#/components/schemas/address" - }, - "hash": { - "title": "transaction hash", - "$ref": "#/components/schemas/hash32" - }, - "transactionIndex": { - "title": "transaction index", - "$ref": "#/components/schemas/uint" - } - } - }, - { - "$ref": "#/components/schemas/TransactionSigned" - } - ] - }, - "GenericTransaction": { - "type": "object", - "title": "Transaction object generic to all types", - "additionalProperties": false, - "properties": { - "type": { - "title": "type", - "$ref": "#/components/schemas/byte" - }, - "nonce": { - "title": "nonce", - "$ref": "#/components/schemas/uint" - }, - "to": { - "title": "to address", - "oneOf": [ - { - "title": "Contract Creation (null)", - "type": "null" - }, - { - "title": "Address", - "$ref": "#/components/schemas/address" - } - ] - }, - "from": { - "title": "from address", - "$ref": "#/components/schemas/address" - }, - "gas": { - "title": "gas limit", - "$ref": "#/components/schemas/uint" - }, - "value": { - "title": "value", - "$ref": "#/components/schemas/uint" - }, - "input": { - "title": "input data", - "$ref": "#/components/schemas/bytes" - }, - "gasPrice": { - "title": "gas price", - "description": "The gas price willing to be paid by the sender in wei", - "$ref": "#/components/schemas/uint" - }, - "maxPriorityFeePerGas": { - "title": "max priority fee per gas", - "description": "Maximum fee per gas the sender is willing to pay to miners in wei", - "$ref": "#/components/schemas/uint" - }, - "maxFeePerGas": { - "title": "max fee per gas", - "description": "The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei", - "$ref": "#/components/schemas/uint" - }, - "maxFeePerBlobGas": { - "title": "max fee per blob gas", - "description": "The maximum total fee per gas the sender is willing to pay for blob gas in wei", - "$ref": "#/components/schemas/uint" - }, - "accessList": { - "title": "accessList", - "description": "EIP-2930 access list", - "$ref": "#/components/schemas/AccessList" - }, - "blobVersionedHashes": { - "title": "blobVersionedHashes", - "description": "List of versioned blob hashes associated with the transaction's EIP-4844 data blobs.", - "type": "array", - "items": { - "$ref": "#/components/schemas/hash32" - } - }, - "blobs": { - "title": "blobs", - "description": "Raw blob data.", - "type": "array", - "items": { - "$ref": "#/components/schemas/bytes" - } - }, - "chainId": { - "title": "chainId", - "description": "Chain ID that this transaction is valid on.", - "$ref": "#/components/schemas/uint" - } - } - }, - "Withdrawal": { - "type": "object", - "title": "Validator withdrawal", - "required": [ - "index", - "validatorIndex", - "address", - "amount" - ], - "additionalProperties": false, - "properties": { - "index": { - "title": "index of withdrawal", - "$ref": "#/components/schemas/uint64" - }, - "validatorIndex": { - "title": "index of validator that generated withdrawal", - "$ref": "#/components/schemas/uint64" - }, - "address": { - "title": "recipient address for withdrawal value", - "$ref": "#/components/schemas/address" - }, - "amount": { - "title": "value contained in withdrawal", - "$ref": "#/components/schemas/uint256" - } - } - } - } - } -} diff --git a/substrate/frame/revive/rpc/codegen/src/LICENSE.txt b/substrate/frame/revive/rpc/codegen/src/LICENSE.txt deleted file mode 100644 index ecd364a6d62e..000000000000 --- a/substrate/frame/revive/rpc/codegen/src/LICENSE.txt +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. diff --git a/substrate/frame/revive/rpc/codegen/src/generator.rs b/substrate/frame/revive/rpc/codegen/src/generator.rs deleted file mode 100644 index c4881a186c50..000000000000 --- a/substrate/frame/revive/rpc/codegen/src/generator.rs +++ /dev/null @@ -1,758 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use inflector::Inflector; -use std::{ - collections::{BTreeMap, HashMap, HashSet}, - mem, - sync::LazyLock, -}; - -use crate::{ - open_rpc::*, - printer::{ - doc_str_from_schema, Fields, Required, TypeContent, TypeInfo, TypeNameProvider, - TypePrinter, Variants, - }, - writeln, -}; - -pub const LICENSE: &str = include_str!("LICENSE.txt"); - -/// List of supported Ethereum RPC methods we want to generate. -pub static SUPPORTED_ETH_METHODS: LazyLock> = LazyLock::new(|| { - vec![ - "eth_accounts", - "eth_blockNumber", - "eth_call", - "eth_chainId", - "eth_estimateGas", - "eth_gasPrice", - "eth_getBalance", - "eth_getBlockByHash", - "eth_getBlockByNumber", - "eth_getBlockTransactionCountByHash", - "eth_getBlockTransactionCountByNumber", - "eth_getCode", - "eth_getStorageAt", - "eth_getTransactionByBlockHashAndIndex", - "eth_getTransactionByBlockNumberAndIndex", - "eth_getTransactionByHash", - "eth_getTransactionCount", - "eth_getTransactionReceipt", - "eth_sendRawTransaction", - "eth_sendTransaction", - "eth_syncing", - "net_version", - ] -}); - -/// Mapping of primitive schema types to their Rust counterparts. -pub static PRIMITIVE_MAPPINGS: LazyLock> = - LazyLock::new(|| { - HashMap::from([ - ("#/components/schemas/address", "Address"), - ("#/components/schemas/byte", "Byte"), - ("#/components/schemas/bytes", "Bytes"), - ("#/components/schemas/bytes256", "Bytes256"), - ("#/components/schemas/hash32", "H256"), - ("#/components/schemas/bytes32", "H256"), - ("#/components/schemas/bytes8", "Bytes8"), - ("#/components/schemas/uint", "U256"), - ("#/components/schemas/uint256", "U256"), - ("#/components/schemas/uint64", "U256"), - ]) - }); - -/// Mapping of legacy aliases to their new names. -pub static LEGACY_ALIASES: LazyLock>> = - LazyLock::new(|| { - HashMap::from([ - // We accept "data" and "input" for backwards-compatibility reasons. - // Issue detail: https://github.com/ethereum/go-ethereum/issues/15628 - ("#/components/schemas/GenericTransaction", HashMap::from([("input", "data")])), - ]) - }); - -/// Custom Default impl -pub static CUSTOM_DEFAULT_VARIANTS: LazyLock> = - LazyLock::new(|| { - HashMap::from([ - ("TransactionUnsigned", "TransactionLegacyUnsigned"), - ("TransactionSigned", "TransactionLegacySigned"), - ("BlockNumberOrTagOrHash", "BlockTag"), - ("BlockNumberOrTag", "BlockTag"), - ("BlockTag", "Latest"), - ]) - }); - -/// Read the OpenRPC specs, and inject extra methods and legacy aliases. -pub fn read_specs() -> anyhow::Result { - let content = include_str!("../openrpc.json"); - let mut specs: OpenRpc = serde_json::from_str(content)?; - - // Inject legacy aliases. - inject_legacy_aliases(&mut specs); - - // Inject extra methods. - specs.methods.push(RefOr::Inline(Method { - name: "net_version".to_string(), - summary: Some("The string value of current network id".to_string()), - result: Some(RefOr::Reference { reference: "String".to_string() }), - ..Default::default() - })); - - Ok(specs) -} - -// Inject legacy aliases declared by [`LEGACY_ALIASES`]. -pub fn inject_legacy_aliases(specs: &mut OpenRpc) { - for (alias, mapping) in LEGACY_ALIASES.iter() { - let schema = specs.get_schema_mut(alias).unwrap(); - match &mut schema.contents { - SchemaContents::Object(o) | SchemaContents::Literal(Literal::Object(o)) => { - o.legacy_aliases = - mapping.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(); - }, - _ => { - panic!("Alias should be an object got {:?} instead", schema.contents); - }, - } - } -} - -/// Format the given code using rustfmt. -pub fn format_code(code: &str) -> anyhow::Result { - use std::{io::Write, process::*}; - let mut rustfmt = Command::new("rustup") - .args(["run", "nightly", "rustfmt"]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - - let stdin = rustfmt.stdin.as_mut().expect("Failed to open stdin"); - stdin.write_all(code.as_bytes())?; - - let output = rustfmt.wait_with_output()?; - if !output.status.success() { - anyhow::bail!("rustfmt failed: {}", String::from_utf8_lossy(&output.stderr)); - } - - let formatted_code = String::from_utf8_lossy(&output.stdout).to_string(); - Ok(formatted_code) -} - -/// Type generator for generating RPC methods and types. -#[derive(Default)] -pub struct TypeGenerator { - /// List of collected types, that are not yet generated. - collected: BTreeMap, - /// List of already generated types. - generated: HashSet, - /// List of filtered method names, we want to generate. - filtered_method_names: HashSet, - /// Stripped prefix for the generated method names. - prefix: String, -} - -/// Reference or schema -pub enum ReferenceOrSchema { - // A reference to a schema such as `#/components/schemas/Foo`. - Reference(String), - // A schema definition. - Schema(Schema), -} - -impl ReferenceOrSchema { - /// Return the schema for the reference or the schema itself. - fn schema<'a>(&'a self, specs: &'a OpenRpc) -> &'a Schema { - match self { - Self::Schema(schema) => schema, - Self::Reference(reference) => specs.get_schema(reference).unwrap(), - } - } -} - -impl TypeGenerator { - /// Create a new type generator. - pub fn new() -> Self { - let mut generated = - HashSet::from_iter(["notFound"].into_iter().map(|name| name.to_pascal_case())); - - generated.extend(PRIMITIVE_MAPPINGS.keys().map(|name| reference_to_name(name))); - generated.extend(PRIMITIVE_MAPPINGS.values().map(|name| name.to_string())); - let filtered_method_names = - SUPPORTED_ETH_METHODS.iter().map(|name| name.to_string()).collect(); - - Self { - collected: Default::default(), - filtered_method_names, - generated, - prefix: "eth".to_string(), - } - } - - /// Generate the RPC method, and add the collected types. - pub fn generate_rpc_methods(&mut self, specs: &OpenRpc) -> String { - let methods = specs - .methods - .iter() - .map(RefOr::unwrap_inline) - .filter(|method| self.filtered_method_names.contains(&method.name)) - .collect::>(); - - if methods.len() != self.filtered_method_names.len() { - let available = - methods.iter().map(|method| method.name.clone()).collect::>(); - let missing = self.filtered_method_names.difference(&available).collect::>(); - panic!("Missing methods: {missing:?}"); - } - - let mut code = LICENSE.to_string(); - code.push_str( - r#" - //! Generated JSON-RPC methods. - #![allow(missing_docs)] - - use super::*; - use jsonrpsee::core::RpcResult; - use jsonrpsee::proc_macros::rpc; - - #[rpc(server, client)] - pub trait EthRpc { - "#, - ); - - for method in methods { - self.generate_rpc_method(&mut code, method); - code.push('\n'); - } - code.push('}'); - code.push('\n'); - code - } - - pub fn collect_extra_type(&mut self, type_name: &str) { - self.collect( - type_name, - ReferenceOrSchema::Reference(format!("#/components/schemas/{}", type_name)), - ); - } - - /// Recursively collect the types and generate them. - /// - /// Note: This should be called after [`TypeGenerator::generate_rpc_methods`] to collect the - /// types used in the RPC methods. - pub fn generate_types(&mut self, specs: &OpenRpc) -> String { - let mut code = LICENSE.to_string(); - code.push_str( - r#"//! Generated JSON-RPC types. - #![allow(missing_docs)] - - use super::{byte::*, TypeEip1559, TypeEip2930, TypeEip4844, TypeLegacy}; - use alloc::vec::Vec; - use codec::{Decode, Encode}; - use derive_more::{From, TryInto}; - pub use ethereum_types::*; - use scale_info::TypeInfo; - use serde::{Deserialize, Serialize}; - - "#, - ); - loop { - let collected = mem::take(&mut self.collected); - self.generated.extend(collected.keys().cloned()); - - if collected.is_empty() { - break; - } - - for (name, ref_or_schema) in collected { - let r#type = self.generate_type(name, ref_or_schema.schema(specs)); - r#type.print(&mut code); - code.push('\n'); - } - } - - code - } - - /// Return the type printer for the given schema. - fn generate_type(&mut self, name: String, schema: &Schema) -> TypePrinter { - let doc = doc_str_from_schema(schema); - - let content = match &schema.contents { - &SchemaContents::Literal(Literal::Object(ref o)) | &SchemaContents::Object(ref o) => - TypeContent::Struct(Fields::from(o, self)), - SchemaContents::AllOf { all_of } => - TypeContent::Struct(Fields::from_all_of(all_of, self)), - &SchemaContents::AnyOf { any_of: ref items } | - &SchemaContents::OneOf { one_of: ref items } => - TypeContent::Enum(Variants::from_one_of(items, self)), - &SchemaContents::Literal(Literal::Array(ArrayLiteral { items: Some(ref schema) })) => { - let mut type_info = - self.type_info(schema).expect("Anonymous array type not supported"); - type_info.array = true; - - TypeContent::TypeAlias(type_info) - }, - &SchemaContents::Literal(Literal::String(StringLiteral { - min_length: None, - max_length: None, - pattern: None, - format: None, - enumeration: Some(ref enumeration), - })) => TypeContent::UntaggedEnum(enumeration.clone()), - v => { - panic!("Unsupported type {name} {v:#?}") - }, - }; - - let default_variant = CUSTOM_DEFAULT_VARIANTS.get(&name.as_str()).map(|v| v.to_string()); - TypePrinter::new(doc, name, content, default_variant) - } - - fn generate_rpc_method(&mut self, buffer: &mut String, method: &Method) { - let Method { ref summary, ref name, ref params, ref result, .. } = method; - writeln!(@doc buffer, summary); - - let result = result - .as_ref() - .map(|content| match content { - RefOr::Inline(descriptor) => self - .type_info(&descriptor.schema) - .expect("Result type should be defined") - .get_type(), - RefOr::Reference { reference } => reference.clone(), - }) - .unwrap_or("()".to_string()); - - let parameters = params - .iter() - .map(RefOr::unwrap_inline) - .map(|ContentDescriptor { name, required, schema, .. }| { - let name_arg = name.to_snake_case().replace(' ', "_"); - let name_type = self - .type_info(schema) - .expect("Parameter type should be defined") - .set_required(*required) - .get_type(); - format!("{name_arg}: {name_type}") - }) - .collect::>() - .join(", "); - - writeln!(buffer, "#[method(name = \"{name}\")]"); - let method_name = name.trim_start_matches(&self.prefix).to_snake_case(); - writeln!(buffer, "async fn {method_name}(&self, {parameters}) -> RpcResult<{result}>;"); - } - - /// Collect the type if it's not yet generated or collected. - fn collect(&mut self, type_name: &str, ref_or_schema: ReferenceOrSchema) { - if !self.generated.contains(type_name) && !self.collected.contains_key(type_name) { - self.collected.insert(type_name.to_string(), ref_or_schema); - } - } -} - -/// Convert a reference to a type name. -fn reference_to_name(reference: &str) -> String { - if PRIMITIVE_MAPPINGS.contains_key(reference) { - return PRIMITIVE_MAPPINGS[reference].to_string(); - } - reference.split('/').last().unwrap().to_pascal_case() -} - -impl TypeNameProvider for TypeGenerator { - fn record_inline_type(&mut self, type_name: String, schema: &Schema) -> TypeInfo { - self.collect(&type_name, ReferenceOrSchema::Schema(schema.clone())); - TypeInfo { name: type_name, required: Required::Yes, array: false } - } - - fn type_info(&mut self, schema: &Schema) -> Option { - match &schema.contents { - SchemaContents::Reference { reference } => { - let type_name = reference_to_name(reference); - self.collect(&type_name, ReferenceOrSchema::Reference(reference.to_string())); - Some(type_name.into()) - }, - SchemaContents::Literal(Literal::Array(ArrayLiteral { items: Some(ref schema) })) => { - let mut type_info = - self.type_info(schema).expect("Anonymous array type not supported"); - type_info.array = true; - Some(type_info) - }, - SchemaContents::AllOf { all_of } => Some( - all_of - .iter() - .map(|s| self.type_info(s).expect("Anonymous all_of type not supported").name) - .collect::>() - .join("And") - .into(), - ), - SchemaContents::AnyOf { any_of: ref items } | - SchemaContents::OneOf { one_of: ref items } => { - let mut required = Required::Yes; - let items = items - .iter() - .filter_map(|s| { - let info = self.type_info(s).expect("Anonymous any_of type not supported"); - let name = info.name; - - if name == "Null" || name == "NotFound" { - required = Required::No { skip_if_null: false }; - None - } else { - Some(name) - } - }) - .collect::>(); - - let name = items.join("Or"); - if items.len() > 1 { - self.collect(&name, ReferenceOrSchema::Schema(schema.clone())); - } - - Some(TypeInfo { name, required, array: false }) - }, - SchemaContents::Literal(Literal::Null) => Some("Null".into()), - - // Use Type0, Type1, Type2, ... for String that have a single digit pattern. - SchemaContents::Literal(Literal::String(StringLiteral { - min_length: None, - max_length: None, - pattern: Some(ref pattern), - format: None, - enumeration: None, - })) if ["^0x0$", "^0x1$", "^0x2$", "^0x3$"].contains(&pattern.as_str()) => - match pattern.as_str() { - "^0x0$" => Some("TypeLegacy".into()), - "^0x1$" => Some("TypeEip2930".into()), - "^0x2$" => Some("TypeEip1559".into()), - "^0x3$" => Some("TypeEip4844".into()), - _ => unreachable!(), - }, - - SchemaContents::Literal(Literal::Boolean) => Some("bool".into()), - SchemaContents::Object(_) => None, - SchemaContents::Literal(Literal::Object(_)) => None, - v => { - panic!("No type name for {v:#?}"); - }, - } - } -} - -#[cfg(test)] -pub fn assert_code_match(expected: &str, actual: &str) { - pretty_assertions::assert_eq!( - format_code(expected).unwrap().trim(), - format_code(actual).unwrap().trim() - ); -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn generate_works() { - let specs = read_specs().unwrap(); - - let mut generator = TypeGenerator::new(); - SUPPORTED_ETH_METHODS.iter().for_each(|name| { - generator.filtered_method_names.insert(name.to_string()); - }); - - let buffer = generator.generate_rpc_methods(&specs); - println!("{}", buffer); - } - - #[test] - fn generate_rpc_works() { - let method = serde_json::from_str::( - r###" - { - "name": "eth_estimateGas", - "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", - "params": [ - { - "name": "Transaction", - "required": true, - "schema": { - "$ref": "#/components/schemas/GenericTransaction" - } - }, - { - "name": "Block", - "required": false, - "schema": { - "$ref": "#/components/schemas/BlockNumberOrTag" - } - } - ], - "result": { - "name": "Gas used", - "schema": { - "$ref": "#/components/schemas/uint" - } - } - } - "###, - ) - .unwrap(); - - let mut buffer = String::new(); - let mut generator = TypeGenerator::new(); - - generator.generate_rpc_method(&mut buffer, &method); - assert_code_match( - &buffer, - r#" - /// Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. - #[method(name = "eth_estimateGas")] - async fn estimate_gas(&self, transaction: GenericTransaction, block: Option) -> RpcResult; - "#, - ); - } - - #[test] - fn generate_type_name_works() { - let mut generator = TypeGenerator::new(); - - let schema: Schema = serde_json::from_str( - r###" - { - "title": "to address", - "oneOf": [ - { "title": "Contract Creation (null)", "type": "null" }, - { "title": "Address", "$ref": "#/components/schemas/address" } - ] - } - "###, - ) - .unwrap(); - - assert_eq!(&generator.type_info(&schema).unwrap().get_type(), "Option
"); - } - - #[test] - fn generate_all_off_type_works() { - let specs = read_specs().unwrap(); - let mut generator = TypeGenerator::new(); - let res = generator.generate_type( - "Transaction4844Signed".to_string(), - specs.get_schema("#/components/schemas/Transaction4844Signed").unwrap(), - ); - let mut buffer = String::new(); - res.print(&mut buffer); - assert_code_match( - &buffer, - r#" - /// Signed 4844 Transaction - #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] - pub struct Transaction4844Signed { - #[serde(flatten)] - pub transaction_4844_unsigned: Transaction4844Unsigned, - /// r - pub r: U256, - /// s - pub s: U256, - /// yParity - /// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature. - #[serde(rename = "yParity")] - pub y_parity: U256, - } - "#, - ); - } - - #[test] - fn generate_one_of_type_works() { - let specs = read_specs().unwrap(); - let mut generator = TypeGenerator::new(); - let res = generator.generate_type( - "TransactionUnsigned".to_string(), - specs.get_schema("#/components/schemas/TransactionUnsigned").unwrap(), - ); - let mut buffer = String::new(); - res.print(&mut buffer); - assert_code_match( - &buffer, - r#" - #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)] - #[serde(untagged)] - pub enum TransactionUnsigned { - Transaction4844Unsigned(Transaction4844Unsigned), - Transaction1559Unsigned(Transaction1559Unsigned), - Transaction2930Unsigned(Transaction2930Unsigned), - TransactionLegacyUnsigned(TransactionLegacyUnsigned), - } - impl Default for TransactionUnsigned { - fn default() -> Self { - TransactionUnsigned::TransactionLegacyUnsigned(Default::default()) - } - } - "#, - ); - } - - #[test] - fn generate_type_with_inline_variant_works() { - let specs = read_specs().unwrap(); - let mut generator = TypeGenerator::new(); - let res = generator.generate_type( - "SyncingStatus".to_string(), - specs.get_schema("#/components/schemas/SyncingStatus").unwrap(), - ); - let mut buffer = String::new(); - res.print(&mut buffer); - - assert_code_match( - &buffer, - r#" - /// Syncing status - #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)] - #[serde(untagged)] - pub enum SyncingStatus { - /// Syncing progress - SyncingProgress(SyncingProgress), - /// Not syncing - /// Should always return false if not syncing. - Bool(bool), - } - impl Default for SyncingStatus { - fn default() -> Self { - SyncingStatus::SyncingProgress(Default::default()) - } - } - "#, - ); - } - - #[test] - fn generate_array_type_works() { - let specs = read_specs().unwrap(); - let mut generator = TypeGenerator::new(); - let res = generator.generate_type( - "AccessList".to_string(), - specs.get_schema("#/components/schemas/AccessList").unwrap(), - ); - let mut buffer = String::new(); - res.print(&mut buffer); - assert_code_match( - &buffer, - r#" - /// Access list - pub type AccessList = Vec; - "#, - ); - } - - #[test] - fn generate_one_of_with_null_variant_works() { - let specs = read_specs().unwrap(); - let mut generator = TypeGenerator::new(); - let res = generator.generate_type( - "FilterTopics".to_string(), - specs.get_schema("#/components/schemas/FilterTopics").unwrap(), - ); - let mut buffer = String::new(); - res.print(&mut buffer); - assert_code_match( - &buffer, - r#" - /// Filter Topics - pub type FilterTopics = Vec; - "#, - ); - } - - #[test] - fn generate_object_type_works() { - let specs = read_specs().unwrap(); - let mut generator = TypeGenerator::new(); - let res = generator.generate_type( - "Transaction".to_string(), - specs.get_schema("#/components/schemas/GenericTransaction").unwrap(), - ); - - let mut buffer = String::new(); - res.print(&mut buffer); - assert_code_match( - &buffer, - r#" - /// Transaction object generic to all types - #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] - pub struct Transaction { - /// accessList - /// EIP-2930 access list - #[serde(rename = "accessList", skip_serializing_if = "Option::is_none")] - pub access_list: Option, - /// blobVersionedHashes - /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. - #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Vec::is_empty")] - pub blob_versioned_hashes: Vec, - /// blobs - /// Raw blob data. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub blobs: Vec, - /// chainId - /// Chain ID that this transaction is valid on. - #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] - pub chain_id: Option, - /// from address - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option
, - /// gas limit - #[serde(skip_serializing_if = "Option::is_none")] - pub gas: Option, - /// gas price - /// The gas price willing to be paid by the sender in wei - #[serde(rename = "gasPrice", skip_serializing_if = "Option::is_none")] - pub gas_price: Option, - /// input data - #[serde(alias = "data", skip_serializing_if = "Option::is_none")] - pub input: Option, - /// max fee per blob gas - /// The maximum total fee per gas the sender is willing to pay for blob gas in wei - #[serde(rename = "maxFeePerBlobGas", skip_serializing_if = "Option::is_none")] - pub max_fee_per_blob_gas: Option, - /// max fee per gas - /// The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei - #[serde(rename = "maxFeePerGas", skip_serializing_if = "Option::is_none")] - pub max_fee_per_gas: Option, - /// max priority fee per gas - /// Maximum fee per gas the sender is willing to pay to miners in wei - #[serde(rename = "maxPriorityFeePerGas", skip_serializing_if = "Option::is_none")] - pub max_priority_fee_per_gas: Option, - /// nonce - #[serde(skip_serializing_if = "Option::is_none")] - pub nonce: Option, - /// to address - pub to: Option
, - /// type - #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, - /// value - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option, - } - "#, - ); - } -} diff --git a/substrate/frame/revive/rpc/codegen/src/main.rs b/substrate/frame/revive/rpc/codegen/src/main.rs deleted file mode 100644 index e0b660ea84e5..000000000000 --- a/substrate/frame/revive/rpc/codegen/src/main.rs +++ /dev/null @@ -1,64 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use crate::generator::{format_code, TypeGenerator}; -use anyhow::Context; -use std::path::Path; - -mod generator; -mod open_rpc; -mod printer; - -fn main() -> anyhow::Result<()> { - let specs = generator::read_specs()?; - - let mut generator = TypeGenerator::new(); - generator.collect_extra_type("TransactionUnsigned"); - - let out_dir = if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { - Path::new(&dir).join("../src") - } else { - "../src".into() - } - .canonicalize() - .with_context(|| "Failed to find the api directory")?; - - let out = out_dir.join("rpc_methods_gen.rs"); - println!("Generating rpc_methods at {out:?}"); - format_and_write_file(&out, &generator.generate_rpc_methods(&specs)) - .with_context(|| format!("Failed to generate code to {out:?}"))?; - - let out_dir = if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { - Path::new(&dir).join("../../src/evm/api") - } else { - "../../src/evm/api".into() - } - .canonicalize() - .with_context(|| "Failed to find the api directory")?; - - let out = std::fs::canonicalize(out_dir.join("rpc_types_gen.rs"))?; - println!("Generating rpc_types at {out:?}"); - format_and_write_file(&out, &generator.generate_types(&specs)) - .with_context(|| format!("Failed to generate code to {out:?}"))?; - - Ok(()) -} - -fn format_and_write_file(path: &Path, content: &str) -> anyhow::Result<()> { - let code = format_code(content)?; - std::fs::write(path, code).expect("Unable to write file"); - Ok(()) -} diff --git a/substrate/frame/revive/rpc/codegen/src/open_rpc.rs b/substrate/frame/revive/rpc/codegen/src/open_rpc.rs deleted file mode 100644 index fa7510a50561..000000000000 --- a/substrate/frame/revive/rpc/codegen/src/open_rpc.rs +++ /dev/null @@ -1,834 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//! Defines the types defined by the [`OpenRPC`](https://spec.open-rpc.org) specification. - -#![warn(missing_docs, missing_debug_implementations)] - -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; - -/// Represents an OpenRPC document. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct OpenRpc { - /// The semantic version number of the OpenRPC Specification version that the OpenRPC document - /// uses. - /// - /// This field should be used by tooling specifications and clients to interpret the OpenRPC - /// document. - pub openrpc: String, - /// Provides metadata about the API. - /// - /// This metadata may be used by tooling as required. - pub info: Info, - /// An array of [`Server`] objects, which provide connectivity information to a target server. - /// - /// If the `servers` property is not provided, or is an empty array, the default value would - /// be a [`Server`] with a `url` value of `localhost`. This is taken care of by the - /// [`open-rpc`](crate) crate. - #[serde(default = "serde_fns::servers")] - pub servers: Vec, - /// The available methods for the API. While this field is required, it is legal to leave it - /// empty. - pub methods: Vec>, - /// Holds various schemas for the specification. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub components: Option, - /// Contains additional documentation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub external_docs: Option, -} - -impl OpenRpc { - /// Returns the [`Method`] with the given path reference. - /// - /// # Examples - /// - /// ```no_run - /// let path = "#/components/schemas/MY_SCHEMA"; - /// let schema = openrpc.get_schema(path).unwrap(); - /// ``` - pub fn get_schema(&self, reference: &str) -> Option<&Schema> { - let mut components = reference.split('/'); - - if !matches!(components.next(), Some("#")) { - return None; - } - - if !matches!(components.next(), Some("components")) { - return None; - } - - if !matches!(components.next(), Some("schemas")) { - return None; - } - - let name = components.next()?; - self.components.as_ref()?.schemas.get(name) - } - - /// Same as [`OpenRpc::get_schema`] but returns a &mut reference - pub fn get_schema_mut(&mut self, reference: &str) -> Option<&mut Schema> { - let mut components = reference.split('/'); - - if !matches!(components.next(), Some("#")) { - return None; - } - - if !matches!(components.next(), Some("components")) { - return None; - } - - if !matches!(components.next(), Some("schemas")) { - return None; - } - - let name = components.next()?; - self.components.as_mut()?.schemas.get_mut(name) - } -} - -/// Provides metadata about the API. -/// -/// The metadata may be used by clients if needed, and may be presented in editing or -/// documentation generation tools for convenience. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Info { - /// The title of the application. - #[serde(default)] - pub title: String, - /// A verbose description of the application. - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// A URL to the Terms of Service for the API. - /// - /// This must contain an URL. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub terms_of_service: Option, - /// contact information for the exposed API. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub contact: Option, - /// License information for the exposed API. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub license: Option, - /// The version of the OpenRPC document. - /// - /// Note that this is distinct from the `openrpc` field of [`OpenRpc`] which specifies the - /// version of the OpenRPC Specification used. - #[serde(default)] - pub version: String, -} - -/// Contact information for the exposed API. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Contact { - /// The identifying name of the contact person/organization. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub name: Option, - /// The URL pointing to the contact information. - /// - /// This must contain an URL. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub url: Option, - /// The email address of the contact person/organization. - /// - /// This must contain an email address. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub email: Option, -} - -/// License information for the exposed API. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct License { - /// The name of the license used for the API. - #[serde(default)] - pub name: String, - /// The URL pointing to the license used for the API. - /// - /// This must contain an URL. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub url: Option, -} - -/// A server. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Server { - /// A name to be used as the canonical name for the server. - #[serde(default)] - pub name: String, - /// A URL to the target host. - /// - /// This URL supports Server Variables and may be relative to indicate that the host location - /// is relative to the location where the OpenRPC document is being served. - /// - /// Server Variables are passed into the Runtime Expression to produce a server URL. - pub url: RuntimeExpression, - /// A short description of what the server is. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// Describes the host designated by the URL. - /// - /// GitHub Flavored Markdown may be used for rich text presentation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// The values of this object are passed to the [`RuntimeExpression`] to produce an actual - /// URL. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub variables: BTreeMap, -} - -/// An object representing a Server Variable for server URL template substitution. -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct ServerVariable { - /// An enumeration of string values to be used if the substitution options are from a limited - /// set. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub enum_: Vec, - /// The default value to use for substitution, which shall be sent if an alternate value is - /// not supplied. - /// - /// Note this behavior is different than the Schema Object's treatment of default values, - /// because in those cases parameter values are optional. - #[serde(default)] - pub default: String, - /// An optional description for the server variable. - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, -} - -/// Describes the interface for the given method name. -/// -/// The method name is used as the `method` field of the JSON-RPC body. It therefore must be -/// unique. -#[derive(Default, Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Method { - /// The canonical name of the method. - /// - /// This name must be unique within the methods array. - #[serde(default)] - pub name: String, - /// A list of tags for API documentation control. Tags can be used for logical grouping - /// of methods by resources or any other qualifier. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub tags: Vec>, - /// A short summary of what the method does. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// A verbose explanation of the method behavior. - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Additional external documentation for this method. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub external_docs: Option, - /// A list of parameters that are applicable for this method. - /// - /// The list must not include duplicated parameters and therefore require `name` to be - /// unique. - /// - /// All required parameters must be listed *before* any optional parameters. - #[serde(default)] - pub params: Vec>, - /// The description of the result returned by the method. - /// - /// If defined, it must be a [`ContentDescriptor`] or a Reference. - /// - /// If undefined, the method must only be used as a *notification*. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub result: Option>, - /// Declares this method as deprecated. - /// - /// Consumers should refrain from usage of the declared method. - /// - /// The default value is `false`. - #[serde(default, skip_serializing_if = "serde_fns::is_false")] - pub deprecated: bool, - /// An alternative `servers` array to service this method. - /// - /// If specified, it overrides the `servers` array defined at the root level. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub servers: Option>, - /// A list of custom application-defined errors that may be returned. - /// - /// The errors must have unique error codes. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub errors: Vec>, - /// A list of possible links from this method call. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub links: Vec>, - /// The expected format of the parameters. - /// - /// The parameters of a method may be an array, an object, or either. When a method - /// has a `param_structure` value of [`ByName`], callers of the method must pass an - /// object as the parameters. When a method has a `param_structure` value of [`ByPosition`], - /// callers of the method must pass an array as the parameters. Otherwise, callers may - /// pass either an array or an object as the parameters. - /// - /// The default value is [`Either`]. - /// - /// [`ByName`]: ParamStructure::ByName - /// [`ByPosition`]: ParamStructure::ByPosition - /// [`Either`]: ParamStructure::Either - #[serde(default, skip_serializing_if = "serde_fns::is_default")] - pub param_structure: ParamStructure, - /// An array of [`ExamplePairing`] objects, where each example includes a valid - /// params-to-result [`ContentDescriptor`] pairing. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub examples: Vec>, -} - -/// A possible value for the `param_structure` field of [`Method`]. -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)] -#[serde(rename_all = "kebab-case")] -pub enum ParamStructure { - /// Parameters must be passed as a JSON object. - ByName, - /// Parameters must be passed as a JSON array. - ByPosition, - /// Parameters may be passed as either a JSON object or a JSON array. - #[default] - Either, -} - -/// Content descriptors are that do just as they suggest - describe content. They are reusable -/// ways of describing either parameters or results. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ContentDescriptor { - /// The name of the content being described. - /// - /// If the content described is a method parameter assignable - /// [`ByName`](ParamStructure::ByName), this field must be the name of the parameter. - #[serde(default)] - pub name: String, - /// A short summary of the content that is being described. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// A verbose explanation of the content being described. - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Determines if the content is a required field. - /// - /// Default is `false`. - #[serde(default, skip_serializing_if = "serde_fns::is_false")] - pub required: bool, - /// A [`Schema`] that describes what is allowed in the content. - #[serde(default)] - pub schema: Schema, - /// Whether the content is deprecated. - /// - /// Default is `false`. - #[serde(default, skip_serializing_if = "serde_fns::is_false")] - pub deprecated: bool, -} - -/// Allows the definition of input and output data types. -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct Schema { - /// The title of the schema. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, - /// The description of the schema. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// The contents of the schema. - #[serde(flatten)] - pub contents: SchemaContents, -} - -/// The content of a schema. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum SchemaContents { - /// The schema contains a reference to another schema. - Reference { - /// The reference string. - #[serde(rename = "$ref")] - reference: String, - }, - /// The schema is made of a combination of other schemas. - /// - /// The final object must match *all* of the schemas. - AllOf { - /// The schemas that the final object must match. - #[serde(rename = "allOf")] - all_of: Vec, - }, - /// The schema is made of a combination of other schemas. - /// - /// The final object must match *any* of the schemas. - AnyOf { - /// The schemas that the final object must match. - #[serde(rename = "anyOf")] - any_of: Vec, - }, - /// The schema is made of a combination of other schemas. - /// - /// The final object must match exactly *one* of the schemas. - OneOf { - /// The schemas that the final object must match. - #[serde(rename = "oneOf")] - one_of: Vec, - }, - /// The schema contains a literal value. - Literal(Literal), - /// The schema contains an Object. - /// - /// Note this is a workaround to parse Literal(Literal::ObjectLiteral), that don't havethe - /// type: "object" field. - Object(ObjectLiteral), -} - -impl Default for SchemaContents { - #[inline] - fn default() -> Self { - Self::Literal(Literal::Null) - } -} - -/// A literal value. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum Literal { - /// The literal is a boolean. - Boolean, - /// The literal is an integer. - Integer(IntegerLiteral), - /// The literal is a number. - Number(NumberLiteral), - /// The literal is a string. - String(StringLiteral), - // The literal is an object. - Object(ObjectLiteral), - /// The literal is an array. - Array(ArrayLiteral), - /// The literal is a null value. - Null, -} - -/// The constraints that may be applied to an integer literal schema. -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[derive(Debug, Clone)] -pub struct IntegerLiteral { - /// The integer must be a multiple of this value. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub multiple_of: Option, - /// The minimum value of the integer. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub minimum: Option, - /// The maximum value of the integer. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub maximum: Option, - /// Whether the minimum value is exclusive. - /// - /// Default is `false`. - #[serde(default, skip_serializing_if = "serde_fns::is_false")] - pub exclusive_minimum: bool, - /// Whether the maximum value is exclusive. - /// - /// Default is `false`. - #[serde(default, skip_serializing_if = "serde_fns::is_false")] - pub exclusive_maximum: bool, -} - -/// The constraints that may be applied to a number literal schema. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct NumberLiteral { - /// The number must be a multiple of this value. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub multiple_of: Option, - /// The minimum value of the number. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub minimum: Option, - /// The maximum value of the number. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub maximum: Option, - /// Whether the minimum value is exclusive. - /// - /// Default is `false`. - #[serde(default, skip_serializing_if = "serde_fns::is_false")] - pub exclusive_minimum: bool, - /// Whether the maximum value is exclusive. - /// - /// Default is `false`. - #[serde(default, skip_serializing_if = "serde_fns::is_false")] - pub exclusive_maximum: bool, -} - -/// The constraints that may be applied to an array literal schema. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ArrayLiteral { - /// The schema that the items in the array must match. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub items: Option>, -} - -/// The constraints that may be applied to an string literal schema. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct StringLiteral { - /// The minimum length of the string. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub min_length: Option, - /// The maximum length of the string.s - #[serde(default, skip_serializing_if = "Option::is_none")] - pub max_length: Option, - /// The pattern that the string must match. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub pattern: Option, - /// The format that the string must be in. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub format: Option, - /// A list of possible values for the string. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "enum")] - pub enumeration: Option>, -} - -/// A string format. -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[serde(rename_all = "kebab-case")] -pub enum StringFormat { - /// Date and time together, for example, `2018-11-13T20:20:39+00:00`. - DateTime, - /// Time, for example, `20:20:39+00:00`. - Time, - /// Date, for example, `2018-11-13`. - Date, - /// A duration as defined by the [ISO 8601 ABNF](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A). - Duration, - /// An email. See [RFC 5321](http://tools.ietf.org/html/rfc5321#section-4.1.2). - Email, - /// The internationalized version of an email. See [RFC 6531](https://tools.ietf.org/html/rfc6531). - IdnEmail, - /// A host name. See [RFC 1123](https://datatracker.ietf.org/doc/html/rfc1123#section-2.1). - Hostname, - /// The internationalized version of a host name. See [RFC 5890](https://tools.ietf.org/html/rfc5890#section-2.3.2.3). - IdnHostname, - /// An IP v4. See [RFC 2673](http://tools.ietf.org/html/rfc2673#section-3.2). - #[serde(rename = "ipv4")] - IpV4, - /// An IP v6. See [RFC 2373](http://tools.ietf.org/html/rfc2373#section-2.2). - #[serde(rename = "ipv6")] - IpV6, - /// A universally unique identifier. See [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122). - Uuid, - /// A universal resource identifier . See [RFC 3986](http://tools.ietf.org/html/rfc3986). - Uri, - /// A URI reference. See (RFC 3986)[]. - UriReference, - /// The internationalized version of a URI. See [RFC 3987](https://tools.ietf.org/html/rfc3987). - Iri, - /// The internationalized version of a URI reference. See [RFC 3987](https://tools.ietf.org/html/rfc3987). - IriReference, - /// A URI template. See [RFC 6570](https://tools.ietf.org/html/rfc6570). - UriTemplate, - /// A JSON pointer. See [RFC 6901](https://tools.ietf.org/html/rfc6901). - JsonPointer, - /// A relative JSON pointer. See [Relative JSON Pointer](https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01). - RelativeJsonPointer, - /// A regular expression. See [ECMA 262](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). - Regex, -} - -/// The constraints that may be applied to an object literal schema. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ObjectLiteral { - /// The properties that the object might have. - pub properties: BTreeMap, - - /// List of legacy aliases for properties. - #[serde(skip)] - pub legacy_aliases: HashMap, - - /// A list of properties that the object must have. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub required: Vec, -} - -/// A set of example parameters and a result. -/// -/// This result is what you'd expect from the JSON-RPC service given the exact params. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ExamplePairing { - /// The name for the example pairing. - #[serde(default)] - pub name: String, - /// A verbose description of the example pairing. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// A short summary of the example pairing. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// Example parameters. - #[serde(default)] - pub params: Vec>, - /// Example result. - /// - /// When undefined, shows the usage of the method as a notification. - #[serde(default)] - pub result: RefOr, -} - -/// Defines an example that is intended to match a [`Schema`] of a given [`ContentDescriptor`]. -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ExampleObject { - /// Canonical name of the example. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub name: Option, - /// A verbose description of the example - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// A short summary of the example. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// The value of the example. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub value: Option, -} - -/// The example value of an [`ExampleObject`]. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ExampleValue { - /// The value is a JSON object embedded in the document. - /// A link to an external document containing the value. - #[serde(rename = "externalValue")] - External(String), -} - -/// Represents a possible design-time link for a result. -/// -/// The presence of a link does not guarantee the caller's ability to successfully invoke it, -/// rather it provides a known relationship and traversal mechanism between results and other -/// methods. -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[derive(Debug, Clone)] -pub struct Link { - /// Canonical name for the link. - #[serde(default)] - pub name: String, - /// A description of the link. - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Short description for the link. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// The name of an *existing*, resolvable OpenRPC method, as defined with a unique - /// `method`. This field must resolve to a unique [`Method`] object. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub method: Option, - /// The parameters to pass to a method as specified with `method`. The key is the parameter - /// name to be used, whereas the value can be a constant or a [`RuntimeExpression`] to be - /// evaluated and passed to the linked method. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub params: Option, - /// A server object to be used by the target method. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub server: Option, -} - -/// The content of the `params` field of a [`Link`]. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum LinkParams { - /// A [`RuntimeExpression`] that evaluates to the parameters. - Dynamic(RuntimeExpression), -} - -/// Runtime expressions allow the user to define an expression which will evaluate to a -/// string once the desired value(s) are known. -/// -/// They are used when the desired value of a link or server can only be constructed at -/// run time. This mechanism is used by [`Link`] objects and [`ServerVariable`]s. -/// -/// This runtime expression makes use of JSON template strings. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(transparent)] -pub struct RuntimeExpression(pub String); - -/// An application-level error. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Error { - /// An application-defined error code. - #[serde(default)] - pub code: i64, - /// A string providing a short description of the error. - /// - /// The message should be limited to a concise single sentence. - #[serde(default)] - pub message: String, -} - -/// Holds a set of reusable objects for different aspects of the OpenRPC document. -/// -/// All objects defined within the [`Components`] object will have no effect on the API -/// unless they are explicitly referenced from properties outside of the [`Components`] -/// object. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Components { - /// A list of reusable [`ContentDescriptor`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub content_descriptors: BTreeMap, - /// A list of reusable [`Schema`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub schemas: BTreeMap, - /// A list of reusable [`ExampleObject`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub examples: BTreeMap, - /// A list of reusable [`Link`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub links: BTreeMap, - /// A list of reusable [`Error`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub errors: BTreeMap, - /// A list of reusable [`ExamplePairing`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "examplePairingObjects")] - pub example_pairings: BTreeMap, - /// A list of reusable [`Tag`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub tags: BTreeMap, -} - -/// Adds metadata to a single tag that is used by the [`Method`] Object. -/// -/// It is not mandatory to have a [`Tag`] Object per tag defined in the [`Method`] -/// Object instances. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Tag { - /// The name of the tag. - #[serde(default)] - pub name: String, - /// A short summary of the tag. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// A verbose explanation of the tag. - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Additional external documentation for this tag. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub external_docs: Option, -} - -/// Allows referencing an external resource for extended documentation. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ExternalDocumentation { - /// A verbose explanation of the target documentation. - /// - /// GitHub Flavored Markdown syntax may be used for rich text representation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - /// A URL for the target documentation. - /// - /// This must contain an URL. - #[serde(default)] - pub url: String, -} - -/// Either a reference or an inline object. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum RefOr { - /// A reference to an object defined elsewhere. - Reference { - /// The reference string. - #[serde(rename = "$ref")] - reference: String, - }, - /// An inline object. - Inline(T), -} - -impl RefOr { - /// Unwraps the inlined object. - pub fn unwrap_inline(&self) -> &T { - match self { - RefOr::Reference { reference } => panic!("Unexpected reference: {reference}"), - RefOr::Inline(v) => v, - } - } -} - -impl Default for RefOr { - #[inline] - fn default() -> Self { - RefOr::Inline(T::default()) - } -} - -/// Functions used by `serde`, such as predicates and default values. -mod serde_fns { - use std::collections::BTreeMap; - - use super::{RuntimeExpression, Server}; - - /// Returns the default value of the `servers` field. - pub fn servers() -> Vec { - vec![Server { - name: "default".into(), - url: RuntimeExpression("localhost".into()), - summary: None, - description: None, - variables: BTreeMap::new(), - }] - } - - /// Returns whether `b` is `false`. - pub fn is_false(b: &bool) -> bool { - !*b - } - - /// Returns whether the given value is the default value of its type. - pub fn is_default(t: &T) -> bool { - *t == T::default() - } -} - -#[test] -fn parsing_works() { - let content = include_str!("../openrpc.json"); - let _: OpenRpc = dbg!(serde_json::from_str(content).unwrap()); -} diff --git a/substrate/frame/revive/rpc/codegen/src/printer.rs b/substrate/frame/revive/rpc/codegen/src/printer.rs deleted file mode 100644 index 8a8933b4f432..000000000000 --- a/substrate/frame/revive/rpc/codegen/src/printer.rs +++ /dev/null @@ -1,527 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use crate::open_rpc::*; -use inflector::Inflector; - -/// Type information used for generating the type. -#[derive(Debug, Clone)] -pub struct TypeInfo { - /// The type name. - pub name: String, - /// Whether the type is an array. - pub array: bool, - /// Whether the type is required. - pub required: Required, -} - -impl TypeInfo { - pub fn set_required(mut self, required: bool) -> Self { - if required { - self.required = Required::Yes; - } else { - self.required = Required::No { skip_if_null: true }; - } - self - } - - /// Return Whether the type is optional. - pub fn is_optional(&self) -> bool { - matches!(self.required, Required::No { .. }) - } -} - -/// A trait to provide type names. -pub trait TypeNameProvider { - /// Returns type information for a schema. - fn type_info(&mut self, schema: &Schema) -> Option; - - /// Record an inline type. - fn record_inline_type(&mut self, name: String, schema: &Schema) -> TypeInfo; -} - -/// Describes whether the type is required or not. -#[derive(Debug, Clone)] -pub enum Required { - /// The type is required. - Yes, - /// The type is not required, and may be skipped when serializing if it's None and skip_if_null - /// is true. - No { skip_if_null: bool }, -} - -impl TypeInfo { - //// Convert the type info to a string we can use in the generated code. - pub fn get_type(&self) -> String { - let mut type_name = self.name.clone(); - if self.array { - type_name = format!("Vec<{}>", type_name) - } else if self.is_optional() { - type_name = format!("Option<{}>", type_name) - } - type_name - } -} - -impl From for TypeInfo -where - T: Into, -{ - fn from(name: T) -> Self { - Self { name: name.into(), required: Required::Yes, array: false } - } -} -/// Represents a field in a struct. -#[derive(Debug)] -pub struct Field { - /// The documentation for the field. - doc: Option, - /// The name of the field. - name: String, - /// the type information for the field. - type_info: TypeInfo, - /// Whether to flatten the field, when serializing. - flatten: bool, - /// Legacy alias for the field. - alias: Option, -} - -/// Represents a collection of fields. -#[derive(Debug)] -pub struct Fields(Vec); - -impl From> for Fields { - fn from(value: Vec) -> Self { - Self(value) - } -} - -impl IntoIterator for Fields { - type Item = Field; - type IntoIter = std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl Fields { - /// Creates a collection of fields from an [`ObjectLiteral]. - /// - /// The methods also takes a [`TypeNameProvider`] to resolve the types of the fields, and to - /// collect child types. - pub fn from(value: &ObjectLiteral, provider: &mut impl TypeNameProvider) -> Self { - let ObjectLiteral { properties, legacy_aliases, required } = value; - - properties - .iter() - .map(|(name, schema)| { - let mut type_info = provider.type_info(schema).expect("Type should be defined"); - if matches!(type_info.required, Required::Yes) && !required.contains(name) { - type_info.required = Required::No { skip_if_null: true }; - } - - let doc = doc_str_from_schema(schema); - Field { - doc, - name: name.clone(), - type_info, - alias: legacy_aliases.get(name).cloned(), - flatten: false, - } - }) - .collect::>() - .into() - } - - /// Creates a collection of fields from the items of a [`SchemaContents::AllOf`] schema. - pub fn from_all_of(all_of: &[Schema], provider: &mut impl TypeNameProvider) -> Fields { - all_of - .iter() - .flat_map(|schema| { - let doc = doc_str_from_schema(schema); - if let Some(type_info) = provider.type_info(schema) { - vec![Field { - doc, - name: type_info.name.clone(), - type_info, - alias: None, - flatten: true, - }] - } else { - let object = match &schema.contents { - SchemaContents::Object(object) => object, - SchemaContents::Literal(Literal::Object(object)) => object, - v => panic!("Unsupported anonymous all_of type {:?}", v), - }; - - Fields::from(object, provider).0 - } - }) - .collect::>() - .into() - } -} - -/// The variant of an enum. -#[derive(Debug)] -pub struct Variant { - /// The documentation for the variant. - doc: Option, - /// The type information for the variant. - type_info: TypeInfo, -} - -impl Variant { - pub fn name(&self) -> String { - let name = self.type_info.name.to_pascal_case(); - if self.type_info.array { - format!("{}s", name) - } else { - name - } - } -} - -pub fn doc_str_from_schema(schema: &Schema) -> Option { - let mut doc = schema.title.clone(); - - if let Some(description) = &schema.description { - doc = Some(doc.map_or_else(|| description.clone(), |doc| format!("{doc}\n{description}"))); - } - - doc -} - -#[derive(Debug)] -pub struct Variants(Vec); -impl Variants { - /// Creates a collection of variants from the items of a [`SchemaContents::OneOf`] schema. - pub(crate) fn from_one_of(one_of: &[Schema], provider: &mut impl TypeNameProvider) -> Variants { - one_of - .iter() - .filter_map(|schema| { - let doc = doc_str_from_schema(schema); - if let Some(type_info) = provider.type_info(schema) { - if type_info.name == "Null" || type_info.name == "NotFound" { - return None; - } - - Some(Variant { doc, type_info }) - } else { - let name = schema - .title - .clone() - .expect("Title should be defined for inline variant") - .to_pascal_case(); - - let type_info = provider.record_inline_type(name.clone(), schema); - Some(Variant { doc, type_info }) - } - }) - .collect::>() - .into() - } -} - -impl From> for Variants { - fn from(value: Vec) -> Self { - Self(value) - } -} - -/// The content of a type. -#[derive(Debug)] -pub enum TypeContent { - /// A struct type. - Struct(Fields), - /// A unit struct type. - TypeAlias(TypeInfo), - /// An enum type. - Enum(Variants), - /// A serde untagged enum type. - UntaggedEnum(Vec), -} - -/// A type printer. -#[derive(Debug)] -pub struct TypePrinter { - pub doc: Option, - pub name: String, - pub content: TypeContent, - custom_default_variant: Option, -} - -/// A macro to write a formatted line to a buffer. -#[macro_export] -macro_rules! writeln { - (@doc $s: ident, $doc: ident) => { - $crate::writeln!(@doc $s, $doc, 0) - }; - (@doc $s: ident, $doc: ident, $indent: literal) => { - if let Some(doc) = $doc { - for line in doc.lines() { - writeln!($s, "{:indent$}/// {}", "", line, indent = $indent); - } - } - }; - ($s: ident, $($arg: tt)*) => { - $s.push_str(&format!($($arg)*)); - $s.push_str("\n"); - }; - - - -} - -impl TypePrinter { - pub fn new( - doc: Option, - name: String, - content: TypeContent, - custom_default_variant: Option, - ) -> Self { - Self { doc, name, content, custom_default_variant } - } - - /// Prints the type to a buffer. - pub fn print(self, buffer: &mut String) { - let Self { doc, name, content, .. } = self; - - writeln!(@doc buffer, doc); - match content { - TypeContent::Enum(variants) if variants.0.len() == 1 => { - let type_info = &variants.0[0].type_info; - writeln!(buffer, "pub type {name} = {};", type_info.get_type()); - }, - TypeContent::TypeAlias(type_info) => { - writeln!(buffer, "pub type {name} = {};", type_info.get_type()); - }, - TypeContent::Enum(variants) => { - writeln!( - buffer, - "#[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)]" - ); - writeln!(buffer, "#[serde(untagged)]"); - writeln!(buffer, "pub enum {name} {{"); - for variant in variants.0.iter() { - let doc = &variant.doc; - writeln!(@doc buffer, doc, 2); - writeln!(buffer, " {}({}),", variant.name(), variant.type_info.get_type()); - } - writeln!(buffer, "}}"); - - // Implement Default trait - let default_variant = self - .custom_default_variant - .map(|s| s.to_string()) - .unwrap_or_else(|| variants.0[0].name()); - - writeln!(buffer, "impl Default for {name} {{"); - writeln!(buffer, " fn default() -> Self {{"); - writeln!(buffer, " {name}::{default_variant}(Default::default())"); - writeln!(buffer, " }}"); - writeln!(buffer, "}}"); - }, - TypeContent::UntaggedEnum(variants) => { - writeln!( - buffer, - "#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)]" - ); - writeln!(buffer, "pub enum {name} {{"); - - let default_variant_index = self.custom_default_variant.map_or(0, |v| { - variants - .iter() - .position(|x| x.eq_ignore_ascii_case(&v)) - .expect("Default variant not found") - }); - - for (i, name) in variants.iter().enumerate() { - writeln!(buffer, " #[serde(rename = \"{name}\")]"); - if i == default_variant_index { - writeln!(buffer, " #[default]"); - } - let pascal_name = name.to_pascal_case(); - writeln!(buffer, " {pascal_name},"); - } - writeln!(buffer, "}}"); - }, - TypeContent::Struct(fields) => { - writeln!( - buffer, - "#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)]" - ); - - writeln!(buffer, "pub struct {name} {{"); - for Field { doc, name, type_info, alias, flatten } in fields { - writeln!(@doc buffer, doc, 2); - let mut snake_name = name.to_snake_case(); - let mut serde_params = vec![]; - - if flatten { - serde_params.push("flatten".to_string()); - } else if snake_name != name { - serde_params.push(format!("rename = \"{}\"", name)); - } - - if let Some(alias) = alias { - serde_params.push(format!("alias = \"{}\"", alias)); - } - - if matches!(type_info.required, Required::No { skip_if_null: true }) { - if type_info.array { - serde_params.push( - "default, skip_serializing_if = \"Vec::is_empty\"".to_string(), - ); - } else { - serde_params - .push("skip_serializing_if = \"Option::is_none\"".to_string()); - } - } - - if !serde_params.is_empty() { - writeln!(buffer, " #[serde({})]", serde_params.join(", ")); - } - - let type_name = type_info.get_type(); - - if snake_name == "type" { - snake_name = "r#type".to_string() - } - writeln!(buffer, " pub {snake_name}: {type_name},"); - } - writeln!(buffer, "}}"); - }, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::generator::assert_code_match; - - #[test] - fn print_struct_works() { - let gen = TypePrinter { - doc: Some("A simple struct".to_string()), - name: "SimpleStruct".to_string(), - content: TypeContent::Struct( - vec![ - Field { - doc: Some("The first field".to_string()), - name: "firstField".to_string(), - type_info: "u32".into(), - flatten: false, - alias: None, - }, - Field { - doc: None, - name: "second".to_string(), - type_info: TypeInfo { - name: "String".to_string(), - required: Required::No { skip_if_null: true }, - array: false, - }, - flatten: true, - alias: None, - }, - ] - .into(), - ), - custom_default_variant: None, - }; - let mut buffer = String::new(); - gen.print(&mut buffer); - assert_code_match( - &buffer, - r#" - /// A simple struct - #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] - pub struct SimpleStruct { - /// The first field - #[serde(rename = "firstField")] - pub first_field: u32, - #[serde(flatten, skip_serializing_if = "Option::is_none")] - pub second: Option, - } - "#, - ); - } - - #[test] - fn print_untagged_enum_works() { - let gen = TypePrinter { - doc: Some("A simple untagged enum".to_string()), - name: "SimpleUntaggedEnum".to_string(), - content: TypeContent::UntaggedEnum(vec!["first".to_string(), "second".to_string()]), - custom_default_variant: None, - }; - let mut buffer = String::new(); - gen.print(&mut buffer); - assert_code_match( - &buffer, - r#" - /// A simple untagged enum - #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] - pub enum SimpleUntaggedEnum { - #[serde(rename = "first")] - #[default] - First, - #[serde(rename = "second")] - Second, - } - "#, - ); - } - - #[test] - fn print_enum_works() { - let gen = TypePrinter { - doc: Some("A simple enum".to_string()), - name: "SimpleEnum".to_string(), - content: TypeContent::Enum( - vec![ - Variant { doc: Some("The Foo variant".to_string()), type_info: "Foo".into() }, - Variant { doc: Some("The Bar variant".to_string()), type_info: "Bar".into() }, - ] - .into(), - ), - custom_default_variant: None, - }; - let mut buffer = String::new(); - gen.print(&mut buffer); - assert_code_match( - &buffer, - r#" - /// A simple enum - #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq)] - #[serde(untagged)] - pub enum SimpleEnum { - /// The Foo variant - Foo(Foo), - /// The Bar variant - Bar(Bar), - } - impl Default for SimpleEnum { - fn default() -> Self { - SimpleEnum::Foo(Default::default()) - } - } - "#, - ); - } -} From 8210606c565ca7183cdb56589a42b264789facd2 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 15:25:35 +0100 Subject: [PATCH 10/67] rm prdoc --- prdoc/pr_5926.prdoc | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 prdoc/pr_5926.prdoc diff --git a/prdoc/pr_5926.prdoc b/prdoc/pr_5926.prdoc deleted file mode 100644 index f05aeb93eb71..000000000000 --- a/prdoc/pr_5926.prdoc +++ /dev/null @@ -1,13 +0,0 @@ -# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 -# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json - -title: "[pallet-revive] add codegen for Ethereum RPC API" - -doc: - - audience: Runtime Dev - description: | - Add codegen crate for generating Ethereum RPC methods and types from the spec. - -crates: - - name: pallet-revive-rpc-codegen - bump: patch From 45f8b7b5636eda50587c29e8fbdd9bced93409a2 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 16:15:31 +0100 Subject: [PATCH 11/67] fix benchmarking tests --- Cargo.lock | 11 ----------- substrate/frame/revive/src/benchmarking/mod.rs | 2 +- substrate/frame/revive/src/exec.rs | 1 + 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65580fe0239c..fe0bff33edc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14836,17 +14836,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "pallet-revive-rpc-codegen" -version = "0.1.0" -dependencies = [ - "Inflector", - "anyhow", - "pretty_assertions", - "serde", - "serde_json", -] - [[package]] name = "pallet-revive-uapi" version = "0.1.0" diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 9c4d817a07de..b73815bfb9ea 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -103,7 +103,7 @@ where origin, 0u32.into(), Weight::MAX, - default_deposit_limit::(), + DepositLimit::Balance(default_deposit_limit::()), Code::Upload(module.code), data, salt, diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 3f88b3087e9c..ce8ca3f94a40 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -859,6 +859,7 @@ where gas_meter, storage_meter, value.into(), + false, debug_message, ) .unwrap() From 0733fb66205a744999065f28109016a9e1ffc084 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 16:25:01 +0100 Subject: [PATCH 12/67] fix tests & doc --- substrate/frame/revive/rpc/src/tests.rs | 2 +- substrate/frame/revive/src/primitives.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 686cebcf657d..cbcd7c4dae9f 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -236,7 +236,7 @@ async fn revert_call() -> anyhow::Result<()> { .unwrap_err(); let call_err = unwrap_call_err!(err.source().unwrap()); - assert_eq!(call_err.message(), "Execution reverted: revert message"); + assert_eq!(call_err.message(), "execution reverted: revert message"); assert_eq!(call_err.code(), 3); Ok(()) } diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index c091ecf288c5..a7127f812b4b 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -29,9 +29,12 @@ use sp_runtime::{ }; #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum DepositLimit { +pub enum DepositLimit { + /// Allows bypassing all balance transfer checks. Unchecked, - Balance(T), + + /// Specifies a maximum allowable balance for a deposit. + Balance(Balance), } impl DepositLimit { From 0941800fd38a545c416f1b4f636ae1b8b21f9743 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 16:33:35 +0100 Subject: [PATCH 13/67] rename unchecked to skip_transfer --- substrate/frame/revive/src/exec.rs | 16 ++++++++-------- substrate/frame/revive/src/lib.rs | 13 +++---------- substrate/frame/revive/src/storage/meter.rs | 8 ++++---- substrate/frame/revive/src/wasm/mod.rs | 4 ++-- 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index ce8ca3f94a40..050b8e1449ea 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -563,7 +563,7 @@ pub struct Stack<'a, T: Config, E> { /// Transient storage used to store data, which is kept for the duration of a transaction. transient_storage: TransientStorage, /// Whether or not actual transfer of funds should be performed. - unchecked: bool, + skip_transfer: bool, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData, } @@ -779,7 +779,7 @@ where storage_meter: &'a mut storage::meter::Meter, value: U256, input_data: Vec, - unchecked: bool, + skip_transfer: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); @@ -789,7 +789,7 @@ where gas_meter, storage_meter, value, - unchecked, + skip_transfer, debug_message, )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) @@ -816,7 +816,7 @@ where value: U256, input_data: Vec, salt: Option<&[u8; 32]>, - unchecked: bool, + skip_transfer: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result<(H160, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( @@ -830,7 +830,7 @@ where gas_meter, storage_meter, value, - unchecked, + skip_transfer, debug_message, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); @@ -876,7 +876,7 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: U256, - unchecked: bool, + skip_transfer: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result, ExecError> { origin.ensure_mapped()?; @@ -904,7 +904,7 @@ where frames: Default::default(), debug_message, transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), - unchecked, + skip_transfer, _phantom: Default::default(), }; @@ -1082,7 +1082,7 @@ where &frame.account_id, frame.contract_info.get(&frame.account_id), executable.code_info(), - self.unchecked, + self.skip_transfer, )?; // Needs to be incremented before calling into the code so that it is visible // in case of recursion. diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 12a55004600c..efc0322b5f6c 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1241,17 +1241,10 @@ where /// /// # Parameters /// - /// - `origin`: The origin of the call. - /// - `dest`: The destination address of the call. - /// - `value`: The EVM value to transfer. - /// - `input`: The input data. + /// - `tx`: The Ethereum transaction to simulate. /// - `gas_limit`: The gas limit enforced during contract execution. - /// - `storage_deposit_limit`: The maximum balance that can be charged to the caller for storage - /// usage. /// - `utx_encoded_size`: A function that takes a call and returns the encoded size of the /// unchecked extrinsic. - /// - `debug`: Debugging configuration. - /// - `collect_events`: Event collection configuration. pub fn bare_eth_transact( mut tx: GenericTransaction, gas_limit: Weight, @@ -1498,10 +1491,10 @@ where origin: T::AccountId, code: Vec, storage_deposit_limit: BalanceOf, - unchecked: bool, + skip_transfer: bool, ) -> Result<(WasmBlob, BalanceOf), DispatchError> { let mut module = WasmBlob::from_code(code, origin)?; - let deposit = module.store_code(unchecked)?; + let deposit = module.store_code(skip_transfer)?; ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); Ok((module, deposit)) } diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index f068509c34f1..6eddf048be98 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -387,9 +387,9 @@ where pub fn try_into_deposit( self, origin: &Origin, - unchecked: bool, + skip_transfer: bool, ) -> Result, DispatchError> { - if !unchecked { + if !skip_transfer { // Only refund or charge deposit if the origin is not root. let origin = match origin { Origin::Root => return Ok(Deposit::Charge(Zero::zero())), @@ -437,14 +437,14 @@ impl> RawMeter { contract: &T::AccountId, contract_info: &mut ContractInfo, code_info: &CodeInfo, - unchecked: bool, + skip_transfer: bool, ) -> Result<(), DispatchError> { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); // We need to make sure that the contract's account exists. let ed = Pallet::::min_balance(); self.total_deposit = Deposit::Charge(ed); - if unchecked { + if skip_transfer { T::Currency::set_balance(contract, ed); } else { T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?; diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 82aa67a1d678..6f6108dd1222 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -183,7 +183,7 @@ where } /// Puts the module blob into storage, and returns the deposit collected for the storage. - pub fn store_code(&mut self, unchecked: bool) -> Result, Error> { + pub fn store_code(&mut self, skip_transfer: bool) -> Result, Error> { let code_hash = *self.code_hash(); >::mutate(code_hash, |stored_code_info| { match stored_code_info { @@ -196,7 +196,7 @@ where None => { let deposit = self.code_info.deposit; - if !unchecked { + if !skip_transfer { T::Currency::hold( &HoldReason::CodeUploadDepositReserve.into(), &self.code_info.owner, From d4460d66f98aea3b3e95c520615199fbda3cc061 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 16:39:57 +0100 Subject: [PATCH 14/67] clippy --- substrate/frame/revive/rpc/src/client.rs | 2 +- substrate/frame/revive/rpc/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 5f664be115e2..60dbc12501f5 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -121,7 +121,7 @@ fn extract_revert_message(exec_data: &[u8]) -> Option { match function_selector { // assert(false) [0x4E, 0x48, 0x7B, 0x71] => { - let panic_code: u32 = U256::from_big_endian(&exec_data.get(4..36)?).try_into().ok()?; + let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require let msg = match panic_code { diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 8072de4d3ce3..ccd8bb043e90 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -131,7 +131,7 @@ impl EthRpcServer for EthRpcServerImpl { block: Option, ) -> RpcResult { let dry_run = self.client.dry_run(transaction, block.unwrap_or_default().into()).await?; - Ok(U256::from(dry_run.eth_gas)) + Ok(dry_run.eth_gas) } async fn call( From c1643687f8db4bed63819ae14b1286bc86ee89b7 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 16:51:00 +0100 Subject: [PATCH 15/67] lint --- .../rpc/examples/js/abi/errorTester.json | 210 +++++++++--------- .../revive/rpc/examples/js/abi/errorTester.ts | 210 +++++++++--------- .../revive/rpc/examples/js/abi/event.json | 66 +++--- .../frame/revive/rpc/examples/js/abi/event.ts | 66 +++--- .../revive/rpc/examples/js/abi/piggyBank.json | 128 +++++------ .../revive/rpc/examples/js/abi/piggyBank.ts | 128 +++++------ .../frame/revive/rpc/examples/js/package.json | 40 ++-- .../revive/rpc/examples/js/src/balance.ts | 1 - .../rpc/examples/js/src/geth-diff-setup.ts | 161 ++++++++++++++ .../rpc/examples/js/src/geth-diff.test.ts | 173 +-------------- .../frame/revive/rpc/examples/js/src/lib.ts | 9 +- 11 files changed, 589 insertions(+), 603 deletions(-) create mode 100644 substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.json b/substrate/frame/revive/rpc/examples/js/abi/errorTester.json index 2d8dccc771e8..e660a0a054a4 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/errorTester.json +++ b/substrate/frame/revive/rpc/examples/js/abi/errorTester.json @@ -1,106 +1,106 @@ [ - { - "inputs": [ - { - "internalType": "string", - "name": "message", - "type": "string" - } - ], - "name": "CustomError", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "newState", - "type": "bool" - } - ], - "name": "setState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "state", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "triggerAssertError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerCustomError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerDivisionByZero", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerOutOfBoundsError", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerRequireError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerRevertError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "valueMatch", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] \ No newline at end of file + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "CustomError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "newState", + "type": "bool" + } + ], + "name": "setState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "state", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "triggerAssertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerCustomError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerDivisionByZero", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerOutOfBoundsError", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRequireError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRevertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "valueMatch", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts index d1ad60c1f55a..93daf34e02b6 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts +++ b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts @@ -1,106 +1,106 @@ export const abi = [ - { - inputs: [ - { - internalType: "string", - name: "message", - type: "string", - }, - ], - name: "CustomError", - type: "error", - }, - { - inputs: [ - { - internalType: "bool", - name: "newState", - type: "bool", - }, - ], - name: "setState", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "state", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "triggerAssertError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerCustomError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerDivisionByZero", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerOutOfBoundsError", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerRequireError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerRevertError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [ - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "valueMatch", - outputs: [], - stateMutability: "payable", - type: "function", - }, -] as const; + { + inputs: [ + { + internalType: 'string', + name: 'message', + type: 'string', + }, + ], + name: 'CustomError', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bool', + name: 'newState', + type: 'bool', + }, + ], + name: 'setState', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'state', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'triggerAssertError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerCustomError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerDivisionByZero', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerOutOfBoundsError', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerRequireError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerRevertError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'valueMatch', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.json b/substrate/frame/revive/rpc/examples/js/abi/event.json index a64c920c4068..d36089fbc84e 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/event.json +++ b/substrate/frame/revive/rpc/examples/js/abi/event.json @@ -1,34 +1,34 @@ [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "message", - "type": "string" - } - ], - "name": "ExampleEvent", - "type": "event" - }, - { - "inputs": [], - "name": "triggerEvent", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "ExampleEvent", + "type": "event" + }, + { + "inputs": [], + "name": "triggerEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.ts b/substrate/frame/revive/rpc/examples/js/abi/event.ts index 317ed00b92f9..c389e2daf1da 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/event.ts +++ b/substrate/frame/revive/rpc/examples/js/abi/event.ts @@ -1,34 +1,34 @@ export const abi = [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "sender", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "value", - type: "uint256", - }, - { - indexed: false, - internalType: "string", - name: "message", - type: "string", - }, - ], - name: "ExampleEvent", - type: "event", - }, - { - inputs: [], - name: "triggerEvent", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, -] as const; + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + indexed: false, + internalType: 'string', + name: 'message', + type: 'string', + }, + ], + name: 'ExampleEvent', + type: 'event', + }, + { + inputs: [], + name: 'triggerEvent', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json index e6655889e21a..2c2cfd5f7533 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json +++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json @@ -1,65 +1,65 @@ [ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "deposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "getDeposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "withdrawAmount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [ - { - "internalType": "uint256", - "name": "remainingBal", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "remainingBal", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts index bc685e0b827a..3d44cd998ad1 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts +++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts @@ -1,65 +1,65 @@ export const abi = [ - { - inputs: [], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [], - name: "deposit", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "payable", - type: "function", - }, - { - inputs: [], - name: "getDeposit", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "owner", - outputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint256", - name: "withdrawAmount", - type: "uint256", - }, - ], - name: "withdraw", - outputs: [ - { - internalType: "uint256", - name: "remainingBal", - type: "uint256", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, -] as const; + { + inputs: [], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'deposit', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'getDeposit', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'withdrawAmount', + type: 'uint256', + }, + ], + name: 'withdraw', + outputs: [ + { + internalType: 'uint256', + name: 'remainingBal', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index fa101dcad1d9..559ca8a5b4fb 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -1,22 +1,22 @@ { - "name": "demo", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview" - }, - "dependencies": { - "ethers": "^6.13.4", - "prettier": "^3.3.3", - "solc": "^0.8.28", - "viem": "^2.21.47" - }, - "devDependencies": { - "@types/bun": "^1.1.13", - "typescript": "^5.5.3", - "vite": "^5.4.8" - } + "name": "demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "ethers": "^6.13.4", + "prettier": "^3.3.3", + "solc": "^0.8.28", + "viem": "^2.21.47" + }, + "devDependencies": { + "@types/bun": "^1.1.13", + "typescript": "^5.5.3", + "vite": "^5.4.8" + } } diff --git a/substrate/frame/revive/rpc/examples/js/src/balance.ts b/substrate/frame/revive/rpc/examples/js/src/balance.ts index 0c5e59d07849..1261dcab7812 100644 --- a/substrate/frame/revive/rpc/examples/js/src/balance.ts +++ b/substrate/frame/revive/rpc/examples/js/src/balance.ts @@ -6,4 +6,3 @@ try { } catch (err) { console.error(err) } - diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts new file mode 100644 index 000000000000..b48fcd9620ea --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts @@ -0,0 +1,161 @@ +import { spawn, spawnSync, Subprocess } from 'bun' +import { join } from 'path' +import { readFileSync } from 'fs' +import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +export function getByteCode(name: string, evm: boolean): Hex { + const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) + return `0x${Buffer.from(bytecode).toString('hex')}` +} + +export type JsonRpcError = { + code: number + message: string + data: Hex +} + +export function killProcessOnPort(port: number) { + // Check which process is using the specified port + const result = spawnSync(['lsof', '-ti', `:${port}`]) + const output = result.stdout.toString().trim() + + if (output) { + console.log(`Port ${port} is in use. Killing process...`) + const pids = output.split('\n') + + // Kill each process using the port + for (const pid of pids) { + spawnSync(['kill', '-9', pid]) + console.log(`Killed process with PID: ${pid}`) + } + } +} + +export let jsonRpcErrors: JsonRpcError[] = [] +export async function createEnv(name: 'geth' | 'kitchensink') { + const gethPort = process.env.GETH_PORT || '8546' + const kitchensinkPort = process.env.KITCHENSINK_PORT || '8545' + const url = `http://localhost:${name == 'geth' ? gethPort : kitchensinkPort}` + const chain = defineChain({ + id: name == 'geth' ? 1337 : 420420420, + name, + nativeCurrency: { + name: 'Westie', + symbol: 'WST', + decimals: 18, + }, + rpcUrls: { + default: { + http: [url], + }, + }, + testnet: true, + }) + + const transport = http(url, { + onFetchResponse: async (response) => { + const raw = await response.clone().json() + if (raw.error) { + jsonRpcErrors.push(raw.error as JsonRpcError) + } + }, + }) + + const wallet = createWalletClient({ + transport, + chain, + }) + + const [account] = await wallet.getAddresses() + const serverWallet = createWalletClient({ + account, + transport, + chain, + }).extend(publicActions) + + const accountWallet = createWalletClient({ + account: privateKeyToAccount( + '0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f' + ), + transport, + chain, + }).extend(publicActions) + + return { serverWallet, accountWallet, evm: name == 'geth' } +} + +// wait for http request to return 200 +export function waitForHealth(url: string) { + return new Promise((resolve, reject) => { + const start = Date.now() + const interval = setInterval(() => { + fetch(url) + .then((res) => { + if (res.status === 200) { + clearInterval(interval) + resolve() + } + }) + .catch(() => { + const elapsed = Date.now() - start + if (elapsed > 30_000) { + clearInterval(interval) + reject(new Error('hit timeout')) + } + }) + }, 1000) + }) +} + +export const procs: Subprocess[] = [] +if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + // + (() => { + killProcessOnPort(8546) + return spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: join(process.env.HOME!, 'polkadot-sdk'), + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: join(process.env.HOME!, 'polkadot-sdk'), + } + ) + await waitForHealth('http://localhost:8545/health').catch() + return proc + })() + ) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index c5cb64e63afd..bb642b9aa62e 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -1,177 +1,10 @@ -import { spawn, spawnSync, Subprocess } from 'bun' -import { join } from 'path' -import { readFileSync } from 'fs' +import { jsonRpcErrors, procs, createEnv, getByteCode } from './geth-diff-setup.ts' import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' -import { - createWalletClient, - defineChain, - encodeFunctionData, - Hex, - http, - parseEther, - publicActions, -} from 'viem' -import { privateKeyToAccount } from 'viem/accounts' +import { encodeFunctionData, Hex, parseEther } from 'viem' import { abi } from '../abi/errorTester' -export function getByteCode(name: string, evm: boolean): Hex { - const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) - return `0x${Buffer.from(bytecode).toString('hex')}` -} - -type JsonRpcError = { - code: number - message: string - data: Hex -} - -function killProcessOnPort(port: number) { - // Check which process is using the specified port - const result = spawnSync(['lsof', '-ti', `:${port}`]) - const output = result.stdout.toString().trim() - - if (output) { - console.log(`Port ${port} is in use. Killing process...`) - const pids = output.split('\n') - - // Kill each process using the port - for (const pid of pids) { - spawnSync(['kill', '-9', pid]) - console.log(`Killed process with PID: ${pid}`) - } - } -} - -let jsonRpcErrors: JsonRpcError[] = [] -async function createEnv(name: 'geth' | 'kitchensink') { - const gethPort = process.env.GETH_PORT || '8546' - const kitchensinkPort = process.env.KITCHENSINK_PORT || '8545' - const url = `http://localhost:${name == 'geth' ? gethPort : kitchensinkPort}` - const chain = defineChain({ - id: name == 'geth' ? 1337 : 420420420, - name, - nativeCurrency: { - name: 'Westie', - symbol: 'WST', - decimals: 18, - }, - rpcUrls: { - default: { - http: [url], - }, - }, - testnet: true, - }) - - const transport = http(url, { - onFetchResponse: async (response) => { - const raw = await response.clone().json() - if (raw.error) { - jsonRpcErrors.push(raw.error as JsonRpcError) - } - }, - }) - - const wallet = createWalletClient({ - transport, - chain, - }) - - const [account] = await wallet.getAddresses() - const serverWallet = createWalletClient({ - account, - transport, - chain, - }).extend(publicActions) - - const accountWallet = createWalletClient({ - account: privateKeyToAccount( - '0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f' - ), - transport, - chain, - }).extend(publicActions) - - return { serverWallet, accountWallet, evm: name == 'geth' } -} - -// wait for http request to return 200 -export function waitForHealth(url: string) { - return new Promise((resolve, reject) => { - const start = Date.now() - const interval = setInterval(() => { - fetch(url) - .then((res) => { - if (res.status === 200) { - clearInterval(interval) - resolve() - } - }) - .catch(() => { - const elapsed = Date.now() - start - if (elapsed > 30_000) { - clearInterval(interval) - reject(new Error('hit timeout')) - } - }) - }, 1000) - }) -} - -const procs: Subprocess[] = [] -if (!process.env.USE_LIVE_SERVERS) { - procs.push( - // Run geth on port 8546 - // - (() => { - killProcessOnPort(8546) - return spawn( - 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( - ' ' - ), - { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } - ) - })(), - //Run the substate node - (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: join(process.env.HOME!, 'polkadot-sdk'), - } - ) - })(), - // Run eth-rpc on 8545 - await (async () => { - killProcessOnPort(8545) - const proc = spawn( - [ - './target/debug/eth-rpc', - '--dev', - '--node-rpc-url=ws://localhost:9944', - '-l=rpc-metrics=debug,eth-rpc=debug', - ], - { - stdout: Bun.file('/tmp/eth-rpc.out.log'), - stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: join(process.env.HOME!, 'polkadot-sdk'), - } - ) - await waitForHealth('http://localhost:8545/health').catch() - return proc - })() - ) -} - afterEach(() => { - jsonRpcErrors = [] + jsonRpcErrors.length = 0 }) afterAll(async () => { diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts index d1f14bbc064e..e1f0e780d95b 100644 --- a/substrate/frame/revive/rpc/examples/js/src/lib.ts +++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts @@ -1,14 +1,7 @@ import { readFileSync } from 'node:fs' import { spawn } from 'node:child_process' import { parseArgs } from 'node:util' -import { - createWalletClient, - defineChain, - Hex, - http, - parseEther, - publicActions, -} from 'viem' +import { createWalletClient, defineChain, Hex, http, parseEther, publicActions } from 'viem' import { privateKeyToAccount } from 'viem/accounts' const { From 434b2d175ccd0ba3c7ff0b73fb81d12187f952d6 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 17:02:31 +0100 Subject: [PATCH 16/67] use relative path for polkadot-sdk --- .../frame/revive/rpc/examples/js/src/geth-diff-setup.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts index b48fcd9620ea..92b20473d165 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts @@ -1,5 +1,5 @@ import { spawn, spawnSync, Subprocess } from 'bun' -import { join } from 'path' +import { join, resolve } from 'path' import { readFileSync } from 'fs' import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' import { privateKeyToAccount } from 'viem/accounts' @@ -109,6 +109,7 @@ export function waitForHealth(url: string) { } export const procs: Subprocess[] = [] +const polkadotSdkPath = resolve(__dirname, '../../../../../../..') if (!process.env.USE_LIVE_SERVERS) { procs.push( // Run geth on port 8546 @@ -134,7 +135,7 @@ if (!process.env.USE_LIVE_SERVERS) { { stdout: Bun.file('/tmp/kitchensink.out.log'), stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: join(process.env.HOME!, 'polkadot-sdk'), + cwd: polkadotSdkPath, } ) })(), @@ -151,7 +152,7 @@ if (!process.env.USE_LIVE_SERVERS) { { stdout: Bun.file('/tmp/eth-rpc.out.log'), stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: join(process.env.HOME!, 'polkadot-sdk'), + cwd: polkadotSdkPath, } ) await waitForHealth('http://localhost:8545/health').catch() From 868ae85e475c8a958f659b374686e4cb16ffa92c Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 17:03:13 +0100 Subject: [PATCH 17/67] rm console.log --- substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index bb642b9aa62e..ea7a7b28bf2a 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -133,7 +133,6 @@ for (const env of envs) { } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) - console.log(lastJsonRpcError?.message) expect(lastJsonRpcError?.message).toInclude('insufficient funds') expect(lastJsonRpcError?.data).toBeUndefined() } From 0e1c3dcf04bccb9330f8f35887788e9f65b47af4 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 26 Nov 2024 16:09:21 +0000 Subject: [PATCH 18/67] Update from pgherveou running command 'prdoc --audience runtime_dev --bump minor' --- prdoc/pr_6608.prdoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 prdoc/pr_6608.prdoc diff --git a/prdoc/pr_6608.prdoc b/prdoc/pr_6608.prdoc new file mode 100644 index 000000000000..d025b94ed53e --- /dev/null +++ b/prdoc/pr_6608.prdoc @@ -0,0 +1,21 @@ +title: '[pallet-revive] eth-prc fix geth diff' +doc: +- audience: Runtime Dev + description: |- + * Add a bunch of differential tests to ensure that responses from eth-rpc matches the one from `geth` + - These [tests](https://github.com/paritytech/polkadot-sdk/blob/pg/fix-geth-diff/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts) are not run in CI for now but can be run locally with + ```bash + cd revive/rpc/examples/js + bun test + ``` + + * EVM RPC server will not fail gas_estimation if no gas is specified, I updated pallet-revive to add an extra `skip_transfer` boolean check to replicate this behavior in our pallet + + * `eth_transact` and `bare_eth_transact` api have been updated to use `GenericTransaction` directly as this is what is used by `eth_estimateGas` and `eth_call` +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor +- name: asset-hub-westend-runtime + bump: minor From edd099c82e1fbb4aed93693b690350a97b090118 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 26 Nov 2024 17:33:42 +0100 Subject: [PATCH 19/67] fix tests --- substrate/frame/revive/mock-network/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs index bd05726a1a45..34f797c2b530 100644 --- a/substrate/frame/revive/mock-network/src/tests.rs +++ b/substrate/frame/revive/mock-network/src/tests.rs @@ -24,7 +24,7 @@ use frame_support::traits::{fungibles::Mutate, Currency}; use frame_system::RawOrigin; use pallet_revive::{ test_utils::{self, builder::*}, - Code, + Code, DepositLimit, }; use pallet_revive_fixtures::compile_module; use pallet_revive_uapi::ReturnErrorCode; @@ -52,7 +52,7 @@ fn instantiate_test_contract(name: &str) -> Contract { RawOrigin::Signed(ALICE).into(), Code::Upload(wasm), ) - .storage_deposit_limit(1_000_000_000_000) + .storage_deposit_limit(DepositLimit::Balance(1_000_000_000_000)) .build_and_unwrap_contract() }); From 65caf0b7cb05f129482cb83bc04077d0590cebeb Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Tue, 26 Nov 2024 18:21:07 +0100 Subject: [PATCH 20/67] Update substrate/frame/revive/rpc/src/client.rs --- substrate/frame/revive/rpc/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 60dbc12501f5..54d094a2e616 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -171,7 +171,7 @@ pub enum ClientError { #[error(transparent)] CodecError(#[from] codec::Error), /// Contract reverted - #[error("Contract reverted")] + #[error("contract reverted")] Reverted(EthTransactError), /// A decimal conversion failed. #[error("conversion failed")] From 326f52bd16b3d1ebde3dc063e3bf3e7e17b9b37a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 27 Nov 2024 10:17:37 +0100 Subject: [PATCH 21/67] update comment --- substrate/frame/revive/src/evm/api/rlp_codec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 18b7e7c17e09..9b61cd042ec5 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -89,7 +89,7 @@ impl TransactionSigned { } impl TransactionUnsigned { - /// Get a signed transaction with a dummy 65 bytes signature. + /// Get a signed transaction payload with a dummy 65 bytes signature. pub fn dummy_signed_payload(&self) -> Vec { const DUMMY_SIGNATURE: [u8; 65] = [0u8; 65]; self.unsigned_payload() From 58ed03f4fda5699c9e60bb1bc273ef851e579559 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 28 Nov 2024 11:53:28 +0100 Subject: [PATCH 22/67] wip --- substrate/frame/revive/src/evm.rs | 3 + substrate/frame/revive/src/evm/gas_encoder.rs | 248 ++++++++++++++++++ substrate/frame/revive/src/evm/runtime.rs | 19 +- substrate/frame/revive/src/lib.rs | 24 +- substrate/frame/revive/src/tests.rs | 1 + 5 files changed, 271 insertions(+), 24 deletions(-) create mode 100644 substrate/frame/revive/src/evm/gas_encoder.rs diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index c3495fc0559d..713e16376ea7 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -20,3 +20,6 @@ mod api; pub use api::*; pub mod runtime; + +mod gas_encoder; +pub use gas_encoder::*; diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs new file mode 100644 index 000000000000..dca14888e7a1 --- /dev/null +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -0,0 +1,248 @@ +use std::ops::{Div, Rem}; + +use frame_support::ensure; +use sp_arithmetic::traits::{AtLeast32BitUnsigned, One, Zero}; +use sp_core::U256; +use sp_weights::Weight; + +/// Encodes gas values for use in the EVM. +/// +/// The encoding follows the pattern `g...grrrpppddd`, where: +/// - `ddd`: Deposit value, encoded in the lowest 3 digits. +/// - `ppp`: Proof size, encoded in the next 3 digits. +/// - `rrr`: Reference time, encoded in the next 3 digits. +/// - `g...g`: Gas limit, encoded in the highest digits. +/// +/// Each component is scaled using the `SCALE` factor. +pub struct EthGasEncoder { + /// Encodes the raw gas limit. Rounded to the nearest non-zero multiple of this value. + raw_gas_mask: u128, + /// Encodes the weight reference time. + ref_time_mask: u64, + /// Encodes the weight proof size. + proof_size_mask: u64, + /// Encodes the deposit limit. + deposit_mask: Balance, +} + +/// Errors that can occur during encoding. +#[derive(Debug, PartialEq, Eq)] +pub enum GasEncodingError { + /// Reference time exceeds the allowed limit. + RefTimeOverflow, + /// Proof size exceeds the allowed limit. + ProofSizeOverflow, + /// Deposit exceeds the allowed limit. + DepositOverflow, + /// Raw gas limit exceeds the allowed limit. + RawGasLimitOverflow, +} + +// We use 3 digits to store each component. +const SCALE: u64 = 1_000; + +/// Rounds up the given value to the nearest multiple of the mask. +/// +/// # Panics +/// Panics if the `mask` is zero. +fn round_up(value: T, mask: T) -> T +where + T: One + Zero + Copy + Rem + Div, + ::Output: PartialEq, +{ + let rest = if value % mask == T::zero() { T::zero() } else { T::one() }; + value / mask + rest +} + +impl EthGasEncoder +where + Balance: Copy + AtLeast32BitUnsigned + TryFrom + Into, +{ + /// Returns the maximum reference time that can be encoded. + fn max_ref_time(&self) -> u64 { + self.ref_time_mask * SCALE + } + + /// Returns the maximum proof size that can be encoded. + fn max_proof_size(&self) -> u64 { + self.proof_size_mask * SCALE + } + + /// Returns the maximum deposit that can be encoded. + fn max_deposit(&self) -> Balance { + self.deposit_mask * (SCALE as u32).into() + } + + /// Creates a new encoder with the given maximum weight and deposit limit. + pub fn new(max_weight: Weight, max_deposit_limit: Balance) -> Self { + let ref_time_mask = max_weight.ref_time() / SCALE; + let proof_size_mask = max_weight.proof_size() / SCALE; + let deposit_mask = max_deposit_limit / (SCALE as u32).into(); + let raw_gas_mask = SCALE.pow(3) as _; + + Self { raw_gas_mask, ref_time_mask, proof_size_mask, deposit_mask } + } + + /// Encodes all components (deposit limit, weight reference time, and proof size) into a single + /// gas value. + pub fn encode( + &self, + gas_limit: U256, + weight: Weight, + deposit: Balance, + ) -> Result { + let gas_limit: u128 = + gas_limit.try_into().map_err(|_| GasEncodingError::RawGasLimitOverflow)?; + let ref_time = weight.ref_time(); + let proof_size = weight.proof_size(); + + ensure!(ref_time <= self.max_ref_time(), GasEncodingError::RefTimeOverflow); + ensure!(proof_size <= self.max_proof_size(), GasEncodingError::ProofSizeOverflow); + ensure!(deposit <= self.max_deposit(), GasEncodingError::DepositOverflow); + + let raw_gas_component = if gas_limit < self.raw_gas_mask { + self.raw_gas_mask + } else { + round_up(gas_limit, self.raw_gas_mask).saturating_mul(self.raw_gas_mask) + }; + + let deposit_component = round_up(deposit, self.deposit_mask); + let ref_time_component = round_up(ref_time, self.ref_time_mask); + let proof_size_component = round_up(proof_size, self.proof_size_mask); + + Ok(U256::from(raw_gas_component) + .saturating_add(deposit_component.into()) + .saturating_add(U256::from(SCALE) * U256::from(proof_size_component)) + .saturating_add(U256::from(SCALE.pow(2)) * U256::from(ref_time_component))) + } + + /// Decodes the weight and deposit from the encoded gas value. + pub fn decode(&self, gas: U256) -> (Weight, Balance) { + let deposit = gas % SCALE; + let gas_without_deposit = gas - deposit; + + // Casting with as_* is safe since all values are maxed by `SCALE`. + let deposit: Balance = deposit.as_u32().into(); + let proof_time: u64 = ((gas_without_deposit / SCALE) % SCALE).as_u64(); + let ref_time: u64 = ((gas_without_deposit / SCALE.pow(2)) % SCALE).as_u64(); + + ( + Weight::from_parts( + ref_time.saturating_mul(self.proof_size_mask), + proof_time.saturating_mul(self.ref_time_mask), + ), + deposit.saturating_mul(self.deposit_mask), + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + use frame_support::assert_err; + + #[test] + fn test_gas_encoding_with_small_values() { + let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); + let max_deposit = 1_000_000_000u128; + let encoder = EthGasEncoder::new(max_weight, max_deposit); + + let raw_gas_limit = 444_000_000_000_000u128; + let weight = Weight::from_parts(66_000, 44_000); + let deposit = 22_000u128; + + let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); + assert!(encoded_gas > raw_gas_limit.into()); + assert_eq!(encoded_gas, U256::from(444_000_001_001_001u128)); + + let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); + assert_eq!( + Weight::from_parts(encoder.ref_time_mask, encoder.proof_size_mask), + decoded_weight + ); + assert_eq!(encoder.deposit_mask, decoded_deposit); + assert!(decoded_weight.all_gte(weight)); + assert!(decoded_deposit >= deposit); + } + + #[test] + fn test_gas_encoding_with_exact_values() { + let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); + let max_deposit = 1_000_000_000u128; + let encoder = EthGasEncoder::new(max_weight, max_deposit); + + let raw_gas_limit = 444_000_000_000_000u128; + let weight = Weight::from_parts(100_000_000, 100_000_000); + let deposit = 100_000_000u128; + + let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); + assert_eq!(encoded_gas, U256::from(444_000_100_100_100u128)); + + let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); + assert_eq!(weight, decoded_weight); + assert_eq!(deposit, decoded_deposit); + } + + #[test] + fn test_gas_encoding_with_large_values() { + let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); + let max_deposit = 1_000_000_000u128; + let encoder = EthGasEncoder::new(max_weight, max_deposit); + + let raw_gas_limit = 111_111_999_999_999u128; + let weight = Weight::from_parts(222_999_999, 333_999_999); + let deposit = 444_999_999; + let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); + + assert!(encoded_gas > raw_gas_limit.into()); + assert_eq!(encoded_gas, U256::from(111_112_223_334_445u128)); + + let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); + assert_eq!(Weight::from_parts(223_000_000, 334_000_000), decoded_weight); + assert_eq!(445_000_000, decoded_deposit); + assert!(decoded_weight.all_gte(weight)); + assert!(decoded_deposit >= deposit); + } + + #[test] + fn test_gas_encoding_with_zero_values() { + let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); + let max_deposit = 1_000_000_000u128; + let encoder = EthGasEncoder::new(max_weight, max_deposit); + + let raw_gas_limit = 0u128; + let weight = Weight::from_parts(0, 0); + let deposit = 0u128; + + let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); + assert_eq!(encoded_gas, U256::from(encoder.raw_gas_mask)); + + let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); + assert_eq!(Weight::from_parts(0, 0), decoded_weight); + assert_eq!(0u128, decoded_deposit); + } + + #[test] + fn test_encoding_invalid_values() { + let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); + let max_deposit = 1_000_000_000u128; + let encoder = EthGasEncoder::new(max_weight, max_deposit); + + assert_err!( + encoder.encode(U256::MAX, max_weight.add_ref_time(1), 0u128), + GasEncodingError::RawGasLimitOverflow + ); + assert_err!( + encoder.encode(U256::zero(), max_weight.add_ref_time(1), 0u128), + GasEncodingError::RefTimeOverflow + ); + assert_err!( + encoder.encode(U256::zero(), max_weight.add_proof_size(1), 0u128), + GasEncodingError::ProofSizeOverflow + ); + assert_err!( + encoder.encode(U256::zero(), max_weight, max_deposit + 1), + GasEncodingError::DepositOverflow + ); + } +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index a25ea53d276c..26835ceb3b19 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -17,7 +17,7 @@ //! Runtime types for integrating `pallet-revive` with the EVM. use crate::{ evm::api::{GenericTransaction, TransactionSigned}, - AccountIdOf, AddressMapper, BalanceOf, MomentOf, Weight, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, MomentOf, LOG_TARGET, }; use codec::{Decode, Encode}; use frame_support::{ @@ -136,15 +136,8 @@ where fn check(self, lookup: &Lookup) -> Result { if !self.0.is_signed() { if let Ok(call) = self.0.function.clone().try_into() { - if let crate::Call::eth_transact { payload, gas_limit, storage_deposit_limit } = - call - { - let checked = E::try_into_checked_extrinsic( - payload, - gas_limit, - storage_deposit_limit, - self.encoded_size(), - )?; + if let crate::Call::eth_transact { payload } = call { + let checked = E::try_into_checked_extrinsic(payload, self.encoded_size())?; return Ok(checked) }; } @@ -277,8 +270,6 @@ pub trait EthExtra { /// - `encoded_len`: The encoded length of the extrinsic. fn try_into_checked_extrinsic( payload: Vec, - gas_limit: Weight, - storage_deposit_limit: BalanceOf, encoded_len: usize, ) -> Result< CheckedExtrinsic, CallOf, Self::Extension>, @@ -320,6 +311,10 @@ pub trait EthExtra { })?; let data = input.unwrap_or_default().0; + + let gas_encoder = ::EthGasEncoder::get(); + let (gas_limit, storage_deposit_limit) = gas_encoder.decode(gas.unwrap_or_default()); + let call = if let Some(dest) = to { crate::Call::call:: { dest, diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index efc0322b5f6c..268b12e00ade 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,7 +41,7 @@ pub mod test_utils; pub mod weights; use crate::{ - evm::{runtime::GAS_PRICE, GenericTransaction}, + evm::{runtime::GAS_PRICE, EthGasEncoder, GenericTransaction}, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, @@ -308,6 +308,9 @@ pub mod pallet { /// The ratio between the decimal representation of the native token and the ETH token. #[pallet::constant] type NativeToEthRatio: Get; + + #[pallet::no_default] + type EthGasEncoder: Get>>; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -784,12 +787,7 @@ pub mod pallet { #[allow(unused_variables)] #[pallet::call_index(0)] #[pallet::weight(Weight::MAX)] - pub fn eth_transact( - origin: OriginFor, - payload: Vec, - gas_limit: Weight, - #[pallet::compact] storage_deposit_limit: BalanceOf, - ) -> DispatchResultWithPostInfo { + pub fn eth_transact(origin: OriginFor, payload: Vec) -> DispatchResultWithPostInfo { Err(frame_system::Error::CallFiltered::.into()) } @@ -1426,17 +1424,16 @@ where // the encoded length of the gas limit specified in the transaction (tx.gas). // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. // with a maximum of 3 iterations to avoid an infinite loop. + let gas_encoder = T::EthGasEncoder::get(); + for _ in 0..3 { let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { log::debug!(target: LOG_TARGET, "Failed to convert to unsigned"); return Err(EthTransactError::Message("Invalid transaction".into())); }; - let eth_dispatch_call = crate::Call::::eth_transact { - payload: unsigned_tx.dummy_signed_payload(), - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, - }; + let eth_dispatch_call = + crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; let encoded_len = utx_encoded_size(eth_dispatch_call); let fee = pallet_transaction_payment::Pallet::::compute_fee( encoded_len, @@ -1445,6 +1442,9 @@ where ) .into(); let eth_gas: U256 = (fee / GAS_PRICE.into()).into(); + let eth_gas = gas_encoder + .encode(eth_gas, result.gas_required, result.storage_deposit) + .map_err(|_| EthTransactError::Message("Failed to encode gas".into()))?; if eth_gas == result.eth_gas { log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 1df300f031a7..76d55c579553 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -523,6 +523,7 @@ impl Config for Test { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = TestDebug; type ChainId = ChainId; + type EthGasEncoder = (); } impl TryFrom for crate::Call { From ac57f2fc211685d5972526f68744bf29963d32e3 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 11 Dec 2024 13:02:25 -0500 Subject: [PATCH 23/67] rm files added by merge --- .../rpc/examples/js/abi/ErrorTester.json | 106 ------------------ .../rpc/examples/js/abi/errorTester.json | 106 ------------------ .../frame/revive/rpc/examples/js/abi/event.ts | 34 ------ 3 files changed, 246 deletions(-) delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/errorTester.json delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/event.ts diff --git a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json b/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json deleted file mode 100644 index 2d8dccc771e8..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "message", - "type": "string" - } - ], - "name": "CustomError", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "newState", - "type": "bool" - } - ], - "name": "setState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "state", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "triggerAssertError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerCustomError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerDivisionByZero", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerOutOfBoundsError", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerRequireError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerRevertError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "valueMatch", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.json b/substrate/frame/revive/rpc/examples/js/abi/errorTester.json deleted file mode 100644 index 2d8dccc771e8..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/errorTester.json +++ /dev/null @@ -1,106 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "message", - "type": "string" - } - ], - "name": "CustomError", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "newState", - "type": "bool" - } - ], - "name": "setState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "state", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "triggerAssertError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerCustomError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerDivisionByZero", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerOutOfBoundsError", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerRequireError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "triggerRevertError", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "valueMatch", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.ts b/substrate/frame/revive/rpc/examples/js/abi/event.ts deleted file mode 100644 index c389e2daf1da..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/event.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const abi = [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - indexed: false, - internalType: 'string', - name: 'message', - type: 'string', - }, - ], - name: 'ExampleEvent', - type: 'event', - }, - { - inputs: [], - name: 'triggerEvent', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const From cdca27bf2799a110b7c8e9abc3dcd17ae1ebae02 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 11 Dec 2024 13:04:26 -0500 Subject: [PATCH 24/67] add back files --- .../rpc/examples/js/abi/ErrorTester.json | 106 ++++++++++++++++++ .../frame/revive/rpc/examples/js/bun.lockb | Bin 33662 -> 40649 bytes .../frame/revive/rpc/examples/js/package.json | 4 +- .../rpc/examples/js/pvm/ErrorTester.polkavm | Bin 0 -> 8919 bytes 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm diff --git a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json b/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json new file mode 100644 index 000000000000..2d8dccc771e8 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json @@ -0,0 +1,106 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "CustomError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "newState", + "type": "bool" + } + ], + "name": "setState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "state", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "triggerAssertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerCustomError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerDivisionByZero", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerOutOfBoundsError", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRequireError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRevertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "valueMatch", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 0ff3d54157db21a636e30076634343a24c812dca..67df5841e43fba141c7a146a1e4a8958b4c7a84c 100755 GIT binary patch delta 9749 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 delta 5458 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 diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 6d8d00fd4214..0119f4f34a17 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -9,10 +9,10 @@ "preview": "vite preview" }, "dependencies": { + "@parity/revive": "^0.0.5", "ethers": "^6.13.4", "solc": "^0.8.28", - "viem": "^2.21.47", - "@parity/revive": "^0.0.5" + "viem": "^2.21.47" }, "devDependencies": { "prettier": "^3.3.3", diff --git a/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..ffdbbe2f9c4b52b7a37a159bc88e2bdac706c561 GIT binary patch literal 8919 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 V%})b=$2aO0=DrCNDJ{O0`#(LIA`$=q literal 0 HcmV?d00001 From 6126baef1d9413981986acaa458a3506c120d6cf Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 11 Dec 2024 15:50:19 -0500 Subject: [PATCH 25/67] fix compilation --- substrate/bin/node/runtime/src/lib.rs | 10 +++++++++- substrate/frame/revive/src/evm/gas_encoder.rs | 4 ++-- substrate/frame/revive/src/evm/runtime.rs | 14 +++----------- substrate/frame/revive/src/lib.rs | 3 ++- substrate/frame/revive/src/tests.rs | 10 +++++++--- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faffcd23fbcf..02a248aca11a 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -84,7 +84,7 @@ use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_nfts::PalletFeatures; use pallet_nis::WithMaximumOf; use pallet_nomination_pools::PoolId; -use pallet_revive::{evm::runtime::EthExtra, AddressMapper}; +use pallet_revive::{evm::runtime::EthExtra, AddressMapper, EthGasEncoder}; use pallet_session::historical as pallet_session_historical; use sp_core::U256; // Can't use `FungibleAdapter` here until Treasury pallet migrates to fungibles @@ -1445,6 +1445,13 @@ impl pallet_contracts::Config for Runtime { type Xcm = (); } +parameter_types! { + pub GasEncoder: EthGasEncoder = EthGasEncoder::::new( + RuntimeBlockWeights::get().max_block, + deposit(0, 5 * 1024 * 1024) + ); +} + impl pallet_revive::Config for Runtime { type Time = Timestamp; type Currency = Balances; @@ -1468,6 +1475,7 @@ impl pallet_revive::Config for Runtime { type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = GasEncoder; } impl pallet_sudo::Config for Runtime { diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index dca14888e7a1..ee73a02ef4b5 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -1,5 +1,4 @@ -use std::ops::{Div, Rem}; - +use core::ops::{Div, Rem}; use frame_support::ensure; use sp_arithmetic::traits::{AtLeast32BitUnsigned, One, Zero}; use sp_core::U256; @@ -14,6 +13,7 @@ use sp_weights::Weight; /// - `g...g`: Gas limit, encoded in the highest digits. /// /// Each component is scaled using the `SCALE` factor. +#[derive(Debug, Clone)] pub struct EthGasEncoder { /// Encodes the raw gas limit. Rounded to the nearest non-zero multiple of this value. raw_gas_mask: u128, diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index aca348833e64..a81d0ea9cb41 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -410,6 +410,7 @@ mod test { evm::*, test_utils::*, tests::{ExtBuilder, RuntimeCall, RuntimeOrigin, Test}, + Weight, }; use frame_support::{error::LookupError, traits::fungible::Mutate}; use pallet_revive_fixtures::compile_module; @@ -521,12 +522,7 @@ mod test { /// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension. fn check(&self) -> Result<(RuntimeCall, SignedExtra), TransactionValidityError> { ExtBuilder::default().build().execute_with(|| { - let UncheckedExtrinsicBuilder { - tx, - gas_limit, - storage_deposit_limit, - before_validate, - } = self.clone(); + let UncheckedExtrinsicBuilder { tx, before_validate, .. } = self.clone(); // Fund the account. let account = Account::default(); @@ -537,11 +533,7 @@ mod test { let payload = account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload(); - let call = RuntimeCall::Contracts(crate::Call::eth_transact { - payload, - gas_limit, - storage_deposit_limit, - }); + let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload }); let encoded_len = call.encoded_size(); let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b25fdbcff56b..6cd3cc1c77b9 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,7 +41,7 @@ pub mod test_utils; pub mod weights; use crate::{ - evm::{runtime::GAS_PRICE, EthGasEncoder, GenericTransaction}, + evm::{runtime::GAS_PRICE, GenericTransaction}, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, @@ -81,6 +81,7 @@ use sp_runtime::{ pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, debug::Tracing, + evm::EthGasEncoder, exec::MomentOf, pallet::*, }; diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 67ee25e621c4..ba165802846d 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -39,8 +39,8 @@ use crate::{ wasm::Memory, weights::WeightInfo, AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError, - HoldReason, Origin, Pallet, PristineCode, H160, + ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthGasEncoder, + EthTransactError, HoldReason, Origin, Pallet, PristineCode, H160, }; use crate::test_utils::builder::Contract; @@ -506,6 +506,10 @@ where } parameter_types! { pub static UnstableInterface: bool = true; + pub static GasEncoder: EthGasEncoder = EthGasEncoder::new( + Weight::from_parts(1_000_000_000, 1_000_000_000), + 1_000_000_000u64 + ); } #[derive_impl(crate::config_preludes::TestDefaultConfig)] @@ -524,7 +528,7 @@ impl Config for Test { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = TestDebug; type ChainId = ChainId; - type EthGasEncoder = (); + type EthGasEncoder = GasEncoder; } impl TryFrom for crate::Call { From c4f6ea24e891c071a4550135d11cbb922b04e3dc Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 11 Dec 2024 16:07:58 -0500 Subject: [PATCH 26/67] fixes --- substrate/frame/revive/rpc/src/client.rs | 4 ++-- substrate/frame/revive/src/evm/runtime.rs | 25 ++++++++++++++++++----- substrate/frame/revive/src/lib.rs | 7 +++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 901c15e9756b..de97844eccbb 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -17,7 +17,7 @@ //! The client connects to the source substrate chain //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ - runtime::GAS_PRICE, + runtime::gas_from_fee, subxt_client::{ revive::{calls::types::EthTransact, events::ContractEmitted}, runtime_types::pallet_revive::storage::ContractInfo, @@ -771,7 +771,7 @@ impl Client { pub async fn evm_block(&self, block: Arc) -> Result { let runtime_api = self.inner.api.runtime_api().at(block.hash()); let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; - let gas_limit = U256::from(max_fee / GAS_PRICE as u128); + let gas_limit = gas_from_fee(max_fee); let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index a81d0ea9cb41..c9e8f98c628a 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -19,6 +19,7 @@ use crate::{ evm::api::{GenericTransaction, TransactionSigned}, AccountIdOf, AddressMapper, BalanceOf, MomentOf, LOG_TARGET, }; +use alloc::vec::Vec; use codec::{Decode, Encode}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, @@ -31,15 +32,13 @@ use sp_core::{Get, H256, U256}; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, traits::{ - self, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, IdentifyAccount, Member, - TransactionExtension, + self, AtLeast32BitUnsigned, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, + IdentifyAccount, Member, TransactionExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, RuntimeDebug, Saturating, }; -use alloc::vec::Vec; - type CallOf = ::RuntimeCall; /// The EVM gas price. @@ -48,7 +47,23 @@ type CallOf = ::RuntimeCall; /// We use a fixed value for the gas price. /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. -pub const GAS_PRICE: u32 = 1u32; +pub const GAS_PRICE: u32 = 1_000_000_000u32; + +/// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. +/// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. +pub fn gas_from_fee(fee: Balance) -> U256 +where + u32: Into, + Balance: Into + AtLeast32BitUnsigned + Copy, +{ + let gas_price = GAS_PRICE.into(); + let remainder = fee % gas_price; + if remainder.is_zero() { + (fee / gas_price).into() + } else { + (fee.saturating_add(gas_price) / gas_price).into() + } +} /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 6cd3cc1c77b9..c55e87bb3954 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,7 +41,10 @@ pub mod test_utils; pub mod weights; use crate::{ - evm::{runtime::GAS_PRICE, GenericTransaction}, + evm::{ + runtime::{gas_from_fee, GAS_PRICE}, + GenericTransaction, + }, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, @@ -1439,7 +1442,7 @@ where 0u32.into(), ) .into(); - let eth_gas: U256 = (fee / GAS_PRICE.into()).into(); + let eth_gas = gas_from_fee(fee); let eth_gas = gas_encoder .encode(eth_gas, result.gas_required, result.storage_deposit) .map_err(|_| EthTransactError::Message("Failed to encode gas".into()))?; From 2c4fa2b6f873624a977ee8ad87a1a5a509e1b453 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 11 Dec 2024 16:11:59 -0500 Subject: [PATCH 27/67] fix piggy-bank example --- .../revive/rpc/examples/js/src/piggy-bank.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts index 0040b0c78dc4..8289ac8b76e3 100644 --- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts +++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts @@ -1,9 +1,9 @@ import { assert, getByteCode, walletClient } from './lib.ts' -import { abi } from '../abi/piggyBank.ts' +import { PiggyBankAbi } from '../abi/piggyBank.ts' import { parseEther } from 'viem' const hash = await walletClient.deployContract({ - abi, + abi: PiggyBankAbi, bytecode: getByteCode('piggyBank'), }) const deployReceipt = await walletClient.waitForTransactionReceipt({ hash }) @@ -16,7 +16,7 @@ assert(contractAddress, 'Contract address should be set') const result = await walletClient.estimateContractGas({ account: walletClient.account, address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'deposit', value: parseEther('10'), }) @@ -26,7 +26,7 @@ assert(contractAddress, 'Contract address should be set') const { request } = await walletClient.simulateContract({ account: walletClient.account, address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'deposit', value: parseEther('10'), }) @@ -36,9 +36,6 @@ assert(contractAddress, 'Contract address should be set') const receipt = await walletClient.waitForTransactionReceipt({ hash }) console.log(`Deposit receipt: ${receipt.status}`) - if (process.env.STOP) { - process.exit(0) - } } // Withdraw 5 WST @@ -46,7 +43,7 @@ assert(contractAddress, 'Contract address should be set') const { request } = await walletClient.simulateContract({ account: walletClient.account, address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'withdraw', args: [parseEther('5')], }) @@ -58,7 +55,7 @@ assert(contractAddress, 'Contract address should be set') // Check remaining balance const balance = await walletClient.readContract({ address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'getDeposit', }) From 1cade87f7d1ea94a18f51a1eadd311dd9d985fc7 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 11 Dec 2024 16:17:42 -0500 Subject: [PATCH 28/67] update subxt codegen --- .../frame/revive/rpc/revive_chain.metadata | Bin 659977 -> 659916 bytes substrate/frame/revive/rpc/src/lib.rs | 23 ++---------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 64b1f2014dd06815fcea6a87bc96306eb00eda8b..903731a245d25b8c0a3cf5cb6a11958e85545556 100644 GIT binary patch delta 61 zcmeBNqH$)iMnel@3sVbo3(FQ()+NF$j4}m@l{xu|DHe<}VX4VBr55d>OIU%J4T#x+ Mm}7hB63#8T0D;IAR{#J2 delta 66 zcmX@JSfg`^Mnel@3sVbo3(FQ()+NF`j4}m@l{xu|DHe<}VX4VBr54j2R RpcResult { let hash = H256(keccak_256(&transaction.0)); - - let tx = TransactionSigned::decode(&transaction.0).map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}"); - EthRpcError::from(err) - })?; - - let eth_addr = tx.recover_eth_address().map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to recover eth address: {err:?}"); - EthRpcError::InvalidSignature - })?; - - let tx = GenericTransaction::from_signed(tx, Some(eth_addr)); - - // Dry run the transaction to get the weight limit and storage deposit limit - let dry_run = self.client.dry_run(tx, BlockTag::Latest.into()).await?; - - let call = subxt_client::tx().revive().eth_transact( - transaction.0, - dry_run.gas_required.into(), - dry_run.storage_deposit, - ); + let call = subxt_client::tx().revive().eth_transact(transaction.0); self.client.submit(call).await.map_err(|err| { log::debug!(target: LOG_TARGET, "submit call failed: {err:?}"); err })?; + log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}"); Ok(hash) } From 4b0d975af0ae547df0f4787653bc052f3bc06737 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 12 Dec 2024 13:37:07 -0500 Subject: [PATCH 29/67] update Bytes Debug impl --- substrate/frame/revive/src/evm/api/byte.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index df4ed1740ecd..c2d64f8e5e42 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -116,7 +116,10 @@ macro_rules! impl_hex { impl Debug for $type { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, concat!(stringify!($type), "({})"), self.0.to_hex()) + let hex_str = self.0.to_hex(); + let truncated = &hex_str[..hex_str.len().min(100)]; + let ellipsis = if hex_str.len() > 100 { "..." } else { "" }; + write!(f, concat!(stringify!($type), "({}{})"), truncated,ellipsis) } } From ef6eeabf7f0b1e98454e667a2505c29cad743f4d Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 12 Dec 2024 13:38:43 -0500 Subject: [PATCH 30/67] remove eth_gas % check --- substrate/frame/revive/src/evm/runtime.rs | 13 +------------ substrate/frame/revive/src/lib.rs | 5 ++++- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index c9e8f98c628a..93d3a29740cb 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -27,7 +27,6 @@ use frame_support::{ }; use pallet_transaction_payment::OnChargeTransaction; use scale_info::{StaticTypeInfo, TypeInfo}; -use sp_arithmetic::Percent; use sp_core::{Get, H256, U256}; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, @@ -390,7 +389,7 @@ pub trait EthExtra { Default::default(), ) .into(); - log::trace!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); // The fees from the Ethereum transaction should be greater or equal to the actual fees paid // by the account. @@ -399,16 +398,6 @@ pub trait EthExtra { return Err(InvalidTransaction::Payment.into()) } - let min = actual_fee.min(eth_fee_no_tip); - let max = actual_fee.max(eth_fee_no_tip); - let diff = Percent::from_rational(max - min, min); - if diff > Percent::from_percent(10) { - log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}"); - return Err(InvalidTransaction::Call.into()) - } else { - log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}: {diff:?}"); - } - let tip = eth_fee.saturating_sub(eth_fee_no_tip); log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce {nonce:?} and tip: {tip:?}"); Ok(CheckedExtrinsic { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index c55e87bb3954..ea853303aaee 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1445,7 +1445,10 @@ where let eth_gas = gas_from_fee(fee); let eth_gas = gas_encoder .encode(eth_gas, result.gas_required, result.storage_deposit) - .map_err(|_| EthTransactError::Message("Failed to encode gas".into()))?; + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to encode gas {err:?}"); + EthTransactError::Message("Failed to encode gas".into()) + })?; if eth_gas == result.eth_gas { log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); From 993376384aae1c590c53f0570991d5b427260f2a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 12 Dec 2024 13:40:36 -0500 Subject: [PATCH 31/67] add gas_encoder log statement --- substrate/frame/revive/src/evm/gas_encoder.rs | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index ee73a02ef4b5..e663cbed1acd 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -56,7 +56,7 @@ where impl EthGasEncoder where - Balance: Copy + AtLeast32BitUnsigned + TryFrom + Into, + Balance: Copy + AtLeast32BitUnsigned + TryFrom + Into + Debug, { /// Returns the maximum reference time that can be encoded. fn max_ref_time(&self) -> u64 { @@ -80,6 +80,10 @@ where let deposit_mask = max_deposit_limit / (SCALE as u32).into(); let raw_gas_mask = SCALE.pow(3) as _; + log::debug!( + target: LOG_TARGET, + "Creating gas encoder: ref_time_mask={ref_time_mask:?}, proof_size_mask={proof_size_mask:?}, deposit_mask={deposit_mask:?}"); + Self { raw_gas_mask, ref_time_mask, proof_size_mask, deposit_mask } } @@ -110,10 +114,13 @@ where let ref_time_component = round_up(ref_time, self.ref_time_mask); let proof_size_component = round_up(proof_size, self.proof_size_mask); - Ok(U256::from(raw_gas_component) + let encoded = U256::from(raw_gas_component) .saturating_add(deposit_component.into()) .saturating_add(U256::from(SCALE) * U256::from(proof_size_component)) - .saturating_add(U256::from(SCALE.pow(2)) * U256::from(ref_time_component))) + .saturating_add(U256::from(SCALE.pow(2)) * U256::from(ref_time_component)); + + log::debug!(target: LOG_TARGET, "Encoding: gas_limit={gas_limit:?}, weight={weight:?}, deposit={deposit:?} in {encoded:?}"); + Ok(encoded) } /// Decodes the weight and deposit from the encoded gas value. @@ -126,13 +133,14 @@ where let proof_time: u64 = ((gas_without_deposit / SCALE) % SCALE).as_u64(); let ref_time: u64 = ((gas_without_deposit / SCALE.pow(2)) % SCALE).as_u64(); - ( - Weight::from_parts( - ref_time.saturating_mul(self.proof_size_mask), - proof_time.saturating_mul(self.ref_time_mask), - ), - deposit.saturating_mul(self.deposit_mask), - ) + let weight = Weight::from_parts( + ref_time.saturating_mul(self.proof_size_mask), + proof_time.saturating_mul(self.ref_time_mask), + ); + let deposit = deposit.saturating_mul(self.deposit_mask); + + log::debug!(target: LOG_TARGET, "Decoding: gas: {gas:?} into weight={weight:?}, deposit={deposit:?}"); + (weight, deposit) } } From e55d3f4e5a2d564247ab8521e2d311770bd991aa Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 17 Dec 2024 17:08:45 -0500 Subject: [PATCH 32/67] use log2 to encode gas component --- substrate/bin/node/runtime/src/lib.rs | 10 +- substrate/frame/revive/src/evm.rs | 4 +- substrate/frame/revive/src/evm/gas_encoder.rs | 297 +++++------------- substrate/frame/revive/src/evm/runtime.rs | 14 +- substrate/frame/revive/src/lib.rs | 19 +- substrate/frame/revive/src/tests.rs | 9 +- 6 files changed, 108 insertions(+), 245 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 02a248aca11a..faffcd23fbcf 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -84,7 +84,7 @@ use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_nfts::PalletFeatures; use pallet_nis::WithMaximumOf; use pallet_nomination_pools::PoolId; -use pallet_revive::{evm::runtime::EthExtra, AddressMapper, EthGasEncoder}; +use pallet_revive::{evm::runtime::EthExtra, AddressMapper}; use pallet_session::historical as pallet_session_historical; use sp_core::U256; // Can't use `FungibleAdapter` here until Treasury pallet migrates to fungibles @@ -1445,13 +1445,6 @@ impl pallet_contracts::Config for Runtime { type Xcm = (); } -parameter_types! { - pub GasEncoder: EthGasEncoder = EthGasEncoder::::new( - RuntimeBlockWeights::get().max_block, - deposit(0, 5 * 1024 * 1024) - ); -} - impl pallet_revive::Config for Runtime { type Time = Timestamp; type Currency = Balances; @@ -1475,7 +1468,6 @@ impl pallet_revive::Config for Runtime { type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. - type EthGasEncoder = GasEncoder; } impl pallet_sudo::Config for Runtime { diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index 713e16376ea7..2bb66bfafd0a 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,7 +19,5 @@ mod api; pub use api::*; +pub mod gas_encoder; pub mod runtime; - -mod gas_encoder; -pub use gas_encoder::*; diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index e663cbed1acd..6f1b027419f5 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -1,45 +1,30 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! Encodes/Decodes EVM gas values. + +use crate::{BalanceOf, Config}; use core::ops::{Div, Rem}; -use frame_support::ensure; -use sp_arithmetic::traits::{AtLeast32BitUnsigned, One, Zero}; +use frame_support::pallet_prelude::CheckedShl; +use sp_arithmetic::traits::{One, Zero}; use sp_core::U256; use sp_weights::Weight; -/// Encodes gas values for use in the EVM. -/// -/// The encoding follows the pattern `g...grrrpppddd`, where: -/// - `ddd`: Deposit value, encoded in the lowest 3 digits. -/// - `ppp`: Proof size, encoded in the next 3 digits. -/// - `rrr`: Reference time, encoded in the next 3 digits. -/// - `g...g`: Gas limit, encoded in the highest digits. -/// -/// Each component is scaled using the `SCALE` factor. -#[derive(Debug, Clone)] -pub struct EthGasEncoder { - /// Encodes the raw gas limit. Rounded to the nearest non-zero multiple of this value. - raw_gas_mask: u128, - /// Encodes the weight reference time. - ref_time_mask: u64, - /// Encodes the weight proof size. - proof_size_mask: u64, - /// Encodes the deposit limit. - deposit_mask: Balance, -} - -/// Errors that can occur during encoding. -#[derive(Debug, PartialEq, Eq)] -pub enum GasEncodingError { - /// Reference time exceeds the allowed limit. - RefTimeOverflow, - /// Proof size exceeds the allowed limit. - ProofSizeOverflow, - /// Deposit exceeds the allowed limit. - DepositOverflow, - /// Raw gas limit exceeds the allowed limit. - RawGasLimitOverflow, -} - // We use 3 digits to store each component. -const SCALE: u64 = 1_000; +const SCALE: u128 = 100; /// Rounds up the given value to the nearest multiple of the mask. /// @@ -54,203 +39,95 @@ where value / mask + rest } -impl EthGasEncoder +/// Rounds up the log2 of the given value to the nearest integer. +fn log2_round_up(val: T) -> u128 where - Balance: Copy + AtLeast32BitUnsigned + TryFrom + Into + Debug, + T: Into, { - /// Returns the maximum reference time that can be encoded. - fn max_ref_time(&self) -> u64 { - self.ref_time_mask * SCALE - } - - /// Returns the maximum proof size that can be encoded. - fn max_proof_size(&self) -> u64 { - self.proof_size_mask * SCALE - } - - /// Returns the maximum deposit that can be encoded. - fn max_deposit(&self) -> Balance { - self.deposit_mask * (SCALE as u32).into() - } - - /// Creates a new encoder with the given maximum weight and deposit limit. - pub fn new(max_weight: Weight, max_deposit_limit: Balance) -> Self { - let ref_time_mask = max_weight.ref_time() / SCALE; - let proof_size_mask = max_weight.proof_size() / SCALE; - let deposit_mask = max_deposit_limit / (SCALE as u32).into(); - let raw_gas_mask = SCALE.pow(3) as _; - - log::debug!( - target: LOG_TARGET, - "Creating gas encoder: ref_time_mask={ref_time_mask:?}, proof_size_mask={proof_size_mask:?}, deposit_mask={deposit_mask:?}"); - - Self { raw_gas_mask, ref_time_mask, proof_size_mask, deposit_mask } - } - - /// Encodes all components (deposit limit, weight reference time, and proof size) into a single - /// gas value. - pub fn encode( - &self, - gas_limit: U256, - weight: Weight, - deposit: Balance, - ) -> Result { - let gas_limit: u128 = - gas_limit.try_into().map_err(|_| GasEncodingError::RawGasLimitOverflow)?; - let ref_time = weight.ref_time(); - let proof_size = weight.proof_size(); - - ensure!(ref_time <= self.max_ref_time(), GasEncodingError::RefTimeOverflow); - ensure!(proof_size <= self.max_proof_size(), GasEncodingError::ProofSizeOverflow); - ensure!(deposit <= self.max_deposit(), GasEncodingError::DepositOverflow); - - let raw_gas_component = if gas_limit < self.raw_gas_mask { - self.raw_gas_mask - } else { - round_up(gas_limit, self.raw_gas_mask).saturating_mul(self.raw_gas_mask) - }; - - let deposit_component = round_up(deposit, self.deposit_mask); - let ref_time_component = round_up(ref_time, self.ref_time_mask); - let proof_size_component = round_up(proof_size, self.proof_size_mask); - - let encoded = U256::from(raw_gas_component) - .saturating_add(deposit_component.into()) - .saturating_add(U256::from(SCALE) * U256::from(proof_size_component)) - .saturating_add(U256::from(SCALE.pow(2)) * U256::from(ref_time_component)); + let val = val.into(); + val.checked_ilog2() + .map(|v| if 1u128 << v == val { v } else { v + 1 }) + .unwrap_or(0) as u128 +} - log::debug!(target: LOG_TARGET, "Encoding: gas_limit={gas_limit:?}, weight={weight:?}, deposit={deposit:?} in {encoded:?}"); - Ok(encoded) - } +/// Encodes all components (deposit limit, weight reference time, and proof size) into a single +/// gas value. +/// +/// The encoding follows the pattern `g...grrppdd`, where: +/// - `dd`: log2 Deposit value, encoded in the lowest 2 digits. +/// - `pp`: log2 Proof size, encoded in the next 2 digits. +/// - `rr`: log2 Reference time, encoded in the next 2 digits. +/// - `g...g`: Gas limit, encoded in the highest digits. +/// +/// # Note +/// - Encoding fails if the deposit is not convertible to `u128` +/// - The deposit value is maxed by 2^99 +pub fn encode(gas_limit: U256, weight: Weight, deposit: BalanceOf) -> Option { + let raw_gas_mask = U256::from(SCALE).pow(3.into()); + let raw_gas_component = if gas_limit < raw_gas_mask { + raw_gas_mask + } else { + round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) + }; + + let deposit: u128 = deposit.try_into().ok()?; + let deposit_component = log2_round_up(deposit); + + let ref_time = weight.ref_time(); + let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); + + let proof_size = weight.proof_size(); + let proof_size_component = SCALE * log2_round_up(proof_size); + + let components = deposit_component + proof_size_component + ref_time_component; + let encoded = raw_gas_component.saturating_add(components.into()); + + encoded.into() +} - /// Decodes the weight and deposit from the encoded gas value. - pub fn decode(&self, gas: U256) -> (Weight, Balance) { - let deposit = gas % SCALE; - let gas_without_deposit = gas - deposit; +/// Decodes the weight and deposit from the encoded gas value. +/// Returns `None` if the gas value is invalid +pub fn decode(gas: U256) -> Option<(Weight, BalanceOf)> { + let deposit = gas % SCALE; + let gas_without_deposit = gas - deposit; - // Casting with as_* is safe since all values are maxed by `SCALE`. - let deposit: Balance = deposit.as_u32().into(); - let proof_time: u64 = ((gas_without_deposit / SCALE) % SCALE).as_u64(); - let ref_time: u64 = ((gas_without_deposit / SCALE.pow(2)) % SCALE).as_u64(); + // Casting with as_u32 is safe since all values are maxed by `SCALE`. + let deposit = deposit.as_u32(); + let proof_time = ((gas_without_deposit / SCALE) % SCALE).as_u32(); + let ref_time = ((gas_without_deposit / SCALE.pow(2)) % SCALE).as_u32(); - let weight = Weight::from_parts( - ref_time.saturating_mul(self.proof_size_mask), - proof_time.saturating_mul(self.ref_time_mask), - ); - let deposit = deposit.saturating_mul(self.deposit_mask); + let weight = Weight::from_parts(1u64.checked_shl(ref_time)?, 1u64.checked_shl(proof_time)?); + let deposit = BalanceOf::::one().checked_shl(deposit)?; - log::debug!(target: LOG_TARGET, "Decoding: gas: {gas:?} into weight={weight:?}, deposit={deposit:?}"); - (weight, deposit) - } + Some((weight, deposit)) } #[cfg(test)] mod test { use super::*; - use frame_support::assert_err; - - #[test] - fn test_gas_encoding_with_small_values() { - let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); - let max_deposit = 1_000_000_000u128; - let encoder = EthGasEncoder::new(max_weight, max_deposit); - - let raw_gas_limit = 444_000_000_000_000u128; - let weight = Weight::from_parts(66_000, 44_000); - let deposit = 22_000u128; - - let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); - assert!(encoded_gas > raw_gas_limit.into()); - assert_eq!(encoded_gas, U256::from(444_000_001_001_001u128)); - - let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); - assert_eq!( - Weight::from_parts(encoder.ref_time_mask, encoder.proof_size_mask), - decoded_weight - ); - assert_eq!(encoder.deposit_mask, decoded_deposit); - assert!(decoded_weight.all_gte(weight)); - assert!(decoded_deposit >= deposit); - } - - #[test] - fn test_gas_encoding_with_exact_values() { - let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); - let max_deposit = 1_000_000_000u128; - let encoder = EthGasEncoder::new(max_weight, max_deposit); - - let raw_gas_limit = 444_000_000_000_000u128; - let weight = Weight::from_parts(100_000_000, 100_000_000); - let deposit = 100_000_000u128; - - let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); - assert_eq!(encoded_gas, U256::from(444_000_100_100_100u128)); - - let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); - assert_eq!(weight, decoded_weight); - assert_eq!(deposit, decoded_deposit); - } + use crate::tests::Test; #[test] - fn test_gas_encoding_with_large_values() { - let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); - let max_deposit = 1_000_000_000u128; - let encoder = EthGasEncoder::new(max_weight, max_deposit); - + fn test_gas_encoding_decoding_works() { let raw_gas_limit = 111_111_999_999_999u128; let weight = Weight::from_parts(222_999_999, 333_999_999); let deposit = 444_999_999; - let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); + let encoded_gas = encode::(raw_gas_limit.into(), weight, deposit).unwrap(); + assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128)); assert!(encoded_gas > raw_gas_limit.into()); - assert_eq!(encoded_gas, U256::from(111_112_223_334_445u128)); - let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); - assert_eq!(Weight::from_parts(223_000_000, 334_000_000), decoded_weight); - assert_eq!(445_000_000, decoded_deposit); + let (decoded_weight, decoded_deposit) = decode::(encoded_gas).unwrap(); assert!(decoded_weight.all_gte(weight)); - assert!(decoded_deposit >= deposit); - } + assert!(weight.mul(2).all_gte(weight)); - #[test] - fn test_gas_encoding_with_zero_values() { - let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); - let max_deposit = 1_000_000_000u128; - let encoder = EthGasEncoder::new(max_weight, max_deposit); - - let raw_gas_limit = 0u128; - let weight = Weight::from_parts(0, 0); - let deposit = 0u128; - - let encoded_gas = encoder.encode(raw_gas_limit.into(), weight, deposit).unwrap(); - assert_eq!(encoded_gas, U256::from(encoder.raw_gas_mask)); - - let (decoded_weight, decoded_deposit) = encoder.decode(encoded_gas); - assert_eq!(Weight::from_parts(0, 0), decoded_weight); - assert_eq!(0u128, decoded_deposit); + assert!(decoded_deposit >= deposit); + assert!(deposit * 2 >= decoded_deposit); } #[test] - fn test_encoding_invalid_values() { - let max_weight = Weight::from_parts(1_000_000_000, 1_000_000_000); - let max_deposit = 1_000_000_000u128; - let encoder = EthGasEncoder::new(max_weight, max_deposit); - - assert_err!( - encoder.encode(U256::MAX, max_weight.add_ref_time(1), 0u128), - GasEncodingError::RawGasLimitOverflow - ); - assert_err!( - encoder.encode(U256::zero(), max_weight.add_ref_time(1), 0u128), - GasEncodingError::RefTimeOverflow - ); - assert_err!( - encoder.encode(U256::zero(), max_weight.add_proof_size(1), 0u128), - GasEncodingError::ProofSizeOverflow - ); - assert_err!( - encoder.encode(U256::zero(), max_weight, max_deposit + 1), - GasEncodingError::DepositOverflow - ); + fn test_overflow() { + assert_eq!(None, decode::(65_00u128.into()), "Invalid proof size"); + assert_eq!(None, decode::(65_00_00u128.into()), "Invalid ref_time"); } } diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 93d3a29740cb..820db49251f0 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -16,7 +16,10 @@ // limitations under the License. //! Runtime types for integrating `pallet-revive` with the EVM. use crate::{ - evm::api::{GenericTransaction, TransactionSigned}, + evm::{ + api::{GenericTransaction, TransactionSigned}, + gas_encoder, + }, AccountIdOf, AddressMapper, BalanceOf, MomentOf, LOG_TARGET, }; use alloc::vec::Vec; @@ -46,7 +49,7 @@ type CallOf = ::RuntimeCall; /// We use a fixed value for the gas price. /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. -pub const GAS_PRICE: u32 = 1_000_000_000u32; +pub const GAS_PRICE: u32 = 1_000u32; /// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. /// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. @@ -330,8 +333,11 @@ pub trait EthExtra { let data = input.unwrap_or_default().0; - let gas_encoder = ::EthGasEncoder::get(); - let (gas_limit, storage_deposit_limit) = gas_encoder.decode(gas.unwrap_or_default()); + let (gas_limit, storage_deposit_limit) = + gas_encoder::decode::(gas.unwrap_or_default()).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); + InvalidTransaction::Call + })?; let call = if let Some(dest) = to { crate::Call::call:: { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index ea853303aaee..34df2e7e5b38 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -42,6 +42,7 @@ pub mod weights; use crate::{ evm::{ + gas_encoder, runtime::{gas_from_fee, GAS_PRICE}, GenericTransaction, }, @@ -84,7 +85,6 @@ use sp_runtime::{ pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, debug::Tracing, - evm::EthGasEncoder, exec::MomentOf, pallet::*, }; @@ -312,9 +312,6 @@ pub mod pallet { /// The ratio between the decimal representation of the native token and the ETH token. #[pallet::constant] type NativeToEthRatio: Get; - - #[pallet::no_default] - type EthGasEncoder: Get>>; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -1425,8 +1422,6 @@ where // the encoded length of the gas limit specified in the transaction (tx.gas). // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. // with a maximum of 3 iterations to avoid an infinite loop. - let gas_encoder = T::EthGasEncoder::get(); - for _ in 0..3 { let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { log::debug!(target: LOG_TARGET, "Failed to convert to unsigned"); @@ -1443,12 +1438,12 @@ where ) .into(); let eth_gas = gas_from_fee(fee); - let eth_gas = gas_encoder - .encode(eth_gas, result.gas_required, result.storage_deposit) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to encode gas {err:?}"); - EthTransactError::Message("Failed to encode gas".into()) - })?; + let eth_gas = + gas_encoder::encode::(eth_gas, result.gas_required, result.storage_deposit) + .ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Failed to encode gas {eth_gas:?} {result:?}"); + EthTransactError::Message("Failed to encode gas".into()) + })?; if eth_gas == result.eth_gas { log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index ba165802846d..58d4721b4e53 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -39,8 +39,8 @@ use crate::{ wasm::Memory, weights::WeightInfo, AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthGasEncoder, - EthTransactError, HoldReason, Origin, Pallet, PristineCode, H160, + ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError, + HoldReason, Origin, Pallet, PristineCode, H160, }; use crate::test_utils::builder::Contract; @@ -506,10 +506,6 @@ where } parameter_types! { pub static UnstableInterface: bool = true; - pub static GasEncoder: EthGasEncoder = EthGasEncoder::new( - Weight::from_parts(1_000_000_000, 1_000_000_000), - 1_000_000_000u64 - ); } #[derive_impl(crate::config_preludes::TestDefaultConfig)] @@ -528,7 +524,6 @@ impl Config for Test { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = TestDebug; type ChainId = ChainId; - type EthGasEncoder = GasEncoder; } impl TryFrom for crate::Call { From 33db56eca226b64d4bf273667b0597085e4afd8b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 18 Dec 2024 11:38:49 -0500 Subject: [PATCH 33/67] Update --- substrate/frame/revive/src/evm/gas_encoder.rs | 42 +++++++++++++------ substrate/frame/revive/src/evm/runtime.rs | 37 ++++++++-------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index 6f1b027419f5..01d5fcbbe113 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -63,13 +63,6 @@ where /// - Encoding fails if the deposit is not convertible to `u128` /// - The deposit value is maxed by 2^99 pub fn encode(gas_limit: U256, weight: Weight, deposit: BalanceOf) -> Option { - let raw_gas_mask = U256::from(SCALE).pow(3.into()); - let raw_gas_component = if gas_limit < raw_gas_mask { - raw_gas_mask - } else { - round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) - }; - let deposit: u128 = deposit.try_into().ok()?; let deposit_component = log2_round_up(deposit); @@ -79,10 +72,16 @@ pub fn encode(gas_limit: U256, weight: Weight, deposit: BalanceOf) let proof_size = weight.proof_size(); let proof_size_component = SCALE * log2_round_up(proof_size); - let components = deposit_component + proof_size_component + ref_time_component; - let encoded = raw_gas_component.saturating_add(components.into()); + let components = U256::from(deposit_component + proof_size_component + ref_time_component); + + let raw_gas_mask = U256::from(SCALE).pow(3.into()); + let raw_gas_component = if gas_limit < raw_gas_mask.saturating_add(components) { + raw_gas_mask + } else { + round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) + }; - encoded.into() + Some(components.saturating_add(raw_gas_component)) } /// Decodes the weight and deposit from the encoded gas value. @@ -96,8 +95,15 @@ pub fn decode(gas: U256) -> Option<(Weight, BalanceOf)> { let proof_time = ((gas_without_deposit / SCALE) % SCALE).as_u32(); let ref_time = ((gas_without_deposit / SCALE.pow(2)) % SCALE).as_u32(); - let weight = Weight::from_parts(1u64.checked_shl(ref_time)?, 1u64.checked_shl(proof_time)?); - let deposit = BalanceOf::::one().checked_shl(deposit)?; + let weight = Weight::from_parts( + if ref_time == 0 { 0 } else { 1u64.checked_shl(ref_time)? }, + if proof_time == 0 { 0 } else { 1u64.checked_shl(proof_time)? }, + ); + let deposit = if deposit == 0 { + BalanceOf::::zero() + } else { + BalanceOf::::one().checked_shl(deposit)? + }; Some((weight, deposit)) } @@ -125,6 +131,18 @@ mod test { assert!(deposit * 2 >= decoded_deposit); } + #[test] + fn test_encoding_zero_values_work() { + let encoded_gas = + encode::(Default::default(), Default::default(), Default::default()).unwrap(); + + assert_eq!(encoded_gas, U256::from(1_00_00_00)); + + let (decoded_weight, decoded_deposit) = decode::(encoded_gas).unwrap(); + assert_eq!(Weight::default(), decoded_weight); + assert_eq!(BalanceOf::::zero(), decoded_deposit); + } + #[test] fn test_overflow() { assert_eq!(None, decode::(65_00u128.into()), "Invalid proof size"); diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 820db49251f0..98c00cea7065 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -49,6 +49,11 @@ type CallOf = ::RuntimeCall; /// We use a fixed value for the gas price. /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. +/// +/// The chosen constant value is balanced to ensure: +/// - It is not too high, allowing components (ref_time, proof_size, and deposit) to be encoded in +/// the lower digits of the gas value. +/// - It is not too low, enabling users to adjust the gas price to define a tip. pub const GAS_PRICE: u32 = 1_000u32; /// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. @@ -400,10 +405,15 @@ pub trait EthExtra { // The fees from the Ethereum transaction should be greater or equal to the actual fees paid // by the account. if eth_fee < actual_fee { - log::debug!(target: LOG_TARGET, "fees {eth_fee:?} too low for the extrinsic {actual_fee:?}"); + log::debug!(target: LOG_TARGET, "eth fees {eth_fee:?} too low, actual fees: {actual_fee:?}"); return Err(InvalidTransaction::Payment.into()) } + if eth_fee_no_tip > actual_fee.saturating_mul(2u32.into()) { + log::debug!(target: LOG_TARGET, "actual fees {actual_fee:?} too high, base eth fees: {eth_fee_no_tip:?}"); + return Err(InvalidTransaction::Call.into()) + } + let tip = eth_fee.saturating_sub(eth_fee_no_tip); log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce {nonce:?} and tip: {tip:?}"); Ok(CheckedExtrinsic { @@ -495,6 +505,10 @@ mod test { Ok(dry_run) => { log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); self.tx.gas = Some(dry_run.eth_gas); + let (gas_limit, deposit) = + gas_encoder::decode::(dry_run.eth_gas).unwrap(); + self.gas_limit = gas_limit; + self.storage_deposit_limit = deposit; }, Err(err) => { log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err); @@ -650,7 +664,7 @@ mod test { #[test] fn check_transaction_fees() { - let scenarios: [(_, Box, _); 5] = [ + let scenarios: Vec<(_, Box, _)> = vec![ ( "Eth fees too low", Box::new(|tx| { @@ -668,24 +682,9 @@ mod test { ( "Gas fees too low", Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 2); - }), - InvalidTransaction::Call, - ), - ( - "Diff > 10%", - Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 111 / 100); - }), - InvalidTransaction::Call, - ), - ( - "Diff < 10%", - Box::new(|tx| { - tx.gas_price = Some(tx.gas_price.unwrap() * 2); - tx.gas = Some(tx.gas.unwrap() * 89 / 100); + tx.gas = Some(tx.gas.unwrap() / 2); }), - InvalidTransaction::Call, + InvalidTransaction::Payment, ), ]; From bc3e24909abb4eadf63e0bbe028a2069562b03a8 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 18 Dec 2024 15:52:07 -0500 Subject: [PATCH 34/67] fix import --- substrate/frame/revive/src/evm/gas_encoder.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index 01d5fcbbe113..a585f0df9cb9 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -16,12 +16,11 @@ // limitations under the License. //! Encodes/Decodes EVM gas values. -use crate::{BalanceOf, Config}; +use crate::{BalanceOf, Config, Weight}; use core::ops::{Div, Rem}; use frame_support::pallet_prelude::CheckedShl; use sp_arithmetic::traits::{One, Zero}; use sp_core::U256; -use sp_weights::Weight; // We use 3 digits to store each component. const SCALE: u128 = 100; From 30897855b4b0eb4bc70a2bf693209c041b27f945 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 18 Dec 2024 16:02:23 -0500 Subject: [PATCH 35/67] fixes --- substrate/frame/revive/Cargo.toml | 1 - substrate/frame/revive/src/evm/gas_encoder.rs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index ed32ed157007..2e069bacf737 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -62,7 +62,6 @@ pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } hex-literal = { workspace = true } -env_logger = { workspace = true } # Polkadot SDK Dependencies pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index a585f0df9cb9..51c7c24281d0 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -65,12 +65,12 @@ pub fn encode(gas_limit: U256, weight: Weight, deposit: BalanceOf) let deposit: u128 = deposit.try_into().ok()?; let deposit_component = log2_round_up(deposit); - let ref_time = weight.ref_time(); - let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); - let proof_size = weight.proof_size(); let proof_size_component = SCALE * log2_round_up(proof_size); + let ref_time = weight.ref_time(); + let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); + let components = U256::from(deposit_component + proof_size_component + ref_time_component); let raw_gas_mask = U256::from(SCALE).pow(3.into()); From bcfe7fc26f49f93052a173404659c2f669c30de2 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Wed, 18 Dec 2024 21:10:06 +0000 Subject: [PATCH 36/67] Update from pgherveou running command 'prdoc --audience runtime_dev --bump minor' --- prdoc/pr_6689.prdoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 prdoc/pr_6689.prdoc diff --git a/prdoc/pr_6689.prdoc b/prdoc/pr_6689.prdoc new file mode 100644 index 000000000000..596713e1ca2d --- /dev/null +++ b/prdoc/pr_6689.prdoc @@ -0,0 +1,19 @@ +title: '[pallet-revive] Update gas encoding' +doc: +- audience: Runtime Dev + description: |- + Update the current approach to attach the `ref_time`, `pov` and `deposit` parameters to an Ethereum transaction. + Previously we will pass these 3 parameters along with the signed payload, and check that the fees resulting from `gas x gas_price` match the actual fees paid by the user for the extrinsic. + + This approach unfortunately can be attacked. A malicious actor could force such a transaction to fail by injecting low values for some of these extra parameters as they are not part of the signed payload. + + The new approach encodes these 3 extra parameters in the lower digits of the transaction gas, approximating the the log2 of the actual values to encode each components on 2 digits +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-mock-network + bump: minor From bdd36e4ea508dae3f3034c859fc76c64ecb9d21b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 18 Dec 2024 16:20:58 -0500 Subject: [PATCH 37/67] fix lock --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 13b1e9846106..9ae662452539 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14828,7 +14828,6 @@ dependencies = [ "array-bytes", "assert_matches", "derive_more 0.99.17", - "env_logger 0.11.3", "environmental", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", From dcaf27feb6b0a1cdf37716705c9617136bce0de4 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 18 Dec 2024 17:19:50 -0500 Subject: [PATCH 38/67] rm extraneous test --- .../rpc/examples/js/src/geth-diff.test.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index 37ebbc9ea3b3..d5a396aa2aca 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -289,27 +289,5 @@ for (const env of envs) { ], }) }) - - test.only('eth_estimate (no gas specified) child_call', async () => { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) - expect(balance).toBe(0n) - - const data = encodeFunctionData({ - abi: FlipperCallerAbi, - functionName: 'callFlip', - }) - - await env.accountWallet.request({ - method: 'eth_estimateGas', - params: [ - { - data, - from: env.accountWallet.account.address, - to: flipperCallerAddr, - gas: `0x${Number(1000000).toString(16)}`, - }, - ], - }) - }) }) } From d8e6c2b45f8f5447f7a9ff97c5389acd6dc6fc53 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 18 Dec 2024 17:29:16 -0500 Subject: [PATCH 39/67] simplify --- substrate/frame/revive/src/evm/gas_encoder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index 51c7c24281d0..cbe263da468b 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -91,8 +91,8 @@ pub fn decode(gas: U256) -> Option<(Weight, BalanceOf)> { // Casting with as_u32 is safe since all values are maxed by `SCALE`. let deposit = deposit.as_u32(); - let proof_time = ((gas_without_deposit / SCALE) % SCALE).as_u32(); - let ref_time = ((gas_without_deposit / SCALE.pow(2)) % SCALE).as_u32(); + let proof_time = ((gas / SCALE) % SCALE).as_u32(); + let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32(); let weight = Weight::from_parts( if ref_time == 0 { 0 } else { 1u64.checked_shl(ref_time)? }, From a59194aadb9f74a55099aca495e1b4aee286c59e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 19 Dec 2024 04:14:07 -0500 Subject: [PATCH 40/67] fix --- substrate/frame/revive/src/evm/gas_encoder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index cbe263da468b..27bc688c4bfa 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -87,7 +87,6 @@ pub fn encode(gas_limit: U256, weight: Weight, deposit: BalanceOf) /// Returns `None` if the gas value is invalid pub fn decode(gas: U256) -> Option<(Weight, BalanceOf)> { let deposit = gas % SCALE; - let gas_without_deposit = gas - deposit; // Casting with as_u32 is safe since all values are maxed by `SCALE`. let deposit = deposit.as_u32(); From 24f62b25154ab35860aaf5a99a8abbe9fee56dae Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 19 Dec 2024 05:34:03 -0500 Subject: [PATCH 41/67] Use associated type --- .../assets/asset-hub-westend/src/lib.rs | 3 +- substrate/bin/node/runtime/src/lib.rs | 1 + substrate/frame/revive/src/evm.rs | 3 +- substrate/frame/revive/src/evm/gas_encoder.rs | 163 ++++++++++-------- substrate/frame/revive/src/evm/runtime.rs | 16 +- substrate/frame/revive/src/lib.rs | 14 +- 6 files changed, 114 insertions(+), 86 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index ffd54ce4c8ac..5424a11dfd3d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -125,7 +125,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_002, + spec_version: 1_017_003, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -977,6 +977,7 @@ impl pallet_revive::Config for Runtime { type Xcm = pallet_xcm::Pallet; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = (); } impl TryFrom for pallet_revive::Call { diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faffcd23fbcf..26b40bba5a5e 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1468,6 +1468,7 @@ impl pallet_revive::Config for Runtime { type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = (); } impl pallet_sudo::Config for Runtime { diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index 2bb66bfafd0a..c8c967fbe091 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,5 +19,6 @@ mod api; pub use api::*; -pub mod gas_encoder; +mod gas_encoder; +pub use gas_encoder::*; pub mod runtime; diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index 27bc688c4bfa..3b8e081062d6 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -16,7 +16,7 @@ // limitations under the License. //! Encodes/Decodes EVM gas values. -use crate::{BalanceOf, Config, Weight}; +use crate::Weight; use core::ops::{Div, Rem}; use frame_support::pallet_prelude::CheckedShl; use sp_arithmetic::traits::{One, Zero}; @@ -49,79 +49,88 @@ where .unwrap_or(0) as u128 } -/// Encodes all components (deposit limit, weight reference time, and proof size) into a single -/// gas value. -/// -/// The encoding follows the pattern `g...grrppdd`, where: -/// - `dd`: log2 Deposit value, encoded in the lowest 2 digits. -/// - `pp`: log2 Proof size, encoded in the next 2 digits. -/// - `rr`: log2 Reference time, encoded in the next 2 digits. -/// - `g...g`: Gas limit, encoded in the highest digits. -/// -/// # Note -/// - Encoding fails if the deposit is not convertible to `u128` -/// - The deposit value is maxed by 2^99 -pub fn encode(gas_limit: U256, weight: Weight, deposit: BalanceOf) -> Option { - let deposit: u128 = deposit.try_into().ok()?; - let deposit_component = log2_round_up(deposit); +/// Encodes/Decodes EVM gas values. +pub trait GasEncoder { + /// Encodes all components (deposit limit, weight reference time, and proof size) into a single + /// gas value. + fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256; - let proof_size = weight.proof_size(); - let proof_size_component = SCALE * log2_round_up(proof_size); + /// Decodes the weight and deposit from the encoded gas value. + /// Returns `None` if the gas value is invalid + fn decode(gas: U256) -> Option<(Weight, Balance)>; +} - let ref_time = weight.ref_time(); - let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); +impl GasEncoder for () +where + Balance: Zero + One + CheckedShl + Into, +{ + /// The encoding follows the pattern `g...grrppdd`, where: + /// - `dd`: log2 Deposit value, encoded in the lowest 2 digits. + /// - `pp`: log2 Proof size, encoded in the next 2 digits. + /// - `rr`: log2 Reference time, encoded in the next 2 digits. + /// - `g...g`: Gas limit, encoded in the highest digits. + /// + /// # Note + /// - The deposit value is maxed by 2^99 + fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256 { + let deposit: u128 = deposit.into(); + let deposit_component = log2_round_up(deposit); + + let proof_size = weight.proof_size(); + let proof_size_component = SCALE * log2_round_up(proof_size); + + let ref_time = weight.ref_time(); + let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); + + let components = U256::from(deposit_component + proof_size_component + ref_time_component); + + let raw_gas_mask = U256::from(SCALE).pow(3.into()); + let raw_gas_component = if gas_limit < raw_gas_mask.saturating_add(components) { + raw_gas_mask + } else { + round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) + }; + + components.saturating_add(raw_gas_component) + } - let components = U256::from(deposit_component + proof_size_component + ref_time_component); + fn decode(gas: U256) -> Option<(Weight, Balance)> { + let deposit = gas % SCALE; - let raw_gas_mask = U256::from(SCALE).pow(3.into()); - let raw_gas_component = if gas_limit < raw_gas_mask.saturating_add(components) { - raw_gas_mask - } else { - round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) - }; + // Casting with as_u32 is safe since all values are maxed by `SCALE`. + let deposit = deposit.as_u32(); + let proof_time = ((gas / SCALE) % SCALE).as_u32(); + let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32(); - Some(components.saturating_add(raw_gas_component)) -} + let weight = Weight::from_parts( + if ref_time == 0 { 0 } else { 1u64.checked_shl(ref_time)? }, + if proof_time == 0 { 0 } else { 1u64.checked_shl(proof_time)? }, + ); + let deposit = + if deposit == 0 { Balance::zero() } else { Balance::one().checked_shl(deposit)? }; -/// Decodes the weight and deposit from the encoded gas value. -/// Returns `None` if the gas value is invalid -pub fn decode(gas: U256) -> Option<(Weight, BalanceOf)> { - let deposit = gas % SCALE; - - // Casting with as_u32 is safe since all values are maxed by `SCALE`. - let deposit = deposit.as_u32(); - let proof_time = ((gas / SCALE) % SCALE).as_u32(); - let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32(); - - let weight = Weight::from_parts( - if ref_time == 0 { 0 } else { 1u64.checked_shl(ref_time)? }, - if proof_time == 0 { 0 } else { 1u64.checked_shl(proof_time)? }, - ); - let deposit = if deposit == 0 { - BalanceOf::::zero() - } else { - BalanceOf::::one().checked_shl(deposit)? - }; - - Some((weight, deposit)) + Some((weight, deposit)) + } } #[cfg(test)] mod test { use super::*; - use crate::tests::Test; + use crate::{tests::Test, Config}; #[test] fn test_gas_encoding_decoding_works() { let raw_gas_limit = 111_111_999_999_999u128; let weight = Weight::from_parts(222_999_999, 333_999_999); - let deposit = 444_999_999; + let deposit = 444_999_999u64; - let encoded_gas = encode::(raw_gas_limit.into(), weight, deposit).unwrap(); + let encoded_gas = + ::EthGasEncoder::encode(raw_gas_limit.into(), weight, deposit); assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128)); assert!(encoded_gas > raw_gas_limit.into()); - let (decoded_weight, decoded_deposit) = decode::(encoded_gas).unwrap(); + let (decoded_weight, decoded_deposit) = + ::EthGasEncoder::decode(encoded_gas).unwrap(); assert!(decoded_weight.all_gte(weight)); assert!(weight.mul(2).all_gte(weight)); @@ -129,21 +138,33 @@ mod test { assert!(deposit * 2 >= decoded_deposit); } - #[test] - fn test_encoding_zero_values_work() { - let encoded_gas = - encode::(Default::default(), Default::default(), Default::default()).unwrap(); - - assert_eq!(encoded_gas, U256::from(1_00_00_00)); - - let (decoded_weight, decoded_deposit) = decode::(encoded_gas).unwrap(); - assert_eq!(Weight::default(), decoded_weight); - assert_eq!(BalanceOf::::zero(), decoded_deposit); - } - - #[test] - fn test_overflow() { - assert_eq!(None, decode::(65_00u128.into()), "Invalid proof size"); - assert_eq!(None, decode::(65_00_00u128.into()), "Invalid ref_time"); - } + //#[test] + //fn test_encoding_zero_values_work() { + // let encoded_gas = ::EthGasEncoder::encode( + // Default::default(), + // Default::default(), + // Default::default(), + // ); + // + // assert_eq!(encoded_gas, U256::from(1_00_00_00)); + // + // let (decoded_weight, decoded_deposit) = + // ::EthGasEncoder::decode(encoded_gas).unwrap(); + // assert_eq!(Weight::default(), decoded_weight); + // assert_eq!(0u64, decoded_deposit); + //} + // + //#[test] + //fn test_overflow() { + // assert_eq!( + // None, + // ::EthGasEncoder::decode(65_00u128.into()), + // "Invalid proof size" + // ); + // assert_eq!( + // None, + // ::EthGasEncoder::decode(65_00_00u128.into()), + // "Invalid ref_time" + // ); + //} } diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 98c00cea7065..d98094cf296b 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -18,7 +18,7 @@ use crate::{ evm::{ api::{GenericTransaction, TransactionSigned}, - gas_encoder, + GasEncoder, }, AccountIdOf, AddressMapper, BalanceOf, MomentOf, LOG_TARGET, }; @@ -339,10 +339,11 @@ pub trait EthExtra { let data = input.unwrap_or_default().0; let (gas_limit, storage_deposit_limit) = - gas_encoder::decode::(gas.unwrap_or_default()).ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); - InvalidTransaction::Call - })?; + ::EthGasEncoder::decode(gas.unwrap_or_default()) + .ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); + InvalidTransaction::Call + })?; let call = if let Some(dest) = to { crate::Call::call:: { @@ -506,7 +507,10 @@ mod test { log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); self.tx.gas = Some(dry_run.eth_gas); let (gas_limit, deposit) = - gas_encoder::decode::(dry_run.eth_gas).unwrap(); + <::EthGasEncoder as GasEncoder<_>>::decode( + dry_run.eth_gas, + ) + .unwrap(); self.gas_limit = gas_limit; self.storage_deposit_limit = deposit; }, diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 540e832b429e..b879fa5b360e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -42,9 +42,8 @@ pub mod weights; use crate::{ evm::{ - gas_encoder, runtime::{gas_from_fee, GAS_PRICE}, - GenericTransaction, + GasEncoder, GenericTransaction, }, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, gas::GasMeter, @@ -299,6 +298,10 @@ pub mod pallet { /// The ratio between the decimal representation of the native token and the ETH token. #[pallet::constant] type NativeToEthRatio: Get; + + /// Encode and decode Ethereum gas values. See [`GasEncoder`]. + #[pallet::no_default_bounds] + type EthGasEncoder: GasEncoder>; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -372,6 +375,7 @@ pub mod pallet { type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; type ChainId = ConstU64<0>; type NativeToEthRatio = ConstU32<1>; + type EthGasEncoder = (); } } @@ -1418,11 +1422,7 @@ where .into(); let eth_gas = gas_from_fee(fee); let eth_gas = - gas_encoder::encode::(eth_gas, result.gas_required, result.storage_deposit) - .ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Failed to encode gas {eth_gas:?} {result:?}"); - EthTransactError::Message("Failed to encode gas".into()) - })?; + T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); if eth_gas == result.eth_gas { log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); From ee6e7db49f5e5df5eed0efb8ca07a9daa1f65d8d Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 19 Dec 2024 05:55:18 -0500 Subject: [PATCH 42/67] fix tests --- substrate/frame/revive/src/evm/gas_encoder.rs | 56 ++++++++----------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index 3b8e081062d6..1e4724a53a25 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -116,7 +116,6 @@ where #[cfg(test)] mod test { use super::*; - use crate::{tests::Test, Config}; #[test] fn test_gas_encoding_decoding_works() { @@ -124,13 +123,12 @@ mod test { let weight = Weight::from_parts(222_999_999, 333_999_999); let deposit = 444_999_999u64; - let encoded_gas = - ::EthGasEncoder::encode(raw_gas_limit.into(), weight, deposit); + let encoded_gas = <() as GasEncoder>::encode(raw_gas_limit.into(), weight, deposit); assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128)); assert!(encoded_gas > raw_gas_limit.into()); let (decoded_weight, decoded_deposit) = - ::EthGasEncoder::decode(encoded_gas).unwrap(); + <() as GasEncoder>::decode(encoded_gas).unwrap(); assert!(decoded_weight.all_gte(weight)); assert!(weight.mul(2).all_gte(weight)); @@ -138,33 +136,25 @@ mod test { assert!(deposit * 2 >= decoded_deposit); } - //#[test] - //fn test_encoding_zero_values_work() { - // let encoded_gas = ::EthGasEncoder::encode( - // Default::default(), - // Default::default(), - // Default::default(), - // ); - // - // assert_eq!(encoded_gas, U256::from(1_00_00_00)); - // - // let (decoded_weight, decoded_deposit) = - // ::EthGasEncoder::decode(encoded_gas).unwrap(); - // assert_eq!(Weight::default(), decoded_weight); - // assert_eq!(0u64, decoded_deposit); - //} - // - //#[test] - //fn test_overflow() { - // assert_eq!( - // None, - // ::EthGasEncoder::decode(65_00u128.into()), - // "Invalid proof size" - // ); - // assert_eq!( - // None, - // ::EthGasEncoder::decode(65_00_00u128.into()), - // "Invalid ref_time" - // ); - //} + #[test] + fn test_encoding_zero_values_work() { + let encoded_gas = <() as GasEncoder>::encode( + Default::default(), + Default::default(), + Default::default(), + ); + + assert_eq!(encoded_gas, U256::from(1_00_00_00)); + + let (decoded_weight, decoded_deposit) = + <() as GasEncoder>::decode(encoded_gas).unwrap(); + assert_eq!(Weight::default(), decoded_weight); + assert_eq!(0u64, decoded_deposit); + } + + #[test] + fn test_overflow() { + assert_eq!(None, <() as GasEncoder>::decode(65_00u128.into()), "Invalid proof size"); + assert_eq!(None, <() as GasEncoder>::decode(65_00_00u128.into()), "Invalid ref_time"); + } } From e5deeb21d01711fb6766169055a4f4bbfe1cd994 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 19 Dec 2024 06:00:13 -0500 Subject: [PATCH 43/67] use crate::Config --- substrate/frame/revive/src/evm/runtime.rs | 32 +++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index d98094cf296b..8c87c2c63fe5 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,7 +20,7 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, GasEncoder, }, - AccountIdOf, AddressMapper, BalanceOf, MomentOf, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode}; @@ -266,7 +266,7 @@ where /// EthExtra convert an unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`]. pub trait EthExtra { /// The Runtime configuration. - type Config: crate::Config + pallet_transaction_payment::Config; + type Config: Config + pallet_transaction_payment::Config; /// The Runtime's transaction extension. /// It should include at least: @@ -320,12 +320,11 @@ pub trait EthExtra { InvalidTransaction::BadProof })?; - let signer = - ::AddressMapper::to_fallback_account_id(&signer); + let signer = ::AddressMapper::to_fallback_account_id(&signer); let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } = GenericTransaction::from_signed(tx, None); - if chain_id.unwrap_or_default() != ::ChainId::get().into() { + if chain_id.unwrap_or_default() != ::ChainId::get().into() { log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); return Err(InvalidTransaction::Call); } @@ -338,12 +337,13 @@ pub trait EthExtra { let data = input.unwrap_or_default().0; - let (gas_limit, storage_deposit_limit) = - ::EthGasEncoder::decode(gas.unwrap_or_default()) - .ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); - InvalidTransaction::Call - })?; + let (gas_limit, storage_deposit_limit) = ::EthGasEncoder::decode( + gas.unwrap_or_default(), + ) + .ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); + InvalidTransaction::Call + })?; let call = if let Some(dest) = to { crate::Call::call:: { @@ -484,7 +484,7 @@ mod test { Self { tx: GenericTransaction { from: Some(Account::default().address()), - chain_id: Some(::ChainId::get().into()), + chain_id: Some(::ChainId::get().into()), gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, @@ -507,10 +507,8 @@ mod test { log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); self.tx.gas = Some(dry_run.eth_gas); let (gas_limit, deposit) = - <::EthGasEncoder as GasEncoder<_>>::decode( - dry_run.eth_gas, - ) - .unwrap(); + <::EthGasEncoder as GasEncoder<_>>::decode(dry_run.eth_gas) + .unwrap(); self.gas_limit = gas_limit; self.storage_deposit_limit = deposit; }, @@ -554,7 +552,7 @@ mod test { // Fund the account. let account = Account::default(); - let _ = ::Currency::set_balance( + let _ = ::Currency::set_balance( &account.substrate_account(), 100_000_000_000_000, ); From 972556f537f819e1837469d8dabf044af9090192 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 19 Dec 2024 21:15:43 +0100 Subject: [PATCH 44/67] Add indexer for eth tx hash --- .cargo/config.toml | 1 + Cargo.lock | 475 +++++++++++++-- prdoc/pr_6836.prdoc | 17 + substrate/frame/revive/rpc/.gitignore | 3 + ...20958b338f9fef0d2575056f74f8c67bc3966.json | 20 + ...77e451e9e4324ee5bd235fa130e7c00892c06.json | 12 + ...810cde33043000207872088fa8903fa3c0e9d.json | 26 + substrate/frame/revive/rpc/Cargo.toml | 10 +- .../rpc/examples/westend_local_network.toml | 8 +- ...241205165418_create_transaction_hashes.sql | 15 + .../revive/rpc/src/block_info_provider.rs | 208 +++++++ substrate/frame/revive/rpc/src/cli.rs | 61 +- substrate/frame/revive/rpc/src/client.rs | 544 +++++++----------- substrate/frame/revive/rpc/src/eth-indexer.rs | 85 +++ substrate/frame/revive/rpc/src/lib.rs | 6 + .../frame/revive/rpc/src/receipt_provider.rs | 238 ++++++++ .../revive/rpc/src/receipt_provider/cache.rs | 148 +++++ .../revive/rpc/src/receipt_provider/db.rs | 159 +++++ 18 files changed, 1663 insertions(+), 373 deletions(-) create mode 100644 prdoc/pr_6836.prdoc create mode 100644 substrate/frame/revive/rpc/.gitignore create mode 100644 substrate/frame/revive/rpc/.sqlx/query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json create mode 100644 substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json create mode 100644 substrate/frame/revive/rpc/.sqlx/query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json create mode 100644 substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql create mode 100644 substrate/frame/revive/rpc/src/block_info_provider.rs create mode 100644 substrate/frame/revive/rpc/src/eth-indexer.rs create mode 100644 substrate/frame/revive/rpc/src/receipt_provider.rs create mode 100644 substrate/frame/revive/rpc/src/receipt_provider/cache.rs create mode 100644 substrate/frame/revive/rpc/src/receipt_provider/db.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 68a0d7b552dc..8573f582e258 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,6 +9,7 @@ rustdocflags = [ CC_x86_64_unknown_linux_musl = { value = ".cargo/musl-gcc", force = true, relative = true } CXX_x86_64_unknown_linux_musl = { value = ".cargo/musl-g++", force = true, relative = true } CARGO_WORKSPACE_ROOT_DIR = { value = "", relative = true } +SQLX_OFFLINE = "true" [net] retry = 5 diff --git a/Cargo.lock b/Cargo.lock index 726c8f5a1885..2bae43979562 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,7 +1396,7 @@ dependencies = [ "futures-lite 2.3.0", "parking", "polling 3.4.0", - "rustix 0.38.21", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.52.0", @@ -1478,7 +1478,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.3.0", - "rustix 0.38.21", + "rustix 0.38.42", "tracing", ] @@ -1494,7 +1494,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.21", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.52.0", @@ -1592,6 +1592,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-take" version = "1.1.0" @@ -1880,6 +1889,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -4391,6 +4403,21 @@ dependencies = [ "wasmtime-types", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.3.2" @@ -5926,6 +5953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -6207,6 +6235,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast" version = "0.11.0" @@ -6332,6 +6366,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -6540,23 +6577,23 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.2" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cc", - "libc", + "cfg-if", + "home", + "windows-sys 0.48.0", ] [[package]] @@ -6753,9 +6790,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastrlp" @@ -6970,6 +7007,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -7819,7 +7867,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.21", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -7888,6 +7936,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.3", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -7915,7 +7974,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -8351,6 +8410,15 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.3.3" @@ -9082,7 +9150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.9", - "rustix 0.38.21", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -9683,6 +9751,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "lazycell" @@ -9698,9 +9769,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libflate" @@ -10342,6 +10413,17 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.12" @@ -10401,9 +10483,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lioness" @@ -10685,6 +10767,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.7.4" @@ -10860,7 +10952,7 @@ dependencies = [ "c2-chacha", "curve25519-dalek 4.1.3", "either", - "hashlink", + "hashlink 0.8.4", "lioness", "log", "parking_lot 0.12.3", @@ -11542,6 +11634,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.4" @@ -14921,6 +15030,7 @@ dependencies = [ "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", "sp-weights 27.0.0", + "sqlx", "static_init", "substrate-cli-test-utils", "substrate-prometheus-endpoint", @@ -16633,6 +16743,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "penpal-emulated-chain" version = "0.0.0" @@ -17007,6 +17126,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -20181,7 +20311,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.21", + "rustix 0.38.42", "tracing", "windows-sys 0.52.0", ] @@ -20489,7 +20619,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.21", + "rustix 0.38.42", ] [[package]] @@ -21022,11 +21152,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] @@ -21684,6 +21814,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "rsa" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "rstest" version = "0.18.2" @@ -21858,15 +22008,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.10", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.14", + "windows-sys 0.59.0", ] [[package]] @@ -24591,6 +24741,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "smol" @@ -27962,6 +28115,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spinners" @@ -27984,6 +28140,210 @@ dependencies = [ "der", ] +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.3.1", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink 0.9.1", + "hex", + "indexmap 2.7.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.87", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2 1.0.86", + "quote 1.0.37", + "serde", + "serde_json", + "sha2 0.10.8", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.87", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "bytes", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array 0.14.7", + "hex", + "hkdf", + "hmac 0.12.1", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac 0.12.1", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + [[package]] name = "ss58-registry" version = "1.43.0" @@ -28311,6 +28671,17 @@ dependencies = [ "serde", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.8.0" @@ -29289,15 +29660,15 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.1.0", - "redox_syscall 0.4.1", - "rustix 0.38.21", - "windows-sys 0.48.0", + "fastrand 2.3.0", + "once_cell", + "rustix 0.38.42", + "windows-sys 0.59.0", ] [[package]] @@ -29326,7 +29697,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.21", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -30291,6 +30662,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -30315,6 +30692,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.5.1" @@ -30558,6 +30941,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.95" @@ -31297,6 +31686,16 @@ dependencies = [ "westend-emulated-chain", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall 0.5.8", + "wasite", +] + [[package]] name = "wide" version = "0.7.11" diff --git a/prdoc/pr_6836.prdoc b/prdoc/pr_6836.prdoc new file mode 100644 index 000000000000..1de081bbaa40 --- /dev/null +++ b/prdoc/pr_6836.prdoc @@ -0,0 +1,17 @@ +title: '[pallet-revive-eth-rpc] persist eth transaction hash' +doc: +- audience: Runtime Dev + description: |- + Add an option to persist EVM transaction hash to a SQL db. + This make it possible to run a full archive ETH RPC node (assuming the substrate node is also a full archive node) + + Some queries such as eth_getTransactionByHash, eth_getBlockTransactionCountByHash, and other need to work with a transaction hash index, which is not available in Substrate and need to be stored by the eth-rpc proxy. + + The refactoring break down the Client into a `BlockInfoProvider` and `ReceiptProvider` + - BlockInfoProvider does not need any persistence data, as we can fetch all block info from the source substrate chain + - ReceiptProvider comes in two flavor, + - An in memory cache implementation - This is the one we had so far. + - A DB implementation - This one persist rows with the block_hash, the transaction_index and the transaction_hash, so that we can later fetch the block and extrinsic for that receipt and reconstruct the ReceiptInfo object. +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/.gitignore b/substrate/frame/revive/rpc/.gitignore new file mode 100644 index 000000000000..c7434965b841 --- /dev/null +++ b/substrate/frame/revive/rpc/.gitignore @@ -0,0 +1,3 @@ +*.db +*.db-wal +*.db-shm diff --git a/substrate/frame/revive/rpc/.sqlx/query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json b/substrate/frame/revive/rpc/.sqlx/query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json new file mode 100644 index 000000000000..fc1858a521ad --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = ?\n ", + "describe": { + "columns": [ + { + "name": "count", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json b/substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json new file mode 100644 index 000000000000..ac67147f464a --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES (?, ?, ?)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json b/substrate/frame/revive/rpc/.sqlx/query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json new file mode 100644 index 000000000000..dcd8bc42f266 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = ?\n\t\t\t", + "describe": { + "columns": [ + { + "name": "block_hash", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "transaction_index", + "ordinal": 1, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d" +} diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 31b8f505dedc..70922f92cd54 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -7,11 +7,16 @@ license = "Apache-2.0" homepage.workspace = true repository.workspace = true description = "An Ethereum JSON-RPC server for pallet-revive." +default-run = "eth-rpc" [[bin]] name = "eth-rpc" path = "src/main.rs" +[[bin]] +name = "eth-indexer" +path = "src/eth-indexer.rs" + [[example]] name = "deploy" path = "examples/rust/deploy.rs" @@ -44,7 +49,9 @@ futures = { workspace = true, features = ["thread-pool"] } jsonrpsee = { workspace = true, features = ["full"] } thiserror = { workspace = true } sp-crypto-hashing = { workspace = true } -subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } +subxt = { workspace = true, default-features = true, features = [ + "reconnecting-rpc-client", +] } tokio = { workspace = true, features = ["full"] } codec = { workspace = true, features = ["derive"] } log = { workspace = true } @@ -62,6 +69,7 @@ subxt-signer = { workspace = true, optional = true, features = [ ] } hex = { workspace = true } ethabi = { version = "18.0.0" } +sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "sqlite"] } [features] example = ["rlp", "subxt-signer"] diff --git a/substrate/frame/revive/rpc/examples/westend_local_network.toml b/substrate/frame/revive/rpc/examples/westend_local_network.toml index 28295db76133..76561be814ec 100644 --- a/substrate/frame/revive/rpc/examples/westend_local_network.toml +++ b/substrate/frame/revive/rpc/examples/westend_local_network.toml @@ -29,13 +29,9 @@ name = "asset-hub-westend-collator1" rpc_port = 9011 ws_port = 9944 command = "{{POLKADOT_PARACHAIN_BINARY}}" -args = [ - "-lparachain=debug,runtime::revive=debug", -] +args = ["-lparachain=debug,runtime::revive=debug"] [[parachains.collators]] name = "asset-hub-westend-collator2" command = "{{POLKADOT_PARACHAIN_BINARY}}" -args = [ - "-lparachain=debug,runtime::revive=debug", -] +args = ["-lparachain=debug,runtime::revive=debug"] diff --git a/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql b/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql new file mode 100644 index 000000000000..43405bea9d04 --- /dev/null +++ b/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql @@ -0,0 +1,15 @@ +-- Create DB: +-- DATABASE_URL="..." cargo sqlx database create +-- +-- Run migration: +-- DATABASE_URL="..." cargo sqlx migrate run +-- +-- Update compile time artifacts: +-- DATABASE_URL="..." cargo sqlx prepare +CREATE TABLE transaction_hashes ( + transaction_hash CHAR(64) NOT NULL PRIMARY KEY, + transaction_index INTEGER NOT NULL, + block_hash CHAR(64) NOT NULL +); + +CREATE INDEX idx_block_hash ON transaction_hashes (block_hash); diff --git a/substrate/frame/revive/rpc/src/block_info_provider.rs b/substrate/frame/revive/rpc/src/block_info_provider.rs new file mode 100644 index 000000000000..e1bb51e9fc04 --- /dev/null +++ b/substrate/frame/revive/rpc/src/block_info_provider.rs @@ -0,0 +1,208 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + client::{SubstrateBlock, SubstrateBlockNumber}, + subxt_client::SrcChainConfig, + ClientError, LOG_TARGET, +}; +use sp_core::H256; +use std::{ + collections::{HashMap, VecDeque}, + sync::Arc, +}; +use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient}; +use tokio::sync::RwLock; + +/// Provides information about blocks. +#[derive(Clone)] +pub struct BlockInfoProvider { + /// The shared in memory cache. + cache: Arc>>, + + /// The rpc client, used to fetch blocks not in the cache. + rpc: LegacyRpcMethods, + + /// The api client, used to fetch blocks not in the cache. + api: OnlineClient, +} + +impl BlockInfoProvider { + /// Create a new `BlockInfoProvider` with the given cache size, rpc client and api client. + pub fn new( + cache_size: usize, + api: OnlineClient, + rpc: LegacyRpcMethods, + ) -> Self { + Self { api, rpc, cache: Arc::new(RwLock::new(BlockCache::new(cache_size))) } + } + + /// Get a read access on the shared cache. + async fn cache(&self) -> tokio::sync::RwLockReadGuard<'_, BlockCache> { + self.cache.read().await + } + + /// Cache new block and return the pruned block hash. + pub async fn cache_block(&self, block: SubstrateBlock) -> Option { + let mut cache = self.cache.write().await; + cache.insert(block) + } + + /// Return the latest ingested block. + pub async fn latest_block(&self) -> Option> { + let cache = self.cache().await; + cache.buffer.back().cloned() + } + + /// Get block by block_number. + pub async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result>, ClientError> { + let cache = self.cache().await; + if let Some(block) = cache.blocks_by_number.get(&block_number).cloned() { + return Ok(Some(block)); + } + + let Some(hash) = self.rpc.chain_get_block_hash(Some(block_number.into())).await? else { + return Ok(None); + }; + + self.block_by_hash(&hash).await + } + + /// Get block by block hash. + pub async fn block_by_hash( + &self, + hash: &H256, + ) -> Result>, ClientError> { + let cache = self.cache().await; + if let Some(block) = cache.blocks_by_hash.get(hash).cloned() { + return Ok(Some(block)); + } + + match self.api.blocks().at(*hash).await { + Ok(block) => Ok(Some(Arc::new(block))), + Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +/// The cache maintains a buffer of the last N blocks, +struct BlockCache { + /// The maximum buffer's size. + max_cache_size: usize, + + /// A double-ended queue of the last N blocks. + /// The most recent block is at the back of the queue, and the oldest block is at the front. + buffer: VecDeque>, + + /// A map of blocks by block number. + blocks_by_number: HashMap>, + + /// A map of blocks by block hash. + blocks_by_hash: HashMap>, +} + +/// Provides information about a block, +/// This is an abstratction on top of [`SubstrateBlock`] used to test the [`BlockCache`]. +trait BlockInfo { + /// Returns the block hash. + fn hash(&self) -> H256; + /// Returns the block number. + fn number(&self) -> SubstrateBlockNumber; +} + +impl BlockInfo for SubstrateBlock { + fn hash(&self) -> H256 { + SubstrateBlock::hash(self) + } + fn number(&self) -> u32 { + SubstrateBlock::number(self) + } +} + +impl BlockCache { + /// Create a new cache with the given maximum buffer size. + pub fn new(max_cache_size: usize) -> Self { + Self { + max_cache_size, + buffer: Default::default(), + blocks_by_number: Default::default(), + blocks_by_hash: Default::default(), + } + } + + /// Insert an entry into the cache, and prune the oldest entry if the cache is full. + pub fn insert(&mut self, block: B) -> Option { + let mut pruned_block_hash = None; + if self.buffer.len() >= self.max_cache_size { + if let Some(block) = self.buffer.pop_front() { + log::trace!(target: LOG_TARGET, "Pruning block: {}", block.number()); + let hash = block.hash(); + self.blocks_by_hash.remove(&hash); + self.blocks_by_number.remove(&block.number()); + pruned_block_hash = Some(hash); + } + } + + let block = Arc::new(block); + self.buffer.push_back(block.clone()); + self.blocks_by_number.insert(block.number(), block.clone()); + self.blocks_by_hash.insert(block.hash(), block); + pruned_block_hash + } +} + +#[cfg(test)] +mod test { + use super::*; + + struct MockBlock { + block_number: SubstrateBlockNumber, + block_hash: H256, + } + + impl BlockInfo for MockBlock { + fn hash(&self) -> H256 { + self.block_hash + } + + fn number(&self) -> u32 { + self.block_number + } + } + + #[test] + fn cache_insert_works() { + let mut cache = BlockCache::::new(2); + + let pruned = cache.insert(MockBlock { block_number: 1, block_hash: H256::from([1; 32]) }); + assert_eq!(pruned, None); + + let pruned = cache.insert(MockBlock { block_number: 2, block_hash: H256::from([2; 32]) }); + assert_eq!(pruned, None); + + let pruned = cache.insert(MockBlock { block_number: 3, block_hash: H256::from([3; 32]) }); + assert_eq!(pruned, Some(H256::from([1; 32]))); + + assert_eq!(cache.buffer.len(), 2); + assert_eq!(cache.blocks_by_number.len(), 2); + assert_eq!(cache.blocks_by_hash.len(), 2); + } +} diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index c0f81fcafd77..cda55b55f9ca 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -16,8 +16,9 @@ // limitations under the License. //! The Ethereum JSON-RPC server. use crate::{ - client::Client, EthRpcServer, EthRpcServerImpl, SystemHealthRpcServer, - SystemHealthRpcServerImpl, + client::{connect, Client}, + BlockInfoProvider, CacheReceiptProvider, DBReceiptProvider, EthRpcServer, EthRpcServerImpl, + ReceiptProvider, SystemHealthRpcServer, SystemHealthRpcServerImpl, }; use clap::Parser; use futures::{pin_mut, FutureExt}; @@ -27,6 +28,7 @@ use sc_service::{ config::{PrometheusConfig, RpcConfiguration}, start_rpc_servers, TaskManager, }; +use std::sync::Arc; // Default port if --prometheus-port is not specified const DEFAULT_PROMETHEUS_PORT: u16 = 9616; @@ -42,6 +44,21 @@ pub struct CliCommand { #[clap(long, default_value = "ws://127.0.0.1:9944")] pub node_rpc_url: String, + /// The maximum number of blocks to cache in memory. + #[clap(long, default_value = "256")] + pub cache_size: usize, + + /// The database used to store Ethereum transaction hashes. + /// This is only useful if the node needs to act as an archive node and respond to Ethereum RPC + /// querires for transactions that are not in the in memory cache. + #[clap(long)] + pub database_url: Option, + + /// If true, we will only read from the database and not write to it. + /// Only useful if `--database-url` is specified. + #[clap(long, default_value = "true")] + pub database_read_only: bool, + #[allow(missing_docs)] #[clap(flatten)] pub shared_params: SharedParams, @@ -78,7 +95,16 @@ fn init_logger(params: &SharedParams) -> anyhow::Result<()> { /// Start the JSON-RPC server using the given command line arguments. pub fn run(cmd: CliCommand) -> anyhow::Result<()> { - let CliCommand { rpc_params, prometheus_params, node_rpc_url, shared_params, .. } = cmd; + let CliCommand { + rpc_params, + prometheus_params, + node_rpc_url, + cache_size, + database_url, + database_read_only, + shared_params, + .. + } = cmd; #[cfg(not(test))] init_logger(&shared_params)?; @@ -110,19 +136,41 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let tokio_runtime = sc_cli::build_runtime()?; let tokio_handle = tokio_runtime.handle(); - let signals = tokio_runtime.block_on(async { Signals::capture() })?; let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; let essential_spawn_handle = task_manager.spawn_essential_handle(); let gen_rpc_module = || { let signals = tokio_runtime.block_on(async { Signals::capture() })?; - let fut = Client::from_url(&node_rpc_url, &essential_spawn_handle).fuse(); + let fut = async { + let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; + let block_provider = BlockInfoProvider::new(cache_size, api.clone(), rpc.clone()); + let receipt_provider: Arc = + if let Some(database_url) = database_url.as_ref() { + Arc::new(( + CacheReceiptProvider::default(), + DBReceiptProvider::new( + database_url, + database_read_only, + block_provider.clone(), + ) + .await?, + )) + } else { + Arc::new(CacheReceiptProvider::default()) + }; + + let client = + Client::new(api, rpc_client, rpc, block_provider, receipt_provider).await?; + client.subscribe_and_cache_blocks(&essential_spawn_handle); + Ok::<_, crate::ClientError>(client) + } + .fuse(); pin_mut!(fut); match tokio_handle.block_on(signals.try_until_signal(fut)) { Ok(Ok(client)) => rpc_module(is_dev, client), Ok(Err(err)) => { - log::error!("Error connecting to the node at {node_rpc_url}: {err}"); + log::error!("Error initializing: {err:?}"); Err(sc_service::Error::Application(err.into())) }, Err(_) => Err(sc_service::Error::Application("Client connection interrupted".into())), @@ -142,6 +190,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { start_rpc_servers(&rpc_config, prometheus_registry, tokio_handle, gen_rpc_module, None)?; task_manager.keep_alive(rpc_server_handle); + let signals = tokio_runtime.block_on(async { Signals::capture() })?; tokio_runtime.block_on(signals.run_until_signal(task_manager.future().fuse()))?; Ok(()) } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index de97844eccbb..b1bfa9f07dca 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -17,30 +17,23 @@ //! The client connects to the source substrate chain //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ + extract_receipts_from_block, runtime::gas_from_fee, subxt_client::{ - revive::{calls::types::EthTransact, events::ContractEmitted}, - runtime_types::pallet_revive::storage::ContractInfo, + revive::calls::types::EthTransact, runtime_types::pallet_revive::storage::ContractInfo, }, - LOG_TARGET, + BlockInfoProvider, ReceiptProvider, LOG_TARGET, }; -use futures::{stream, StreamExt}; use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pallet_revive::{ - create1, evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, Log, - ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, + Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, ReceiptInfo, + SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, }, EthTransactError, EthTransactInfo, }; -use sp_core::keccak_256; use sp_weights::Weight; -use std::{ - collections::{HashMap, VecDeque}, - sync::Arc, - time::Duration, -}; +use std::{ops::ControlFlow, sync::Arc, time::Duration}; use subxt::{ backend::{ legacy::{rpc_methods::SystemHealth, LegacyRpcMethods}, @@ -54,11 +47,10 @@ use subxt::{ storage::Storage, Config, OnlineClient, }; -use subxt_client::transaction_payment::events::TransactionFeePaid; use thiserror::Error; -use tokio::sync::{watch::Sender, RwLock}; +use tokio::{sync::RwLock, try_join}; -use crate::subxt_client::{self, system::events::ExtrinsicSuccess, SrcChainConfig}; +use crate::subxt_client::{self, SrcChainConfig}; /// The substrate block type. pub type SubstrateBlock = subxt::blocks::Block>; @@ -75,29 +67,6 @@ pub type Shared = Arc>; /// The runtime balance type. pub type Balance = u128; -/// The cache maintains a buffer of the last N blocks, -#[derive(Default)] -struct BlockCache { - /// A double-ended queue of the last N blocks. - /// The most recent block is at the back of the queue, and the oldest block is at the front. - buffer: VecDeque>, - - /// A map of blocks by block number. - blocks_by_number: HashMap>, - - /// A map of blocks by block hash. - blocks_by_hash: HashMap>, - - /// A map of receipts by hash. - receipts_by_hash: HashMap, - - /// A map of Signed transaction by hash. - signed_tx_by_hash: HashMap, - - /// A map of receipt hashes by block hash. - tx_hashes_by_block_and_index: HashMap>, -} - /// Unwrap the original `jsonrpsee::core::client::Error::Call` error. fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { use subxt::backend::rpc::reconnecting_rpc_client; @@ -167,6 +136,9 @@ pub enum ClientError { /// A [`RpcError`] wrapper error. #[error(transparent)] RpcError(#[from] RpcError), + /// A [`sqlx::Error`] wrapper error. + #[error(transparent)] + SqlxError(#[from] sqlx::Error), /// A [`codec::Error`] wrapper error. #[error(transparent)] CodecError(#[from] codec::Error), @@ -179,9 +151,18 @@ pub enum ClientError { /// The block hash was not found. #[error("hash not found")] BlockNotFound, + + #[error("No Ethereum extrinsic found")] + EthExtrinsicNotFound, /// The transaction fee could not be found #[error("transactionFeePaid event not found")] TxFeeNotFound, + /// Failed to decode a raw payload into a signed transaction. + #[error("Failed to decode a raw payload into a signed transaction")] + TxDecodingFailed, + /// Failed to recover eth address. + #[error("failed to recover eth address")] + RecoverEthAddressFailed, /// The cache is empty. #[error("cache is empty")] CacheEmpty, @@ -214,163 +195,18 @@ impl From for ErrorObjectOwned { } } -/// The number of recent blocks maintained by the cache. -/// For each block in the cache, we also store the EVM transaction receipts. -pub const CACHE_SIZE: usize = 256; - -impl BlockCache { - fn latest_block(&self) -> Option<&Arc> { - self.buffer.back() - } - - /// Insert an entry into the cache, and prune the oldest entry if the cache is full. - fn insert(&mut self, block: SubstrateBlock) { - if self.buffer.len() >= N { - if let Some(block) = self.buffer.pop_front() { - log::trace!(target: LOG_TARGET, "Pruning block: {}", block.number()); - let hash = block.hash(); - self.blocks_by_hash.remove(&hash); - self.blocks_by_number.remove(&block.number()); - if let Some(entries) = self.tx_hashes_by_block_and_index.remove(&hash) { - for hash in entries.values() { - self.receipts_by_hash.remove(hash); - } - } - } - } - - let block = Arc::new(block); - self.buffer.push_back(block.clone()); - self.blocks_by_number.insert(block.number(), block.clone()); - self.blocks_by_hash.insert(block.hash(), block); - } -} - /// A client connect to a node and maintains a cache of the last `CACHE_SIZE` blocks. #[derive(Clone)] pub struct Client { - /// The inner state of the client. - inner: Arc, - /// A watch channel to signal cache updates. - pub updates: tokio::sync::watch::Receiver<()>, -} - -/// The inner state of the client. -struct ClientInner { api: OnlineClient, rpc_client: ReconnectingRpcClient, rpc: LegacyRpcMethods, - cache: Shared>, + receipt_provider: Arc, + block_provider: BlockInfoProvider, chain_id: u64, max_block_weight: Weight, } -impl ClientInner { - /// Create a new client instance connecting to the substrate node at the given URL. - async fn from_url(url: &str) -> Result { - let rpc_client = ReconnectingRpcClient::builder() - .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) - .build(url.to_string()) - .await?; - - let api = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; - let cache = Arc::new(RwLock::new(BlockCache::::default())); - - let rpc = LegacyRpcMethods::::new(RpcClient::new(rpc_client.clone())); - - let (chain_id, max_block_weight) = - tokio::try_join!(chain_id(&api), max_block_weight(&api))?; - - Ok(Self { api, rpc_client, rpc, cache, chain_id, max_block_weight }) - } - - /// Get the receipt infos from the extrinsics in a block. - async fn receipt_infos( - &self, - block: &SubstrateBlock, - ) -> Result, ClientError> { - // Get extrinsics from the block - let extrinsics = block.extrinsics().await?; - - // Filter extrinsics from pallet_revive - let extrinsics = extrinsics.iter().flat_map(|ext| { - let call = ext.as_extrinsic::().ok()??; - let transaction_hash = H256(keccak_256(&call.payload)); - let signed_tx = TransactionSigned::decode(&call.payload).ok()?; - let from = signed_tx.recover_eth_address().ok()?; - let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); - let contract_address = if tx_info.to.is_none() { - Some(create1(&from, tx_info.nonce.unwrap_or_default().try_into().ok()?)) - } else { - None - }; - - Some((from, signed_tx, tx_info, transaction_hash, contract_address, ext)) - }); - - // Map each extrinsic to a receipt - stream::iter(extrinsics) - .map(|(from, signed_tx, tx_info, transaction_hash, contract_address, ext)| async move { - let events = ext.events().await?; - let tx_fees = - events.find_first::()?.ok_or(ClientError::TxFeeNotFound)?; - - let gas_price = tx_info.gas_price.unwrap_or_default(); - let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee)) - .checked_div(gas_price.as_u128()) - .unwrap_or_default(); - - let success = events.has::()?; - let transaction_index = ext.index(); - let block_hash = block.hash(); - let block_number = block.number().into(); - - // get logs from ContractEmitted event - let logs = events.iter() - .filter_map(|event_details| { - let event_details = event_details.ok()?; - let event = event_details.as_event::().ok()??; - - Some(Log { - address: event.contract, - topics: event.topics, - data: Some(event.data.into()), - block_number: Some(block_number), - transaction_hash, - transaction_index: Some(transaction_index.into()), - block_hash: Some(block_hash), - log_index: Some(event_details.index().into()), - ..Default::default() - }) - }).collect(); - - - log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: {block_number:?}"); - let receipt = ReceiptInfo::new( - block_hash, - block_number, - contract_address, - from, - logs, - tx_info.to, - gas_price, - gas_used.into(), - success, - transaction_hash, - transaction_index.into(), - tx_info.r#type.unwrap_or_default() - ); - - Ok::<_, ClientError>((receipt.transaction_hash, (signed_tx, receipt))) - }) - .buffer_unordered(10) - .collect::>>() - .await - .into_iter() - .collect::, _>>() - } -} - /// Fetch the chain ID from the substrate chain. async fn chain_id(api: &OnlineClient) -> Result { let query = subxt_client::constants().revive().chain_id(); @@ -395,23 +231,181 @@ async fn extract_block_timestamp(block: &SubstrateBlock) -> Option { Some(ext.value.now / 1000) } +/// Connect to a node at the given URL, and return the underlying API, RPC client, and legacy RPC +/// clients. +pub async fn connect( + node_rpc_url: &str, +) -> Result< + (OnlineClient, ReconnectingRpcClient, LegacyRpcMethods), + ClientError, +> { + log::info!(target: LOG_TARGET, "Connecting to node at: {node_rpc_url} ..."); + let rpc_client = ReconnectingRpcClient::builder() + .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) + .build(node_rpc_url.to_string()) + .await?; + log::info!(target: LOG_TARGET, "Connected to node at: {node_rpc_url}"); + + let api = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; + let rpc = LegacyRpcMethods::::new(RpcClient::new(rpc_client.clone())); + Ok((api, rpc_client, rpc)) +} + impl Client { /// Create a new client instance. - /// The client will subscribe to new blocks and maintain a cache of [`CACHE_SIZE`] blocks. - pub async fn from_url( - url: &str, - spawn_handle: &sc_service::SpawnEssentialTaskHandle, + pub async fn new( + api: OnlineClient, + rpc_client: ReconnectingRpcClient, + rpc: LegacyRpcMethods, + block_provider: BlockInfoProvider, + receipt_provider: Arc, ) -> Result { - log::info!(target: LOG_TARGET, "Connecting to node at: {url} ..."); - let inner: Arc = Arc::new(ClientInner::from_url(url).await?); - log::info!(target: LOG_TARGET, "Connected to node at: {url}"); + let (chain_id, max_block_weight) = + tokio::try_join!(chain_id(&api), max_block_weight(&api))?; + + Ok(Self { + api, + rpc_client, + rpc, + receipt_provider, + block_provider, + chain_id, + max_block_weight, + }) + } + + /// Subscribe to past blocks executing the callback for each block. + /// The subscription continues iterating past blocks until the closure returns + /// `ControlFlow::Break`. Blocks are iterated starting from the latest block and moving + /// backward. + #[allow(dead_code)] + async fn subscribe_past_blocks(&self, callback: F) -> Result<(), ClientError> + where + F: Fn(SubstrateBlock) -> Fut + Send + Sync, + Fut: std::future::Future, ClientError>> + Send, + { + log::info!(target: LOG_TARGET, "Subscribing to past blocks"); + let mut block = self.api.blocks().at_latest().await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to fetch latest block: {err:?}"); + })?; + + loop { + let block_number = block.number(); + log::debug!(target: LOG_TARGET, "Processing block {block_number}"); + + let parent_hash = block.header().parent_hash; + let control_flow = callback(block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}"); + })?; + + match control_flow { + ControlFlow::Continue(_) => { + if block_number == 0 { + log::info!(target: LOG_TARGET, "All past blocks processed"); + return Ok(()); + } + block = self.api.blocks().at(parent_hash).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to fetch block at {parent_hash:?}: {err:?}"); + })?; + }, + ControlFlow::Break(_) => { + log::info!(target: LOG_TARGET, "Stopping past block subscription at {block_number}"); + return Ok(()); + }, + } + } + } + + /// Subscribe to new best blocks, and execute the async closure with + /// the extracted block and ethereum transactions + async fn subscribe_new_blocks(&self, callback: F) -> Result<(), ClientError> + where + F: Fn(SubstrateBlock) -> Fut + Send + Sync, + Fut: std::future::Future> + Send, + { + log::info!(target: LOG_TARGET, "Subscribing to new blocks"); + let mut block_stream = match self.api.blocks().subscribe_best().await { + Ok(s) => s, + Err(err) => { + log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); + return Err(err.into()); + }, + }; - let (tx, mut updates) = tokio::sync::watch::channel(()); + while let Some(block) = block_stream.next().await { + let block = match block { + Ok(block) => block, + Err(err) => { + if err.is_disconnected_will_reconnect() { + log::warn!( + target: LOG_TARGET, + "The RPC connection was lost and we may have missed a few blocks" + ); + continue; + } - spawn_handle.spawn("subscribe-blocks", None, Self::subscribe_blocks(inner.clone(), tx)); + log::error!(target: LOG_TARGET, "Failed to fetch block: {err:?}"); + return Err(err.into()); + }, + }; - updates.changed().await.expect("tx is not dropped"); - Ok(Self { inner, updates }) + log::debug!(target: LOG_TARGET, "Pushing block: {}", block.number()); + callback(block).await?; + } + + log::info!(target: LOG_TARGET, "Block subscription ended"); + Ok(()) + } + + /// Start the block subscription, and populate the block cache. + pub fn subscribe_and_cache_blocks(&self, spawn_handle: &sc_service::SpawnEssentialTaskHandle) { + let client = self.clone(); + spawn_handle.spawn("subscribe-blocks", None, async move { + let res = client + .subscribe_new_blocks(|block| async { + let receipts = extract_receipts_from_block(&block).await?; + + client.receipt_provider.insert(&block.hash(), &receipts).await; + if let Some(pruned) = client.block_provider.cache_block(block).await { + client.receipt_provider.remove(&pruned).await; + } + + Ok(()) + }) + .await; + + if let Err(err) = res { + log::error!(target: LOG_TARGET, "Block subscription error: {err:?}"); + } + }); + } + + /// Start the block subscription, and populate the block cache. + pub async fn subscribe_and_cache_receipts( + &self, + oldest_block: Option, + ) -> Result<(), ClientError> { + let new_blocks_fut = self.subscribe_new_blocks(|block| async move { + let receipts = extract_receipts_from_block(&block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to extract receipts from block: {err:?}"); + })?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + Ok(()) + }); + + let Some(oldest_block) = oldest_block else { return new_blocks_fut.await }; + + let old_blocks_fut = self.subscribe_past_blocks(|block| async move { + let receipts = extract_receipts_from_block(&block).await?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + if block.number() == oldest_block { + Ok(ControlFlow::Break(())) + } else { + Ok(ControlFlow::Continue(())) + } + }); + + try_join!(new_blocks_fut, old_blocks_fut).map(|_| ()) } /// Expose the storage API. @@ -425,14 +419,14 @@ impl Client { (*block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; - Ok(self.inner.api.storage().at(hash)) + Ok(self.api.storage().at(hash)) }, - BlockNumberOrTagOrHash::H256(hash) => Ok(self.inner.api.storage().at(*hash)), + BlockNumberOrTagOrHash::H256(hash) => Ok(self.api.storage().at(*hash)), BlockNumberOrTagOrHash::BlockTag(_) => { if let Some(block) = self.latest_block().await { - return Ok(self.inner.api.storage().at(block.hash())); + return Ok(self.api.storage().at(block.hash())); } - let storage = self.inner.api.storage().at_latest().await?; + let storage = self.api.storage().at_latest().await?; Ok(storage) }, } @@ -452,90 +446,24 @@ impl Client { (*block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; - Ok(self.inner.api.runtime_api().at(hash)) + Ok(self.api.runtime_api().at(hash)) }, - BlockNumberOrTagOrHash::H256(hash) => Ok(self.inner.api.runtime_api().at(*hash)), + BlockNumberOrTagOrHash::H256(hash) => Ok(self.api.runtime_api().at(*hash)), BlockNumberOrTagOrHash::BlockTag(_) => { if let Some(block) = self.latest_block().await { - return Ok(self.inner.api.runtime_api().at(block.hash())); + return Ok(self.api.runtime_api().at(block.hash())); } - let api = self.inner.api.runtime_api().at_latest().await?; + let api = self.api.runtime_api().at_latest().await?; Ok(api) }, } } - /// Subscribe to new blocks and update the cache. - async fn subscribe_blocks(inner: Arc, tx: Sender<()>) { - log::info!(target: LOG_TARGET, "Subscribing to new blocks"); - let mut block_stream = match inner.as_ref().api.blocks().subscribe_best().await { - Ok(s) => s, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); - return; - }, - }; - - while let Some(block) = block_stream.next().await { - let block = match block { - Ok(block) => block, - Err(err) => { - if err.is_disconnected_will_reconnect() { - log::warn!( - target: LOG_TARGET, - "The RPC connection was lost and we may have missed a few blocks" - ); - continue; - } - - log::error!(target: LOG_TARGET, "Failed to fetch block: {err:?}"); - return; - }, - }; - - log::trace!(target: LOG_TARGET, "Pushing block: {}", block.number()); - let mut cache = inner.cache.write().await; - - let receipts = inner - .receipt_infos(&block) - .await - .inspect_err(|err| { - log::error!(target: LOG_TARGET, "Failed to get receipts: {err:?}"); - }) - .unwrap_or_default(); - - if !receipts.is_empty() { - let values = receipts - .iter() - .map(|(hash, (_, receipt))| (receipt.transaction_index, *hash)) - .collect::>(); - - cache.tx_hashes_by_block_and_index.insert(block.hash(), values); - - cache - .receipts_by_hash - .extend(receipts.iter().map(|(hash, (_, receipt))| (*hash, receipt.clone()))); - - cache.signed_tx_by_hash.extend( - receipts.iter().map(|(hash, (signed_tx, _))| (*hash, signed_tx.clone())), - ) - } - - cache.insert(block); - tx.send_replace(()); - } - - log::info!(target: LOG_TARGET, "Block subscription ended"); - } -} - -impl Client { /// Get the most recent block stored in the cache. pub async fn latest_block(&self) -> Option> { - let cache = self.inner.cache.read().await; - let block = cache.latest_block()?; - Some(block.clone()) + let block = self.block_provider.latest_block().await?; + Some(block) } /// Expose the transaction API. @@ -543,23 +471,22 @@ impl Client { &self, call: subxt::tx::DefaultPayload, ) -> Result { - let ext = self.inner.api.tx().create_unsigned(&call).map_err(ClientError::from)?; + let ext = self.api.tx().create_unsigned(&call).map_err(ClientError::from)?; let hash = ext.submit().await?; Ok(hash) } /// Get an EVM transaction receipt by hash. pub async fn receipt(&self, tx_hash: &H256) -> Option { - let cache = self.inner.cache.read().await; - cache.receipts_by_hash.get(tx_hash).cloned() + self.receipt_provider.receipt_by_hash(tx_hash).await } /// Get the syncing status of the chain. pub async fn syncing(&self) -> Result { - let health = self.inner.rpc.system_health().await?; + let health = self.rpc.system_health().await?; let status = if health.is_syncing { - let client = RpcClient::new(self.inner.rpc_client.clone()); + let client = RpcClient::new(self.rpc_client.clone()); let sync_state: sc_rpc::system::SyncState = client.request("system_syncState", Default::default()).await?; @@ -582,27 +509,23 @@ impl Client { block_hash: &H256, transaction_index: &U256, ) -> Option { - let cache = self.inner.cache.read().await; - let receipt_hash = - cache.tx_hashes_by_block_and_index.get(block_hash)?.get(transaction_index)?; - let receipt = cache.receipts_by_hash.get(receipt_hash)?; - Some(receipt.clone()) + self.receipt_provider + .receipt_by_block_hash_and_index(block_hash, transaction_index) + .await } pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option { - let cache = self.inner.cache.read().await; - cache.signed_tx_by_hash.get(tx_hash).cloned() + self.receipt_provider.signed_tx_by_hash(tx_hash).await } /// Get receipts count per block. pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option { - let cache = self.inner.cache.read().await; - cache.tx_hashes_by_block_and_index.get(block_hash).map(|v| v.len()) + self.receipt_provider.receipts_count_per_block(block_hash).await } /// Get the system health. pub async fn system_health(&self) -> Result { - let health = self.inner.rpc.system_health().await?; + let health = self.rpc.system_health().await?; Ok(health) } @@ -697,8 +620,8 @@ impl Client { /// Get the block number of the latest block. pub async fn block_number(&self) -> Result { - let cache = self.inner.cache.read().await; - let latest_block = cache.buffer.back().ok_or(ClientError::CacheEmpty)?; + let latest_block = + self.block_provider.latest_block().await.ok_or(ClientError::CacheEmpty)?; Ok(latest_block.number()) } @@ -707,13 +630,8 @@ impl Client { &self, block_number: SubstrateBlockNumber, ) -> Result, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_number.get(&block_number) { - return Ok(Some(block.hash())); - } - - let hash = self.inner.rpc.chain_get_block_hash(Some(block_number.into())).await?; - Ok(hash) + let maybe_block = self.block_provider.block_by_number(block_number).await?; + Ok(maybe_block.map(|block| block.hash())) } /// Get a block for the specified hash or number. @@ -727,8 +645,8 @@ impl Client { self.block_by_number(n).await }, BlockNumberOrTag::BlockTag(_) => { - let cache = self.inner.cache.read().await; - Ok(cache.buffer.back().cloned()) + let block = self.block_provider.latest_block().await; + Ok(block) }, } } @@ -738,16 +656,7 @@ impl Client { &self, hash: &SubstrateBlockHash, ) -> Result>, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_hash.get(hash) { - return Ok(Some(block.clone())); - } - - match self.inner.api.blocks().at(*hash).await { - Ok(block) => Ok(Some(Arc::new(block))), - Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), - Err(err) => Err(err.into()), - } + self.block_provider.block_by_hash(hash).await } /// Get a block by number @@ -755,21 +664,12 @@ impl Client { &self, block_number: SubstrateBlockNumber, ) -> Result>, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_number.get(&block_number) { - return Ok(Some(block.clone())); - } - - let Some(hash) = self.get_block_hash(block_number).await? else { - return Ok(None); - }; - - self.block_by_hash(&hash).await + self.block_provider.block_by_number(block_number).await } /// Get the EVM block for the given hash. pub async fn evm_block(&self, block: Arc) -> Result { - let runtime_api = self.inner.api.runtime_api().at(block.hash()); + let runtime_api = self.api.runtime_api().at(block.hash()); let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; let gas_limit = gas_from_fee(max_fee); @@ -811,11 +711,11 @@ impl Client { /// Get the chain ID. pub fn chain_id(&self) -> u64 { - self.inner.chain_id + self.chain_id } /// Get the Max Block Weight. pub fn max_block_weight(&self) -> Weight { - self.inner.max_block_weight + self.max_block_weight } } diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs new file mode 100644 index 000000000000..1eab2b2f768c --- /dev/null +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -0,0 +1,85 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! The Ethereum JSON-RPC server. +use clap::Parser; +use pallet_revive_eth_rpc::{ + client::{connect, Client, SubstrateBlockNumber}, + BlockInfoProvider, DBReceiptProvider, ReceiptProvider, +}; +use sc_cli::SharedParams; +use std::sync::Arc; + +// Parsed command instructions from the command line +#[derive(Parser, Debug)] +#[clap(author, about, version)] +pub struct CliCommand { + /// The node url to connect to + #[clap(long, default_value = "ws://127.0.0.1:9944")] + pub node_rpc_url: String, + + /// If specified, indexing will walk back and index block until this block number. + #[clap(long)] + pub oldest_block: Option, + + /// The database used to store Ethereum transaction hashes. + #[clap(long)] + pub database_url: String, + + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, +} + +/// Initialize the logger +#[cfg(not(test))] +fn init_logger(params: &SharedParams) -> anyhow::Result<()> { + let mut logger = sc_cli::LoggerBuilder::new(params.log_filters().join(",")); + logger + .with_log_reloading(params.enable_log_reloading) + .with_detailed_output(params.detailed_log_output); + + if let Some(tracing_targets) = ¶ms.tracing_targets { + let tracing_receiver = params.tracing_receiver.into(); + logger.with_profiling(tracing_receiver, tracing_targets); + } + + if params.disable_log_color { + logger.with_colors(false); + } + + logger.init()?; + Ok(()) +} + +#[tokio::main] +pub async fn main() -> anyhow::Result<()> { + let CliCommand { node_rpc_url, database_url, shared_params, oldest_block, .. } = + CliCommand::parse(); + + #[cfg(not(test))] + init_logger(&shared_params)?; + + let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; + let block_provider = BlockInfoProvider::new(0, api.clone(), rpc.clone()); + let receipt_provider: Arc = + Arc::new(DBReceiptProvider::new(&database_url, false, block_provider.clone()).await?); + + let client = Client::new(api, rpc_client, rpc, block_provider, receipt_provider).await?; + client.subscribe_and_cache_receipts(oldest_block).await?; + + Ok(()) +} diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 230f2f8b7ef9..5e737eb4f932 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -35,6 +35,12 @@ pub mod subxt_client; #[cfg(test)] mod tests; +mod block_info_provider; +pub use block_info_provider::*; + +mod receipt_provider; +pub use receipt_provider::*; + mod rpc_health; pub use rpc_health::*; diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs new file mode 100644 index 000000000000..12f90bc341a0 --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -0,0 +1,238 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + client::SubstrateBlock, + subxt_client::{ + revive::{calls::types::EthTransact, events::ContractEmitted}, + system::events::ExtrinsicSuccess, + transaction_payment::events::TransactionFeePaid, + SrcChainConfig, + }, + ClientError, LOG_TARGET, +}; +use futures::{stream, StreamExt}; +use jsonrpsee::core::async_trait; +use pallet_revive::{ + create1, + evm::{GenericTransaction, Log, ReceiptInfo, TransactionSigned, H256, U256}, +}; +use sp_core::keccak_256; +use tokio::join; + +mod cache; +pub use cache::CacheReceiptProvider; + +mod db; +pub use db::DBReceiptProvider; + +/// Provide means to store and retrieve receipts. +#[async_trait] +pub trait ReceiptProvider: Send + Sync { + /// Insert receipts into the provider. + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]); + + /// Remove receipts with the given block hash. + async fn remove(&self, block_hash: &H256); + + /// Get the receipt for the given block hash and transaction index. + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option; + + /// Get the number of receipts per block. + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option; + + /// Get the receipt for the given transaction hash. + async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option; + + /// Get the signed transaction for the given transaction hash. + async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option; +} + +#[async_trait] +impl ReceiptProvider for (Main, Fallback) { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + join!(self.0.insert(block_hash, receipts), self.1.insert(block_hash, receipts)); + } + + async fn remove(&self, block_hash: &H256) { + join!(self.0.remove(block_hash), self.1.remove(block_hash)); + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option { + if let Some(receipt) = + self.0.receipt_by_block_hash_and_index(block_hash, transaction_index).await + { + return Some(receipt); + } + + self.1.receipt_by_block_hash_and_index(block_hash, transaction_index).await + } + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { + if let Some(count) = self.0.receipts_count_per_block(block_hash).await { + return Some(count); + } + self.1.receipts_count_per_block(block_hash).await + } + + async fn receipt_by_hash(&self, hash: &H256) -> Option { + if let Some(receipt) = self.0.receipt_by_hash(hash).await { + return Some(receipt); + } + self.1.receipt_by_hash(hash).await + } + + async fn signed_tx_by_hash(&self, hash: &H256) -> Option { + if let Some(tx) = self.0.signed_tx_by_hash(hash).await { + return Some(tx); + } + self.1.signed_tx_by_hash(hash).await + } +} + +/// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] and from an extrinsic. +pub async fn extract_receipt_from_extrinsic( + block: &SubstrateBlock, + ext: subxt::blocks::ExtrinsicDetails>, + call: EthTransact, +) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let events = ext.events().await?; + let tx_fees = events + .find_first::()? + .ok_or(ClientError::TxFeeNotFound) + .inspect_err( + |_| log::debug!(target: LOG_TARGET, "TransactionFeePaid not found in events for {}", block.number()), + )?; + let transaction_hash = H256(keccak_256(&call.payload)); + + let signed_tx = + TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?; + let from = signed_tx + .recover_eth_address() + .map_err(|_| ClientError::RecoverEthAddressFailed)?; + + let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); + let gas_price = tx_info.gas_price.unwrap_or_default(); + let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee)) + .checked_div(gas_price.as_u128()) + .unwrap_or_default(); + + let success = events.has::()?; + let transaction_index = ext.index(); + let block_number = U256::from(block.number()); + let block_hash = block.hash(); + + // get logs from ContractEmitted event + let logs = events + .iter() + .filter_map(|event_details| { + let event_details = event_details.ok()?; + let event = event_details.as_event::().ok()??; + + Some(Log { + address: event.contract, + topics: event.topics, + data: Some(event.data.into()), + block_number: Some(block_number), + transaction_hash, + transaction_index: Some(transaction_index.into()), + block_hash: Some(block_hash), + log_index: Some(event_details.index().into()), + ..Default::default() + }) + }) + .collect(); + + let contract_address = if tx_info.to.is_none() { + Some(create1( + &from, + tx_info + .nonce + .unwrap_or_default() + .try_into() + .map_err(|_| ClientError::ConversionFailed)?, + )) + } else { + None + }; + + log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: + {block_number:?}"); + let receipt = ReceiptInfo::new( + block_hash, + block_number, + contract_address, + from, + logs, + tx_info.to, + gas_price, + gas_used.into(), + success, + transaction_hash, + transaction_index.into(), + tx_info.r#type.unwrap_or_default(), + ); + Ok((signed_tx, receipt)) +} + +/// Extract receipts from block. +pub async fn extract_receipts_from_block( + block: &SubstrateBlock, +) -> Result, ClientError> { + // Filter extrinsics from pallet_revive + let extrinsics = block.extrinsics().await.inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number()); + })?; + + let extrinsics = extrinsics.iter().flat_map(|ext| { + let call = ext.as_extrinsic::().ok()??; + Some((ext, call)) + }); + + stream::iter(extrinsics) + .map(|(ext, call)| async move { extract_receipt_from_extrinsic(block, ext, call).await }) + .buffer_unordered(10) + .collect::>>() + .await + .into_iter() + .collect::, _>>() +} + +/// Extract receipt from transaction +pub async fn extract_receipts_from_transaction( + block: &SubstrateBlock, + transaction_index: usize, +) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let extrinsics = block.extrinsics().await?; + let ext = extrinsics + .iter() + .nth(transaction_index) + .ok_or(ClientError::EthExtrinsicNotFound)?; + + let call = ext + .as_extrinsic::()? + .ok_or_else(|| ClientError::EthExtrinsicNotFound)?; + extract_receipt_from_extrinsic(block, ext, call).await +} diff --git a/substrate/frame/revive/rpc/src/receipt_provider/cache.rs b/substrate/frame/revive/rpc/src/receipt_provider/cache.rs new file mode 100644 index 000000000000..39124929ec07 --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider/cache.rs @@ -0,0 +1,148 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use super::ReceiptProvider; +use jsonrpsee::core::async_trait; +use pallet_revive::evm::{ReceiptInfo, TransactionSigned, H256, U256}; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::RwLock; + +/// A `[ReceiptProvider]` that caches receipts in memory. +#[derive(Clone, Default)] +pub struct CacheReceiptProvider { + cache: Arc>, +} + +impl CacheReceiptProvider { + /// Get a read access on the shared cache. + async fn cache(&self) -> tokio::sync::RwLockReadGuard<'_, ReceiptCache> { + self.cache.read().await + } +} + +#[async_trait] +impl ReceiptProvider for CacheReceiptProvider { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + let mut cache = self.cache.write().await; + cache.insert(block_hash, receipts); + } + + async fn remove(&self, block_hash: &H256) { + let mut cache = self.cache.write().await; + cache.remove(block_hash); + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option { + let cache = self.cache().await; + let receipt_hash = cache + .transaction_hashes_by_block_and_index + .get(block_hash)? + .get(transaction_index)?; + let receipt = cache.receipts_by_hash.get(receipt_hash)?; + Some(receipt.clone()) + } + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { + let cache = self.cache().await; + cache.transaction_hashes_by_block_and_index.get(block_hash).map(|v| v.len()) + } + + async fn receipt_by_hash(&self, hash: &H256) -> Option { + let cache = self.cache().await; + cache.receipts_by_hash.get(hash).cloned() + } + + async fn signed_tx_by_hash(&self, hash: &H256) -> Option { + let cache = self.cache().await; + cache.signed_tx_by_hash.get(hash).cloned() + } +} + +#[derive(Default)] +struct ReceiptCache { + /// A map of receipts by transaction hash. + receipts_by_hash: HashMap, + + /// A map of Signed transaction by transaction hash. + signed_tx_by_hash: HashMap, + + /// A map of receipt hashes by block hash. + transaction_hashes_by_block_and_index: HashMap>, +} + +impl ReceiptCache { + /// Insert new receipts into the cache. + pub fn insert(&mut self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + if !receipts.is_empty() { + let values = receipts + .iter() + .map(|(_, receipt)| (receipt.transaction_index, receipt.transaction_hash)) + .collect::>(); + + self.transaction_hashes_by_block_and_index.insert(*block_hash, values); + + self.receipts_by_hash.extend( + receipts.iter().map(|(_, receipt)| (receipt.transaction_hash, receipt.clone())), + ); + + self.signed_tx_by_hash.extend( + receipts + .iter() + .map(|(signed_tx, receipt)| (receipt.transaction_hash, signed_tx.clone())), + ) + } + } + + /// Remove entry from the cache. + pub fn remove(&mut self, hash: &H256) { + if let Some(entries) = self.transaction_hashes_by_block_and_index.remove(hash) { + for hash in entries.values() { + self.receipts_by_hash.remove(hash); + self.signed_tx_by_hash.remove(hash); + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn cache_insert_and_remove_works() { + let mut cache = ReceiptCache::default(); + + for i in 1u8..=3 { + let hash = H256::from([i; 32]); + cache.insert( + &hash, + &[( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: hash, ..Default::default() }, + )], + ); + } + + cache.remove(&H256::from([1u8; 32])); + assert_eq!(cache.transaction_hashes_by_block_and_index.len(), 2); + assert_eq!(cache.receipts_by_hash.len(), 2); + assert_eq!(cache.signed_tx_by_hash.len(), 2); + } +} diff --git a/substrate/frame/revive/rpc/src/receipt_provider/db.rs b/substrate/frame/revive/rpc/src/receipt_provider/db.rs new file mode 100644 index 000000000000..4657bb20818b --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider/db.rs @@ -0,0 +1,159 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use super::*; +use crate::BlockInfoProvider; +use jsonrpsee::core::async_trait; +use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; +use sp_core::{H256, U256}; +use sqlx::SqlitePool; + +/// A `[ReceiptProvider]` that stores receipts in a SQLite database. +#[derive(Clone)] +pub struct DBReceiptProvider { + /// The SQLite database pool. + pool: SqlitePool, + /// The block provider used to fetch blocks, and reconstruct receipts. + block_provider: BlockInfoProvider, + /// weather or not we should write to the DB. + read_only: bool, +} + +impl DBReceiptProvider { + /// Create a new `DBReceiptProvider` with the given database URL and block provider. + pub async fn new( + database_url: &str, + read_only: bool, + block_provider: BlockInfoProvider, + ) -> Result { + let pool = SqlitePool::connect(database_url).await?; + Ok(Self { pool, block_provider, read_only }) + } +} + +#[async_trait] +impl ReceiptProvider for DBReceiptProvider { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + if self.read_only { + return + } + + let block_hash_str = hex::encode(block_hash); + for (_, receipt) in receipts { + let transaction_hash = hex::encode(receipt.transaction_hash); + let transaction_index = receipt.transaction_index.as_u32() as i32; + let result = sqlx::query!( + r#" + INSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index) + VALUES (?, ?, ?) + + ON CONFLICT(transaction_hash) DO UPDATE SET + block_hash = EXCLUDED.block_hash, + transaction_index = EXCLUDED.transaction_index + "#, + transaction_hash, + block_hash_str, + transaction_index + ) + .execute(&self.pool) + .await; + + if let Err(err) = result { + log::error!( + "Error inserting transaction for block hash {block_hash:?}: {:?}", + err + ); + } + } + } + + async fn remove(&self, _block_hash: &H256) {} + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { + let block_hash = hex::encode(block_hash); + let row = sqlx::query!( + r#" + SELECT COUNT(*) as count + FROM transaction_hashes + WHERE block_hash = ? + "#, + block_hash + ) + .fetch_one(&self.pool) + .await + .ok()?; + + Some(row.count as usize) + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option { + let block = self.block_provider.block_by_hash(block_hash).await.ok()??; + let transaction_index: usize = transaction_index.as_usize(); // TODO: check for overflow + let (_, receipt) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(receipt) + } + + async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { + let transaction_hash = hex::encode(transaction_hash); + let result = sqlx::query!( + r#" + SELECT block_hash, transaction_index + FROM transaction_hashes + WHERE transaction_hash = ? + "#, + transaction_hash + ) + .fetch_optional(&self.pool) + .await + .ok()??; + + let block_hash = result.block_hash.parse::().ok()?; + let transaction_index = result.transaction_index.try_into().ok()?; + + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (_, receipt) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(receipt) + } + + async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option { + let transaction_hash = hex::encode(transaction_hash); + let result = sqlx::query!( + r#" + SELECT block_hash, transaction_index + FROM transaction_hashes + WHERE transaction_hash = ? + "#, + transaction_hash + ) + .fetch_optional(&self.pool) + .await + .ok()??; + + let block_hash = result.block_hash.parse::().ok()?; + let transaction_index = result.transaction_index.try_into().ok()?; + + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (signed_tx, _) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(signed_tx) + } +} From 27cd348a64f5c3515e098b1929e780f4dbeb9c7a Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 20 Dec 2024 17:23:41 +0100 Subject: [PATCH 45/67] Add dockerfiles and tests --- ...81605402995b8c528d1008a6d087c7a221eab.json | 12 ++ ...cd84772906e269f43cd995909fede8836de8.json} | 4 +- ...77e451e9e4324ee5bd235fa130e7c00892c06.json | 12 -- ...710e21e167c8280deb0fa64deb340624f281e.json | 22 +++ ...aec40bd34c9d008d5dabc8676d1fa8189f24c.json | 28 ++++ ...65a14f4f538fdf91a7b903f6bedacb24a2792.json | 16 ++ ...04028e5fd1edb7287253421c6875e8ed73e9.json} | 4 +- substrate/frame/revive/rpc/Cargo.toml | 3 +- .../rpc/dockerfiles/eth-indexer/Dockerfile | 28 ++++ .../rpc/{ => dockerfiles/eth-rpc}/Dockerfile | 0 .../revive/rpc/src/block_info_provider.rs | 73 +++++++-- substrate/frame/revive/rpc/src/cli.rs | 8 +- substrate/frame/revive/rpc/src/client.rs | 4 +- substrate/frame/revive/rpc/src/eth-indexer.rs | 12 +- .../revive/rpc/src/receipt_provider/db.rs | 138 ++++++++++++++---- 15 files changed, 296 insertions(+), 68 deletions(-) create mode 100644 substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json rename substrate/frame/revive/rpc/.sqlx/{query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json => query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json} (52%) delete mode 100644 substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json create mode 100644 substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json create mode 100644 substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json create mode 100644 substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json rename substrate/frame/revive/rpc/.sqlx/{query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json => query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json} (62%) create mode 100644 substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile rename substrate/frame/revive/rpc/{ => dockerfiles/eth-rpc}/Dockerfile (100%) diff --git a/substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json b/substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json new file mode 100644 index 000000000000..faeddc993ad0 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "-- sqlite\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES ($1, $2, $3)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json b/substrate/frame/revive/rpc/.sqlx/query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json similarity index 52% rename from substrate/frame/revive/rpc/.sqlx/query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json rename to substrate/frame/revive/rpc/.sqlx/query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json index fc1858a521ad..2553cc0e92a6 100644 --- a/substrate/frame/revive/rpc/.sqlx/query-3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966.json +++ b/substrate/frame/revive/rpc/.sqlx/query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = ?\n ", + "query": "-- sqlite\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = $1\n ", "describe": { "columns": [ { @@ -16,5 +16,5 @@ false ] }, - "hash": "3e34e8e4bdeba2c5f2de915301d20958b338f9fef0d2575056f74f8c67bc3966" + "hash": "2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8" } diff --git a/substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json b/substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json deleted file mode 100644 index ac67147f464a..000000000000 --- a/substrate/frame/revive/rpc/.sqlx/query-8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES (?, ?, ?)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "8a99a538df1ca7c194b8c573c9f77e451e9e4324ee5bd235fa130e7c00892c06" -} diff --git a/substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json b/substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json new file mode 100644 index 000000000000..1d08450d36d9 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "-- pgsql\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Bpchar" + ] + }, + "nullable": [ + null + ] + }, + "hash": "9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json b/substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json new file mode 100644 index 000000000000..da9da6aa00a8 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "-- pgsql\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = $1\n\t\t\t", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "block_hash", + "type_info": "Bpchar" + }, + { + "ordinal": 1, + "name": "transaction_index", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Bpchar" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json b/substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json new file mode 100644 index 000000000000..f5f66cf0db8a --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "-- pgsql\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES ($1, $2, $3)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bpchar", + "Bpchar", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json b/substrate/frame/revive/rpc/.sqlx/query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json similarity index 62% rename from substrate/frame/revive/rpc/.sqlx/query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json rename to substrate/frame/revive/rpc/.sqlx/query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json index dcd8bc42f266..c5fcf311a49c 100644 --- a/substrate/frame/revive/rpc/.sqlx/query-920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d.json +++ b/substrate/frame/revive/rpc/.sqlx/query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = ?\n\t\t\t", + "query": "-- sqlite\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = $1\n\t\t\t", "describe": { "columns": [ { @@ -22,5 +22,5 @@ false ] }, - "hash": "920b0500651656cf52d1394d9c1810cde33043000207872088fa8903fa3c0e9d" + "hash": "ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9" } diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 70922f92cd54..528890322c83 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -69,10 +69,11 @@ subxt-signer = { workspace = true, optional = true, features = [ ] } hex = { workspace = true } ethabi = { version = "18.0.0" } -sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "sqlite"] } +sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "postgres", "sqlite"] } [features] example = ["rlp", "subxt-signer"] +sqlite = [] [dev-dependencies] env_logger = { workspace = true } diff --git a/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile new file mode 100644 index 000000000000..02af98af097e --- /dev/null +++ b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile @@ -0,0 +1,28 @@ +FROM rust AS builder + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + protobuf-compiler \ + clang libclang-dev + +WORKDIR /polkadot +COPY . /polkadot +RUN rustup component add rust-src +RUN cargo build --locked --profile production -p pallet-revive-eth-rpc --bin eth-indexer + +FROM docker.io/parity/base-bin:latest +COPY --from=builder /polkadot/target/production/eth-rpc /usr/local/bin + +USER root +RUN useradd -m -u 1001 -U -s /bin/sh -d /polkadot polkadot && \ +# unclutter and minimize the attack surface + rm -rf /usr/bin /usr/sbin && \ +# check if executable works in this container + /usr/local/bin/eth-indexer --help + +USER polkadot + +ENTRYPOINT ["/usr/local/bin/eth-indexer"] + +# We call the help by default +CMD ["--help"] diff --git a/substrate/frame/revive/rpc/Dockerfile b/substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile similarity index 100% rename from substrate/frame/revive/rpc/Dockerfile rename to substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile diff --git a/substrate/frame/revive/rpc/src/block_info_provider.rs b/substrate/frame/revive/rpc/src/block_info_provider.rs index e1bb51e9fc04..0c6132a02a39 100644 --- a/substrate/frame/revive/rpc/src/block_info_provider.rs +++ b/substrate/frame/revive/rpc/src/block_info_provider.rs @@ -20,6 +20,7 @@ use crate::{ subxt_client::SrcChainConfig, ClientError, LOG_TARGET, }; +use jsonrpsee::core::async_trait; use sp_core::H256; use std::{ collections::{HashMap, VecDeque}, @@ -28,9 +29,28 @@ use std::{ use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient}; use tokio::sync::RwLock; +/// BlockInfoProvider cache and retrieves information about blocks. +#[async_trait] +pub trait BlockInfoProvider: Send + Sync { + /// Cache a new block and return the pruned block hash. + async fn cache_block(&self, block: SubstrateBlock) -> Option; + + /// Return the latest ingested block. + async fn latest_block(&self) -> Option>; + + /// Get block by block_number. + async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result>, ClientError>; + + /// Get block by block hash. + async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError>; +} + /// Provides information about blocks. #[derive(Clone)] -pub struct BlockInfoProvider { +pub struct BlockInfoProviderImpl { /// The shared in memory cache. cache: Arc>>, @@ -41,8 +61,7 @@ pub struct BlockInfoProvider { api: OnlineClient, } -impl BlockInfoProvider { - /// Create a new `BlockInfoProvider` with the given cache size, rpc client and api client. +impl BlockInfoProviderImpl { pub fn new( cache_size: usize, api: OnlineClient, @@ -51,25 +70,24 @@ impl BlockInfoProvider { Self { api, rpc, cache: Arc::new(RwLock::new(BlockCache::new(cache_size))) } } - /// Get a read access on the shared cache. async fn cache(&self) -> tokio::sync::RwLockReadGuard<'_, BlockCache> { self.cache.read().await } +} - /// Cache new block and return the pruned block hash. - pub async fn cache_block(&self, block: SubstrateBlock) -> Option { +#[async_trait] +impl BlockInfoProvider for BlockInfoProviderImpl { + async fn cache_block(&self, block: SubstrateBlock) -> Option { let mut cache = self.cache.write().await; cache.insert(block) } - /// Return the latest ingested block. - pub async fn latest_block(&self) -> Option> { + async fn latest_block(&self) -> Option> { let cache = self.cache().await; cache.buffer.back().cloned() } - /// Get block by block_number. - pub async fn block_by_number( + async fn block_by_number( &self, block_number: SubstrateBlockNumber, ) -> Result>, ClientError> { @@ -85,11 +103,7 @@ impl BlockInfoProvider { self.block_by_hash(&hash).await } - /// Get block by block hash. - pub async fn block_by_hash( - &self, - hash: &H256, - ) -> Result>, ClientError> { + async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError> { let cache = self.cache().await; if let Some(block) = cache.blocks_by_hash.get(hash).cloned() { return Ok(Some(block)); @@ -170,7 +184,7 @@ impl BlockCache { } #[cfg(test)] -mod test { +pub mod test { use super::*; struct MockBlock { @@ -205,4 +219,31 @@ mod test { assert_eq!(cache.blocks_by_number.len(), 2); assert_eq!(cache.blocks_by_hash.len(), 2); } + + pub struct MockBlockInfoProvider; + + #[async_trait] + impl BlockInfoProvider for MockBlockInfoProvider { + async fn cache_block(&self, _block: SubstrateBlock) -> Option { + None + } + + async fn latest_block(&self) -> Option> { + None + } + + async fn block_by_number( + &self, + _block_number: SubstrateBlockNumber, + ) -> Result>, ClientError> { + Ok(None) + } + + async fn block_by_hash( + &self, + _hash: &H256, + ) -> Result>, ClientError> { + Ok(None) + } + } } diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index cda55b55f9ca..b18aed0bce8d 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -17,8 +17,9 @@ //! The Ethereum JSON-RPC server. use crate::{ client::{connect, Client}, - BlockInfoProvider, CacheReceiptProvider, DBReceiptProvider, EthRpcServer, EthRpcServerImpl, - ReceiptProvider, SystemHealthRpcServer, SystemHealthRpcServerImpl, + BlockInfoProvider, BlockInfoProviderImpl, CacheReceiptProvider, DBReceiptProvider, + EthRpcServer, EthRpcServerImpl, ReceiptProvider, SystemHealthRpcServer, + SystemHealthRpcServerImpl, }; use clap::Parser; use futures::{pin_mut, FutureExt}; @@ -143,7 +144,8 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let signals = tokio_runtime.block_on(async { Signals::capture() })?; let fut = async { let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; - let block_provider = BlockInfoProvider::new(cache_size, api.clone(), rpc.clone()); + let block_provider: Arc = + Arc::new(BlockInfoProviderImpl::new(cache_size, api.clone(), rpc.clone())); let receipt_provider: Arc = if let Some(database_url) = database_url.as_ref() { Arc::new(( diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index b1bfa9f07dca..1162b9c9d887 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -202,7 +202,7 @@ pub struct Client { rpc_client: ReconnectingRpcClient, rpc: LegacyRpcMethods, receipt_provider: Arc, - block_provider: BlockInfoProvider, + block_provider: Arc, chain_id: u64, max_block_weight: Weight, } @@ -257,7 +257,7 @@ impl Client { api: OnlineClient, rpc_client: ReconnectingRpcClient, rpc: LegacyRpcMethods, - block_provider: BlockInfoProvider, + block_provider: Arc, receipt_provider: Arc, ) -> Result { let (chain_id, max_block_weight) = diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs index 1eab2b2f768c..1c14dbc4adbb 100644 --- a/substrate/frame/revive/rpc/src/eth-indexer.rs +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -18,7 +18,7 @@ use clap::Parser; use pallet_revive_eth_rpc::{ client::{connect, Client, SubstrateBlockNumber}, - BlockInfoProvider, DBReceiptProvider, ReceiptProvider, + BlockInfoProvider, BlockInfoProviderImpl, DBReceiptProvider, ReceiptProvider, }; use sc_cli::SharedParams; use std::sync::Arc; @@ -67,14 +67,16 @@ fn init_logger(params: &SharedParams) -> anyhow::Result<()> { #[tokio::main] pub async fn main() -> anyhow::Result<()> { - let CliCommand { node_rpc_url, database_url, shared_params, oldest_block, .. } = - CliCommand::parse(); + let CliCommand { + node_rpc_url, database_url, shared_params: _shared_params, oldest_block, .. + } = CliCommand::parse(); #[cfg(not(test))] - init_logger(&shared_params)?; + init_logger(&_shared_params)?; let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; - let block_provider = BlockInfoProvider::new(0, api.clone(), rpc.clone()); + let block_provider: Arc = + Arc::new(BlockInfoProviderImpl::new(0, api.clone(), rpc.clone())); let receipt_provider: Arc = Arc::new(DBReceiptProvider::new(&database_url, false, block_provider.clone()).await?); diff --git a/substrate/frame/revive/rpc/src/receipt_provider/db.rs b/substrate/frame/revive/rpc/src/receipt_provider/db.rs index 4657bb20818b..c160f13fdbb8 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider/db.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider/db.rs @@ -14,20 +14,45 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + use super::*; use crate::BlockInfoProvider; use jsonrpsee::core::async_trait; use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; use sp_core::{H256, U256}; +use std::sync::Arc; + +#[cfg(not(any(feature = "sqlite", test)))] +use sqlx::PgPool; +#[cfg(any(feature = "sqlite", test))] use sqlx::SqlitePool; +/// Wrapper around [`sqlx::query!`] that injects a comment with the database type to the query +/// string, so that we can generate sqlx artifact with different hashes for each database. +macro_rules! query { + ($query:expr, $($args:tt)*) => {{ + #[cfg(any(feature = "sqlite", test))] + { + sqlx::query!("-- sqlite" + $query, $($args)*) + } + #[cfg(not(any(feature = "sqlite", test)))] + { + sqlx::query!("-- pgsql" + $query, $($args)*) + } + }}; +} + /// A `[ReceiptProvider]` that stores receipts in a SQLite database. #[derive(Clone)] pub struct DBReceiptProvider { - /// The SQLite database pool. + /// The database pool. + #[cfg(any(feature = "sqlite", test))] pool: SqlitePool, + #[cfg(not(any(feature = "sqlite", test)))] + pool: PgPool, + /// The block provider used to fetch blocks, and reconstruct receipts. - block_provider: BlockInfoProvider, + block_provider: Arc, /// weather or not we should write to the DB. read_only: bool, } @@ -37,11 +62,34 @@ impl DBReceiptProvider { pub async fn new( database_url: &str, read_only: bool, - block_provider: BlockInfoProvider, + block_provider: Arc, ) -> Result { + #[cfg(any(feature = "sqlite", test))] let pool = SqlitePool::connect(database_url).await?; + #[cfg(not(any(feature = "sqlite", test)))] + let pool = PgPool::connect(database_url).await?; + Ok(Self { pool, block_provider, read_only }) } + + async fn fetch_row(&self, transaction_hash: &H256) -> Option<(H256, usize)> { + let transaction_hash = hex::encode(transaction_hash); + let result = query!( + r#" + SELECT block_hash, transaction_index + FROM transaction_hashes + WHERE transaction_hash = $1 + "#, + transaction_hash + ) + .fetch_optional(&self.pool) + .await + .ok()??; + + let block_hash = result.block_hash.parse::().ok()?; + let transaction_index = result.transaction_index.try_into().ok()?; + Some((block_hash, transaction_index)) + } } #[async_trait] @@ -55,10 +103,11 @@ impl ReceiptProvider for DBReceiptProvider { for (_, receipt) in receipts { let transaction_hash = hex::encode(receipt.transaction_hash); let transaction_index = receipt.transaction_index.as_u32() as i32; - let result = sqlx::query!( + + let result = query!( r#" INSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index) - VALUES (?, ?, ?) + VALUES ($1, $2, $3) ON CONFLICT(transaction_hash) DO UPDATE SET block_hash = EXCLUDED.block_hash, @@ -84,11 +133,11 @@ impl ReceiptProvider for DBReceiptProvider { async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { let block_hash = hex::encode(block_hash); - let row = sqlx::query!( + let row = query!( r#" SELECT COUNT(*) as count FROM transaction_hashes - WHERE block_hash = ? + WHERE block_hash = $1 "#, block_hash ) @@ -96,7 +145,12 @@ impl ReceiptProvider for DBReceiptProvider { .await .ok()?; - Some(row.count as usize) + #[cfg(any(feature = "sqlite", test))] + let count = row.count as usize; + #[cfg(not(any(feature = "sqlite", test)))] + let count = row.count.unwrap_or_default() as usize; + + Some(count) } async fn receipt_by_block_hash_and_index( @@ -112,21 +166,7 @@ impl ReceiptProvider for DBReceiptProvider { } async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { - let transaction_hash = hex::encode(transaction_hash); - let result = sqlx::query!( - r#" - SELECT block_hash, transaction_index - FROM transaction_hashes - WHERE transaction_hash = ? - "#, - transaction_hash - ) - .fetch_optional(&self.pool) - .await - .ok()??; - - let block_hash = result.block_hash.parse::().ok()?; - let transaction_index = result.transaction_index.try_into().ok()?; + let (block_hash, transaction_index) = self.fetch_row(&transaction_hash).await?; let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; let (_, receipt) = @@ -136,11 +176,11 @@ impl ReceiptProvider for DBReceiptProvider { async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option { let transaction_hash = hex::encode(transaction_hash); - let result = sqlx::query!( + let result = query!( r#" SELECT block_hash, transaction_index FROM transaction_hashes - WHERE transaction_hash = ? + WHERE transaction_hash = $1 "#, transaction_hash ) @@ -157,3 +197,51 @@ impl ReceiptProvider for DBReceiptProvider { Some(signed_tx) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::MockBlockInfoProvider; + use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; + use sp_core::H256; + use sqlx::SqlitePool; + + async fn setup_sqlite_provider(pool: SqlitePool) -> DBReceiptProvider { + DBReceiptProvider { + pool, + block_provider: Arc::new(MockBlockInfoProvider {}), + read_only: false, + } + } + + #[sqlx::test] + async fn test_insert(pool: SqlitePool) { + let provider = setup_sqlite_provider(pool).await; + let block_hash = H256::default(); + let receipts = vec![(TransactionSigned::default(), ReceiptInfo::default())]; + + provider.insert(&block_hash, &receipts).await; + let row = provider.fetch_row(&receipts[0].1.transaction_hash).await; + assert_eq!(row, Some((block_hash, 0))); + } + + #[sqlx::test] + async fn test_receipts_count_per_block(pool: SqlitePool) { + let provider = setup_sqlite_provider(pool).await; + let block_hash = H256::default(); + let receipts = vec![ + ( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: H256::from([0u8; 32]), ..Default::default() }, + ), + ( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: H256::from([1u8; 32]), ..Default::default() }, + ), + ]; + + provider.insert(&block_hash, &receipts).await; + let count = provider.receipts_count_per_block(&block_hash).await; + assert_eq!(count, Some(2)); + } +} From b5b23cc114c3582624fd27fa347ce4d19e4164e0 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 20 Dec 2024 17:37:36 +0100 Subject: [PATCH 46/67] small fixes --- substrate/frame/revive/rpc/src/block_info_provider.rs | 3 ++- substrate/frame/revive/rpc/src/cli.rs | 2 +- substrate/frame/revive/rpc/src/eth-indexer.rs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/block_info_provider.rs b/substrate/frame/revive/rpc/src/block_info_provider.rs index 0c6132a02a39..86ff53fb5dfd 100644 --- a/substrate/frame/revive/rpc/src/block_info_provider.rs +++ b/substrate/frame/revive/rpc/src/block_info_provider.rs @@ -135,6 +135,7 @@ struct BlockCache { /// Provides information about a block, /// This is an abstratction on top of [`SubstrateBlock`] used to test the [`BlockCache`]. +/// Can be removed once https://github.com/paritytech/subxt/issues/1883 is fixed. trait BlockInfo { /// Returns the block hash. fn hash(&self) -> H256; @@ -167,7 +168,6 @@ impl BlockCache { let mut pruned_block_hash = None; if self.buffer.len() >= self.max_cache_size { if let Some(block) = self.buffer.pop_front() { - log::trace!(target: LOG_TARGET, "Pruning block: {}", block.number()); let hash = block.hash(); self.blocks_by_hash.remove(&hash); self.blocks_by_number.remove(&block.number()); @@ -220,6 +220,7 @@ pub mod test { assert_eq!(cache.blocks_by_hash.len(), 2); } + /// A Noop BlockInfoProvider used to test [`db::DBReceiptProvider`]. pub struct MockBlockInfoProvider; #[async_trait] diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index b18aed0bce8d..d63d596ab7a8 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -51,7 +51,7 @@ pub struct CliCommand { /// The database used to store Ethereum transaction hashes. /// This is only useful if the node needs to act as an archive node and respond to Ethereum RPC - /// querires for transactions that are not in the in memory cache. + /// queries for transactions that are not in the in memory cache. #[clap(long)] pub database_url: Option, diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs index 1c14dbc4adbb..3e7f6b6fa91b 100644 --- a/substrate/frame/revive/rpc/src/eth-indexer.rs +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -31,7 +31,8 @@ pub struct CliCommand { #[clap(long, default_value = "ws://127.0.0.1:9944")] pub node_rpc_url: String, - /// If specified, indexing will walk back and index block until this block number. + /// Specifies the block number to start indexing from, going backwards from the current block. + /// If not provided, only new blocks will be indexed #[clap(long)] pub oldest_block: Option, From 33d2cc10b6ce9bc0f7333b38d2108237f5ecae59 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 20 Dec 2024 17:48:11 +0100 Subject: [PATCH 47/67] nit --- .../frame/revive/rpc/src/receipt_provider.rs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs index 12f90bc341a0..5c102b3d3d41 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -118,20 +118,28 @@ pub async fn extract_receipt_from_extrinsic( ext: subxt::blocks::ExtrinsicDetails>, call: EthTransact, ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let transaction_index = ext.index(); + let block_number = U256::from(block.number()); + let block_hash = block.hash(); let events = ext.events().await?; + + let success = events.has::().inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to lookup for ExtrinsicSuccess event in block {block_number}: {err:?}") + })?; let tx_fees = events .find_first::()? .ok_or(ClientError::TxFeeNotFound) .inspect_err( - |_| log::debug!(target: LOG_TARGET, "TransactionFeePaid not found in events for {}", block.number()), + |err| log::debug!(target: LOG_TARGET, "TransactionFeePaid not found in events for block {block_number}\n{err:?}") )?; let transaction_hash = H256(keccak_256(&call.payload)); let signed_tx = TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?; - let from = signed_tx - .recover_eth_address() - .map_err(|_| ClientError::RecoverEthAddressFailed)?; + let from = signed_tx.recover_eth_address().map_err(|_| { + log::error!(target: LOG_TARGET, "Failed to recover eth address from signed tx"); + ClientError::RecoverEthAddressFailed + })?; let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); let gas_price = tx_info.gas_price.unwrap_or_default(); @@ -139,11 +147,6 @@ pub async fn extract_receipt_from_extrinsic( .checked_div(gas_price.as_u128()) .unwrap_or_default(); - let success = events.has::()?; - let transaction_index = ext.index(); - let block_number = U256::from(block.number()); - let block_hash = block.hash(); - // get logs from ContractEmitted event let logs = events .iter() @@ -178,8 +181,7 @@ pub async fn extract_receipt_from_extrinsic( None }; - log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: - {block_number:?}"); + log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: {block_number:?}"); let receipt = ReceiptInfo::new( block_hash, block_number, From d27a12a951e4a2918743af86c0694b2501136900 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sun, 22 Dec 2024 15:43:34 +0100 Subject: [PATCH 48/67] refactor js stuff --- .../frame/revive/rpc/examples/js/bun.lockb | Bin 40649 -> 43178 bytes .../rpc/examples/js/contracts/Flipper.sol | 38 +++---- .../frame/revive/rpc/examples/js/package.json | 12 +- .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4532 -> 5260 bytes .../rpc/examples/js/src/build-contracts.ts | 7 +- .../rpc/examples/js/src/geth-diff.test.ts | 66 ++++++++++- .../revive/rpc/examples/js/src/spammer.ts | 104 ++++++++++++++++++ .../js/src/{geth-diff-setup.ts => util.ts} | 74 +++---------- .../revive/rpc/src/block_info_provider.rs | 2 +- 9 files changed, 212 insertions(+), 91 deletions(-) create mode 100644 substrate/frame/revive/rpc/examples/js/src/spammer.ts rename substrate/frame/revive/rpc/examples/js/src/{geth-diff-setup.ts => util.ts} (62%) diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 67df5841e43fba141c7a146a1e4a8958b4c7a84c..e800d6d4d2d9d684d5e3b778792bc3fe6c545c40 100755 GIT binary patch delta 9078 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 VAIyeI6GfUAhV!v)wJ%KI{~N+$#c==t delta 7866 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 diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol index 51aaafcae428..36681ae1be93 100644 --- a/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol +++ b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol @@ -14,22 +14,22 @@ contract Flipper { } } -// FlipperCaller - Interacts with the Flipper contract -contract FlipperCaller { - // Address of the Flipper contract - address public flipperAddress; - - // Constructor to initialize Flipper's address - constructor(address _flipperAddress) { - flipperAddress = _flipperAddress; - } - - function callFlip() external { - Flipper(flipperAddress).flip(); - } - - function callGetValue() external view returns (bool) { - return Flipper(flipperAddress).getValue(); - } -} - +// // FlipperCaller - Interacts with the Flipper contract +// contract FlipperCaller { +// // Address of the Flipper contract +// address public flipperAddress; +// +// // Constructor to initialize Flipper's address +// constructor(address _flipperAddress) { +// flipperAddress = _flipperAddress; +// } +// +// function callFlip() external { +// Flipper(flipperAddress).flip(); +// } +// +// function callGetValue() external view returns (bool) { +// return Flipper(flipperAddress).getValue(); +// } +// } +// diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 0119f4f34a17..11d742b2b978 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -9,15 +9,15 @@ "preview": "vite preview" }, "dependencies": { - "@parity/revive": "^0.0.5", + "@parity/revive": "^0.0.8", "ethers": "^6.13.4", "solc": "^0.8.28", - "viem": "^2.21.47" + "viem": "^2.21.55" }, "devDependencies": { - "prettier": "^3.3.3", - "@types/bun": "^1.1.13", - "typescript": "^5.5.3", - "vite": "^5.4.8" + "prettier": "^3.4.2", + "@types/bun": "^1.1.14", + "typescript": "^5.7.2", + "vite": "^5.4.11" } } diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index 585fbb392a314c15a35e7e529106738bde3be02a..1e6f0127e60a5bed2e8d3ddc8f00f651150b9439 100644 GIT binary patch literal 5260 zcmcIoeQX=&dB1z_#P^POG>^1MollBIX{RsGS=IC-Y_e{Qz{Gcyt+EuIa`%RjEbDrA zloGr0hbTM1)JCgl*FYsknJB01Tu+TY(zNc@RIbwi-XE=#ro}p-7`(qKU zA%Do)bEIXvPKFf0Cg3@e@4fHy{d=D0Rr1L_6m_bWs{GNI`llFsk&3b7f9G{qSRfye z4DVwf27V+Y5Gg_x9K0#zou<8j?SUC(Rt=w zrj~t#eVRRAbG2sG^C12WzK&nz2(Nf8uTQ8KP6;L9yFyI-y7;R2AHLQtXSe)tOSk`= z{}=wF(s}9MB{9$u_-G(Cw&j?_V!!t>7JNj1SA;}1H zb|?}ItOk$LEM_^6gr4um5`}y;#k#y0G>YUNCa}M5fQhoq0MqF{+F7O_M_2}Xut#Ef zkK~zT>_BY8kj_&^=p1E)7b!b*kpjl4a5kLDhLhQ_mJP>{73<*x#7Yu7LmZ9dVq%FC z7qB>q{rE>z9|KJL5D4mHK}H`1((H#D)rUEALR5G2y#?N!5>$aRzvK;$t+uVSE%q*O zh7+xy9d8_^+ulmQg~eC+bO~epHx$c7SkRCA?k$XuvK&|in!68h)&L_@$graW3@rC( z$G-s9!+wwCeI9me#E$yp2xQ-5KDogs%Raf*CrdtA^vRr0hMlU*S^OOAZJ_Nai)ofu zdpv=v54Zf~Ek7PWl#w%rIAd5c_RGd##OQA~20B?BtH_b+;p{5Nu_wnVb&?}F$vnxc zOWxjsm)M+n(yN~2$dGxSD;LVk<+<`GD}bu~*oFt+4-apKLL}Et24wLpCQc_Aj)-sZ z;w;XMM#LrFnTx>pelAHNKOWsMhN7sQ#p9G=OCBj$UdMP~gIG~}bli0m#SjP9YyWwj zIFYtlV$1b~NMnIc&w>)|Z7U?#*|yZXz>BZo-aPN;>#LTA_W~bfW77o{4^;ShB>53| zwQ-9KN6DoEMcy2z%46k|Z40?lq^*!%;Ev)A_yZSH4YL?hU~k^fy-F@k!PXL09yP4? z@}yyPX2bbx_+U0{XT$w=-726U^@VJmoUN0xbzBywjvJP2*R9>ZPSHj#f-a!r$nG~B z$k7cJ_aHE9|70yG$j)d4>;?aTwUB|?))JL%odPNVjZ^lv)!^J}YT78t#;ioFb_3jC zbsDqiG_t`y;3hzB^>CZfRQBA&ICzWXqGUlMiy5*MBg@J1@$#g&!a*>~b08&Gh=@O^ z#HGafxnHd#FtXDlH3Z=7zpi^Z$VkljT$X^k9t<$)bP>7zcQ-Kp{yM~p*qvM+WU{>L zXg8rzp^|zm6@e8yaOS2VWf&urWQ?%J*r9%=g1}8Jn-_o+T~kRUV<>qlt+g~BY4+^pItt)mBqW>I6|&`oc8Z}{(EbmKg1gQlhrU9mi7mc1e2jMTp&)eJV$bw><+-)Qi(Darzm5g09E?-xz-SzI{@c0DDUQ#n^c~_hxAd9`H(*P zHb19A!}zbEY-K7q ziIJrwS$dV%a z6?uyy`xIGJWI>U=ip(oA@WG1gQREs$W)+#)tH>y&$n@X3rBmS|5ajZxlZOJLmFIF$ zWlLNo&dzB!@=(cOjd3JoSR8a13Va&{>I7;BiXfI_Q!5^6rvlm^rok9MwnzFi0e+ga zuQi4uzSe#u+ZHq^YY>hc1hUcHdTCgBWLpt<`y5m>NXc>JYn?!>VHaRM2J5E~viea^ zz>f_ESrpS#1I0vUU^A3?DD^px0ngT#B2O+pna7;OA}a!$$Y3X93~FS!oiT=??nfA7 zCNQr)&WsfI-XA%i{5mjOyX7Ls ziJG(jU&vXa06JITLI0|Xou5%K`WesKQui;nxmajG9f`sO5U}IQuIFJO==zH;zp#eR#VLe4x@X-$^25G>D4eJqD zAEsdtrCoeH4s+{TmflS4P8g_IffZTSBmMav7Sg*q=lsPpApFugZD%&S@9nU}((vNj zFf?VzELaBVppjCB%*70-4$vZE?hq4&V515Kqp4%WiMex!og_n==uUTlM7M5~I0tga zorC+<$7#stEfhF9RkzMU7xsMuX-f@ zD7@rg@`zMQ))N^J_76A7pd{=H2%045K_Ri#8RbA-XHIgKWte=xRzV91daaNM3*B{2 zfpaZgmYmmR@q$-etXQ2#f<_CvkU&B=^WH6jULzz}q1z+u2FC6@<-9IAKb6I|h1&zw z2}aPj2?<%~R)pR4@7*IL9uT@4gx%YPU5(B%2a=tiO3tV(UUElIx6zF8!Y*D&a6-3N z*e$q5!1^62D_BVQgldmZTo*>oWHRN=09d!Ox!@DmBy!eq{J_Ohw-OwRN$*s;QN2y^#HkmTLs46q+w^I z$wmHL9-(<_|sUkHy~CMRPaYZgFt$sT4eLZNWT?%n@Y%sbGHJ(>B!hRC-7hP1M$%-YNKn5mLvghj7D$piXeTE1dcw zZg^QxU-tH{cpLtT2e|Z>ynb)DQ469rBlqN2uV6e!m927yY=ePS;mXU^+ot!*{FKzCjjQSxRlV3%{9e~iSXZZ2 zJ+<7$HNkhiP|Htqe21P&6ur~G&F#X*>97b!lGJ zXQZZYye#R<-lOh|}pnn)HC!RDFq@o|v)2{_r|axbby_}S8-r&D?{$WPDD z1OownTH8}BreBy>L0flMb8~Zb(|l2FafKv`pkqr@^=3F<3 z9&h-ULSer7WV{(PY^qqH>iV*tDuSNw!E~w+2BadOJ5iibPZSF9x?L~Ci;L5R=H^!T p0iJPEr`48Crt_eP`(5u!e^Y{pr@VaA1f^>c;4;vn{{v=KaybA1 literal 4532 zcmcInYiu0V6}~gO>pMGk%-DF>lLutH8Lyl)0&kYmq=36vo5{>N!0h0_mKJOiZ)Vpq ziD%+u@7Po_)Ros+*0Q&uwo@vlk&z$$(MUC~L{*l`a>7fZt!P6fR2T7ss#PICOPY|l zXU1_7N`4eXS9|7h@44sRbIx}jqdssCLbDaf`B50X96?1CareEm!X9x0RRRe>w*Y+$ zs1fKspcbILKs`WD0gVFfZ0qc_sinQGyRBvC-Y)CvTj$=k_N#CE(mlzYd)t#O_Jf}6 zZrRtp*KE_1W^z|cclxnpOJ{Odx0&qTZ|=D&?AmvG-M(a7XUo#yJ`j<9?6LjbZI5;) zofY=TWuXV!I`=1Ay3Mvd`*tNwGuiH`poez5?b8gICGE+s&b?O2@Z?5%sP|!X0QI8Z zp_SAflt%5O?xS<`3HmRz$5rW$xf#z6kH4h0)2b^ zROw{ty0W{d_~k#vC!^U{0YUcDt=k)A0QN+9L1p4AkmnglT*XMI)*|SL$3=BfO?9bDE=TU-|sSIL3>lqn=F4WGX?XBVS5f@T;5NGqU%kul*ST`Oh|59Q@~KxLkW}dodtqgm1vu5X01q`T0K*mr zUaJ7~&abZl16qu9!hudR?|Q{$bf}gtP+^9psf>On72kIJjWb6MxZ>M>z5jb3U`pHe z!`XAYCR|HCGj#cLZUcP)+8r{P_@C8z?)KoRPC z80ax{^|~0aW^4dN0H#ashgc$6{naaQUGwpUIQGEK5da6oPbfoDawtpE zYG|BvB$N$($e12O#?&w}CbL&ze)9=f_XbV$ghgy<*th`OJ5c>{YVQLfOKNw}N{|3` z0i}Ri>93%6E4{3CN+kmvoP`W9h8`ghR-pD$)(|e9kK-F?P|yKxgiNbsCJ}l(lnafA zW{NN2;;T%NIM6+X!7xtp`7>pt1*5ah*j;aQ@YW=RyMyGwF%b3}z-a=IJWCjfUn}u9 zOZ-g|zeeI$OZ+N{Un%jt#8*qaU*dfdUnTK3N_?fnS4cc3@i$0(xx}xK_%ewvm3UU- zy%G;{u*8>0yhq~Q67Sj}@w6uK)K8F+;v`03OTcNNOrAhY%c1chC%_!PH~|u2y_f+3g`$OX50{5DkpyM?>MFa)PnOGT!7b)kQ^-1f*f3+`JCPd zDfc~C^C7(`2hY)qO7MMpF{dAa;m>H20&pRh9CW+fMv8Ie^dgLxxh#EwW+@H7OS25M zqgpUsEI2}iQ$V&k1~i2X{Rp!2jQ%Fv;H;Ltb5ORU+P%uo=V7#=?$dL2UfJ-`ISv2K1IHl{X(ds*|!l0HQUDHFdz^LVye{v;1~e# zwm-O*8iCti{#P9T{$eJTSS~@A{j_-sCXQbu=>!=C4FLi&8B37yhyn2lNjKtDc8CQ? z6YU4d?nv=P2I7@;%Eec%S1R{j1R($!4JR*JlbICAlp%|&+_%328^tez9lLY^Qa9us z87ioi5?oLdaRvtK>}Wj)p#Bi(eV_#-IBFuWJE#eVu+`+KoQ|kmYGEnRcuM8&{0HO( z;Dx-<@W+3~@n1h9?VP!-!jw5vsWf9Z<}(bW!~8gxpW&g#1C~wMXDU@@%x^YT=d%oy zwEPU0KfxD|v&AVU56~yHDO09m#%Qz2^+gs_@t85U*;Ha~2f=pdkUzoYU+0Ujm0oTg z23@AI(v0zD(<*cO>M!z`v75}MHRkr4&24M*GYssUf1S%`_~I$M$+9!JvD4%@ z1!DTba>z!0_DLN7^1^b+{x6Nu6Bn$EQ&D74BJp*fD^Y&4di@y7D9Dj1&hWF*7zO0mV8fN}<{%gWF7*AuKIuF}B5P$Og=O8YB zANJZ~&vxk%yUSRHcm&G34ey$;V6Q9{0>Y@0pc>*)#WC$y^T4ZlY(8 zGVs|nd!VFW?@yf4nPL+6zjlh0YNaNrX6Cx^$!D0kXT0+R-np&#Y>xm-mZe7G^tp{bne1_=*_J+QpSCD<3Aupl_>fSwTPd@Mt+#sza z-a3n(?H9cp#41g$eKv4ge>9uT9um_9ad23S7DR2Tk?~d(3+G;tQEvqR(xw%y&tY>Y+WEwThqwYxVg&4qf#UiDHNK2 zJRy!WMx$CGrWK~-S|tYiXt8Kij0V1`C^L#ykZS^g^-Aq}rFjDO@s5bou*-;8TT>8~ zEB~k*Q+exO#7I2{O= diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index a37b850214b8..446a629b123f 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -55,17 +55,14 @@ for (const file of input) { } console.log('Compiling with revive...') - const reviveOut = await compile(input) + const reviveOut = await compile(input, { wasm: !!process.env.WASM }) for (const contracts of Object.values(reviveOut.contracts)) { for (const [name, contract] of Object.entries(contracts)) { console.log(`📜 Add PVM contract ${name}`) const abi = contract.abi const abiName = `${name}Abi` - writeFileSync( - join(abiDir, `${name}.json`), - JSON.stringify(abi, null, 2) - ) + writeFileSync(join(abiDir, `${name}.json`), JSON.stringify(abi, null, 2)) writeFileSync( join(abiDir, `${name}.ts`), diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index d5a396aa2aca..815ae7dde0b6 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -1,9 +1,73 @@ -import { jsonRpcErrors, procs, createEnv, getByteCode } from './geth-diff-setup.ts' +import { + jsonRpcErrors, + createEnv, + getByteCode, + killProcessOnPort, + waitForHealth, + polkadotSdkPath, +} from './util.ts' import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' import { encodeFunctionData, Hex, parseEther } from 'viem' import { ErrorTesterAbi } from '../abi/ErrorTester' import { FlipperCallerAbi } from '../abi/FlipperCaller' import { FlipperAbi } from '../abi/Flipper' +import { Subprocess, spawn } from 'bun' + +const procs: Subprocess[] = [] +beforeAll(async () => { + if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + await (async () => { + killProcessOnPort(8546) + const proc = spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) + + await waitForHealth('http://localhost:8546').catch() + return proc + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545').catch() + return proc + })() + ) + } +}) afterEach(() => { jsonRpcErrors.length = 0 diff --git a/substrate/frame/revive/rpc/examples/js/src/spammer.ts b/substrate/frame/revive/rpc/examples/js/src/spammer.ts new file mode 100644 index 000000000000..c038afa71f0a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/spammer.ts @@ -0,0 +1,104 @@ +import { spawn } from 'bun' +import { + createEnv, + getByteCode, + killProcessOnPort, + polkadotSdkPath, + timeout, + wait, + waitForHealth, +} from './util' +import { FlipperAbi } from '../abi/Flipper' + +//Run the substate node +console.log('🚀 Start kitchensink...') +killProcessOnPort(9944) +spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } +) + +// Run eth-indexer +console.log('🔍 Start indexer...') +spawn( + [ + './target/debug/eth-indexer', + '--node-rpc-url=ws://localhost:9944', + '-l=eth-rpc=debug', + '--database-url ${polkadotSdkPath}/substrate/frame/revive/rpc/tx_hashes.db', + ], + { + stdout: Bun.file('/tmp/eth-indexer.out.log'), + stderr: Bun.file('/tmp/eth-indexer.err.log'), + cwd: polkadotSdkPath, + } +) + +// Run eth-rpc on 8545 +console.log('💻 Start eth-rpc...') +killProcessOnPort(8545) +spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } +) +await waitForHealth('http://localhost:8545').catch() + +const env = await createEnv('kitchensink') +const wallet = env.accountWallet + +console.log('🚀 Deploy flipper...') +const hash = await wallet.deployContract({ + abi: FlipperAbi, + bytecode: getByteCode('Flipper'), +}) + +const deployReceipt = await wallet.waitForTransactionReceipt({ hash }) +if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') +const flipperAddr = deployReceipt.contractAddress + +let nonce = await wallet.getTransactionCount(wallet.account) +let callCount = 0 + +console.log('🔄 Starting nonce:', nonce) +console.log('🔄 Starting loop...') +try { + while (true) { + callCount++ + console.log(`🔄 Call flip (${callCount})...`) + const { request } = await wallet.simulateContract({ + account: wallet.account, + address: flipperAddr, + abi: FlipperAbi, + functionName: 'flip', + }) + + console.log(`🔄 Submit flip (call ${callCount}...`) + + await Promise.race([ + (async () => { + const hash = await wallet.writeContract(request) + await wallet.waitForTransactionReceipt({ hash }) + })(), + timeout(15_000), + ]) + } +} catch (err) { + console.error('Failed with error:', err) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts b/substrate/frame/revive/rpc/examples/js/src/util.ts similarity index 62% rename from substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts rename to substrate/frame/revive/rpc/examples/js/src/util.ts index 3db2453f2475..bdc64eea1ef5 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts +++ b/substrate/frame/revive/rpc/examples/js/src/util.ts @@ -1,10 +1,10 @@ -import { spawn, spawnSync, Subprocess } from 'bun' +import { spawnSync } from 'bun' import { resolve } from 'path' import { readFileSync } from 'fs' import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' +import { privateKeyToAccount, nonceManager } from 'viem/accounts' -export function getByteCode(name: string, evm: boolean): Hex { +export function getByteCode(name: string, evm: boolean = false): Hex { const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) return `0x${Buffer.from(bytecode).toString('hex')}` } @@ -15,6 +15,8 @@ export type JsonRpcError = { data: Hex } +export const polkadotSdkPath = resolve(__dirname, '../../../../../../..') + export function killProcessOnPort(port: number) { // Check which process is using the specified port const result = spawnSync(['lsof', '-ti', `:${port}`]) @@ -76,7 +78,8 @@ export async function createEnv(name: 'geth' | 'kitchensink') { const accountWallet = createWalletClient({ account: privateKeyToAccount( - '0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f' + '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133', + { nonceManager } ), transport, chain, @@ -85,6 +88,14 @@ export async function createEnv(name: 'geth' | 'kitchensink') { return { serverWallet, accountWallet, evm: name == 'geth' } } +export function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +export function timeout(ms: number) { + return new Promise((_resolve, reject) => setTimeout(() => reject(new Error('timeout hit')), ms)) +} + // wait for http request to return 200 export function waitForHealth(url: string) { return new Promise((resolve, reject) => { @@ -120,58 +131,3 @@ export function waitForHealth(url: string) { }, 1000) }) } - -export const procs: Subprocess[] = [] -const polkadotSdkPath = resolve(__dirname, '../../../../../../..') -if (!process.env.USE_LIVE_SERVERS) { - procs.push( - // Run geth on port 8546 - await (async () => { - killProcessOnPort(8546) - const proc = spawn( - 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( - ' ' - ), - { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } - ) - - await waitForHealth('http://localhost:8546').catch() - return proc - })(), - //Run the substate node - (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: polkadotSdkPath, - } - ) - })(), - // Run eth-rpc on 8545 - await (async () => { - killProcessOnPort(8545) - const proc = spawn( - [ - './target/debug/eth-rpc', - '--dev', - '--node-rpc-url=ws://localhost:9944', - '-l=rpc-metrics=debug,eth-rpc=debug', - ], - { - stdout: Bun.file('/tmp/eth-rpc.out.log'), - stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: polkadotSdkPath, - } - ) - await waitForHealth('http://localhost:8545').catch() - return proc - })() - ) -} diff --git a/substrate/frame/revive/rpc/src/block_info_provider.rs b/substrate/frame/revive/rpc/src/block_info_provider.rs index 86ff53fb5dfd..0e91869cddaa 100644 --- a/substrate/frame/revive/rpc/src/block_info_provider.rs +++ b/substrate/frame/revive/rpc/src/block_info_provider.rs @@ -18,7 +18,7 @@ use crate::{ client::{SubstrateBlock, SubstrateBlockNumber}, subxt_client::SrcChainConfig, - ClientError, LOG_TARGET, + ClientError, }; use jsonrpsee::core::async_trait; use sp_core::H256; From d7dc06a725e6ccec097359e6e2a08916b2007b81 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 7 Jan 2025 08:43:32 +0100 Subject: [PATCH 49/67] format --- substrate/frame/revive/rpc/Cargo.toml | 37 +++++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 528890322c83..70b091823a55 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -43,33 +43,36 @@ path = "examples/rust/remark-extrinsic.rs" required-features = ["example"] [dependencies] -clap = { workspace = true, features = ["derive"] } anyhow = { workspace = true } +clap = { workspace = true, features = ["derive"] } +codec = { workspace = true, features = ["derive"] } +ethabi = { version = "18.0.0" } futures = { workspace = true, features = ["thread-pool"] } +hex = { workspace = true } jsonrpsee = { workspace = true, features = ["full"] } -thiserror = { workspace = true } -sp-crypto-hashing = { workspace = true } -subxt = { workspace = true, default-features = true, features = [ - "reconnecting-rpc-client", -] } -tokio = { workspace = true, features = ["full"] } -codec = { workspace = true, features = ["derive"] } log = { workspace = true } pallet-revive = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-weights = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +rlp = { workspace = true, optional = true } +sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } -sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } -rlp = { workspace = true, optional = true } +sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } +sp-weights = { workspace = true, default-features = true } +sqlx = { version = "0.8.2", features = [ + "macros", + "postgres", + "runtime-tokio", + "sqlite", +] } +subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } subxt-signer = { workspace = true, optional = true, features = [ "unstable-eth", ] } -hex = { workspace = true } -ethabi = { version = "18.0.0" } -sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "postgres", "sqlite"] } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["full"] } [features] example = ["rlp", "subxt-signer"] @@ -77,7 +80,7 @@ sqlite = [] [dev-dependencies] env_logger = { workspace = true } -static_init = { workspace = true } pallet-revive-fixtures = { workspace = true, default-features = true } +static_init = { workspace = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } From 422d4f71cff6d8f842ad243a89eaef3ae5c67cf6 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 7 Jan 2025 08:46:58 +0100 Subject: [PATCH 50/67] fixes --- .../rpc/examples/js/contracts/Flipper.sol | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol index 36681ae1be93..51aaafcae428 100644 --- a/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol +++ b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol @@ -14,22 +14,22 @@ contract Flipper { } } -// // FlipperCaller - Interacts with the Flipper contract -// contract FlipperCaller { -// // Address of the Flipper contract -// address public flipperAddress; -// -// // Constructor to initialize Flipper's address -// constructor(address _flipperAddress) { -// flipperAddress = _flipperAddress; -// } -// -// function callFlip() external { -// Flipper(flipperAddress).flip(); -// } -// -// function callGetValue() external view returns (bool) { -// return Flipper(flipperAddress).getValue(); -// } -// } -// +// FlipperCaller - Interacts with the Flipper contract +contract FlipperCaller { + // Address of the Flipper contract + address public flipperAddress; + + // Constructor to initialize Flipper's address + constructor(address _flipperAddress) { + flipperAddress = _flipperAddress; + } + + function callFlip() external { + Flipper(flipperAddress).flip(); + } + + function callGetValue() external view returns (bool) { + return Flipper(flipperAddress).getValue(); + } +} + From 171165a12d32e6e1378da46fa0697412a3c4f965 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 8 Jan 2025 12:02:06 +0100 Subject: [PATCH 51/67] update metadata --- .../frame/revive/rpc/revive_chain.metadata | Bin 659916 -> 661594 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 903731a245d25b8c0a3cf5cb6a11958e85545556..402e8c2d22b21471929e9c61acd2cc968af614cf 100644 GIT binary patch delta 4612 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 WXuo~SNN;Tl>aib}Kv&8kiTfWzMT52g delta 3409 zcmZu!3s6+o8NTQ4UH&~QaxVpR@sWUvLPyAA0AU0J>UJbHD#l0E(#@*Zu*$Ny4m&geMysuL$ADJ^7zR%eu8F_9=0P*8kT>tlK@mo0mHJF{@^ zp7Vd-`ObIl`Tw(5kEed~`_w3p9B_$kp{SZDaw>}d&C!o7b#Z&n9_5v@M@@lFGE;eN zhw#WsO7ZYsVbR2-XZd^#|PL$4_di2Q>u2y?{J3dPV_w7%(JWFiR!k5R#bl9t z8iV`3ghZvI8IHu^l*J?tTQ9>DAzkDwSaKiYULmRS)_~7l;dih1XNn|urMy1h5$>6b z$1j5oXJ3Jz(M*x!c!tZy16SZpl7~00K-$UzQLrtk3^1L7u$<(H#3C*BR>&O3NpE{S zvcTmr7~!}g7Pn0H2g?H%7Pnh)5C@C3EZHn@yIC+7?eO^o&Y=Z6aNt{*g1b9lIw{6a zJ75ZV3(t2zBDy+YDq6pVA6N&A*+NRt*$F9R1=e)Jk6ClP6KuBCjD}PH@ueciKAd0) z3AvmLuXn<-xDt`riz~`xpIi}G;0m}nPS)mo_bTMr-8!KjF;b~V<<(2|VEa|LM7H4X z{sGJEK^?eS1@8>=)M8tNh5}CKzc@-j-)-3q}@Q$u95t75XPZ&6BgJn>d<{r%(~0rm!vOh z^bNbLE%&m9zpdg=--LqMouVbb(&zTL1MVu>8K|z5{nCnA**WYr=dOt)X>BkkYfe&D zDaXEXoJnAecCK5*i-Rx$FWiDS(xdq_FucF~kchi_VJgvfmhTyf9Nw)yZMXXHqklp! zKQJ;hryGi28`3SKC(?u-g9(pDkX5&!fPWgPPu_w_$lRa86f@)F z3nbTu4ZdO^(!$IfJA1XH$fWWjQo;*^1T!w}gJMGQU?0pQ33#oKWs1oXv1{ooU$t;9 zqul88dXjN$KiIO3qto7X`JK%C^|Em+-d#}{45Z+iewZ=UIGpcsZFO#xWoM=Ab26IA z_(?xYS(EWXKa3w|jJE{5$@shW2NB( zsWyD9#Ko$fOveMa!9f$$zC?*jRQqiB@-|FNo2=vOG#<%LW;07<$vE#0%qU6Gg(n)P zXY^`#nl3Kg7?-ZarE5|y-GKyqCc}&tkZg&anmh&^t{~YgJe-&FG-x(9_rgS+H2`Z! zC)N$H%#){eeKi1u83j7FB9$$izKSBqwDeVknSqN>+X!%L05=v6K{>uL#8STpYN*HVeF9@y8*LP7aUTz<-aq87;SiyN2XA_1WS!nCvi3Gk*}4wTFJ7&U+MIC zeCjdvxbgf0R!i&f$phG$u}??8HxB;T#NGZF~r7@g^Fahyg~OI5G~39k(Qk#lN9n5 zEj7oc7FD`ehHsV@wrc!ED@`HOaCa+h!ntj92}#2b+h{Jy#=o`ElkC`R|C%l!1q*UZj8IO(C&HDe9#Yc}z4}UpFO?T4jmJdXOj$9dtr8 z+q}_h1twZ!p92%fSqzzvmq#uxriZV!pgr$h|d$gN$55uHKk+2YXs^Zp~%u*&! zzfZUG1rlZr(jEBf0}8~U%y>ZGCzfGOIwGHb-70zCMP&Qu&P z=^;L9xTjR>`4O?^^O{?6CZSfeI54({#^bgq>xb-0O2L{#s<9BPo5)W56s&(8QzOL> z^VUiGlpPl9UZU#}t=7|oS=u_rI*(a6CfYimHA|wct4O2rhiGe!z%Fd*vDOB1M!67c Y9U|nM(i~@Xnn<&fJf2NvYLU4A064qV5dZ)H From 2cb0295443235a1e8921ff129a9eb29f2c555d5c Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 8 Jan 2025 17:52:44 +0100 Subject: [PATCH 52/67] add missing net_peerCount method --- substrate/frame/revive/rpc/src/rpc_health.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/substrate/frame/revive/rpc/src/rpc_health.rs b/substrate/frame/revive/rpc/src/rpc_health.rs index f94d4b82a80f..35c5a588f284 100644 --- a/substrate/frame/revive/rpc/src/rpc_health.rs +++ b/substrate/frame/revive/rpc/src/rpc_health.rs @@ -25,6 +25,10 @@ pub trait SystemHealthRpc { /// Proxy the substrate chain system_health RPC call. #[method(name = "system_health")] async fn system_health(&self) -> RpcResult; + + ///Returns the number of peers currently connected to the client. + #[method(name = "net_peerCount")] + async fn net_peer_count(&self) -> RpcResult; } pub struct SystemHealthRpcServerImpl { @@ -47,4 +51,9 @@ impl SystemHealthRpcServer for SystemHealthRpcServerImpl { should_have_peers: health.should_have_peers, }) } + + async fn net_peer_count(&self) -> RpcResult { + let health = self.client.system_health().await?; + Ok((health.peers as u64).into()) + } } From 557019eb8960c6b84836e95963de92021f1014ac Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 8 Jan 2025 21:31:11 +0100 Subject: [PATCH 53/67] Fix up some rpc data --- .../frame/revive/rpc/examples/js/bun.lockb | Bin 43178 -> 41698 bytes .../frame/revive/rpc/examples/js/package.json | 8 ++--- .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 5260 -> 4532 bytes .../rpc/examples/js/src/build-contracts.ts | 2 +- substrate/frame/revive/rpc/src/client.rs | 30 +++++++++++++++--- substrate/frame/revive/rpc/src/lib.rs | 8 ++--- .../frame/revive/src/evm/api/rpc_types_gen.rs | 10 +++--- 7 files changed, 40 insertions(+), 18 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index e800d6d4d2d9d684d5e3b778792bc3fe6c545c40..6d6cb9e6699858db1bbe6a889c1441d78a59b5db 100755 GIT binary patch delta 8112 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 literal 5260 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 diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index 446a629b123f..f26f275ec3d5 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -55,7 +55,7 @@ for (const file of input) { } console.log('Compiling with revive...') - const reviveOut = await compile(input, { wasm: !!process.env.WASM }) + const reviveOut = await compile(input, { bin: 'resolc' }) for (const contracts of Object.values(reviveOut.contracts)) { for (const [name, contract] of Object.entries(contracts)) { diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 1162b9c9d887..1c403a3c517c 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -22,12 +22,12 @@ use crate::{ subxt_client::{ revive::calls::types::EthTransact, runtime_types::pallet_revive::storage::ContractInfo, }, - BlockInfoProvider, ReceiptProvider, LOG_TARGET, + BlockInfoProvider, ReceiptProvider, TransactionInfo, LOG_TARGET, }; use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pallet_revive::{ evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, ReceiptInfo, + Block, BlockNumberOrTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, }, EthTransactError, EthTransactInfo, @@ -668,7 +668,11 @@ impl Client { } /// Get the EVM block for the given hash. - pub async fn evm_block(&self, block: Arc) -> Result { + pub async fn evm_block( + &self, + block: Arc, + hydrated_transactions: bool, + ) -> Result { let runtime_api = self.api.runtime_api().at(block.hash()); let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; let gas_limit = gas_from_fee(max_fee); @@ -681,6 +685,23 @@ impl Client { let state_root = header.state_root.0.into(); let extrinsics_root = header.extrinsics_root.0.into(); + let receipts = extract_receipts_from_block(&block).await?; + let gas_used = + receipts.iter().fold(U256::zero(), |acc, (_, receipt)| acc + receipt.gas_used); + let transactions = if hydrated_transactions { + receipts + .into_iter() + .map(|(signed_tx, receipt)| TransactionInfo::new(receipt, signed_tx).into()) + .collect::>() + .into() + } else { + receipts + .into_iter() + .map(|(_, receipt)| receipt.transaction_hash) + .collect::>() + .into() + }; + Ok(Block { hash: block.hash(), parent_hash, @@ -690,8 +711,9 @@ impl Client { timestamp: timestamp.into(), difficulty: Some(0u32.into()), gas_limit, - logs_bloom: Bytes256([0u8; 256]), + gas_used, receipts_root: extrinsics_root, + transactions, ..Default::default() }) } diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 5e737eb4f932..74ddd307b112 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -203,12 +203,12 @@ impl EthRpcServer for EthRpcServerImpl { async fn get_block_by_hash( &self, block_hash: H256, - _hydrated_transactions: bool, + hydrated_transactions: bool, ) -> RpcResult> { let Some(block) = self.client.block_by_hash(&block_hash).await? else { return Ok(None); }; - let block = self.client.evm_block(block).await?; + let block = self.client.evm_block(block, hydrated_transactions).await?; Ok(Some(block)) } @@ -238,12 +238,12 @@ impl EthRpcServer for EthRpcServerImpl { async fn get_block_by_number( &self, block: BlockNumberOrTag, - _hydrated_transactions: bool, + hydrated_transactions: bool, ) -> RpcResult> { let Some(block) = self.client.block_by_number_or_tag(&block).await? else { return Ok(None); }; - let block = self.client.evm_block(block).await?; + let block = self.client.evm_block(block, hydrated_transactions).await?; Ok(Some(block)) } diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 1d65fdefdde6..5d31613ca314 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -87,7 +87,7 @@ pub struct Block { /// Total difficulty #[serde(rename = "totalDifficulty", skip_serializing_if = "Option::is_none")] pub total_difficulty: Option, - pub transactions: H256OrTransactionInfo, + pub transactions: HashesOrTransactionInfos, /// Transactions root #[serde(rename = "transactionsRoot")] pub transactions_root: H256, @@ -357,15 +357,15 @@ pub enum BlockTag { Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq, )] #[serde(untagged)] -pub enum H256OrTransactionInfo { +pub enum HashesOrTransactionInfos { /// Transaction hashes - H256s(Vec), + Hashes(Vec), /// Full transactions TransactionInfos(Vec), } -impl Default for H256OrTransactionInfo { +impl Default for HashesOrTransactionInfos { fn default() -> Self { - H256OrTransactionInfo::H256s(Default::default()) + HashesOrTransactionInfos::Hashes(Default::default()) } } From 556bda2416f1ea7cccd53769ea8a1086c602ce31 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 9 Jan 2025 16:54:53 +0100 Subject: [PATCH 54/67] tweak logs --- .../frame/revive/rpc/examples/js/src/lib.ts | 1 - .../revive/rpc/examples/js/src/piggy-bank.ts | 4 +--- substrate/frame/revive/rpc/src/client.rs | 1 + substrate/frame/revive/src/evm/api/rpc_types.rs | 12 ++++++++++-- substrate/frame/revive/src/evm/runtime.rs | 16 ++++++++-------- substrate/frame/revive/src/lib.rs | 8 +++----- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts index e1f0e780d95b..1470f492e34d 100644 --- a/substrate/frame/revive/rpc/examples/js/src/lib.ts +++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts @@ -50,7 +50,6 @@ if (geth) { child.unref() await new Promise((resolve) => setTimeout(resolve, 500)) } - const rpcUrl = proxy ? 'http://localhost:8080' : westend diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts index 8289ac8b76e3..4983a6f3b301 100644 --- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts +++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts @@ -4,7 +4,7 @@ import { parseEther } from 'viem' const hash = await walletClient.deployContract({ abi: PiggyBankAbi, - bytecode: getByteCode('piggyBank'), + bytecode: getByteCode('PiggyBank'), }) const deployReceipt = await walletClient.waitForTransactionReceipt({ hash }) const contractAddress = deployReceipt.contractAddress @@ -31,9 +31,7 @@ assert(contractAddress, 'Contract address should be set') value: parseEther('10'), }) - request.nonce = 0 const hash = await walletClient.writeContract(request) - const receipt = await walletClient.waitForTransactionReceipt({ hash }) console.log(`Deposit receipt: ${receipt.status}`) } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 1c403a3c517c..d7c42368fedd 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -710,6 +710,7 @@ impl Client { number: header.number.into(), timestamp: timestamp.into(), difficulty: Some(0u32.into()), + base_fee_per_gas: Some(crate::GAS_PRICE.into()), gas_limit, gas_used, receipts_root: extrinsics_root, diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index ed046cb4da44..b4b2c6ffcf17 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -192,7 +192,11 @@ impl GenericTransaction { value: Some(tx.value), to: Some(tx.to), gas: Some(tx.gas), - gas_price: Some(tx.max_fee_per_blob_gas), + gas_price: Some( + U256::from(crate::GAS_PRICE) + .saturating_add(tx.max_priority_fee_per_gas) + .max(tx.max_fee_per_blob_gas), + ), access_list: Some(tx.access_list), blob_versioned_hashes: tx.blob_versioned_hashes, max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), @@ -209,7 +213,11 @@ impl GenericTransaction { value: Some(tx.value), to: tx.to, gas: Some(tx.gas), - gas_price: Some(tx.gas_price), + gas_price: Some( + U256::from(crate::GAS_PRICE) + .saturating_add(tx.max_priority_fee_per_gas) + .max(tx.max_fee_per_gas), + ), access_list: Some(tx.access_list), max_fee_per_gas: Some(tx.max_fee_per_gas), max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 8c87c2c63fe5..d0ff5a88b329 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -50,11 +50,11 @@ type CallOf = ::RuntimeCall; /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. /// -/// The chosen constant value is balanced to ensure: -/// - It is not too high, allowing components (ref_time, proof_size, and deposit) to be encoded in -/// the lower digits of the gas value. -/// - It is not too low, enabling users to adjust the gas price to define a tip. -pub const GAS_PRICE: u32 = 1_000u32; +/// The chosen constant value is: +/// - Not too high, ensuring the gas value is large enough (at least 7 digits) to encode the +/// ref_time, proof_size, and deposit into the less significant (6 lower) digits of the gas value. +/// - Not too low, enabling users to adjust the gas price to define a tip. +pub const GAS_PRICE: u32 = 100_000u32; /// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. /// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. @@ -401,7 +401,7 @@ pub trait EthExtra { Default::default(), ) .into(); - log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: gas_price: {gas_price:?}, encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); // The fees from the Ethereum transaction should be greater or equal to the actual fees paid // by the account. @@ -411,12 +411,12 @@ pub trait EthExtra { } if eth_fee_no_tip > actual_fee.saturating_mul(2u32.into()) { - log::debug!(target: LOG_TARGET, "actual fees {actual_fee:?} too high, base eth fees: {eth_fee_no_tip:?}"); + log::debug!(target: LOG_TARGET, "actual fees: {actual_fee:?} too high, base eth fees: {eth_fee_no_tip:?}"); return Err(InvalidTransaction::Call.into()) } let tip = eth_fee.saturating_sub(eth_fee_no_tip); - log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce {nonce:?} and tip: {tip:?}"); + log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}"); Ok(CheckedExtrinsic { format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)), function, diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b879fa5b360e..7cc57d49b033 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1420,19 +1420,17 @@ where 0u32.into(), ) .into(); - let eth_gas = gas_from_fee(fee); + let raw_eth_gas = gas_from_fee(fee); let eth_gas = - T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); + T::EthGasEncoder::encode(raw_eth_gas, result.gas_required, result.storage_deposit); if eth_gas == result.eth_gas { - log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); + log::debug!(target: LOG_TARGET, "bare_eth_call: raw_eth_gas: {raw_eth_gas:?} eth_gas: {eth_gas:?}"); break; } result.eth_gas = eth_gas; tx.gas = Some(eth_gas.into()); - log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {eth_gas:?}"); } - Ok(result) } From a6b72d6c4d9af5e2960aac83574bc57324d2bc93 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 9 Jan 2025 17:00:39 +0100 Subject: [PATCH 55/67] Tweak comment --- substrate/frame/revive/src/evm/runtime.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 8c87c2c63fe5..d0ff5a88b329 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -50,11 +50,11 @@ type CallOf = ::RuntimeCall; /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. /// -/// The chosen constant value is balanced to ensure: -/// - It is not too high, allowing components (ref_time, proof_size, and deposit) to be encoded in -/// the lower digits of the gas value. -/// - It is not too low, enabling users to adjust the gas price to define a tip. -pub const GAS_PRICE: u32 = 1_000u32; +/// The chosen constant value is: +/// - Not too high, ensuring the gas value is large enough (at least 7 digits) to encode the +/// ref_time, proof_size, and deposit into the less significant (6 lower) digits of the gas value. +/// - Not too low, enabling users to adjust the gas price to define a tip. +pub const GAS_PRICE: u32 = 100_000u32; /// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. /// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. @@ -401,7 +401,7 @@ pub trait EthExtra { Default::default(), ) .into(); - log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: gas_price: {gas_price:?}, encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); // The fees from the Ethereum transaction should be greater or equal to the actual fees paid // by the account. @@ -411,12 +411,12 @@ pub trait EthExtra { } if eth_fee_no_tip > actual_fee.saturating_mul(2u32.into()) { - log::debug!(target: LOG_TARGET, "actual fees {actual_fee:?} too high, base eth fees: {eth_fee_no_tip:?}"); + log::debug!(target: LOG_TARGET, "actual fees: {actual_fee:?} too high, base eth fees: {eth_fee_no_tip:?}"); return Err(InvalidTransaction::Call.into()) } let tip = eth_fee.saturating_sub(eth_fee_no_tip); - log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce {nonce:?} and tip: {tip:?}"); + log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}"); Ok(CheckedExtrinsic { format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)), function, From 5014edf6e8af28733e8c3bbf01c363e6d2f82e1d Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 10 Jan 2025 11:28:44 +0100 Subject: [PATCH 56/67] fix clippy --- substrate/frame/revive/rpc/src/client.rs | 2 +- substrate/frame/revive/rpc/src/receipt_provider/db.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index d7c42368fedd..cd0effe7faf2 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -691,7 +691,7 @@ impl Client { let transactions = if hydrated_transactions { receipts .into_iter() - .map(|(signed_tx, receipt)| TransactionInfo::new(receipt, signed_tx).into()) + .map(|(signed_tx, receipt)| TransactionInfo::new(receipt, signed_tx)) .collect::>() .into() } else { diff --git a/substrate/frame/revive/rpc/src/receipt_provider/db.rs b/substrate/frame/revive/rpc/src/receipt_provider/db.rs index c160f13fdbb8..632e1f282f16 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider/db.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider/db.rs @@ -166,7 +166,7 @@ impl ReceiptProvider for DBReceiptProvider { } async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { - let (block_hash, transaction_index) = self.fetch_row(&transaction_hash).await?; + let (block_hash, transaction_index) = self.fetch_row(transaction_hash).await?; let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; let (_, receipt) = From 180835b556bd3712f9ac2f8d190e3df01b3b213b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 10 Jan 2025 14:35:54 +0100 Subject: [PATCH 57/67] fix docker images --- .github/workflows/build-publish-eth-rpc.yml | 37 ++++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-publish-eth-rpc.yml b/.github/workflows/build-publish-eth-rpc.yml index 3aa1624096df..a98b3881a145 100644 --- a/.github/workflows/build-publish-eth-rpc.yml +++ b/.github/workflows/build-publish-eth-rpc.yml @@ -12,7 +12,8 @@ concurrency: cancel-in-progress: true env: - IMAGE_NAME: "docker.io/paritypr/eth-rpc" + ETH_RPC_IMAGE_NAME: "docker.io/paritypr/eth-rpc" + ETH_INDEXER_IMAGE_NAME: "docker.io/paritypr/eth-indexer" jobs: set-variables: @@ -34,7 +35,7 @@ jobs: echo "set VERSION=${VERSION}" build_docker: - name: Build docker image + name: Build docker images runs-on: parity-large needs: [set-variables] env: @@ -43,17 +44,26 @@ jobs: - name: Check out the repo uses: actions/checkout@v4 - - name: Build Docker image + - name: Build eth-rpc Docker image uses: docker/build-push-action@v6 with: context: . - file: ./substrate/frame/revive/rpc/Dockerfile + file: ./substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile push: false tags: | - ${{ env.IMAGE_NAME }}:${{ env.VERSION }} + ${{ env.ETH_RPC_IMAGE_NAME }}:${{ env.VERSION }} + + - name: Build eth-indexer Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile + push: false + tags: | + ${{ env.ETH_INDEXER_IMAGE_NAME }}:${{ env.VERSION }} build_push_docker: - name: Build and push docker image + name: Build and push docker images runs-on: parity-large if: github.ref == 'refs/heads/master' needs: [set-variables] @@ -69,11 +79,20 @@ jobs: username: ${{ secrets.PARITYPR_DOCKERHUB_USERNAME }} password: ${{ secrets.PARITYPR_DOCKERHUB_PASSWORD }} - - name: Build Docker image + - name: Build eth-rpc Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile + push: true + tags: | + ${{ env.ETH_RPC_IMAGE_NAME }}:${{ env.VERSION }} + + - name: Build eth-indexer Docker image uses: docker/build-push-action@v6 with: context: . - file: ./substrate/frame/revive/rpc/Dockerfile + file: ./substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile push: true tags: | - ${{ env.IMAGE_NAME }}:${{ env.VERSION }} + ${{ env.ETH_INDEXER_IMAGE_NAME }}:${{ env.VERSION }} From 2b5675a91dd73593eb447b9434b4e9135f1b177f Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Fri, 10 Jan 2025 16:30:54 +0100 Subject: [PATCH 58/67] Update prdoc/pr_6689.prdoc --- prdoc/pr_6689.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_6689.prdoc b/prdoc/pr_6689.prdoc index 596713e1ca2d..f2a7cebfbf37 100644 --- a/prdoc/pr_6689.prdoc +++ b/prdoc/pr_6689.prdoc @@ -3,7 +3,7 @@ doc: - audience: Runtime Dev description: |- Update the current approach to attach the `ref_time`, `pov` and `deposit` parameters to an Ethereum transaction. - Previously we will pass these 3 parameters along with the signed payload, and check that the fees resulting from `gas x gas_price` match the actual fees paid by the user for the extrinsic. +Previously, these three parameters were passed along with the signed payload, and the fees resulting from gas × gas_price were checked to ensure they matched the actual fees paid by the user for the extrinsic This approach unfortunately can be attacked. A malicious actor could force such a transaction to fail by injecting low values for some of these extra parameters as they are not part of the signed payload. From 50232dbf54203bef29c2947fdf6b4d8c9d4f2640 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Fri, 10 Jan 2025 16:32:34 +0100 Subject: [PATCH 59/67] Apply suggestions from code review --- prdoc/pr_6689.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_6689.prdoc b/prdoc/pr_6689.prdoc index f2a7cebfbf37..2cbb49cd7dd2 100644 --- a/prdoc/pr_6689.prdoc +++ b/prdoc/pr_6689.prdoc @@ -7,7 +7,7 @@ Previously, these three parameters were passed along with the signed payload, an This approach unfortunately can be attacked. A malicious actor could force such a transaction to fail by injecting low values for some of these extra parameters as they are not part of the signed payload. - The new approach encodes these 3 extra parameters in the lower digits of the transaction gas, approximating the the log2 of the actual values to encode each components on 2 digits + The new approach encodes these 3 extra parameters in the lower digits of the transaction gas, using the log2 of the actual values to encode each components on 2 digits crates: - name: pallet-revive-eth-rpc bump: minor From 5d08af9985214bb0ff2c09e3045f9b5307486f0b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Fri, 10 Jan 2025 22:32:07 +0100 Subject: [PATCH 60/67] fixes --- Cargo.lock | 1 + substrate/frame/revive/rpc/Cargo.toml | 1 + substrate/frame/revive/rpc/src/lib.rs | 13 ++++++++++++- substrate/frame/revive/rpc/src/rpc_methods_gen.rs | 4 ++++ substrate/frame/revive/src/wasm/mod.rs | 5 +++-- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b96c89b5aaf..ebfd847c6756 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15037,6 +15037,7 @@ dependencies = [ "sc-rpc", "sc-rpc-api", "sc-service", + "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", "sp-weights 27.0.0", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 70b091823a55..ff7ae1c35662 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -59,6 +59,7 @@ sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-arithmetic = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-weights = { workspace = true, default-features = true } sqlx = { version = "0.8.2", features = [ diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 74ddd307b112..5e1341e2a29a 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -24,6 +24,7 @@ use jsonrpsee::{ types::{ErrorCode, ErrorObjectOwned}, }; use pallet_revive::evm::*; +use sp_arithmetic::Permill; use sp_core::{keccak_256, H160, H256, U256}; use thiserror::Error; @@ -127,7 +128,12 @@ impl EthRpcServer for EthRpcServerImpl { transaction_hash: H256, ) -> RpcResult> { let receipt = self.client.receipt(&transaction_hash).await; - log::debug!(target: LOG_TARGET, "transaction_receipt for {transaction_hash:?}: {}", receipt.is_some()); + log::debug!( + target: LOG_TARGET, + "transaction_receipt for {transaction_hash:?}: received: {received} - success: {success:?}", + received = receipt.is_some(), + success = receipt.as_ref().map(|r| r.status == Some(U256::one())) + ); Ok(receipt) } @@ -226,6 +232,11 @@ impl EthRpcServer for EthRpcServerImpl { Ok(U256::from(GAS_PRICE)) } + async fn max_priority_fee_per_gas(&self) -> RpcResult { + // TODO: Provide better estimation + Ok(U256::from(Permill::from_percent(20).mul_ceil(GAS_PRICE))) + } + async fn get_code(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult { let code = self.client.get_contract_code(&address, block).await?; Ok(code.into()) diff --git a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs index ad34dbfdfb49..da60360d9e61 100644 --- a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs +++ b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs @@ -142,6 +142,10 @@ pub trait EthRpc { transaction_hash: H256, ) -> RpcResult>; + /// Returns the current maxPriorityFeePerGas per gas in wei. + #[method(name = "eth_maxPriorityFeePerGas")] + async fn max_priority_fee_per_gas(&self) -> RpcResult; + /// Submits a raw transaction. For EIP-4844 transactions, the raw form must be the network form. /// This means it includes the blobs, KZG commitments, and KZG proofs. #[method(name = "eth_sendRawTransaction")] diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index b24de61314f9..3bd4bde5679f 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -193,8 +193,9 @@ where &HoldReason::CodeUploadDepositReserve.into(), &self.code_info.owner, deposit, - ) .map_err(|err| { log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner); - >::StorageDepositNotEnoughFunds + ) .map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to hold store code deposit {deposit:?} for owner: {:?}: {err:?}", self.code_info.owner); + >::StorageDepositNotEnoughFunds })?; } From 0bb2c6f84459c0a11a97c8b080cfaa2df3debc05 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sat, 11 Jan 2025 09:08:54 +0100 Subject: [PATCH 61/67] fix test reset gas_price to 1_000 or diff between actual gas and encoded are too high for small tx --- substrate/frame/revive/src/evm/runtime.rs | 123 +++++++++++----------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index d0ff5a88b329..44bb790ac26a 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -54,7 +54,7 @@ type CallOf = ::RuntimeCall; /// - Not too high, ensuring the gas value is large enough (at least 7 digits) to encode the /// ref_time, proof_size, and deposit into the less significant (6 lower) digits of the gas value. /// - Not too low, enabling users to adjust the gas price to define a tip. -pub const GAS_PRICE: u32 = 100_000u32; +pub const GAS_PRICE: u32 = 1_000u32; /// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. /// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. @@ -324,6 +324,11 @@ pub trait EthExtra { let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } = GenericTransaction::from_signed(tx, None); + let Some(gas) = gas else { + log::debug!(target: LOG_TARGET, "No gas provided"); + return Err(InvalidTransaction::Call); + }; + if chain_id.unwrap_or_default() != ::ChainId::get().into() { log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); return Err(InvalidTransaction::Call); @@ -337,13 +342,11 @@ pub trait EthExtra { let data = input.unwrap_or_default().0; - let (gas_limit, storage_deposit_limit) = ::EthGasEncoder::decode( - gas.unwrap_or_default(), - ) - .ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); - InvalidTransaction::Call - })?; + let (gas_limit, storage_deposit_limit) = + ::EthGasEncoder::decode(gas).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); + InvalidTransaction::Call + })?; let call = if let Some(dest) = to { crate::Call::call:: { @@ -380,13 +383,13 @@ pub trait EthExtra { // Fees calculated with the fixed `GAS_PRICE` // When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE` let eth_fee_no_tip = U256::from(GAS_PRICE) - .saturating_mul(gas.unwrap_or_default()) + .saturating_mul(gas) .try_into() .map_err(|_| InvalidTransaction::Call)?; // Fees with the actual gas_price from the transaction. let eth_fee: BalanceOf = U256::from(gas_price.unwrap_or_default()) - .saturating_mul(gas.unwrap_or_default()) + .saturating_mul(gas) .try_into() .map_err(|_| InvalidTransaction::Call)?; @@ -411,7 +414,8 @@ pub trait EthExtra { } if eth_fee_no_tip > actual_fee.saturating_mul(2u32.into()) { - log::debug!(target: LOG_TARGET, "actual fees: {actual_fee:?} too high, base eth fees: {eth_fee_no_tip:?}"); + log::debug!(target: LOG_TARGET, "actual fees: {actual_fee:?} too high, base eth fees: + {eth_fee_no_tip:?}"); return Err(InvalidTransaction::Call.into()) } @@ -473,8 +477,6 @@ mod test { #[derive(Clone)] struct UncheckedExtrinsicBuilder { tx: GenericTransaction, - gas_limit: Weight, - storage_deposit_limit: BalanceOf, before_validate: Option>, } @@ -488,8 +490,6 @@ mod test { gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, - gas_limit: Weight::zero(), - storage_deposit_limit: 0, before_validate: None, } } @@ -506,11 +506,6 @@ mod test { Ok(dry_run) => { log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); self.tx.gas = Some(dry_run.eth_gas); - let (gas_limit, deposit) = - <::EthGasEncoder as GasEncoder<_>>::decode(dry_run.eth_gas) - .unwrap(); - self.gas_limit = gas_limit; - self.storage_deposit_limit = deposit; }, Err(err) => { log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err); @@ -522,7 +517,6 @@ mod test { fn call_with(dest: H160) -> Self { let mut builder = Self::new(); builder.tx.to = Some(dest); - ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } @@ -530,23 +524,28 @@ mod test { fn instantiate_with(code: Vec, data: Vec) -> Self { let mut builder = Self::new(); builder.tx.input = Some(Bytes(code.into_iter().chain(data.into_iter()).collect())); - ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } - /// Update the transaction with the given function. - fn update(mut self, f: impl FnOnce(&mut GenericTransaction) -> ()) -> Self { - f(&mut self.tx); - self - } /// Set before_validate function. fn before_validate(mut self, f: impl Fn() + Send + Sync + 'static) -> Self { self.before_validate = Some(std::sync::Arc::new(f)); self } + fn check( + self, + ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + self.mutate_estimate_and_check(Box::new(|_| ())) + } + /// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension. - fn check(&self) -> Result<(RuntimeCall, SignedExtra), TransactionValidityError> { + fn mutate_estimate_and_check( + mut self, + f: Box ()>, + ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + ExtBuilder::default().build().execute_with(|| self.estimate_gas()); + f(&mut self.tx); ExtBuilder::default().build().execute_with(|| { let UncheckedExtrinsicBuilder { tx, before_validate, .. } = self.clone(); @@ -557,8 +556,9 @@ mod test { 100_000_000_000_000, ); - let payload = - account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload(); + let payload = account + .sign_transaction(tx.clone().try_into_unsigned().unwrap()) + .signed_payload(); let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload }); let encoded_len = call.encoded_size(); @@ -578,7 +578,7 @@ mod test { 0, )?; - Ok((result.function, extra)) + Ok((result.function, extra, tx)) }) } } @@ -586,14 +586,18 @@ mod test { #[test] fn check_eth_transact_call_works() { let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); + let (call, _, tx) = builder.check().unwrap(); + let (gas_limit, storage_deposit_limit) = + <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + assert_eq!( - builder.check().unwrap().0, + call, crate::Call::call:: { - dest: builder.tx.to.unwrap(), - value: builder.tx.value.unwrap_or_default().as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - data: builder.tx.input.unwrap_or_default().0 + dest: tx.to.unwrap(), + value: tx.value.unwrap_or_default().as_u64(), + data: tx.input.unwrap_or_default().0, + gas_limit, + storage_deposit_limit } .into() ); @@ -604,16 +608,19 @@ mod test { let (code, _) = compile_module("dummy").unwrap(); let data = vec![]; let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + let (call, _, tx) = builder.check().unwrap(); + let (gas_limit, storage_deposit_limit) = + <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); assert_eq!( - builder.check().unwrap().0, + call, crate::Call::instantiate_with_code:: { - value: builder.tx.value.unwrap_or_default().as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, + value: tx.value.unwrap_or_default().as_u64(), code, data, - salt: None + salt: None, + gas_limit, + storage_deposit_limit } .into() ); @@ -621,11 +628,10 @@ mod test { #[test] fn check_eth_transact_nonce_works() { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.nonce = Some(1u32.into())); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); assert_eq!( - builder.check(), + builder.mutate_estimate_and_check(Box::new(|tx| tx.nonce = Some(1u32.into()))), Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) ); @@ -642,11 +648,10 @@ mod test { #[test] fn check_eth_transact_chain_id_works() { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.chain_id = Some(42.into())); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); assert_eq!( - builder.check(), + builder.mutate_estimate_and_check(Box::new(|tx| tx.chain_id = Some(42.into()))), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) ); } @@ -659,7 +664,7 @@ mod test { // Fail because the tx input fail to get the blob length assert_eq!( - builder.clone().update(|tx| tx.input = Some(Bytes(vec![1, 2, 3]))).check(), + builder.mutate_estimate_and_check(Box::new(|tx| tx.input = Some(Bytes(vec![1, 2, 3])))), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) ); } @@ -691,10 +696,10 @@ mod test { ]; for (msg, update_tx, err) in scenarios { - let builder = - UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx); + let res = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) + .mutate_estimate_and_check(update_tx); - assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg); + assert_eq!(res, Err(TransactionValidityError::Invalid(err)), "{}", msg); } } @@ -702,16 +707,16 @@ mod test { fn check_transaction_tip() { let (code, _) = compile_module("dummy").unwrap(); let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) - .update(|tx| { - tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); - log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); - }); + let (_, extra, tx) = + UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) + .mutate_estimate_and_check(Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); + log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); + })) + .unwrap(); - let tx = &builder.tx; let expected_tip = tx.gas_price.unwrap() * tx.gas.unwrap() - U256::from(GAS_PRICE) * tx.gas.unwrap(); - let (_, extra) = builder.check().unwrap(); assert_eq!(U256::from(extra.1.tip()), expected_tip); } } From 11872c9e267cefd206039f585df27cd399ef658b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sat, 11 Jan 2025 13:20:51 +0100 Subject: [PATCH 62/67] Relax gas_fee too high --- substrate/frame/revive/src/evm/runtime.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 44bb790ac26a..d4b344e20eb8 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -413,12 +413,6 @@ pub trait EthExtra { return Err(InvalidTransaction::Payment.into()) } - if eth_fee_no_tip > actual_fee.saturating_mul(2u32.into()) { - log::debug!(target: LOG_TARGET, "actual fees: {actual_fee:?} too high, base eth fees: - {eth_fee_no_tip:?}"); - return Err(InvalidTransaction::Call.into()) - } - let tip = eth_fee.saturating_sub(eth_fee_no_tip); log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}"); Ok(CheckedExtrinsic { @@ -679,13 +673,6 @@ mod test { }), InvalidTransaction::Payment, ), - ( - "Gas fees too high", - Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 2); - }), - InvalidTransaction::Call, - ), ( "Gas fees too low", Box::new(|tx| { From d07287341402a23253f53bd34e33077678901474 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sun, 12 Jan 2025 15:08:41 +0100 Subject: [PATCH 63/67] fix docker image --- substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile index 02af98af097e..77fa846a145c 100644 --- a/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile +++ b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile @@ -11,7 +11,7 @@ RUN rustup component add rust-src RUN cargo build --locked --profile production -p pallet-revive-eth-rpc --bin eth-indexer FROM docker.io/parity/base-bin:latest -COPY --from=builder /polkadot/target/production/eth-rpc /usr/local/bin +COPY --from=builder /polkadot/target/production/eth-indexer /usr/local/bin USER root RUN useradd -m -u 1001 -U -s /bin/sh -d /polkadot polkadot && \ From 68fd6d523fd42030cd99c9178c04897f4f597bf1 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Sun, 12 Jan 2025 15:16:32 +0100 Subject: [PATCH 64/67] fix taplo --- substrate/frame/revive/rpc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index ff7ae1c35662..90963ac9cb1d 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -58,8 +58,8 @@ sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-weights = { workspace = true, default-features = true } sqlx = { version = "0.8.2", features = [ From d9d996e99392e369342fac871588ae6da47ad430 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 13 Jan 2025 16:38:13 +0100 Subject: [PATCH 65/67] remove sqlite feature flag --- ...c1135227c1150f2c5083d1c7c6086b717ada0.json | 12 +++++++ ...81605402995b8c528d1008a6d087c7a221eab.json | 12 ------- ...8c427245f94b80d37ec3aef04cd96fb36298.json} | 4 +-- ...32be50096d4e37be04ed8b6f46ac5c242043.json} | 4 +-- ...710e21e167c8280deb0fa64deb340624f281e.json | 22 ------------ ...aec40bd34c9d008d5dabc8676d1fa8189f24c.json | 28 --------------- ...65a14f4f538fdf91a7b903f6bedacb24a2792.json | 16 --------- substrate/frame/revive/rpc/Cargo.toml | 2 -- .../revive/rpc/src/receipt_provider/db.rs | 35 ++----------------- 9 files changed, 18 insertions(+), 117 deletions(-) create mode 100644 substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json delete mode 100644 substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json rename substrate/frame/revive/rpc/.sqlx/{query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json => query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json} (52%) rename substrate/frame/revive/rpc/.sqlx/{query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json => query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json} (62%) delete mode 100644 substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json delete mode 100644 substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json delete mode 100644 substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json diff --git a/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json b/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json new file mode 100644 index 000000000000..016276144901 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES ($1, $2, $3)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json b/substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json deleted file mode 100644 index faeddc993ad0..000000000000 --- a/substrate/frame/revive/rpc/.sqlx/query-09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "-- sqlite\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES ($1, $2, $3)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "09ed5df8505cfa966474d57456a81605402995b8c528d1008a6d087c7a221eab" -} diff --git a/substrate/frame/revive/rpc/.sqlx/query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json b/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json similarity index 52% rename from substrate/frame/revive/rpc/.sqlx/query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json rename to substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json index 2553cc0e92a6..507564cd05c5 100644 --- a/substrate/frame/revive/rpc/.sqlx/query-2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8.json +++ b/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "-- sqlite\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = $1\n ", + "query": "\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = $1\n ", "describe": { "columns": [ { @@ -16,5 +16,5 @@ false ] }, - "hash": "2b2c56cd7a73a80e346f1e1a52d3cd84772906e269f43cd995909fede8836de8" + "hash": "2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298" } diff --git a/substrate/frame/revive/rpc/.sqlx/query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json b/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json similarity index 62% rename from substrate/frame/revive/rpc/.sqlx/query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json rename to substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json index c5fcf311a49c..2443035c433d 100644 --- a/substrate/frame/revive/rpc/.sqlx/query-ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9.json +++ b/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "-- sqlite\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = $1\n\t\t\t", + "query": "\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = $1\n\t\t\t", "describe": { "columns": [ { @@ -22,5 +22,5 @@ false ] }, - "hash": "ef458656669caadd17228b367bde04028e5fd1edb7287253421c6875e8ed73e9" + "hash": "29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043" } diff --git a/substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json b/substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json deleted file mode 100644 index 1d08450d36d9..000000000000 --- a/substrate/frame/revive/rpc/.sqlx/query-9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "-- pgsql\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Bpchar" - ] - }, - "nullable": [ - null - ] - }, - "hash": "9b464cc460b3158f12d85db25de710e21e167c8280deb0fa64deb340624f281e" -} diff --git a/substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json b/substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json deleted file mode 100644 index da9da6aa00a8..000000000000 --- a/substrate/frame/revive/rpc/.sqlx/query-c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "-- pgsql\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = $1\n\t\t\t", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "block_hash", - "type_info": "Bpchar" - }, - { - "ordinal": 1, - "name": "transaction_index", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Bpchar" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "c87833134a57bd909e55e0bdddfaec40bd34c9d008d5dabc8676d1fa8189f24c" -} diff --git a/substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json b/substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json deleted file mode 100644 index f5f66cf0db8a..000000000000 --- a/substrate/frame/revive/rpc/.sqlx/query-d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "-- pgsql\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES ($1, $2, $3)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Bpchar", - "Bpchar", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "d2ea7306c5b01efd493e80338f265a14f4f538fdf91a7b903f6bedacb24a2792" -} diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 90963ac9cb1d..9d822f5ff8e2 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -64,7 +64,6 @@ sp-crypto-hashing = { workspace = true } sp-weights = { workspace = true, default-features = true } sqlx = { version = "0.8.2", features = [ "macros", - "postgres", "runtime-tokio", "sqlite", ] } @@ -77,7 +76,6 @@ tokio = { workspace = true, features = ["full"] } [features] example = ["rlp", "subxt-signer"] -sqlite = [] [dev-dependencies] env_logger = { workspace = true } diff --git a/substrate/frame/revive/rpc/src/receipt_provider/db.rs b/substrate/frame/revive/rpc/src/receipt_provider/db.rs index 632e1f282f16..6a9005565341 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider/db.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider/db.rs @@ -20,37 +20,14 @@ use crate::BlockInfoProvider; use jsonrpsee::core::async_trait; use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; use sp_core::{H256, U256}; +use sqlx::{query, SqlitePool}; use std::sync::Arc; -#[cfg(not(any(feature = "sqlite", test)))] -use sqlx::PgPool; -#[cfg(any(feature = "sqlite", test))] -use sqlx::SqlitePool; - -/// Wrapper around [`sqlx::query!`] that injects a comment with the database type to the query -/// string, so that we can generate sqlx artifact with different hashes for each database. -macro_rules! query { - ($query:expr, $($args:tt)*) => {{ - #[cfg(any(feature = "sqlite", test))] - { - sqlx::query!("-- sqlite" + $query, $($args)*) - } - #[cfg(not(any(feature = "sqlite", test)))] - { - sqlx::query!("-- pgsql" + $query, $($args)*) - } - }}; -} - /// A `[ReceiptProvider]` that stores receipts in a SQLite database. #[derive(Clone)] pub struct DBReceiptProvider { /// The database pool. - #[cfg(any(feature = "sqlite", test))] pool: SqlitePool, - #[cfg(not(any(feature = "sqlite", test)))] - pool: PgPool, - /// The block provider used to fetch blocks, and reconstruct receipts. block_provider: Arc, /// weather or not we should write to the DB. @@ -64,11 +41,7 @@ impl DBReceiptProvider { read_only: bool, block_provider: Arc, ) -> Result { - #[cfg(any(feature = "sqlite", test))] let pool = SqlitePool::connect(database_url).await?; - #[cfg(not(any(feature = "sqlite", test)))] - let pool = PgPool::connect(database_url).await?; - Ok(Self { pool, block_provider, read_only }) } @@ -145,11 +118,7 @@ impl ReceiptProvider for DBReceiptProvider { .await .ok()?; - #[cfg(any(feature = "sqlite", test))] let count = row.count as usize; - #[cfg(not(any(feature = "sqlite", test)))] - let count = row.count.unwrap_or_default() as usize; - Some(count) } @@ -166,7 +135,7 @@ impl ReceiptProvider for DBReceiptProvider { } async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { - let (block_hash, transaction_index) = self.fetch_row(transaction_hash).await?; + let (block_hash, transaction_index) = self.fetch_row(&transaction_hash).await?; let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; let (_, receipt) = From 63619b0026bf818af9583b00379b6d139b1c750b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 13 Jan 2025 16:47:48 +0100 Subject: [PATCH 66/67] gitignore --- substrate/frame/revive/rpc/.gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 substrate/frame/revive/rpc/.gitignore diff --git a/substrate/frame/revive/rpc/.gitignore b/substrate/frame/revive/rpc/.gitignore deleted file mode 100644 index c7434965b841..000000000000 --- a/substrate/frame/revive/rpc/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.db -*.db-wal -*.db-shm From 4ef9b5c83c8f468e2d5107327c2c89cee7906225 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Mon, 13 Jan 2025 17:30:37 +0100 Subject: [PATCH 67/67] fix clippy --- substrate/frame/revive/rpc/src/receipt_provider/db.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/src/receipt_provider/db.rs b/substrate/frame/revive/rpc/src/receipt_provider/db.rs index 6a9005565341..63917d6193ea 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider/db.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider/db.rs @@ -135,7 +135,7 @@ impl ReceiptProvider for DBReceiptProvider { } async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { - let (block_hash, transaction_index) = self.fetch_row(&transaction_hash).await?; + let (block_hash, transaction_index) = self.fetch_row(transaction_hash).await?; let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; let (_, receipt) =