From 06e289cec5654e9fd10ecdadaea27ecbfeffd241 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Fri, 22 Nov 2024 15:42:16 +0100 Subject: [PATCH 01/28] 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 533ea4c9e8780..5402b3e60cc4f 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 0000000000000..f05aeb93eb71f --- /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 0000000000000..65d8ba501b1c6 --- /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 0000000000000..2ca838f0db232 --- /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 <https://github.com/ethereum/execution-apis> +- See building instructions to re-generate the openrpc.json <https://github.com/ethereum/execution-apis?tab=readme-ov-file#building> +- Include fixes from <https://github.com/ethereum/execution-apis/pull/552> diff --git a/substrate/frame/revive/rpc/codegen/openrpc.json b/substrate/frame/revive/rpc/codegen/openrpc.json new file mode 100644 index 0000000000000..7b55131590ee5 --- /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 0000000000000..ecd364a6d62e0 --- /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 0000000000000..6419a994b6d37 --- /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<Vec<&'static str>> = 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<HashMap<&'static str, &'static str>> = + 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<HashMap<&'static str, HashMap<&'static str, &'static str>>> = + 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<OpenRpc> { + 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<String> { + 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<String, ReferenceOrSchema>, + /// List of already generated types. + generated: HashSet<String>, + /// List of filtered method names, we want to generate. + filtered_method_names: HashSet<String>, + /// 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::<Vec<_>>(); + + if methods.len() != self.filtered_method_names.len() { + let available = + methods.iter().map(|method| method.name.clone()).collect::<HashSet<_>>(); + let missing = self.filtered_method_names.difference(&available).collect::<Vec<_>>(); + 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::<Vec<_>>() + .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<TypeInfo> { + 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::<Vec<_>>() + .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::<Vec<_>>(); + + 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::<Method>( + 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<BlockNumberOrTag>) -> RpcResult<U256>; + "#, + ); + } + + #[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<Address>"); + } + + #[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<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::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<AccessListEntry>; + "#, + ); + } + + #[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<FilterTopic>; + "#, + ); + } + + #[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<AccessList>, + /// 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<Vec<H256>>, + /// blobs + /// Raw blob data. + #[serde(skip_serializing_if = "Option::is_none")] + pub blobs: Option<Vec<Bytes>>, + /// chainId + /// Chain ID that this transaction is valid on. + #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] + pub chain_id: Option<U256>, + /// from address + #[serde(skip_serializing_if = "Option::is_none")] + pub from: Option<Address>, + /// gas limit + #[serde(skip_serializing_if = "Option::is_none")] + pub gas: Option<U256>, + /// 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<U256>, + /// input data + #[serde(alias = "data", skip_serializing_if = "Option::is_none")] + pub input: Option<Bytes>, + /// 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<U256>, + /// 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<U256>, + /// 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<U256>, + /// nonce + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option<U256>, + /// to address + pub to: Option<Address>, + /// type + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option<Byte>, + /// value + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option<U256>, + } + "#, + ); + } +} 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 0000000000000..e0b660ea84e59 --- /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 0000000000000..fa7510a505610 --- /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<Server>, + /// The available methods for the API. While this field is required, it is legal to leave it + /// empty. + pub methods: Vec<RefOr<Method>>, + /// Holds various schemas for the specification. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub components: Option<Components>, + /// Contains additional documentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_docs: Option<ExternalDocumentation>, +} + +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<String>, + /// 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<String>, + /// contact information for the exposed API. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub contact: Option<Contact>, + /// License information for the exposed API. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub license: Option<License>, + /// 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<String>, + /// The URL pointing to the contact information. + /// + /// This must contain an URL. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option<String>, + /// 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<String>, +} + +/// 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<String>, +} + +/// 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<String>, + /// 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<String>, + /// 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<String, ServerVariable>, +} + +/// 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<String>, + /// 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<String>, +} + +/// 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<RefOr<Tag>>, + /// A short summary of what the method does. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option<String>, + /// 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<String>, + /// Additional external documentation for this method. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_docs: Option<ExternalDocumentation>, + /// 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<RefOr<ContentDescriptor>>, + /// 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<RefOr<ContentDescriptor>>, + /// 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<Vec<Server>>, + /// 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<RefOr<Error>>, + /// A list of possible links from this method call. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub links: Vec<RefOr<Link>>, + /// 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<RefOr<ExamplePairing>>, +} + +/// 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<String>, + /// 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<String>, + /// 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<String>, + /// The description of the schema. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option<String>, + /// 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<Schema>, + }, + /// 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<Schema>, + }, + /// 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<Schema>, + }, + /// 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<i64>, + /// The minimum value of the integer. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub minimum: Option<i64>, + /// The maximum value of the integer. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub maximum: Option<i64>, + /// 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<f64>, + /// The minimum value of the number. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub minimum: Option<f64>, + /// The maximum value of the number. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub maximum: Option<f64>, + /// 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<Box<Schema>>, +} + +/// 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<u64>, + /// The maximum length of the string.s + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_length: Option<u64>, + /// The pattern that the string must match. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pattern: Option<String>, + /// The format that the string must be in. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub format: Option<StringFormat>, + /// A list of possible values for the string. + #[serde(default, skip_serializing_if = "Option::is_none", rename = "enum")] + pub enumeration: Option<Vec<String>>, +} + +/// 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)[<http://tools.ietf.org/html/rfc3986#section-4.1>]. + 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<String, Schema>, + + /// List of legacy aliases for properties. + #[serde(skip)] + pub legacy_aliases: HashMap<String, String>, + + /// A list of properties that the object must have. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub required: Vec<String>, +} + +/// 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<String>, + /// A short summary of the example pairing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option<String>, + /// Example parameters. + #[serde(default)] + pub params: Vec<RefOr<ExampleObject>>, + /// Example result. + /// + /// When undefined, shows the usage of the method as a notification. + #[serde(default)] + pub result: RefOr<ExampleObject>, +} + +/// 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<String>, + /// 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<String>, + /// A short summary of the example. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option<String>, + /// The value of the example. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option<ExampleValue>, +} + +/// 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<String>, + /// Short description for the link. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option<String>, + /// 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<String>, + /// 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<LinkParams>, + /// A server object to be used by the target method. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server: Option<Server>, +} + +/// 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<String, ContentDescriptor>, + /// A list of reusable [`Schema`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub schemas: BTreeMap<String, Schema>, + /// A list of reusable [`ExampleObject`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub examples: BTreeMap<String, ExampleObject>, + /// A list of reusable [`Link`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub links: BTreeMap<String, Link>, + /// A list of reusable [`Error`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub errors: BTreeMap<String, Error>, + /// A list of reusable [`ExamplePairing`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "examplePairingObjects")] + pub example_pairings: BTreeMap<String, ExamplePairing>, + /// A list of reusable [`Tag`]s. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub tags: BTreeMap<String, Tag>, +} + +/// 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<String>, + /// 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<String>, + /// Additional external documentation for this tag. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub external_docs: Option<ExternalDocumentation>, +} + +/// 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<String>, + /// 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<T> { + /// A reference to an object defined elsewhere. + Reference { + /// The reference string. + #[serde(rename = "$ref")] + reference: String, + }, + /// An inline object. + Inline(T), +} + +impl<T> RefOr<T> { + /// Unwraps the inlined object. + pub fn unwrap_inline(&self) -> &T { + match self { + RefOr::Reference { reference } => panic!("Unexpected reference: {reference}"), + RefOr::Inline(v) => v, + } + } +} + +impl<T: Default> Default for RefOr<T> { + #[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<Server> { + 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: Default + PartialEq>(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 0000000000000..4d1bb620c2e4a --- /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<TypeInfo>; + + /// 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<T> From<T> for TypeInfo +where + T: Into<String>, +{ + 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<String>, + /// 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<String>, +} + +/// Represents a collection of fields. +#[derive(Debug)] +pub struct Fields(Vec<Field>); + +impl From<Vec<Field>> for Fields { + fn from(value: Vec<Field>) -> Self { + Self(value) + } +} + +impl IntoIterator for Fields { + type Item = Field; + type IntoIter = std::vec::IntoIter<Self::Item>; + 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::<Vec<_>>() + .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::<Vec<_>>() + .into() + } +} + +/// The variant of an enum. +#[derive(Debug)] +pub struct Variant { + /// The documentation for the variant. + doc: Option<String>, + /// 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<String> { + 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<Variant>); +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::<Vec<_>>() + .into() + } +} + +impl From<Vec<Variant>> for Variants { + fn from(value: Vec<Variant>) -> 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<String>), +} + +/// A type printer. +#[derive(Debug)] +pub struct TypePrinter { + pub doc: Option<String>, + 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<String>, + } + "#, + ); + } + + #[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 <pgherveou@gmail.com> Date: Fri, 22 Nov 2024 15:46:37 +0100 Subject: [PATCH 02/28] 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 0000000000000..2d8dccc771e8b --- /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 0000000000000..d1ad60c1f55a5 --- /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 d36089fbc84ea..a64c920c40687 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 0000000000000..317ed00b92f95 --- /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 2c2cfd5f75337..e6655889e21aa 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 0000000000000..bc685e0b827ac --- /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 0000000000000..d32cc7e11a9a8 --- /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 3ae1f0fbd799a..fa101dcad1d9e 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 0000000000000..0c5e59d078495 --- /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 c6b7700d1ccf4..3bafdd30a34df 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<typeof compile>[0] -type CompileOutput = Awaited<ReturnType<typeof compile>> -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 94cc2560272e6..2e672a9772ff4 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 0000000000000..ef4cde1824e42 --- /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<void>((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 975d8faf15b31..d1f14bbc064e7 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<ReturnType<typeof compile>> -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<BaseContract> { - 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<null | TransactionReceipt> { - 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 7a8edbde36626..0040b0c78dc47 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 ea1bf4eceeb9c..0000000000000 --- 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 ae2dd50f2af86..aef9a487b0c01 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 d37f1d7600653..d7cbca520113f 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<ErrorObjectOwned> { fn extract_revert_message(exec_data: &[u8]) -> Option<String> { 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<u8>), /// 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<ClientError> for ErrorObjectOwned { fn from(err: ClientError) -> Self { @@ -179,7 +204,7 @@ impl From<ClientError> for ErrorObjectOwned { }, ClientError::Reverted(data) => { let data = format!("0x{}", hex::encode(data)); - ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, Some(data)) + ErrorObjectOwned::owned::<String>(REVERT_CODE, msg, Some(data)) }, _ => ErrorObjectOwned::owned::<String>(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<U256, ClientError> { - 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 6a324e63a8573..b35497e34bd79 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<BlockNumberOrTag>, + block: Option<BlockNumberOrTag>, ) -> RpcResult<U256> { - 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<BlockNumberOrTagOrHash>, + ) -> RpcResult<Bytes> { + 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<H256> { @@ -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<BlockNumberOrTagOrHash>, - ) -> RpcResult<Bytes> { - 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 7734c8c572090..686cebcf657de 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 5037ec05d881c..1370ea2b7612e 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<BlockNumberOrTag> 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<AccessListEntry>; )] 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 <pgherveou@gmail.com> Date: Fri, 22 Nov 2024 15:51:20 +0100 Subject: [PATCH 03/28] fixup cargo.toml --- Cargo.toml | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5402b3e60cc4f..5ece645f82c49 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 <pgherveou@gmail.com> Date: Fri, 22 Nov 2024 16:25:14 +0100 Subject: [PATCH 04/28] 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 330c2563d9761..4eb40bc9761ff 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 7b55131590ee5..44e590cd3e7d2 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 6419a994b6d37..bf18efaa17f8e 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 3390803689698..ad34dbfdfb491 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 <pgherveou@gmail.com> Date: Fri, 22 Nov 2024 17:21:27 +0100 Subject: [PATCH 05/28] 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 44e590cd3e7d2..4a91ee18177c0 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 bf18efaa17f8e..c4881a186c500 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<HashMap<&'static str, HashMap<&'static str, ]) }); +/// Custom Default impl +pub static CUSTOM_DEFAULT_VARIANTS: LazyLock<HashMap<&'static str, &'static str>> = + 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<OpenRpc> { 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<U256>, + #[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<AccessList>, /// 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<Vec<H256>>, + #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec<H256>, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Option::is_none")] - pub blobs: Option<Vec<Bytes>>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub blobs: Vec<Bytes>, /// 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 4d1bb620c2e4a..0b13500b85c50 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<String>, pub name: String, pub content: TypeContent, + custom_default_variant: Option<String>, } /// A macro to write a formatted line to a buffer. @@ -287,6 +287,15 @@ macro_rules! writeln { } impl TypePrinter { + pub fn new( + doc: Option<String>, + name: String, + content: TypeContent, + custom_default_variant: Option<String>, + ) -> 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 1cf8d984b68b8..287d84fc186f8 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<BlockNumberOrTag> 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 1370ea2b7612e..a9206e38fcc87 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<H256>, /// Withdrawals - #[serde(skip_serializing_if = "Option::is_none")] - pub withdrawals: Option<Vec<Withdrawal>>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub withdrawals: Vec<Withdrawal>, /// Withdrawals root #[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")] pub withdrawals_root: Option<H256>, @@ -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<BlockNumberOrTag> 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<AccessList>, /// 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<Vec<H256>>, + #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec<H256>, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Option::is_none")] - pub blobs: Option<Vec<Bytes>>, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub blobs: Vec<Bytes>, /// 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 <pgherveou@gmail.com> Date: Fri, 22 Nov 2024 17:24:11 +0100 Subject: [PATCH 06/28] 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 287d84fc186f8..338be88e69b5b 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 14:09:45 +0100 Subject: [PATCH 07/28] 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 4eb40bc9761ff..65580fe0239c2 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 cafea3b6ff8bb..5807d80ab5425 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 = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&address); System::account_nonce(account) } - fn eth_transact( - from: H160, - dest: Option<H160>, - value: U256, - input: Vec<u8>, - gas_limit: Option<Weight>, - storage_deposit_limit: Option<Balance>, - ) -> pallet_revive::EthContractResult<Balance> + + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result<pallet_revive::EthTransactInfo<Balance>, pallet_revive::EthTransactError> { - use pallet_revive::AddressMapper; - let blockweights = <Runtime as frame_system::Config>::BlockWeights::get(); - let origin = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from); + let blockweights: BlockWeights = <Runtime as frame_system::Config>::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 bff2635480873..faffcd23fbcf9 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<H160>, - value: U256, - input: Vec<u8>, - gas_limit: Option<Weight>, - storage_deposit_limit: Option<Balance>, - ) -> pallet_revive::EthContractResult<Balance> + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result<pallet_revive::EthTransactInfo<Balance>, pallet_revive::EthTransactError> { - use pallet_revive::AddressMapper; let blockweights: BlockWeights = <Runtime as frame_system::Config>::BlockWeights::get(); - let origin = <Runtime as pallet_revive::Config>::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 81fbbc8cf38e1..d2026d2ce9eb2 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 0b13500b85c50..8a8933b4f432f 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 ef4cde1824e42..c5cb64e63afd0 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 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 delta 11763 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%0D#q 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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 d7cbca520113f..5f664be115e29 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<u8>), + #[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<ClientError> 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::<Vec<u8>>(CALL_EXECUTION_FAILED_CODE, msg, None) + ErrorObjectOwned::owned::<Vec<u8>>( + 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::<String>(REVERT_CODE, msg, Some(data)) }, - _ => ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None), + ClientError::Reverted(EthTransactError::Message(msg)) => + ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None), + _ => + ErrorObjectOwned::owned::<String>(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<EthContractResult<Balance, Vec<u8>>, ClientError> { + ) -> Result<EthTransactInfo<Balance>, 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 b35497e34bd79..8072de4d3ce36 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<BlockNumberOrTag>, ) -> RpcResult<U256> { - // 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<Bytes> { 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<H256> { @@ -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 a232b231bc7c0..1e1c395028a45 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<A, B>", - with = "::subxt::utils::Static<::pallet_revive::EthContractResult<A, B>>" + path = "pallet_revive::evm::api::rpc_types_gen::GenericTransaction", + with = "::subxt::utils::Static<::pallet_revive::evm::GenericTransaction>" + ), + substitute_type( + path = "pallet_revive::primitives::EthTransactInfo<B>", + with = "::subxt::utils::Static<::pallet_revive::EthTransactInfo<B>>" + ), + 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 3442ed73accab..18b7e7c17e094 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<u8> { - 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::<Vec<_>>() } } @@ -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 338be88e69b5b..ed046cb4da445 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<BlockNumberOrTag> for BlockNumberOrTagOrHash { } } +impl From<TransactionSigned> 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<H160>) -> 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<H160>) -> 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 a9206e38fcc87..1d65fdefdde68 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<H256>, /// Withdrawals - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub withdrawals: Vec<Withdrawal>, /// Withdrawals root #[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")] @@ -148,11 +148,11 @@ pub struct GenericTransaction { pub access_list: Option<AccessList>, /// 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<H256>, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub blobs: Vec<Bytes>, /// 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<bool>, /// topics - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub topics: Vec<H256>, /// transaction hash #[serde(rename = "transactionHash")] diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 40c210304ca2f..e25069b77121d 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -48,7 +48,7 @@ type CallOf<T> = <T as frame_system::Config>::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<Test>, } @@ -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(<Test as crate::Config>::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::<Test>::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::<Test>::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<u8>, data: Vec<u8>) -> 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::<Test> { 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::<Test> { - 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<dyn FnOnce(&mut TransactionLegacyUnsigned)>, _); 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<dyn FnOnce(&mut GenericTransaction)>, _); 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 49c08166483e5..3f88b3087e9cb 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<T>, + /// 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<E>, } @@ -777,6 +779,7 @@ where storage_meter: &'a mut storage::meter::Meter<T>, value: U256, input_data: Vec<u8>, + 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<u8>, 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<T>, storage_meter: &'a mut storage::meter::Meter<T>, value: U256, + unchecked: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result<Option<(Self, E)>, 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::<Test>::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 b55854e2eec58..12a55004600c4 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<R>( impl<T: Config> Pallet<T> where - BalanceOf<T>: Into<U256> + TryFrom<U256>, + BalanceOf<T>: Into<U256> + TryFrom<U256> + Bounded, MomentOf<T>: Into<U256>, T::Hash: frame_support::traits::IsType<H256>, { @@ -1098,7 +1100,7 @@ where dest: H160, value: BalanceOf<T>, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, data: Vec<u8>, 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::<T>::max_value()), + }; let result = ExecStack::<T, WasmBlob<T>>::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<T>, value: BalanceOf<T>, gas_limit: Weight, - mut storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, code: Code, data: Vec<u8>, 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::<T>::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::<T, WasmBlob<T>>::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<H160>, - value: U256, - input: Vec<u8>, + mut tx: GenericTransaction, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, utx_encoded_size: impl Fn(Call<T>) -> u32, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> EthContractResult<BalanceOf<T>> + ) -> Result<EthTransactInfo<BalanceOf<T>>, EthTransactError> where T: pallet_transaction_payment::Config, <T as frame_system::Config>::RuntimeCall: @@ -1247,26 +1267,61 @@ where T::Nonce: Into<U256>, T::Hash: frame_support::traits::IsType<H256>, { - 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 = <System<T>>::account_nonce(&origin); + let storage_deposit_limit = if tx.gas.is_some() { + DepositLimit::Balance(BalanceOf::<T>::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(<System<T>>::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::<T>::TransferFailed.into() || + err == Error::<T>::StorageDepositNotEnoughFunds.into() || + err == Error::<T>::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: <T as Config>::RuntimeCall = crate::Call::<T>::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::<T>::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<T>, ) -> CodeUploadResult<BalanceOf<T>> { 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<u8>, storage_deposit_limit: BalanceOf<T>, + unchecked: bool, ) -> Result<(WasmBlob<T>, BalanceOf<T>), 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, <Error<T>>::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<H160>, - value: U256, - input: Vec<u8>, - gas_limit: Option<Weight>, - storage_deposit_limit: Option<Balance>, - ) -> EthContractResult<Balance>; + fn eth_transact(tx: GenericTransaction) -> Result<EthTransactInfo<Balance>, 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 024b1f3448e12..c091ecf288c51 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<T> { + Unchecked, + Balance(T), +} + +impl<T> DepositLimit<T> { + pub fn is_unchecked(&self) -> bool { + match self { + Self::Unchecked => true, + _ => false, + } + } +} + +impl<T> From<T> for DepositLimit<T> { + 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<R, Balance, EventRecord> { /// The result of the execution of a `eth_transact` call. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct EthContractResult<Balance, R = Result<ExecReturnValue, DispatchError>> { - /// The fee charged for the execution. - pub fee: Balance, +pub struct EthTransactInfo<Balance> { /// 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<u8>, +} + +/// Error type of a `eth_transact` call. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum EthTransactError { + Data(Vec<u8>), + 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 712010bc82574..f068509c34f19 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<T>) -> 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<T>) -> Result<DepositOf<T>, 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<T>, + unchecked: bool, + ) -> Result<DepositOf<T>, 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<T: Config, E: Ext<T>> RawMeter<T, E, Nested> { contract: &T::AccountId, contract_info: &mut ContractInfo<T>, code_info: &CodeInfo<T>, + 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::<T>::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<T: Config, E: Ext<T>> RawMeter<T, E, Nested> { } 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(<Error<T>>::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 e64f588944326..8ba5e73840702 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<T>, value: BalanceOf<T>, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, code: Code, data: Vec<u8>, salt: Option<[u8; 32]>, @@ -159,7 +160,7 @@ builder!( origin, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::<T>(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::<T>()), code, data: vec![], salt: Some([0; 32]), @@ -198,7 +199,7 @@ builder!( dest: H160, value: BalanceOf<T>, gas_limit: Weight, - storage_deposit_limit: BalanceOf<T>, + storage_deposit_limit: DepositLimit<BalanceOf<T>>, data: Vec<u8>, debug: DebugInfo, collect_events: CollectEvents, @@ -216,7 +217,7 @@ builder!( dest, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::<T>(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::<T>()), 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 34afe8aabfe6f..1df300f031a76 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!( <<Test as Config>::Currency as Mutate<AccountId32>>::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 7c4fbba71f656..c9e19e52ace13 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::<Test>(), + DepositLimit::Balance(deposit_limit::<Test>()), Code::Upload(wasm), vec![], Some([0u8; 32]), @@ -198,7 +199,7 @@ fn call_interception_works() { RuntimeOrigin::signed(ALICE), 0, GAS_LIMIT, - deposit_limit::<Test>(), + deposit_limit::<Test>().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 f10c4f5fddf8a..82aa67a1d678f 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<BalanceOf<T>, Error<T>> { + pub fn store_code(&mut self, unchecked: bool) -> Result<BalanceOf<T>, Error<T>> { let code_hash = *self.code_hash(); <CodeInfoOf<T>>::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); <Error<T>>::StorageDepositNotEnoughFunds })?; + } self.code_info.refcount = 0; <PristineCode<T>>::insert(code_hash, &self.code); From 3a10a37f0b80ceb46835c26814fbde8dc85d7556 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 15:22:37 +0100 Subject: [PATCH 08/28] 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 e25069b77121d..a25ea53d276c8 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -48,7 +48,7 @@ type CallOf<T> = <T as frame_system::Config>::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<Test>, + before_validate: Option<std::sync::Arc<dyn Fn() + Send + Sync>>, } 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<u8>, data: Vec<u8>) -> 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 _ = <Test as crate::Config>::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 _ = <Test as crate::Config>::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::<Test> { - 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::<Test> { + 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::<Test> { - 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::<Test> { + 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)) - ); - - <crate::System<Test>>::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(|| { + <crate::System<Test>>::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<dyn FnOnce(&mut GenericTransaction)>, _); 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<dyn FnOnce(&mut GenericTransaction)>, _); 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 15:24:43 +0100 Subject: [PATCH 09/28] 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 5ece645f82c49..533ea4c9e8780 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 65d8ba501b1c6..0000000000000 --- 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 2ca838f0db232..0000000000000 --- 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 <https://github.com/ethereum/execution-apis> -- See building instructions to re-generate the openrpc.json <https://github.com/ethereum/execution-apis?tab=readme-ov-file#building> -- Include fixes from <https://github.com/ethereum/execution-apis/pull/552> diff --git a/substrate/frame/revive/rpc/codegen/openrpc.json b/substrate/frame/revive/rpc/codegen/openrpc.json deleted file mode 100644 index 4a91ee18177c0..0000000000000 --- 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 ecd364a6d62e0..0000000000000 --- 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 c4881a186c500..0000000000000 --- 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<Vec<&'static str>> = 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<HashMap<&'static str, &'static str>> = - 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<HashMap<&'static str, HashMap<&'static str, &'static str>>> = - 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<HashMap<&'static str, &'static str>> = - 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<OpenRpc> { - 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<String> { - 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<String, ReferenceOrSchema>, - /// List of already generated types. - generated: HashSet<String>, - /// List of filtered method names, we want to generate. - filtered_method_names: HashSet<String>, - /// 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::<Vec<_>>(); - - if methods.len() != self.filtered_method_names.len() { - let available = - methods.iter().map(|method| method.name.clone()).collect::<HashSet<_>>(); - let missing = self.filtered_method_names.difference(&available).collect::<Vec<_>>(); - 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::<Vec<_>>() - .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<TypeInfo> { - 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::<Vec<_>>() - .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::<Vec<_>>(); - - 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::<Method>( - 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<BlockNumberOrTag>) -> RpcResult<U256>; - "#, - ); - } - - #[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<Address>"); - } - - #[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<AccessListEntry>; - "#, - ); - } - - #[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<FilterTopic>; - "#, - ); - } - - #[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<AccessList>, - /// 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<H256>, - /// blobs - /// Raw blob data. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub blobs: Vec<Bytes>, - /// chainId - /// Chain ID that this transaction is valid on. - #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] - pub chain_id: Option<U256>, - /// from address - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option<Address>, - /// gas limit - #[serde(skip_serializing_if = "Option::is_none")] - pub gas: Option<U256>, - /// 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<U256>, - /// input data - #[serde(alias = "data", skip_serializing_if = "Option::is_none")] - pub input: Option<Bytes>, - /// 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<U256>, - /// 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<U256>, - /// 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<U256>, - /// nonce - #[serde(skip_serializing_if = "Option::is_none")] - pub nonce: Option<U256>, - /// to address - pub to: Option<Address>, - /// type - #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option<Byte>, - /// value - #[serde(skip_serializing_if = "Option::is_none")] - pub value: Option<U256>, - } - "#, - ); - } -} 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 e0b660ea84e59..0000000000000 --- 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 fa7510a505610..0000000000000 --- 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<Server>, - /// The available methods for the API. While this field is required, it is legal to leave it - /// empty. - pub methods: Vec<RefOr<Method>>, - /// Holds various schemas for the specification. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub components: Option<Components>, - /// Contains additional documentation. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub external_docs: Option<ExternalDocumentation>, -} - -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<String>, - /// 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<String>, - /// contact information for the exposed API. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub contact: Option<Contact>, - /// License information for the exposed API. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub license: Option<License>, - /// 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<String>, - /// The URL pointing to the contact information. - /// - /// This must contain an URL. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub url: Option<String>, - /// 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<String>, -} - -/// 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<String>, -} - -/// 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<String>, - /// 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<String>, - /// 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<String, ServerVariable>, -} - -/// 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<String>, - /// 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<String>, -} - -/// 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<RefOr<Tag>>, - /// A short summary of what the method does. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option<String>, - /// 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<String>, - /// Additional external documentation for this method. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub external_docs: Option<ExternalDocumentation>, - /// 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<RefOr<ContentDescriptor>>, - /// 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<RefOr<ContentDescriptor>>, - /// 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<Vec<Server>>, - /// 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<RefOr<Error>>, - /// A list of possible links from this method call. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub links: Vec<RefOr<Link>>, - /// 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<RefOr<ExamplePairing>>, -} - -/// 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<String>, - /// 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<String>, - /// 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<String>, - /// The description of the schema. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option<String>, - /// 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<Schema>, - }, - /// 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<Schema>, - }, - /// 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<Schema>, - }, - /// 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<i64>, - /// The minimum value of the integer. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub minimum: Option<i64>, - /// The maximum value of the integer. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub maximum: Option<i64>, - /// 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<f64>, - /// The minimum value of the number. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub minimum: Option<f64>, - /// The maximum value of the number. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub maximum: Option<f64>, - /// 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<Box<Schema>>, -} - -/// 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<u64>, - /// The maximum length of the string.s - #[serde(default, skip_serializing_if = "Option::is_none")] - pub max_length: Option<u64>, - /// The pattern that the string must match. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub pattern: Option<String>, - /// The format that the string must be in. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub format: Option<StringFormat>, - /// A list of possible values for the string. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "enum")] - pub enumeration: Option<Vec<String>>, -} - -/// 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)[<http://tools.ietf.org/html/rfc3986#section-4.1>]. - 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<String, Schema>, - - /// List of legacy aliases for properties. - #[serde(skip)] - pub legacy_aliases: HashMap<String, String>, - - /// A list of properties that the object must have. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub required: Vec<String>, -} - -/// 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<String>, - /// A short summary of the example pairing. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option<String>, - /// Example parameters. - #[serde(default)] - pub params: Vec<RefOr<ExampleObject>>, - /// Example result. - /// - /// When undefined, shows the usage of the method as a notification. - #[serde(default)] - pub result: RefOr<ExampleObject>, -} - -/// 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<String>, - /// 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<String>, - /// A short summary of the example. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option<String>, - /// The value of the example. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub value: Option<ExampleValue>, -} - -/// 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<String>, - /// Short description for the link. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub summary: Option<String>, - /// 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<String>, - /// 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<LinkParams>, - /// A server object to be used by the target method. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub server: Option<Server>, -} - -/// 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<String, ContentDescriptor>, - /// A list of reusable [`Schema`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub schemas: BTreeMap<String, Schema>, - /// A list of reusable [`ExampleObject`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub examples: BTreeMap<String, ExampleObject>, - /// A list of reusable [`Link`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub links: BTreeMap<String, Link>, - /// A list of reusable [`Error`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub errors: BTreeMap<String, Error>, - /// A list of reusable [`ExamplePairing`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "examplePairingObjects")] - pub example_pairings: BTreeMap<String, ExamplePairing>, - /// A list of reusable [`Tag`]s. - #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub tags: BTreeMap<String, Tag>, -} - -/// 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<String>, - /// 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<String>, - /// Additional external documentation for this tag. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub external_docs: Option<ExternalDocumentation>, -} - -/// 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<String>, - /// 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<T> { - /// A reference to an object defined elsewhere. - Reference { - /// The reference string. - #[serde(rename = "$ref")] - reference: String, - }, - /// An inline object. - Inline(T), -} - -impl<T> RefOr<T> { - /// Unwraps the inlined object. - pub fn unwrap_inline(&self) -> &T { - match self { - RefOr::Reference { reference } => panic!("Unexpected reference: {reference}"), - RefOr::Inline(v) => v, - } - } -} - -impl<T: Default> Default for RefOr<T> { - #[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<Server> { - 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: Default + PartialEq>(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 8a8933b4f432f..0000000000000 --- 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<TypeInfo>; - - /// 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<T> From<T> for TypeInfo -where - T: Into<String>, -{ - 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<String>, - /// 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<String>, -} - -/// Represents a collection of fields. -#[derive(Debug)] -pub struct Fields(Vec<Field>); - -impl From<Vec<Field>> for Fields { - fn from(value: Vec<Field>) -> Self { - Self(value) - } -} - -impl IntoIterator for Fields { - type Item = Field; - type IntoIter = std::vec::IntoIter<Self::Item>; - 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::<Vec<_>>() - .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::<Vec<_>>() - .into() - } -} - -/// The variant of an enum. -#[derive(Debug)] -pub struct Variant { - /// The documentation for the variant. - doc: Option<String>, - /// 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<String> { - 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<Variant>); -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::<Vec<_>>() - .into() - } -} - -impl From<Vec<Variant>> for Variants { - fn from(value: Vec<Variant>) -> 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<String>), -} - -/// A type printer. -#[derive(Debug)] -pub struct TypePrinter { - pub doc: Option<String>, - pub name: String, - pub content: TypeContent, - custom_default_variant: Option<String>, -} - -/// 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<String>, - name: String, - content: TypeContent, - custom_default_variant: Option<String>, - ) -> 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<String>, - } - "#, - ); - } - - #[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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 15:25:35 +0100 Subject: [PATCH 10/28] 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 f05aeb93eb71f..0000000000000 --- 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 16:15:31 +0100 Subject: [PATCH 11/28] 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 65580fe0239c2..fe0bff33edc06 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 9c4d817a07dee..b73815bfb9ea9 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::<T>(), + DepositLimit::Balance(default_deposit_limit::<T>()), Code::Upload(module.code), data, salt, diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 3f88b3087e9cb..ce8ca3f94a40b 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 16:25:01 +0100 Subject: [PATCH 12/28] 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 686cebcf657de..cbcd7c4dae9f1 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 c091ecf288c51..a7127f812b4b9 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<T> { +pub enum DepositLimit<Balance> { + /// Allows bypassing all balance transfer checks. Unchecked, - Balance(T), + + /// Specifies a maximum allowable balance for a deposit. + Balance(Balance), } impl<T> DepositLimit<T> { From 0941800fd38a545c416f1b4f636ae1b8b21f9743 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 16:33:35 +0100 Subject: [PATCH 13/28] 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 ce8ca3f94a40b..050b8e1449ead 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<T>, /// 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<E>, } @@ -779,7 +779,7 @@ where storage_meter: &'a mut storage::meter::Meter<T>, value: U256, input_data: Vec<u8>, - 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<u8>, 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<T>, storage_meter: &'a mut storage::meter::Meter<T>, value: U256, - unchecked: bool, + skip_transfer: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result<Option<(Self, E)>, 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 12a55004600c4..efc0322b5f6c1 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<u8>, storage_deposit_limit: BalanceOf<T>, - unchecked: bool, + skip_transfer: bool, ) -> Result<(WasmBlob<T>, BalanceOf<T>), 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, <Error<T>>::StorageDepositLimitExhausted); Ok((module, deposit)) } diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index f068509c34f19..6eddf048be98f 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<T>, - unchecked: bool, + skip_transfer: bool, ) -> Result<DepositOf<T>, 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<T: Config, E: Ext<T>> RawMeter<T, E, Nested> { contract: &T::AccountId, contract_info: &mut ContractInfo<T>, code_info: &CodeInfo<T>, - 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::<T>::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 82aa67a1d678f..6f6108dd12224 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<BalanceOf<T>, Error<T>> { + pub fn store_code(&mut self, skip_transfer: bool) -> Result<BalanceOf<T>, Error<T>> { let code_hash = *self.code_hash(); <CodeInfoOf<T>>::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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 16:39:57 +0100 Subject: [PATCH 14/28] 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 5f664be115e29..60dbc12501f54 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<String> { 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 8072de4d3ce36..ccd8bb043e90e 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<BlockNumberOrTag>, ) -> RpcResult<U256> { 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 16:51:00 +0100 Subject: [PATCH 15/28] 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 2d8dccc771e8b..e660a0a054a45 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 d1ad60c1f55a5..93daf34e02b67 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 a64c920c40687..d36089fbc84ea 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 317ed00b92f95..c389e2daf1dae 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 e6655889e21aa..2c2cfd5f75337 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 bc685e0b827ac..3d44cd998ad16 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 fa101dcad1d9e..559ca8a5b4fb2 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 0c5e59d078495..1261dcab7812f 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 0000000000000..b48fcd9620eaa --- /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<void>((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 c5cb64e63afd0..bb642b9aa62e4 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<void>((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 d1f14bbc064e7..e1f0e780d95b4 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 17:02:31 +0100 Subject: [PATCH 16/28] 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 b48fcd9620eaa..92b20473d165a 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 17:03:13 +0100 Subject: [PATCH 17/28] 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 bb642b9aa62e4..ea7a7b28bf2ac 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 <action@github.com> Date: Tue, 26 Nov 2024 16:09:21 +0000 Subject: [PATCH 18/28] 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 0000000000000..d025b94ed53ea --- /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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 17:33:42 +0100 Subject: [PATCH 19/28] 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 bd05726a1a45f..34f797c2b530f 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<parachain::Runtime> { 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 <pgherveou@gmail.com> Date: Tue, 26 Nov 2024 18:21:07 +0100 Subject: [PATCH 20/28] 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 60dbc12501f54..54d094a2e6160 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 <pgherveou@gmail.com> Date: Wed, 27 Nov 2024 10:17:37 +0100 Subject: [PATCH 21/28] 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 18b7e7c17e094..9b61cd042ec54 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<u8> { const DUMMY_SIGNATURE: [u8; 65] = [0u8; 65]; self.unsigned_payload() From 037c58ee5f629868cf589b7537e3b156ecc2a305 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Thu, 28 Nov 2024 22:28:52 +0100 Subject: [PATCH 22/28] fix from address default --- substrate/frame/revive/rpc/examples/js/package.json | 3 ++- substrate/frame/revive/src/lib.rs | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 559ca8a5b4fb2..548c2899d6270 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -12,7 +12,8 @@ "ethers": "^6.13.4", "prettier": "^3.3.3", "solc": "^0.8.28", - "viem": "^2.21.47" + "viem": "^2.21.47", + "@parity/revive": "^0.0.5" }, "devDependencies": { "@types/bun": "^1.1.13", diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index efc0322b5f6c1..857b2a1878f77 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1262,10 +1262,7 @@ where { 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 from = tx.from.unwrap_or_default(); let origin = T::AddressMapper::to_account_id(&from); let storage_deposit_limit = if tx.gas.is_some() { From 31ed649751477dd6af2ca8d56849b0bb969643c6 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Fri, 29 Nov 2024 14:47:10 +0100 Subject: [PATCH 23/28] pr review --- prdoc/pr_6608.prdoc | 7 ------- substrate/frame/revive/rpc/Cargo.toml | 2 +- .../frame/revive/rpc/examples/js/contracts/.solhint.json | 3 +++ .../revive/rpc/examples/js/contracts/ErrorTester.sol | 7 +++---- .../frame/revive/rpc/examples/js/contracts/PiggyBank.sol | 8 ++++---- substrate/frame/revive/rpc/src/client.rs | 1 - substrate/frame/revive/src/exec.rs | 1 + 7 files changed, 12 insertions(+), 17 deletions(-) create mode 100644 substrate/frame/revive/rpc/examples/js/contracts/.solhint.json diff --git a/prdoc/pr_6608.prdoc b/prdoc/pr_6608.prdoc index d025b94ed53ea..b9cd7008de47b 100644 --- a/prdoc/pr_6608.prdoc +++ b/prdoc/pr_6608.prdoc @@ -3,14 +3,7 @@ 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 diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 9f89b74c668f8..fe9cc82dd4d99 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -67,13 +67,13 @@ hex = { workspace = true } hex-literal = { workspace = true, optional = true } scale-info = { workspace = true } secp256k1 = { workspace = true, optional = true, features = ["recovery"] } -env_logger = { workspace = true } ethabi = { version = "18.0.0" } [features] example = ["hex-literal", "rlp", "secp256k1", "subxt-signer"] [dev-dependencies] +env_logger = { workspace = true } static_init = { workspace = true } hex-literal = { workspace = true } pallet-revive-fixtures = { workspace = true } diff --git a/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json new file mode 100644 index 0000000000000..ce2220e0b7560 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json @@ -0,0 +1,3 @@ +{ + "extends": "solhint:recommended" +} diff --git a/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol index d32cc7e11a9a8..f1fdd219624ab 100644 --- a/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol +++ b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol @@ -2,14 +2,13 @@ pragma solidity ^0.8.0; contract ErrorTester { + bool public state; - // Payable function that can be used to test unsifficient funds errors - function valueMatch(uint value) public payable { + // Payable function that can be used to test insufficient funds errors + function valueMatch(uint256 value) public payable { require(msg.value == value , "msg.value does not match value"); } - bool public state; - function setState(bool newState) public { state = newState; } diff --git a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol index 1906c46588895..0c8a4d26f4dc2 100644 --- a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol +++ b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; contract PiggyBank { - uint private balance; + uint256 private balance; address public owner; constructor() { @@ -11,16 +11,16 @@ contract PiggyBank { balance = 0; } - function deposit() public payable returns (uint) { + function deposit() public payable returns (uint256) { balance += msg.value; return balance; } - function getDeposit() public view returns (uint) { + function getDeposit() public view returns (uint256) { return balance; } - function withdraw(uint withdrawAmount) public returns (uint remainingBal) { + function withdraw(uint256 withdrawAmount) public returns (uint256 remainingBal) { require(msg.sender == owner); balance -= withdrawAmount; (bool success, ) = payable(msg.sender).call{value: withdrawAmount}(""); diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 54d094a2e6160..9e5062b40c149 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -188,7 +188,6 @@ pub enum ClientError { } const REVERT_CODE: i32 = 3; -// TODO convert error code to https://eips.ethereum.org/EIPS/eip-1474#error-codes impl From<ClientError> for ErrorObjectOwned { fn from(err: ClientError) -> Self { match err { diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 050b8e1449ead..b23d7e4e60efe 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -563,6 +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<T>, /// Whether or not actual transfer of funds should be performed. + /// This is set to `true` exclusively when we simulate a call through eth_transact. skip_transfer: bool, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData<E>, From 76e3fdaa26fdfec1d2ac71e25649f140f55d2156 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Fri, 29 Nov 2024 14:53:49 +0100 Subject: [PATCH 24/28] PR review --- substrate/frame/revive/rpc/src/client.rs | 4 ++-- substrate/frame/revive/src/lib.rs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 9e5062b40c149..3a00c79cec2b1 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -116,9 +116,9 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option<ErrorObjectOwned> { /// Extract the revert message from a revert("msg") solidity statement. fn extract_revert_message(exec_data: &[u8]) -> Option<String> { - let function_selector = exec_data.get(0..4)?; + let error_selector = exec_data.get(0..4)?; - match function_selector { + match error_selector { // assert(false) [0x4E, 0x48, 0x7B, 0x71] => { let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 857b2a1878f77..6a1cea1ee787e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -573,8 +573,6 @@ 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. From ec41a829fbea4d7495a039d09e4ae71d0b0e3c84 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Fri, 29 Nov 2024 15:19:40 +0100 Subject: [PATCH 25/28] rm json files --- .../rpc/examples/js/abi/errorTester.json | 106 ------------------ .../revive/rpc/examples/js/abi/event.json | 34 ------ .../revive/rpc/examples/js/abi/piggyBank.json | 65 ----------- .../revive/rpc/examples/js/abi/revert.json | 14 --- 4 files changed, 219 deletions(-) delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/errorTester.json delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/event.json delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/piggyBank.json delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/revert.json 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 e660a0a054a45..0000000000000 --- 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" - } -] diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.json b/substrate/frame/revive/rpc/examples/js/abi/event.json deleted file mode 100644 index d36089fbc84ea..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/event.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "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/piggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json deleted file mode 100644 index 2c2cfd5f75337..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "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/revert.json b/substrate/frame/revive/rpc/examples/js/abi/revert.json deleted file mode 100644 index be2945fcc0a59..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/revert.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "doRevert", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] From 80dceac854018ce2b64abe8cb751601b6d13e2a8 Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Fri, 29 Nov 2024 15:21:16 +0100 Subject: [PATCH 26/28] rm types --- .../frame/revive/rpc/examples/js/package.json | 2 +- .../rpc/examples/js/src/build-contracts.ts | 1 - .../js/types/ethers-contracts/Event.ts | 117 ------------------ .../js/types/ethers-contracts/PiggyBank.ts | 96 -------------- .../js/types/ethers-contracts/Revert.ts | 78 ------------ .../js/types/ethers-contracts/common.ts | 100 --------------- .../factories/Event__factory.ts | 51 -------- .../factories/PiggyBank__factory.ts | 82 ------------ .../factories/Revert__factory.ts | 31 ----- .../types/ethers-contracts/factories/index.ts | 6 - .../js/types/ethers-contracts/index.ts | 10 -- 11 files changed, 1 insertion(+), 573 deletions(-) delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 548c2899d6270..6d8d00fd42147 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -10,12 +10,12 @@ }, "dependencies": { "ethers": "^6.13.4", - "prettier": "^3.3.3", "solc": "^0.8.28", "viem": "^2.21.47", "@parity/revive": "^0.0.5" }, "devDependencies": { + "prettier": "^3.3.3", "@types/bun": "^1.1.13", "typescript": "^5.5.3", "vite": "^5.4.8" 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 3bafdd30a34df..b25b5a7f2199e 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -53,7 +53,6 @@ for (const { keypath, contract, file } of input) { const out = JSON.parse(evmCompile(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`, { diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts deleted file mode 100644 index d65f953969f0c..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BigNumberish, - BytesLike, - FunctionFragment, - Result, - Interface, - EventFragment, - AddressLike, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedLogDescription, - TypedListener, - TypedContractMethod, -} from './common' - -export interface EventInterface extends Interface { - getFunction(nameOrSignature: 'triggerEvent'): FunctionFragment - - getEvent(nameOrSignatureOrTopic: 'ExampleEvent'): EventFragment - - encodeFunctionData(functionFragment: 'triggerEvent', values?: undefined): string - - decodeFunctionResult(functionFragment: 'triggerEvent', data: BytesLike): Result -} - -export namespace ExampleEventEvent { - export type InputTuple = [sender: AddressLike, value: BigNumberish, message: string] - export type OutputTuple = [sender: string, value: bigint, message: string] - export interface OutputObject { - sender: string - value: bigint - message: string - } - export type Event = TypedContractEvent<InputTuple, OutputTuple, OutputObject> - export type Filter = TypedDeferredTopicFilter<Event> - export type Log = TypedEventLog<Event> - export type LogDescription = TypedLogDescription<Event> -} - -export interface Event extends BaseContract { - connect(runner?: ContractRunner | null): Event - waitForDeployment(): Promise<this> - - interface: EventInterface - - queryFilter<TCEvent extends TypedContractEvent>( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - queryFilter<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - - on<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - on<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - once<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - once<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - listeners<TCEvent extends TypedContractEvent>( - event: TCEvent - ): Promise<Array<TypedListener<TCEvent>>> - listeners(eventName?: string): Promise<Array<Listener>> - removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this> - - triggerEvent: TypedContractMethod<[], [void], 'nonpayable'> - - getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'triggerEvent'): TypedContractMethod<[], [void], 'nonpayable'> - - getEvent( - key: 'ExampleEvent' - ): TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - - filters: { - 'ExampleEvent(address,uint256,string)': TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - ExampleEvent: TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts deleted file mode 100644 index ca137fcc8b30a..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BigNumberish, - BytesLike, - FunctionFragment, - Result, - Interface, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedListener, - TypedContractMethod, -} from './common' - -export interface PiggyBankInterface extends Interface { - getFunction(nameOrSignature: 'deposit' | 'getDeposit' | 'owner' | 'withdraw'): FunctionFragment - - encodeFunctionData(functionFragment: 'deposit', values?: undefined): string - encodeFunctionData(functionFragment: 'getDeposit', values?: undefined): string - encodeFunctionData(functionFragment: 'owner', values?: undefined): string - encodeFunctionData(functionFragment: 'withdraw', values: [BigNumberish]): string - - decodeFunctionResult(functionFragment: 'deposit', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'getDeposit', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'owner', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'withdraw', data: BytesLike): Result -} - -export interface PiggyBank extends BaseContract { - connect(runner?: ContractRunner | null): PiggyBank - waitForDeployment(): Promise<this> - - interface: PiggyBankInterface - - queryFilter<TCEvent extends TypedContractEvent>( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - queryFilter<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - - on<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - on<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - once<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - once<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - listeners<TCEvent extends TypedContractEvent>( - event: TCEvent - ): Promise<Array<TypedListener<TCEvent>>> - listeners(eventName?: string): Promise<Array<Listener>> - removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this> - - deposit: TypedContractMethod<[], [bigint], 'payable'> - - getDeposit: TypedContractMethod<[], [bigint], 'view'> - - owner: TypedContractMethod<[], [string], 'view'> - - withdraw: TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'> - - getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'deposit'): TypedContractMethod<[], [bigint], 'payable'> - getFunction(nameOrSignature: 'getDeposit'): TypedContractMethod<[], [bigint], 'view'> - getFunction(nameOrSignature: 'owner'): TypedContractMethod<[], [string], 'view'> - getFunction( - nameOrSignature: 'withdraw' - ): TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'> - - filters: {} -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts deleted file mode 100644 index ad6e23b38a65d..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BytesLike, - FunctionFragment, - Result, - Interface, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedListener, - TypedContractMethod, -} from './common' - -export interface RevertInterface extends Interface { - getFunction(nameOrSignature: 'doRevert'): FunctionFragment - - encodeFunctionData(functionFragment: 'doRevert', values?: undefined): string - - decodeFunctionResult(functionFragment: 'doRevert', data: BytesLike): Result -} - -export interface Revert extends BaseContract { - connect(runner?: ContractRunner | null): Revert - waitForDeployment(): Promise<this> - - interface: RevertInterface - - queryFilter<TCEvent extends TypedContractEvent>( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - queryFilter<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise<Array<TypedEventLog<TCEvent>>> - - on<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - on<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - once<TCEvent extends TypedContractEvent>( - event: TCEvent, - listener: TypedListener<TCEvent> - ): Promise<this> - once<TCEvent extends TypedContractEvent>( - filter: TypedDeferredTopicFilter<TCEvent>, - listener: TypedListener<TCEvent> - ): Promise<this> - - listeners<TCEvent extends TypedContractEvent>( - event: TCEvent - ): Promise<Array<TypedListener<TCEvent>>> - listeners(eventName?: string): Promise<Array<Listener>> - removeAllListeners<TCEvent extends TypedContractEvent>(event?: TCEvent): Promise<this> - - doRevert: TypedContractMethod<[], [void], 'nonpayable'> - - getFunction<T extends ContractMethod = ContractMethod>(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'doRevert'): TypedContractMethod<[], [void], 'nonpayable'> - - filters: {} -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts deleted file mode 100644 index 247b9468ece25..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - FunctionFragment, - Typed, - EventFragment, - ContractTransaction, - ContractTransactionResponse, - DeferredTopicFilter, - EventLog, - TransactionRequest, - LogDescription, -} from 'ethers' - -export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent> - extends DeferredTopicFilter {} - -export interface TypedContractEvent< - InputTuple extends Array<any> = any, - OutputTuple extends Array<any> = any, - OutputObject = any, -> { - ( - ...args: Partial<InputTuple> - ): TypedDeferredTopicFilter<TypedContractEvent<InputTuple, OutputTuple, OutputObject>> - name: string - fragment: EventFragment - getFragment(...args: Partial<InputTuple>): EventFragment -} - -type __TypechainAOutputTuple<T> = T extends TypedContractEvent<infer _U, infer W> ? W : never -type __TypechainOutputObject<T> = - T extends TypedContractEvent<infer _U, infer _W, infer V> ? V : never - -export interface TypedEventLog<TCEvent extends TypedContractEvent> extends Omit<EventLog, 'args'> { - args: __TypechainAOutputTuple<TCEvent> & __TypechainOutputObject<TCEvent> -} - -export interface TypedLogDescription<TCEvent extends TypedContractEvent> - extends Omit<LogDescription, 'args'> { - args: __TypechainAOutputTuple<TCEvent> & __TypechainOutputObject<TCEvent> -} - -export type TypedListener<TCEvent extends TypedContractEvent> = ( - ...listenerArg: [...__TypechainAOutputTuple<TCEvent>, TypedEventLog<TCEvent>, ...undefined[]] -) => void - -export type MinEthersFactory<C, ARGS> = { - deploy(...a: ARGS[]): Promise<C> -} - -export type GetContractTypeFromFactory<F> = F extends MinEthersFactory<infer C, any> ? C : never -export type GetARGsTypeFromFactory<F> = - F extends MinEthersFactory<any, any> ? Parameters<F['deploy']> : never - -export type StateMutability = 'nonpayable' | 'payable' | 'view' - -export type BaseOverrides = Omit<TransactionRequest, 'to' | 'data'> -export type NonPayableOverrides = Omit<BaseOverrides, 'value' | 'blockTag' | 'enableCcipRead'> -export type PayableOverrides = Omit<BaseOverrides, 'blockTag' | 'enableCcipRead'> -export type ViewOverrides = Omit<TransactionRequest, 'to' | 'data'> -export type Overrides<S extends StateMutability> = S extends 'nonpayable' - ? NonPayableOverrides - : S extends 'payable' - ? PayableOverrides - : ViewOverrides - -export type PostfixOverrides<A extends Array<any>, S extends StateMutability> = - | A - | [...A, Overrides<S>] -export type ContractMethodArgs<A extends Array<any>, S extends StateMutability> = PostfixOverrides< - { [I in keyof A]-?: A[I] | Typed }, - S -> - -export type DefaultReturnType<R> = R extends Array<any> ? R[0] : R - -// export interface ContractMethod<A extends Array<any> = Array<any>, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { -export interface TypedContractMethod< - A extends Array<any> = Array<any>, - R = any, - S extends StateMutability = 'payable', -> { - ( - ...args: ContractMethodArgs<A, S> - ): S extends 'view' ? Promise<DefaultReturnType<R>> : Promise<ContractTransactionResponse> - - name: string - - fragment: FunctionFragment - - getFragment(...args: ContractMethodArgs<A, S>): FunctionFragment - - populateTransaction(...args: ContractMethodArgs<A, S>): Promise<ContractTransaction> - staticCall(...args: ContractMethodArgs<A, 'view'>): Promise<DefaultReturnType<R>> - send(...args: ContractMethodArgs<A, S>): Promise<ContractTransactionResponse> - estimateGas(...args: ContractMethodArgs<A, S>): Promise<bigint> - staticCallResult(...args: ContractMethodArgs<A, 'view'>): Promise<R> -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts deleted file mode 100644 index 2e16b18a7ed8c..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { Event, EventInterface } from '../Event' - -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 - -export class Event__factory { - static readonly abi = _abi - static createInterface(): EventInterface { - return new Interface(_abi) as EventInterface - } - static connect(address: string, runner?: ContractRunner | null): Event { - return new Contract(address, _abi, runner) as unknown as Event - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts deleted file mode 100644 index 0efea80ed2dc8..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { PiggyBank, PiggyBankInterface } from '../PiggyBank' - -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 - -export class PiggyBank__factory { - static readonly abi = _abi - static createInterface(): PiggyBankInterface { - return new Interface(_abi) as PiggyBankInterface - } - static connect(address: string, runner?: ContractRunner | null): PiggyBank { - return new Contract(address, _abi, runner) as unknown as PiggyBank - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts deleted file mode 100644 index ece1c6b5426ef..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { Revert, RevertInterface } from '../Revert' - -const _abi = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'doRevert', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const - -export class Revert__factory { - static readonly abi = _abi - static createInterface(): RevertInterface { - return new Interface(_abi) as RevertInterface - } - static connect(address: string, runner?: ContractRunner | null): Revert { - return new Contract(address, _abi, runner) as unknown as Revert - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts deleted file mode 100644 index 67370dba411c3..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -export { Event__factory } from './Event__factory' -export { PiggyBank__factory } from './PiggyBank__factory' -export { Revert__factory } from './Revert__factory' diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts deleted file mode 100644 index 3e324e80dcb1c..0000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -export type { Event } from './Event' -export type { PiggyBank } from './PiggyBank' -export type { Revert } from './Revert' -export * as factories from './factories' -export { Event__factory } from './factories/Event__factory' -export { PiggyBank__factory } from './factories/PiggyBank__factory' -export { Revert__factory } from './factories/Revert__factory' From 1117b0d69ea02ca7e467d68e80670a2fc637e1ef Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Fri, 29 Nov 2024 15:31:48 +0100 Subject: [PATCH 27/28] Add extra test --- .../revive/rpc/examples/js/src/geth-diff.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 ea7a7b28bf2ac..468e7860bb9aa 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 @@ -138,6 +138,21 @@ for (const env of envs) { } }) + test('eth_call transfer (not enough funds)', async () => { + expect.assertions(3) + try { + await env.accountWallet.sendTransaction({ + to: '0x75E480dB528101a381Ce68544611C169Ad7EB342', + value: 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 { From 6e7cd93342eb7c608aea04fefc4a5385f4c2fb3b Mon Sep 17 00:00:00 2001 From: pgherveou <pgherveou@gmail.com> Date: Fri, 29 Nov 2024 17:37:36 +0100 Subject: [PATCH 28/28] fix CI error --- substrate/frame/revive/rpc/src/client.rs | 2 +- substrate/frame/revive/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 3a00c79cec2b1..901c15e9756b6 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -149,7 +149,7 @@ fn extract_revert_message(exec_data: &[u8]) -> Option<String> { Some("execution reverted".to_string()) }, _ => { - log::debug!(target: LOG_TARGET, "Unknown revert function selector: {function_selector:?}"); + log::debug!(target: LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); Some("execution reverted".to_string()) }, } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 6a1cea1ee787e..1dee1da03bc47 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1298,7 +1298,7 @@ where { 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 {})", + format!("insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})", tx.gas.unwrap_or_default())) ); }