diff --git a/Cargo.lock b/Cargo.lock index 016681e5e9e44..ee5221d25c8ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14249,6 +14249,7 @@ dependencies = [ "pallet-proxy", "pallet-revive-fixtures", "pallet-revive-proc-macro", + "pallet-revive-types", "pallet-revive-uapi", "pallet-timestamp", "pallet-transaction-payment", @@ -14281,6 +14282,7 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-tracing 16.0.0", "sp-version 29.0.0", + "strum 0.26.3", "substrate-bn", "subxt-signer 0.44.2", "test-case", @@ -14308,6 +14310,7 @@ dependencies = [ "log", "pallet-revive", "pallet-revive-fixtures", + "pallet-revive-types", "parity-scale-codec", "pretty_assertions", "revive-dev-node", @@ -14362,6 +14365,24 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "pallet-revive-types" +version = "0.1.0" +dependencies = [ + "alloy-core", + "derive_more 0.99.17", + "num-traits", + "num_enum", + "parity-scale-codec", + "revm-bytecode", + "scale-info", + "serde", + "serde_json", + "sp-core 28.0.0", + "sp-weights 27.0.0", + "strum 0.26.3", +] + [[package]] name = "pallet-revive-uapi" version = "0.1.0" @@ -17581,6 +17602,7 @@ dependencies = [ "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", + "pallet-revive-types", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", @@ -19948,6 +19970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66c52031b73cae95d84cd1b07725808b5fd1500da3e5e24574a3b2dc13d9f16d" dependencies = [ "bitvec", + "paste", "phf", "revm-primitives", "serde", diff --git a/Cargo.toml b/Cargo.toml index 878bd38eeac40..bf6083840535e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -430,6 +430,7 @@ members = [ "substrate/frame/revive/fixtures", "substrate/frame/revive/proc-macro", "substrate/frame/revive/rpc", + "substrate/frame/revive/types", "substrate/frame/revive/uapi", "substrate/frame/revive/ui-tests", "substrate/frame/root-offences", @@ -1053,6 +1054,7 @@ pallet-referenda = { path = "substrate/frame/referenda", default-features = fals pallet-revive = { path = "substrate/frame/revive", default-features = false } pallet-revive-fixtures = { path = "substrate/frame/revive/fixtures", default-features = false } pallet-revive-proc-macro = { path = "substrate/frame/revive/proc-macro", default-features = false } +pallet-revive-types = { path = "substrate/frame/revive/types", default-features = false } pallet-revive-uapi = { path = "substrate/frame/revive/uapi", default-features = false } pallet-root-offences = { default-features = false, path = "substrate/frame/root-offences" } pallet-root-testing = { path = "substrate/frame/root-testing", default-features = false } @@ -1203,6 +1205,7 @@ remote-externalities = { path = "substrate/utils/frame/remote-externalities", de revive-dev-node = { path = "substrate/frame/revive/dev-node/node" } revive-dev-runtime = { path = "substrate/frame/revive/dev-node/runtime" } revm = { version = "27.0.2", default-features = false } +revm-bytecode = { version = "6.2.2", default-features = false } ripemd = { version = "0.1.3", default-features = false } rlp = { version = "0.6.1", default-features = false } rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/relays/rococo" } diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index a134e334db472..028572165867b 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -51,6 +51,7 @@ frame-support = { workspace = true } frame-system = { workspace = true } pallet-revive-fixtures = { workspace = true, optional = true } pallet-revive-proc-macro = { workspace = true } +pallet-revive-types = { workspace = true } pallet-revive-uapi = { workspace = true, features = ["precompiles-sol-interfaces", "scale"] } pallet-transaction-payment = { workspace = true } ripemd = { workspace = true } @@ -74,6 +75,7 @@ itertools = { workspace = true } pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } +strum = { workspace = true, features = ["derive"] } test-case = { workspace = true } # Polkadot SDK Dependencies @@ -111,6 +113,7 @@ std = [ "num-traits/std", "pallet-proxy/std", "pallet-revive-fixtures?/std", + "pallet-revive-types/std", "pallet-timestamp/std", "pallet-transaction-payment/std", "pallet-utility/std", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 896c9bf7969cd..0b44534378471 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -30,6 +30,7 @@ jsonrpsee = { workspace = true, features = ["full"] } libsqlite3-sys = { workspace = true } log = { workspace = true } pallet-revive = { workspace = true, default-features = true } +pallet-revive-types = { workspace = true, features = ["std"] } prometheus-endpoint = { workspace = true, default-features = true } rlp = { workspace = true } sc-cli = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/rpc/src/apis/debug_apis.rs b/substrate/frame/revive/rpc/src/apis/debug_apis.rs index 778f81340ad4a..24b04b33f6c27 100644 --- a/substrate/frame/revive/rpc/src/apis/debug_apis.rs +++ b/substrate/frame/revive/rpc/src/apis/debug_apis.rs @@ -42,7 +42,7 @@ pub trait DebugRpc { &self, transaction_hash: H256, tracer_config: Option, - ) -> RpcResult; + ) -> RpcResult; /// Dry run a call and returns the transaction's traces. /// @@ -58,7 +58,7 @@ pub trait DebugRpc { transaction: GenericTransaction, block: BlockNumberOrTagOrHash, trace_call_config: Option, - ) -> RpcResult; + ) -> RpcResult; #[method(name = "debug_getAutomine")] async fn get_automine(&self) -> RpcResult; @@ -107,7 +107,7 @@ impl DebugRpcServer for DebugRpcServerImpl { &self, transaction_hash: H256, tracer_config: Option, - ) -> RpcResult { + ) -> RpcResult { let TracerConfig { config, timeout } = tracer_config.unwrap_or_default(); with_timeout(timeout, self.client.trace_transaction(transaction_hash, config)).await } @@ -117,7 +117,7 @@ impl DebugRpcServer for DebugRpcServerImpl { transaction: GenericTransaction, block: BlockNumberOrTagOrHash, trace_call_config: Option, - ) -> RpcResult { + ) -> RpcResult { let TraceCallConfig { tracer_config, state_overrides } = trace_call_config.unwrap_or_default(); let TracerConfig { config, timeout } = tracer_config; diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 8bea126f36ddb..11530a87a0262 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -22,7 +22,7 @@ pub(crate) mod storage_api; use crate::{ BlockInfoProvider, BlockTag, FeeHistoryProvider, ReceiptProvider, SubxtBlockInfoProvider, - SyncLabel, TracerType, TransactionInfo, + SyncLabel, TransactionInfo, block_sync::SyncCheckpoint, subxt_client::{self, SrcChainConfig, revive::calls::types::EthTransact}, }; @@ -33,10 +33,11 @@ use pallet_revive::{ evm::{ Block, BlockNumberOrTag, BlockNumberOrTagOrHash, FeeHistoryResult, Filter, GenericTransaction, H256, HashesOrTransactionInfos, Log, ReceiptInfo, StateOverrideSet, - SyncingProgress, SyncingStatus, Trace, TransactionSigned, TransactionTrace, U256, + SyncingProgress, SyncingStatus, TransactionSigned, TransactionTrace, U256, decode_revert_reason, }, }; +use pallet_revive_types::runtime_api::*; use runtime_api::RuntimeApi; use sp_runtime::traits::Block as BlockT; use sp_weights::Weight; @@ -987,7 +988,7 @@ impl Client { pub async fn trace_block_by_number( &self, at: BlockNumberOrTag, - config: TracerType, + config: TracerTypeV1, ) -> Result, ClientError> { if self.receipt_provider.is_before_earliest_block(&at) { return Ok(vec![]); @@ -1020,8 +1021,8 @@ impl Client { pub async fn trace_transaction( &self, transaction_hash: H256, - config: TracerType, - ) -> Result { + config: TracerTypeV1, + ) -> Result { let (block_hash, transaction_index) = self .receipt_provider .find_transaction(&transaction_hash) @@ -1040,9 +1041,9 @@ impl Client { &self, transaction: GenericTransaction, block: BlockNumberOrTagOrHash, - config: TracerType, + config: TracerTypeV1, state_overrides: Option, - ) -> Result { + ) -> Result { let block_hash = self.block_hash_for_tag(block).await?; let runtime_api = self.runtime_api(block_hash); runtime_api.trace_call(transaction, config, state_overrides).await diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 71a8d6e3c9984..1fa6e155ddf6b 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -25,9 +25,10 @@ use pallet_revive::{ DryRunConfig, EthTransactInfo, TracingConfig, evm::{ Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, GenericTransaction, H160, - ReceiptGasInfo, StateOverrideSet, Trace, U256, + ReceiptGasInfo, StateOverrideSet, U256, }, }; +use pallet_revive_types::runtime_api::*; use sp_core::H256; use sp_timestamp::Timestamp; use subxt::{Error::Metadata, OnlineClient, error::MetadataError, ext::subxt_rpcs::UserError}; @@ -235,8 +236,8 @@ impl RuntimeApi { sp_runtime::OpaqueExtrinsic, >, transaction_index: u32, - tracer_type: crate::TracerType, - ) -> Result { + tracer_type: TracerTypeV1, + ) -> Result { let payload = subxt_client::apis() .revive_api() .trace_tx(block.into(), transaction_index, tracer_type.into()) @@ -253,8 +254,8 @@ impl RuntimeApi { sp_runtime::generic::Header, sp_runtime::OpaqueExtrinsic, >, - tracer_type: crate::TracerType, - ) -> Result, ClientError> { + tracer_type: TracerTypeV1, + ) -> Result, ClientError> { let payload = subxt_client::apis() .revive_api() .trace_block(block.into(), tracer_type.into()) @@ -272,9 +273,9 @@ impl RuntimeApi { pub async fn trace_call( &self, transaction: GenericTransaction, - tracer_type: crate::TracerType, + tracer_type: TracerTypeV1, state_overrides: Option, - ) -> Result { + ) -> Result { let result = if let Some(overrides) = state_overrides { let config = TracingConfig::new().with_state_overrides(overrides); let payload = subxt_client::apis() diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index aef00273d4e27..0293f38389105 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -25,6 +25,7 @@ use jsonrpsee::{ types::{ErrorCode, ErrorObjectOwned}, }; use pallet_revive::evm::*; +use pallet_revive_types::runtime_api::*; use sp_core::{H160, H256, U256}; use sp_crypto_hashing::keccak_256; use subxt::backend::legacy::rpc_methods::TransactionStatus; diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index ca3e42d2cabba..864d07216bd97 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -34,14 +34,6 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; ::sp_runtime::OpaqueExtrinsic >>" ), - substitute_type( - path = "pallet_revive::evm::api::debug_rpc_types::Trace", - with = "::subxt::utils::Static<::pallet_revive::evm::Trace>" - ), - substitute_type( - path = "pallet_revive::evm::api::debug_rpc_types::TracerType", - with = "::subxt::utils::Static<::pallet_revive::evm::TracerType>" - ), substitute_type( path = "pallet_revive::evm::api::rpc_types_gen::GenericTransaction", @@ -79,6 +71,97 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; path = "pallet_revive::evm::block_hash::ReceiptGasInfo", with = "::subxt::utils::Static<::pallet_revive::evm::ReceiptGasInfo>" ), + + // Versioning replacements + substitute_type( + path = "pallet_revive_types::runtime_api::types::tracer::TracerTypeV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TracerTypeV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::tracer::CallTracerConfigV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::CallTracerConfigV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::tracer::PrestateTracerConfigV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::PrestateTracerConfigV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::tracer::ExecutionTracerConfigV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::ExecutionTracerConfigV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::TraceV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::TraceV2", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceV2>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::CallTraceV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::CallTraceV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::CallTraceV2", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::CallTraceV2>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::PrestateTraceV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::PrestateTraceV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::ExecutionTraceV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::ExecutionTraceV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::CallLogV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::CallLogV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::CallLogV2", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::CallLogV2>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::CallTypeV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::CallTypeV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::PrestateTraceInfoV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::PrestateTraceInfoV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::ExecutionStepV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::ExecutionStepV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::types::traces::ExecutionStepKindV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::ExecutionStepKindV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::payloads::trace_block::TraceBlockInputPayloadV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceBlockInputPayloadV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::payloads::trace_block::TraceBlockInputPayloadV2", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceBlockInputPayloadV2>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::payloads::trace_block::TraceBlockVersionedInputPayload", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceBlockVersionedInputPayload>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::payloads::trace_block::TraceBlockOutputPayloadV1", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceBlockOutputPayloadV1>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::payloads::trace_block::TraceBlockOutputPayloadV2", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceBlockOutputPayloadV2>" + ), + substitute_type( + path = "pallet_revive_types::runtime_api::payloads::trace_block::TraceBlockVersionedOutputPayload", + with = "::subxt::utils::Static<::pallet_revive_types::runtime_api::TraceBlockVersionedOutputPayload>" + ), + derive_for_all_types = "codec::Encode, codec::Decode" )] mod src_chain {} diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 67a71ebaea071..815d541e81e94 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -48,14 +48,19 @@ use pallet_revive::{ Account, Block, BlockHeader, BlockNumberOrTag, BlockNumberOrTagOrHash, BlockTag, BoundedOneOrMany, Filter, FilterResults, GenericTransaction, H256, HashesOrTransactionInfos, Log, SubscriptionItem, SubscriptionKind, SubscriptionOptions, - Trace, TransactionInfo, TransactionUnsigned, U256, + TransactionInfo, TransactionUnsigned, U256, }, precompiles::alloy::{ self, - sol_types::{SolCall, SolConstructor, SolEvent}, + sol_types::{SolCall, SolConstructor, SolEvent, SolInterface}, }, }; use pallet_revive_fixtures::{Callee, Counter, TwoSlots}; +use pallet_revive_types::runtime_api::{ + CallTracerConfigV1, TraceBlockInputPayloadV1, TraceBlockInputPayloadV2, + TraceBlockVersionedInputPayload, TraceBlockVersionedOutputPayload, TraceV1, TraceV2, + TracerTypeV1, +}; use sp_runtime::BoundedVec; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use std::{sync::Arc, thread}; @@ -344,6 +349,7 @@ async fn run_all_eth_rpc_tests_inner() -> anyhow::Result<()> { run_tests!( test_fibonacci_call_via_runtime_api, + test_trace_block_returns_v1_trace_on_v1_input_and_v2_trace_on_v2_input, test_transfer, test_deploy_and_call, test_receipt_mixed_revert_and_logs_same_block, @@ -989,7 +995,7 @@ async fn test_earliest_block_tag() -> anyhow::Result<()> { let trace = DebugRpcClient::trace_call(&*client, tx.clone(), BlockTag::Earliest.into(), None).await?; assert!( - matches!(trace, Trace::Call(_) | Trace::Execution(_)), + matches!(trace, TraceV1::Call(_) | TraceV1::Execution(_)), "traceCall should return a trace" ); @@ -1843,6 +1849,111 @@ async fn test_fibonacci_call_via_runtime_api() -> anyhow::Result<()> { Ok(()) } +async fn test_trace_block_returns_v1_trace_on_v1_input_and_v2_trace_on_v2_input() +-> anyhow::Result<()> { + use pallet_revive_fixtures::Host; + + type SubstrateTracingBlock = sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >; + + let client = Arc::new(SharedResources::client().await); + let node_client = SharedResources::node_client().await; + + let (code, _) = pallet_revive_fixtures::compile_module_with_type( + "Host", + pallet_revive_fixtures::FixtureType::Solc, + )?; + let deploy_receipt = TransactionBuilder::new(client.clone()) + .input(code) + .send() + .await? + .wait_for_receipt() + .await?; + let contract_address = deploy_receipt + .contract_address + .ok_or_else(|| anyhow!("deployment should return a contract address"))?; + + let receipt = TransactionBuilder::new(client) + .to(contract_address) + .input(Host::HostCalls::logOps(Host::logOpsCall {}).abi_encode()) + .gas(U256::from(1_000_000)) + .send() + .await? + .wait_for_receipt() + .await?; + assert!(receipt.is_success()); + assert_eq!(receipt.logs.len(), 5); + + let receipt_block_number = u32::try_from(receipt.block_number) + .map_err(|_| anyhow!("receipt block number should fit in u32"))?; + let subxt_block = node_client.blocks().at_latest().await?; + assert_eq!(subxt_block.number(), receipt_block_number); + + let parent_hash = subxt_block.header().parent_hash; + let header = codec::Decode::decode(&mut &codec::Encode::encode(subxt_block.header())[..])?; + let extrinsics = subxt_block + .extrinsics() + .await? + .iter() + .map(|extrinsic| sp_runtime::OpaqueExtrinsic::try_from_encoded_extrinsic(extrinsic.bytes())) + .collect::, _>>()?; + let block = SubstrateTracingBlock { header, extrinsics }; + let config = TracerTypeV1::CallTracer(Some(CallTracerConfigV1 { + with_logs: true, + only_top_call: false, + })); + + let v1_input = TraceBlockVersionedInputPayload::V1(TraceBlockInputPayloadV1 { + block: subxt::utils::Static(block.clone()), + config: config.clone(), + }); + let v1_payload = subxt_client::apis() + .revive_api() + .trace_block_versioned(subxt::utils::Static(v1_input)) + .unvalidated(); + let v1_output = node_client.runtime_api().at(parent_hash).call(v1_payload).await?.0; + let TraceBlockVersionedOutputPayload::V1(v1_output) = v1_output else { + return Err(anyhow!("V1 trace_block input should return V1 output")); + }; + let (_, trace_v1) = v1_output + .traces + .into_iter() + .find(|(_, trace)| matches!(trace, TraceV1::Call(call) if !call.logs.is_empty())) + .ok_or_else(|| anyhow!("V1 output should include a call trace with logs"))?; + let TraceV1::Call(call_v1) = trace_v1 else { + return Err(anyhow!("V1 output should include a call trace")); + }; + assert_eq!(call_v1.logs.len(), 5); + assert!(serde_json::to_value(&call_v1.logs[0])?.get("index").is_none()); + + let v2_input = TraceBlockVersionedInputPayload::V2(TraceBlockInputPayloadV2 { + block: subxt::utils::Static(block), + config, + }); + let v2_payload = subxt_client::apis() + .revive_api() + .trace_block_versioned(subxt::utils::Static(v2_input)) + .unvalidated(); + let v2_output = node_client.runtime_api().at(parent_hash).call(v2_payload).await?.0; + let TraceBlockVersionedOutputPayload::V2(v2_output) = v2_output else { + return Err(anyhow!("V2 trace_block input should return V2 output")); + }; + let (_, trace_v2) = v2_output + .traces + .into_iter() + .find(|(_, trace)| matches!(trace, TraceV2::Call(call) if !call.logs.is_empty())) + .ok_or_else(|| anyhow!("V2 output should include a call trace with logs"))?; + let TraceV2::Call(call_v2) = trace_v2 else { + return Err(anyhow!("V2 output should include a call trace")); + }; + let indexes = call_v2.logs.iter().map(|log| log.index).collect::>(); + assert_eq!(indexes, vec![0, 1, 2, 3, 4]); + + Ok(()) +} + async fn test_gas_estimation_with_no_funds_no_gas_specified() -> anyhow::Result<()> { // Arrange let code = pallet_revive_fixtures::compile_module_with_type( diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index 9b045c01d7aa7..5443990cc15e8 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -16,10 +16,7 @@ // limitations under the License. //! JSON-RPC methods and types, for Ethereum. -mod hex_serde; - -mod byte; -pub use byte::*; +pub use pallet_revive_types::common::{Byte, Bytes, Bytes8, Bytes32, Bytes256}; mod rlp_codec; pub use rlp; diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs index db5abcaa62635..861af94cb6f47 100644 --- a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -17,19 +17,14 @@ use crate::{Weight, evm::Bytes}; use alloc::{collections::BTreeMap, string::String, vec::Vec}; -use codec::{Decode, Encode}; use derive_more::From; +use pallet_revive_types::runtime_api::*; use scale_info::TypeInfo; -use serde::{ - Deserialize, Serialize, - de::{Deserializer, Error, MapAccess, Visitor}, - ser::{SerializeMap, Serializer}, -}; +use serde::{Deserialize, Serialize}; use sp_core::{H160, H256, U256}; /// The type of tracer to use. -#[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, Deserialize, PartialEq)] -#[serde(tag = "tracer", content = "tracerConfig", rename_all = "camelCase")] +#[derive(Debug, Clone, PartialEq, From)] pub enum TracerType { /// A tracer that traces calls. CallTracer(Option), @@ -41,37 +36,29 @@ pub enum TracerType { ExecutionTracer(Option), } -impl From for TracerType { - fn from(config: CallTracerConfig) -> Self { - TracerType::CallTracer(Some(config)) - } -} - -impl From for TracerType { - fn from(config: PrestateTracerConfig) -> Self { - TracerType::PrestateTracer(Some(config)) - } -} - -impl From for TracerType { - fn from(config: ExecutionTracerConfig) -> Self { - TracerType::ExecutionTracer(Some(config)) - } -} - impl Default for TracerType { fn default() -> Self { TracerType::ExecutionTracer(Some(ExecutionTracerConfig::default())) } } +impl From for TracerType { + fn from(value: TracerTypeV1) -> Self { + match value { + TracerTypeV1::CallTracer(config) => Self::CallTracer(config.map(Into::into)), + TracerTypeV1::PrestateTracer(config) => Self::PrestateTracer(config.map(Into::into)), + TracerTypeV1::ExecutionTracer(config) => Self::ExecutionTracer(config.map(Into::into)), + } + } +} + /// Tracer configuration used to trace calls. #[derive(TypeInfo, Debug, Clone, Default, PartialEq)] #[cfg_attr(feature = "std", derive(Serialize), serde(rename_all = "camelCase"))] pub struct TracerConfig { /// The tracer type. #[cfg_attr(feature = "std", serde(flatten, default))] - pub config: TracerType, + pub config: TracerTypeV1, /// Timeout for the tracer. #[cfg_attr(feature = "std", serde(with = "humantime_serde", default))] @@ -82,13 +69,13 @@ pub struct TracerConfig { impl<'de> Deserialize<'de> for TracerConfig { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, + D: serde::de::Deserializer<'de>, { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct TracerConfigWithType { #[serde(flatten)] - config: TracerType, + config: TracerTypeV1, #[serde(with = "humantime_serde", default)] timeout: Option, } @@ -97,7 +84,7 @@ impl<'de> Deserialize<'de> for TracerConfig { #[serde(rename_all = "camelCase")] struct TracerConfigInline { #[serde(flatten, default)] - execution_tracer_config: ExecutionTracerConfig, + execution_tracer_config: ExecutionTracerConfigV1, #[serde(with = "humantime_serde", default)] timeout: Option, } @@ -114,7 +101,7 @@ impl<'de> Deserialize<'de> for TracerConfig { Ok(TracerConfig { config: cfg.config, timeout: cfg.timeout }) }, TracerConfigHelper::Inline(cfg) => Ok(TracerConfig { - config: TracerType::ExecutionTracer(Some(cfg.execution_tracer_config)), + config: TracerTypeV1::ExecutionTracer(Some(cfg.execution_tracer_config)), timeout: cfg.timeout, }), } @@ -140,8 +127,7 @@ pub struct TraceCallConfig { } /// The configuration for the call tracer. -#[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] -#[serde(default, rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq)] pub struct CallTracerConfig { /// Whether to include logs in the trace. pub with_logs: bool, @@ -156,9 +142,14 @@ impl Default for CallTracerConfig { } } +impl From for CallTracerConfig { + fn from(value: CallTracerConfigV1) -> Self { + Self { with_logs: value.with_logs, only_top_call: value.only_top_call } + } +} + /// The configuration for the prestate tracer. -#[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] -#[serde(default, rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq)] pub struct PrestateTracerConfig { /// Whether to include the diff mode in the trace. pub diff_mode: bool, @@ -176,20 +167,18 @@ impl Default for PrestateTracerConfig { } } -fn zero_to_none<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let opt = Option::::deserialize(deserializer)?; - Ok(match opt { - Some(0) => None, - other => other, - }) +impl From for PrestateTracerConfig { + fn from(value: PrestateTracerConfigV1) -> Self { + Self { + diff_mode: value.diff_mode, + disable_storage: value.disable_storage, + disable_code: value.disable_code, + } + } } /// The configuration for the execution tracer. -#[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] -#[serde(default, rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq)] pub struct ExecutionTracerConfig { /// Whether to enable memory capture pub enable_memory: bool, @@ -207,7 +196,6 @@ pub struct ExecutionTracerConfig { pub disable_syscall_details: bool, /// Limit number of steps captured - #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "zero_to_none")] pub limit: Option, /// Maximum number of memory words to capture per step (default: 16) @@ -228,105 +216,22 @@ impl Default for ExecutionTracerConfig { } } -/// Serialization should support the following JSON format: -/// -/// ```json -/// { "tracer": "callTracer", "tracerConfig": { "withLogs": false } } -/// ``` -/// -/// ```json -/// { "tracer": "callTracer" } -/// ``` -/// -/// By default if not specified the tracer is an ExecutionTracer, and it's config is passed inline -/// -/// ```json -/// { "tracer": null, "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } -/// ``` -#[test] -fn test_tracer_config_serialization() { - let tracers = vec![ - ( - r#"{ "enableMemory": true, "disableStack": false, "disableStorage": false, - "enableReturnData": true }"#, - TracerConfig { - config: TracerType::ExecutionTracer(Some(ExecutionTracerConfig { - enable_memory: true, - disable_stack: false, - disable_storage: false, - enable_return_data: true, - disable_syscall_details: false, - limit: None, - memory_word_limit: 16, - })), - timeout: None, - }, - ), - ( - r#"{ }"#, - TracerConfig { - config: TracerType::ExecutionTracer(Some(ExecutionTracerConfig::default())), - timeout: None, - }, - ), - ( - r#"{"tracer": "callTracer"}"#, - TracerConfig { config: TracerType::CallTracer(None), timeout: None }, - ), - ( - r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": false }}"#, - TracerConfig { - config: CallTracerConfig { with_logs: false, only_top_call: false }.into(), - timeout: None, - }, - ), - ( - r#"{"tracer": "callTracer", "tracerConfig": { "onlyTopCall": true }}"#, - TracerConfig { - config: CallTracerConfig { with_logs: true, only_top_call: true }.into(), - timeout: None, - }, - ), - ( - r#"{"tracer": "callTracer", "tracerConfig": { "onlyTopCall": true }, "timeout": - "10ms"}"#, - TracerConfig { - config: CallTracerConfig { with_logs: true, only_top_call: true }.into(), - timeout: Some(core::time::Duration::from_millis(10)), - }, - ), - ( - r#"{"tracer": "executionTracer"}"#, - TracerConfig { config: TracerType::ExecutionTracer(None), timeout: None }, - ), - ( - r#"{"tracer": "executionTracer", "tracerConfig": { "enableMemory": true }}"#, - TracerConfig { - config: ExecutionTracerConfig { enable_memory: true, ..Default::default() }.into(), - timeout: None, - }, - ), - ( - r#"{ "enableMemory": true }"#, - TracerConfig { - config: ExecutionTracerConfig { enable_memory: true, ..Default::default() }.into(), - timeout: None, - }, - ), - ]; - - for (json_data, expected) in tracers { - let result: TracerConfig = - serde_json::from_str(json_data).expect("Deserialization should succeed"); - assert_eq!(result, expected, "invalid serialization for {json_data}"); +impl From for ExecutionTracerConfig { + fn from(value: ExecutionTracerConfigV1) -> Self { + Self { + enable_memory: value.enable_memory, + disable_stack: value.disable_stack, + disable_storage: value.disable_storage, + enable_return_data: value.enable_return_data, + disable_syscall_details: value.disable_syscall_details, + limit: value.limit, + memory_word_limit: value.memory_word_limit, + } } } /// The type of call that was executed. -#[derive( - Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Clone, Debug, -)] -#[serde(rename_all = "UPPERCASE")] +#[derive(Default, Eq, PartialEq, Clone, Debug)] pub enum CallType { /// A regular call. #[default] @@ -343,9 +248,21 @@ pub enum CallType { Selfdestruct, } +impl From for CallTypeV1 { + fn from(value: CallType) -> Self { + match value { + CallType::Call => Self::Call, + CallType::StaticCall => Self::StaticCall, + CallType::DelegateCall => Self::DelegateCall, + CallType::Create => Self::Create, + CallType::Create2 => Self::Create2, + CallType::Selfdestruct => Self::Selfdestruct, + } + } +} + /// A Trace -#[derive(TypeInfo, Deserialize, Serialize, From, Encode, Decode, Clone, Debug, Eq, PartialEq)] -#[serde(untagged)] +#[derive(From, Clone, Debug, Eq, PartialEq)] pub enum Trace { /// A call trace. Call(CallTrace), @@ -355,9 +272,28 @@ pub enum Trace { Execution(ExecutionTrace), } +impl From for TraceV1 { + fn from(value: Trace) -> Self { + match value { + Trace::Call(value) => Self::Call(value.into()), + Trace::Prestate(value) => Self::Prestate(value.into()), + Trace::Execution(value) => Self::Execution(value.into()), + } + } +} + +impl From for TraceV2 { + fn from(value: Trace) -> Self { + match value { + Trace::Call(value) => Self::Call(value.into()), + Trace::Prestate(value) => Self::Prestate(value.into()), + Trace::Execution(value) => Self::Execution(value.into()), + } + } +} + /// A prestate Trace -#[derive(TypeInfo, Encode, Serialize, Decode, Clone, Debug, Eq, PartialEq)] -#[serde(untagged)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum PrestateTrace { /// The Prestate mode returns the accounts necessary to execute a given transaction Prestate(BTreeMap), @@ -366,7 +302,7 @@ pub enum PrestateTrace { /// The result only contains the accounts that were modified by the transaction DiffMode { /// The state before the call. - /// The accounts in the `pre` field will contain all of their basic fields, even if those + /// The accounts in the `pre` field will contain all of their basic fields, even if those /// fields have not been modified. For `storage` however, only non-empty slots that have /// been modified will be included pre: BTreeMap, @@ -376,68 +312,6 @@ pub enum PrestateTrace { }, } -impl<'de> Deserialize<'de> for PrestateTrace { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct PrestateTraceVisitor; - - impl<'de> Visitor<'de> for PrestateTraceVisitor { - type Value = PrestateTrace; - - fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - formatter.write_str("a map representing either Prestate or DiffMode") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut pre_map = None; - let mut post_map = None; - let mut account_map = BTreeMap::new(); - - while let Some(key) = map.next_key::()? { - match key.as_str() { - "pre" => { - if pre_map.is_some() { - return Err(Error::duplicate_field("pre")); - } - pre_map = Some(map.next_value::>()?); - }, - "post" => { - if post_map.is_some() { - return Err(Error::duplicate_field("post")); - } - post_map = Some(map.next_value::>()?); - }, - _ => { - let addr: H160 = - key.parse().map_err(|_| Error::custom("Invalid address"))?; - let info = map.next_value::()?; - account_map.insert(addr, info); - }, - } - } - - match (pre_map, post_map) { - (Some(pre), Some(post)) => { - if !account_map.is_empty() { - return Err(Error::custom("Mixed diff and prestate mode")); - } - Ok(PrestateTrace::DiffMode { pre, post }) - }, - (None, None) => Ok(PrestateTrace::Prestate(account_map)), - _ => Err(Error::custom("diff mode: must have both 'pre' and 'post'")), - } - } - } - - deserializer.deserialize_map(PrestateTraceVisitor) - } -} - impl PrestateTrace { /// Returns the pre and post trace info. pub fn state_mut( @@ -450,58 +324,48 @@ impl PrestateTrace { } } +impl From for PrestateTraceV1 { + fn from(value: PrestateTrace) -> Self { + let convert = |v: BTreeMap| { + v.into_iter().map(|(k, v)| (k, v.into())).collect() + }; + + match value { + PrestateTrace::Prestate(accounts) => Self::Prestate(convert(accounts)), + PrestateTrace::DiffMode { pre, post } => { + Self::DiffMode { pre: convert(pre), post: convert(post) } + }, + } + } +} + /// The info of a prestate trace. -#[derive( - TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, -)] +#[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct PrestateTraceInfo { /// The balance of the account. - #[serde(skip_serializing_if = "Option::is_none")] pub balance: Option, /// The nonce of the account. - #[serde(skip_serializing_if = "Option::is_none")] pub nonce: Option, /// The code of the contract account. - #[serde(skip_serializing_if = "Option::is_none")] pub code: Option, /// The storage of the contract account. - #[serde(default, skip_serializing_if = "is_empty", serialize_with = "serialize_map_skip_none")] pub storage: BTreeMap>, } -/// Returns true if the map has no `Some` element -pub fn is_empty(map: &BTreeMap>) -> bool { - !map.values().any(|v| v.is_some()) -} - -/// Serializes a map, skipping `None` values. -pub fn serialize_map_skip_none( - map: &BTreeMap>, - serializer: S, -) -> Result -where - S: Serializer, - K: serde::Serialize, - V: serde::Serialize, -{ - let len = map.values().filter(|v| v.is_some()).count(); - let mut ser_map = serializer.serialize_map(Some(len))?; - - for (key, opt_val) in map { - if let Some(val) = opt_val { - ser_map.serialize_entry(key, val)?; +impl From for PrestateTraceInfoV1 { + fn from(value: PrestateTraceInfo) -> Self { + Self { + balance: value.balance, + nonce: value.nonce, + code: value.code, + storage: value.storage, } } - - ser_map.end() } /// An execution trace containing the step-by-step execution of EVM opcodes and PVM syscalls. /// This matches Geth's structLogger output format. -#[derive( - Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, -)] -#[serde(default, rename_all = "camelCase")] +#[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct ExecutionTrace { /// Total gas used by the transaction. pub gas: u64, @@ -517,86 +381,77 @@ pub struct ExecutionTrace { pub struct_logs: Vec, } +impl From for ExecutionTraceV1 { + fn from(value: ExecutionTrace) -> Self { + Self { + gas: value.gas, + weight_consumed: value.weight_consumed, + base_call_weight: value.base_call_weight, + failed: value.failed, + return_value: value.return_value, + struct_logs: value.struct_logs.into_iter().map(Into::into).collect(), + } + } +} + /// An execution step which can be either an EVM opcode or a PVM syscall. -#[derive( - TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Default, -)] -#[serde(default, rename_all = "camelCase")] +#[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct ExecutionStep { /// Remaining gas before executing this step. - #[codec(compact)] pub gas: u64, /// Gas Cost of executing this step. - #[codec(compact)] pub gas_cost: u64, /// Weight cost of executing this step. pub weight_cost: Weight, /// Current call depth. pub depth: u16, /// Return data from last frame output. - #[serde(skip_serializing_if = "Bytes::is_empty")] pub return_data: Bytes, /// Any error that occurred during execution. - #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, /// The kind of execution step (EVM opcode or PVM syscall). - #[serde(flatten)] pub kind: ExecutionStepKind, } +impl From for ExecutionStepV1 { + fn from(value: ExecutionStep) -> Self { + Self { + gas: value.gas, + gas_cost: value.gas_cost, + weight_cost: value.weight_cost, + depth: value.depth, + return_data: value.return_data, + error: value.error, + kind: value.kind.into(), + } + } +} + /// The kind of execution step. -#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(untagged)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum ExecutionStepKind { /// An EVM opcode execution. EVMOpcode { /// The program counter. - #[codec(compact)] pc: u32, /// The opcode being executed. - #[serde(serialize_with = "serialize_opcode", deserialize_with = "deserialize_opcode")] op: u8, /// EVM stack contents. - #[serde( - default, - serialize_with = "serialize_stack_minimal", - deserialize_with = "deserialize_stack_minimal" - )] stack: Vec, /// EVM memory contents. - #[serde( - default, - skip_serializing_if = "Vec::is_empty", - serialize_with = "serialize_memory_no_prefix" - )] memory: Vec, /// Contract storage changes. - #[serde( - default, - skip_serializing_if = "Option::is_none", - serialize_with = "serialize_storage_no_prefix" - )] storage: Option>, }, /// A PVM syscall execution. PVMSyscall { /// The executed syscall. - #[serde( - serialize_with = "serialize_syscall_op", - deserialize_with = "deserialize_syscall_op" - )] op: u8, /// The syscall arguments (register values a0-a5). /// Omitted when `disable_syscall_details` is true in ExecutionTracerConfig. - #[serde(default, skip_serializing_if = "Vec::is_empty", with = "super::hex_serde::vec")] args: Vec, /// The syscall return value. /// Omitted when `disable_syscall_details` is true in ExecutionTracerConfig. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "super::hex_serde::option" - )] returned: Option, }, } @@ -607,290 +462,133 @@ impl Default for ExecutionStepKind { } } -macro_rules! define_opcode_functions { - ($($op:ident),* $(,)?) => { - /// Get opcode name from byte value using opcode names - fn get_opcode_name(opcode: u8) -> &'static str { - use revm::bytecode::opcode::*; - match opcode { - $( - $op => stringify!($op), - )* - _ => "INVALID", - } - } - - /// Get opcode byte from name string - fn get_opcode_byte(name: &str) -> Option { - use revm::bytecode::opcode::*; - match name { - $( - stringify!($op) => Some($op), - )* - _ => None, - } +impl From for ExecutionStepKindV1 { + fn from(value: ExecutionStepKind) -> Self { + match value { + ExecutionStepKind::EVMOpcode { pc, op, stack, memory, storage } => { + Self::EVMOpcode { pc, op: EvmOpcodeV1(op), stack, memory, storage } + }, + ExecutionStepKind::PVMSyscall { op, args, returned } => Self::PVMSyscall { + op: op + .try_into() + .expect("qed; all sys calls produced by revive are valid. Tested in env.rs"), + args, + returned, + }, } - }; -} - -define_opcode_functions!( - STOP, - ADD, - MUL, - SUB, - DIV, - SDIV, - MOD, - SMOD, - ADDMOD, - MULMOD, - EXP, - SIGNEXTEND, - LT, - GT, - SLT, - SGT, - EQ, - ISZERO, - AND, - OR, - XOR, - NOT, - BYTE, - SHL, - SHR, - SAR, - KECCAK256, - ADDRESS, - BALANCE, - ORIGIN, - CALLER, - CALLVALUE, - CALLDATALOAD, - CALLDATASIZE, - CALLDATACOPY, - CODESIZE, - CODECOPY, - GASPRICE, - EXTCODESIZE, - EXTCODECOPY, - RETURNDATASIZE, - RETURNDATACOPY, - EXTCODEHASH, - BLOCKHASH, - COINBASE, - TIMESTAMP, - NUMBER, - DIFFICULTY, - GASLIMIT, - CHAINID, - SELFBALANCE, - BASEFEE, - BLOBHASH, - BLOBBASEFEE, - POP, - MLOAD, - MSTORE, - MSTORE8, - SLOAD, - SSTORE, - JUMP, - JUMPI, - PC, - MSIZE, - GAS, - JUMPDEST, - TLOAD, - TSTORE, - MCOPY, - PUSH0, - PUSH1, - PUSH2, - PUSH3, - PUSH4, - PUSH5, - PUSH6, - PUSH7, - PUSH8, - PUSH9, - PUSH10, - PUSH11, - PUSH12, - PUSH13, - PUSH14, - PUSH15, - PUSH16, - PUSH17, - PUSH18, - PUSH19, - PUSH20, - PUSH21, - PUSH22, - PUSH23, - PUSH24, - PUSH25, - PUSH26, - PUSH27, - PUSH28, - PUSH29, - PUSH30, - PUSH31, - PUSH32, - DUP1, - DUP2, - DUP3, - DUP4, - DUP5, - DUP6, - DUP7, - DUP8, - DUP9, - DUP10, - DUP11, - DUP12, - DUP13, - DUP14, - DUP15, - DUP16, - SWAP1, - SWAP2, - SWAP3, - SWAP4, - SWAP5, - SWAP6, - SWAP7, - SWAP8, - SWAP9, - SWAP10, - SWAP11, - SWAP12, - SWAP13, - SWAP14, - SWAP15, - SWAP16, - LOG0, - LOG1, - LOG2, - LOG3, - LOG4, - CREATE, - CALL, - CALLCODE, - RETURN, - DELEGATECALL, - CREATE2, - STATICCALL, - REVERT, - INVALID, - SELFDESTRUCT, -); - -/// Serialize opcode as string using REVM opcode names -fn serialize_opcode(opcode: &u8, serializer: S) -> Result -where - S: serde::Serializer, -{ - let name = get_opcode_name(*opcode); - serializer.serialize_str(name) -} - -/// Serialize a syscall index to its name -fn serialize_syscall_op(idx: &u8, serializer: S) -> Result -where - S: serde::Serializer, -{ - use crate::vm::pvm::env::list_trace_ops; - let Some(name_bytes) = list_trace_ops().get(*idx as usize) else { - return Err(serde::ser::Error::custom(alloc::format!("Unknown trace op: {idx}"))); - }; - let name = core::str::from_utf8(name_bytes).unwrap_or_default(); - serializer.serialize_str(name) -} - -/// Deserialize opcode from string using reverse lookup table -fn deserialize_opcode<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - get_opcode_byte(&s) - .ok_or_else(|| serde::de::Error::custom(alloc::format!("Unknown opcode: {}", s))) -} - -/// Deserialize syscall from string name to index -fn deserialize_syscall_op<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - use crate::vm::pvm::env::list_trace_ops; - let s = String::deserialize(deserializer)?; - let ops = list_trace_ops(); - ops.iter() - .position(|name| core::str::from_utf8(name).unwrap_or_default() == s) - .map(|i| i as u8) - .ok_or_else(|| serde::de::Error::custom(alloc::format!("Unknown trace op: {}", s))) + } } /// A smart contract execution call trace. -#[derive( - TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, -)] -#[serde(rename_all = "camelCase")] +#[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct CallTrace { /// Address of the sender. pub from: H160, /// Amount of gas provided for the call. - #[serde(with = "super::hex_serde")] pub gas: u64, /// Amount of gas used. - #[serde(with = "super::hex_serde")] pub gas_used: u64, /// Address of the receiver. pub to: H160, /// Call input data. pub input: Bytes, /// Return data. - #[serde(skip_serializing_if = "Bytes::is_empty")] pub output: Bytes, /// The error message if the call failed. - #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, /// The revert reason, if the call reverted. - #[serde(skip_serializing_if = "Option::is_none")] pub revert_reason: Option, /// List of sub-calls. - #[serde(skip_serializing_if = "Vec::is_empty")] pub calls: Vec, /// List of logs emitted during the call. - #[serde(skip_serializing_if = "Vec::is_empty")] pub logs: Vec, /// Amount of value transferred. - #[serde(skip_serializing_if = "Option::is_none")] pub value: Option, /// Type of call. - #[serde(rename = "type")] pub call_type: CallType, /// Number of child calls entered (for log position calculation) - #[serde(skip)] pub child_call_count: u32, } +impl From for CallTraceV1 { + fn from(value: CallTrace) -> Self { + Self { + from: value.from, + gas: value.gas, + gas_used: value.gas_used, + to: value.to, + input: value.input, + output: value.output, + error: value.error, + revert_reason: value.revert_reason, + calls: value.calls.into_iter().map(Into::into).collect(), + logs: value.logs.into_iter().map(Into::into).collect(), + value: value.value, + call_type: value.call_type.into(), + child_call_count: value.child_call_count, + } + } +} + +impl From for CallTraceV2 { + fn from(value: CallTrace) -> Self { + Self { + from: value.from, + gas: value.gas, + gas_used: value.gas_used, + to: value.to, + input: value.input, + output: value.output, + error: value.error, + revert_reason: value.revert_reason, + calls: value.calls.into_iter().map(Into::into).collect(), + logs: value.logs.into_iter().map(Into::into).collect(), + value: value.value, + call_type: value.call_type.into(), + child_call_count: value.child_call_count, + } + } +} + /// A log emitted during a call. -#[derive( - Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq, -)] +#[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct CallLog { /// The address of the contract that emitted the log. pub address: H160, /// The topics used to index the log. - #[serde(default, skip_serializing_if = "Vec::is_empty")] pub topics: Vec, /// The log's data. pub data: Bytes, /// Position of the log relative to subcalls within the same trace /// See for details - #[serde(with = "super::hex_serde")] pub position: u32, + /// represents the transaction-level log index (matching `logIndex` in receipts), distinct from + /// the existing `position` field which tracks ordering relative to sub-calls within the same + /// trace frame. + pub index: u32, +} + +impl From for CallLogV1 { + fn from(value: CallLog) -> Self { + Self { + address: value.address, + topics: value.topics, + data: value.data, + position: value.position, + } + } +} + +impl From for CallLogV2 { + fn from(value: CallLog) -> Self { + Self { + address: value.address, + topics: value.topics, + data: value.data, + position: value.position, + index: value.index, + } + } } /// A transaction trace @@ -901,68 +599,116 @@ pub struct TransactionTrace { pub tx_hash: H256, /// The trace of the transaction. #[serde(rename = "result")] - pub trace: Trace, -} - -/// Serialize stack values using minimal hex format (like Geth) -fn serialize_stack_minimal(stack: &Vec, serializer: S) -> Result -where - S: serde::Serializer, -{ - let minimal_values: Vec = stack.iter().map(|bytes| bytes.to_short_hex()).collect(); - minimal_values.serialize(serializer) -} - -/// Deserialize stack values from minimal hex format -fn deserialize_stack_minimal<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let strings = Vec::::deserialize(deserializer)?; - strings - .into_iter() - .map(|s| { - // Parse as U256 to handle minimal hex like "0x0" or "0x4" - let s = s.trim_start_matches("0x"); - let value = sp_core::U256::from_str_radix(s, 16) - .map_err(|e| serde::de::Error::custom(alloc::format!("{:?}", e)))?; - // Convert to bytes, trimming leading zeros to match serialization - let bytes = value.to_big_endian(); - let trimmed = bytes - .iter() - .position(|&b| b != 0) - .map(|pos| bytes[pos..].to_vec()) - .unwrap_or_else(|| alloc::vec![0u8]); - Ok(Bytes::from(trimmed)) - }) - .collect() -} - -/// Serialize memory values without "0x" prefix (like Geth) -fn serialize_memory_no_prefix(memory: &Vec, serializer: S) -> Result -where - S: serde::Serializer, -{ - let hex_values: Vec = memory.iter().map(|bytes| bytes.to_hex_no_prefix()).collect(); - hex_values.serialize(serializer) -} - -/// Serialize storage map without "0x" prefix (like Geth) -fn serialize_storage_no_prefix( - storage: &Option>, - serializer: S, -) -> Result -where - S: serde::Serializer, -{ - match storage { - None => serializer.serialize_none(), - Some(map) => { - let mut ser_map = serializer.serialize_map(Some(map.len()))?; - for (key, value) in map { - ser_map.serialize_entry(&key.to_hex_no_prefix(), &value.to_hex_no_prefix())?; - } - ser_map.end() - }, + pub trace: TraceV1, +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + + /// Serialization should support the following JSON format: + /// + /// ```json + /// { "tracer": "callTracer", "tracerConfig": { "withLogs": false } } + /// ``` + /// + /// ```json + /// { "tracer": "callTracer" } + /// ``` + /// + /// By default if not specified the tracer is an ExecutionTracer, and it's config is passed + /// inline + /// + /// ```json + /// { "tracer": null, "enableMemory": true, "disableStack": false, "disableStorage": false, "enableReturnData": true } + /// ``` + #[test] + fn test_tracer_config_serialization() { + let tracers = vec![ + ( + r#"{ "enableMemory": true, "disableStack": false, "disableStorage": false, + "enableReturnData": true }"#, + TracerConfig { + config: TracerTypeV1::ExecutionTracer(Some(ExecutionTracerConfigV1 { + enable_memory: true, + disable_stack: false, + disable_storage: false, + enable_return_data: true, + disable_syscall_details: false, + limit: None, + memory_word_limit: 16, + })), + timeout: None, + }, + ), + ( + r#"{ }"#, + TracerConfig { + config: TracerTypeV1::ExecutionTracer(Some(ExecutionTracerConfigV1::default())), + timeout: None, + }, + ), + ( + r#"{"tracer": "callTracer"}"#, + TracerConfig { config: TracerTypeV1::CallTracer(None), timeout: None }, + ), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": false }}"#, + TracerConfig { + config: Some(CallTracerConfigV1 { with_logs: false, only_top_call: false }) + .into(), + timeout: None, + }, + ), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "onlyTopCall": true }}"#, + TracerConfig { + config: Some(CallTracerConfigV1 { with_logs: true, only_top_call: true }) + .into(), + timeout: None, + }, + ), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "onlyTopCall": true }, "timeout": + "10ms"}"#, + TracerConfig { + config: Some(CallTracerConfigV1 { with_logs: true, only_top_call: true }) + .into(), + timeout: Some(core::time::Duration::from_millis(10)), + }, + ), + ( + r#"{"tracer": "executionTracer"}"#, + TracerConfig { config: TracerTypeV1::ExecutionTracer(None), timeout: None }, + ), + ( + r#"{"tracer": "executionTracer", "tracerConfig": { "enableMemory": true }}"#, + TracerConfig { + config: Some(ExecutionTracerConfigV1 { + enable_memory: true, + ..Default::default() + }) + .into(), + timeout: None, + }, + ), + ( + r#"{ "enableMemory": true }"#, + TracerConfig { + config: Some(ExecutionTracerConfigV1 { + enable_memory: true, + ..Default::default() + }) + .into(), + timeout: None, + }, + ), + ]; + + for (json_data, expected) in tracers { + let result: TracerConfig = + serde_json::from_str(json_data).expect("Deserialization should succeed"); + assert_eq!(result, expected, "invalid serialization for {json_data}"); + } } } diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs deleted file mode 100644 index 86ca0add31943..0000000000000 --- a/substrate/frame/revive/src/evm/api/hex_serde.rs +++ /dev/null @@ -1,145 +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 alloc::{format, string::String, vec::Vec}; -use alloy_core::hex; -use serde::{Deserialize, Deserializer, Serializer}; - -pub trait HexCodec: Sized { - type Error; - fn to_hex(&self) -> String; - fn from_hex(s: String) -> Result; -} - -macro_rules! impl_hex_codec { - ($($t:ty),*) => { - $( - impl HexCodec for $t { - type Error = core::num::ParseIntError; - fn to_hex(&self) -> String { - format!("0x{:x}", self) - } - fn from_hex(s: String) -> Result { - <$t>::from_str_radix(s.trim_start_matches("0x"), 16) - } - } - )* - }; -} - -impl_hex_codec!(u8, u32, u64); - -impl HexCodec for [u8; T] { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result { - let data = hex::decode(s.trim_start_matches("0x"))?; - data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) - } -} - -impl HexCodec for Vec { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result { - hex::decode(s.trim_start_matches("0x")) - } -} - -pub fn serialize(value: &T, serializer: S) -> Result -where - S: Serializer, - T: HexCodec, -{ - let s = value.to_hex(); - serializer.serialize_str(&s) -} - -pub fn deserialize<'de, D, T>(deserializer: D) -> Result -where - D: Deserializer<'de>, - T: HexCodec, - ::Error: core::fmt::Debug, -{ - let s = String::deserialize(deserializer)?; - let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - Ok(value) -} - -pub mod option { - use super::*; - - pub fn serialize(value: &Option, serializer: S) -> Result - where - S: Serializer, - T: HexCodec, - { - match value { - Some(v) => serializer.serialize_str(&v.to_hex()), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - T: HexCodec, - ::Error: core::fmt::Debug, - { - let opt = Option::::deserialize(deserializer)?; - match opt { - Some(s) => T::from_hex(s) - .map(Some) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), - None => Ok(None), - } - } -} - -pub mod vec { - use super::*; - use serde::ser::SerializeSeq; - - pub fn serialize(values: &Vec, serializer: S) -> Result - where - S: Serializer, - T: HexCodec, - { - let mut seq = serializer.serialize_seq(Some(values.len()))?; - for v in values { - seq.serialize_element(&v.to_hex())?; - } - seq.end() - } - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - T: HexCodec, - ::Error: core::fmt::Debug, - { - let strings = Vec::::deserialize(deserializer)?; - strings - .into_iter() - .map(|s| T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))) - .collect() - } -} 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 d4bc1d5c257fe..fe51158ba2b46 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -17,7 +17,9 @@ //! Generated JSON-RPC types. #![allow(missing_docs)] -use super::{TypeEip1559, TypeEip2930, TypeEip4844, TypeEip7702, TypeLegacy, byte::*}; +use super::{ + Byte, Bytes, Bytes8, Bytes256, TypeEip1559, TypeEip2930, TypeEip4844, TypeEip7702, TypeLegacy, +}; use alloc::{ boxed::Box, collections::{BTreeMap, BTreeSet}, diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index 66c4b1a307671..38799cd3c8765 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -34,12 +34,20 @@ pub struct CallTracer { code_with_salt: Option<(Code, bool)>, /// The tracer configuration. config: CallTracerConfig, + /// Monotonic log counter across the entire transaction (for the `index` field). + log_count: u32, } impl CallTracer { /// Create a new [`CallTracer`] instance. pub fn new(config: CallTracerConfig) -> Self { - Self { traces: Vec::new(), code_with_salt: None, current_stack: Vec::new(), config } + Self { + traces: Vec::new(), + code_with_salt: None, + current_stack: Vec::new(), + config, + log_count: 0, + } } /// Collect the traces and return them. @@ -140,11 +148,15 @@ impl Tracing for CallTracer { let current_index = self.current_stack.last().unwrap(); if let Some(trace) = self.traces.get_mut(*current_index) { + let index = self.log_count; + self.log_count += 1; + let log = CallLog { address, topics: topics.to_vec(), data: data.to_vec().into(), position: trace.child_call_count, + index, }; trace.logs.push(log); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 966978bfab5c8..f99753b761f94 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -34,6 +34,8 @@ mod limits; mod metering; mod primitives; #[doc(hidden)] +pub mod runtime_api; +#[doc(hidden)] pub mod state_overrides; mod storage; #[cfg(test)] @@ -53,7 +55,7 @@ pub mod weights; use crate::{ evm::{ CallTracer, CreateCallMode, ExecutionTracer, GenericTransaction, PrestateTracer, - TYPE_EIP1559, Trace, Tracer, TracerType, block_hash::EthereumBlockBuilderIR, block_storage, + TYPE_EIP1559, Tracer, TracerType, block_hash::EthereumBlockBuilderIR, block_storage, fees::InfoT as FeeInfo, runtime::SetWeightLimit, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, @@ -87,6 +89,7 @@ use frame_system::{ Pallet as System, ensure_signed, pallet_prelude::{BlockNumberFor, OriginFor}, }; +use pallet_revive_types::runtime_api::*; use scale_info::TypeInfo; use sp_runtime::{ AccountId32, DispatchError, FixedPointNumber, FixedU128, SaturatedConversion, @@ -125,6 +128,9 @@ pub use sp_crypto_hashing::keccak_256; pub use sp_runtime; pub use weights::WeightInfo; +// Types re-export, needed to make it easier for runtimes to implement the pallet-revive runtime API +pub extern crate pallet_revive_types; + #[cfg(doc)] pub use crate::vm::pvm::SyscallDoc; @@ -2986,10 +2992,11 @@ sp_api::decl_runtime_apis! { /// parent block. /// /// See eth-rpc `debug_traceBlockByNumber` for usage. + #[deprecated(note = "Use the versioned equivalent `trace_block_versioned` if available on your runtime")] fn trace_block( block: Block, - config: TracerType - ) -> Vec<(u32, Trace)>; + config: TracerTypeV1 + ) -> Vec<(u32, TraceV1)>; /// Traces the execution of a specific transaction within a block. /// @@ -3000,13 +3007,13 @@ sp_api::decl_runtime_apis! { fn trace_tx( block: Block, tx_index: u32, - config: TracerType - ) -> Option; + config: TracerTypeV1 + ) -> Option; /// Dry run and return the trace of the given call. /// /// See eth-rpc `debug_traceCall` for usage. - fn trace_call(tx: GenericTransaction, config: TracerType) -> Result; + fn trace_call(tx: GenericTransaction, config: TracerTypeV1) -> Result; /// Dry run and return the trace of the given call with additional configuration. /// @@ -3015,9 +3022,9 @@ sp_api::decl_runtime_apis! { /// backwards compatibility — see [`TracingConfig`] documentation. fn trace_call_with_config( tx: GenericTransaction, - tracer_type: TracerType, + tracer_type: TracerTypeV1, config: TracingConfig, - ) -> Result; + ) -> Result; /// The address of the validator that produced the current block. fn block_author() -> H160; @@ -3036,6 +3043,11 @@ sp_api::decl_runtime_apis! { /// Construct the new balance and dust components of this EVM balance. fn new_balance_with_dust(balance: U256) -> Result<(Balance, u32), BalanceConversionError>; + + /* Versioned Runtime APIs */ + + #[api_version(2)] + fn trace_block_versioned(input: TraceBlockVersionedInputPayload) -> TraceBlockVersionedOutputPayload; } } @@ -3078,7 +3090,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { impl_runtime_apis! { $($rest)* - + #[api_version(2)] impl pallet_revive::ReviveApi for $Runtime { fn eth_block() -> $crate::EthBlock { @@ -3246,39 +3258,28 @@ macro_rules! impl_runtime_apis_plus_revive_traits { fn trace_block( block: Block, - tracer_type: $crate::evm::TracerType, - ) -> Vec<(u32, $crate::evm::Trace)> { - use $crate::{sp_runtime::traits::Block, tracing::trace}; - - if matches!(tracer_type, $crate::evm::TracerType::ExecutionTracer(_)) && - !$crate::DebugSettings::is_execution_tracing_enabled::() - { - return Default::default() - } - - let mut traces = vec![]; - let (header, extrinsics) = block.deconstruct(); - <$Executive>::initialize_block(&header); - for (index, ext) in extrinsics.into_iter().enumerate() { - let mut tracer = $crate::Pallet::::evm_tracer(tracer_type.clone()); - let t = tracer.as_tracing(); - let _ = trace(t, || <$Executive>::apply_extrinsic(ext)); - - if let Some(tx_trace) = tracer.collect_trace() { - traces.push((index as u32, tx_trace)); - } - } - - traces + tracer_type: $crate::pallet_revive_types::runtime_api::TracerTypeV1, + ) -> Vec<(u32, $crate::pallet_revive_types::runtime_api::TraceV1)> { + use $crate::pallet_revive_types::runtime_api::*; + + let input = TraceBlockVersionedInputPayload::from(TraceBlockInputPayloadV1 { + block, + config: tracer_type + }); + let output = Self::trace_block_versioned(input); + TraceBlockOutputPayloadV1::try_from(output) + .expect("qed; v1 input must produce v1 output") + .traces } fn trace_tx( block: Block, tx_index: u32, - tracer_type: $crate::evm::TracerType, - ) -> Option<$crate::evm::Trace> { + tracer_type: $crate::pallet_revive_types::runtime_api::TracerTypeV1, + ) -> Option<$crate::pallet_revive_types::runtime_api::TraceV1> { use $crate::{sp_runtime::traits::Block, tracing::trace}; + let tracer_type = $crate::evm::TracerType::from(tracer_type); if matches!(tracer_type, $crate::evm::TracerType::ExecutionTracer(_)) && !$crate::DebugSettings::is_execution_tracing_enabled::() { @@ -3299,15 +3300,16 @@ macro_rules! impl_runtime_apis_plus_revive_traits { } } - tracer.collect_trace() + tracer.collect_trace().map(Into::into) } fn trace_call( tx: $crate::evm::GenericTransaction, - tracer_type: $crate::evm::TracerType, - ) -> Result<$crate::evm::Trace, $crate::EthTransactError> { + tracer_type: $crate::pallet_revive_types::runtime_api::TracerTypeV1, + ) -> Result<$crate::pallet_revive_types::runtime_api::TraceV1, $crate::EthTransactError> { use $crate::tracing::trace; + let tracer_type = $crate::evm::TracerType::from(tracer_type); if matches!(tracer_type, $crate::evm::TracerType::ExecutionTracer(_)) && !$crate::DebugSettings::is_execution_tracing_enabled::() { @@ -3328,13 +3330,14 @@ macro_rules! impl_runtime_apis_plus_revive_traits { } else { Ok($crate::Pallet::::evm_tracer(tracer_type).empty_trace()) } + .map(Into::into) } fn trace_call_with_config( tx: $crate::evm::GenericTransaction, - tracer_type: $crate::evm::TracerType, + tracer_type: $crate::pallet_revive_types::runtime_api::TracerTypeV1, config: $crate::evm::TracingConfig, - ) -> Result<$crate::evm::Trace, $crate::EthTransactError> { + ) -> Result<$crate::pallet_revive_types::runtime_api::TraceV1, $crate::EthTransactError> { let $crate::evm::TracingConfig { state_overrides } = config; if let Some(overrides) = state_overrides { @@ -3360,6 +3363,52 @@ macro_rules! impl_runtime_apis_plus_revive_traits { fn new_balance_with_dust(balance: $crate::U256) -> Result<(Balance, u32), $crate::BalanceConversionError> { $crate::Pallet::::new_balance_with_dust(balance) } + + /* Versioned Runtime APIs */ + fn trace_block_versioned( + input: $crate::pallet_revive_types::runtime_api::TraceBlockVersionedInputPayload + ) -> $crate::pallet_revive_types::runtime_api::TraceBlockVersionedOutputPayload { + use $crate::{ + sp_runtime::traits::Block, + tracing::trace, + runtime_api::*, + pallet_revive_types::runtime_api::* + }; + use alloc::boxed::Box; + + let (input, output_wrapper): (_, Box TraceBlockVersionedOutputPayload>) = match input { + TraceBlockVersionedInputPayload::V1(payload) => ( + TraceBlockInputPayload::from(payload), + Box::new(|output| TraceBlockVersionedOutputPayload::V1(output.into())) + ), + TraceBlockVersionedInputPayload::V2(payload) => ( + TraceBlockInputPayload::from(payload), + Box::new(|output| TraceBlockVersionedOutputPayload::V2(output.into())) + ), + }; + + if matches!(input.config, $crate::evm::TracerType::ExecutionTracer(_)) && + !$crate::DebugSettings::is_execution_tracing_enabled::() + { + return output_wrapper(Default::default()) + } + + let mut traces = vec![]; + let (header, extrinsics) = input.block.deconstruct(); + <$Executive>::initialize_block(&header); + for (index, ext) in extrinsics.into_iter().enumerate() { + let mut tracer = $crate::Pallet::::evm_tracer(input.config.clone()); + let t = tracer.as_tracing(); + let _ = trace(t, || <$Executive>::apply_extrinsic(ext)); + + if let Some(tx_trace) = tracer.collect_trace() { + traces.push((index as u32, tx_trace)); + } + } + + let output = TraceBlockOutputPayload { traces }; + output_wrapper(output) + } } } }; diff --git a/substrate/frame/revive/src/runtime_api/mod.rs b/substrate/frame/revive/src/runtime_api/mod.rs new file mode 100644 index 0000000000000..d4967365601ed --- /dev/null +++ b/substrate/frame/revive/src/runtime_api/mod.rs @@ -0,0 +1,20 @@ +// 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. + +mod trace_block; + +pub use trace_block::*; diff --git a/substrate/frame/revive/src/runtime_api/trace_block.rs b/substrate/frame/revive/src/runtime_api/trace_block.rs new file mode 100644 index 0000000000000..c48eb839660c3 --- /dev/null +++ b/substrate/frame/revive/src/runtime_api/trace_block.rs @@ -0,0 +1,76 @@ +// 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 alloc::vec::Vec; +use pallet_revive_types::runtime_api::*; + +use crate::evm::{Trace, TracerType}; + +// ====== +// Input +// ====== + +pub struct TraceBlockInputPayload { + pub block: Block, + pub config: TracerType, +} + +impl From> for TraceBlockInputPayload { + fn from(value: TraceBlockVersionedInputPayload) -> Self { + match value { + TraceBlockVersionedInputPayload::V1(payload) => payload.into(), + TraceBlockVersionedInputPayload::V2(payload) => payload.into(), + } + } +} + +impl From> for TraceBlockInputPayload { + fn from(value: TraceBlockInputPayloadV1) -> Self { + Self { block: value.block, config: value.config.into() } + } +} + +impl From> for TraceBlockInputPayload { + fn from(value: TraceBlockInputPayloadV2) -> Self { + Self { block: value.block, config: value.config.into() } + } +} + +// ======= +// Output +// ======= + +#[derive(Default)] +pub struct TraceBlockOutputPayload { + pub traces: Vec<(u32, Trace)>, +} + +impl From for TraceBlockOutputPayloadV1 { + fn from(value: TraceBlockOutputPayload) -> Self { + Self { + traces: value.traces.into_iter().map(|(index, trace)| (index, trace.into())).collect(), + } + } +} + +impl From for TraceBlockOutputPayloadV2 { + fn from(value: TraceBlockOutputPayload) -> Self { + Self { + traces: value.traces.into_iter().map(|(index, trace)| (index, trace.into())).collect(), + } + } +} diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 1fb416021bd77..3e04f8bd28060 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -58,6 +58,7 @@ use frame_support::{ }; use frame_system::{EventRecord, Phase}; use pallet_revive_fixtures::compile_module; +use pallet_revive_types::runtime_api::*; use pallet_revive_uapi::{ReturnErrorCode as RuntimeReturnCode, ReturnFlags}; use pretty_assertions::{assert_eq, assert_ne}; use sp_core::U256; @@ -4209,23 +4210,27 @@ fn call_tracing_works() { assert_eq!(gas_trace.gas_used, gas_used as u64); for config in tracer_configs { - let logs = if config.with_logs { + let with_logs = config.with_logs; + let make_logs = |start_index: u32| -> Vec { + if !with_logs { + return vec![]; + } vec![ CallLog { address: addr, topics: Default::default(), data: b"before".to_vec().into(), position: 0, + index: start_index, }, CallLog { address: addr, topics: Default::default(), data: b"after".to_vec().into(), position: 1, + index: start_index + 1, }, ] - } else { - vec![] }; let calls = if config.only_top_call { @@ -4252,7 +4257,7 @@ fn call_tracing_works() { to: addr, input: (2u32, addr_callee).encode().into(), call_type: Call, - logs: logs.clone(), + logs: make_logs(2), value: Some(U256::from(0)), gas: 0, gas_used: 0, @@ -4274,7 +4279,7 @@ fn call_tracing_works() { to: addr, input: (1u32, addr_callee).encode().into(), call_type: Call, - logs: logs.clone(), + logs: make_logs(4), value: Some(U256::from(0)), gas: 0, gas_used: 0, @@ -4332,7 +4337,7 @@ fn call_tracing_works() { to: addr, input: (3u32, addr_callee).encode().into(), call_type: Call, - logs: logs.clone(), + logs: make_logs(0), value: Some(U256::from(0)), calls: calls, child_call_count: 2, @@ -5479,18 +5484,15 @@ fn existential_deposit_shall_not_be_charged_twice() { #[test] fn self_destruct_by_syscall_tracing_works() { - use crate::{ - Trace, - evm::{PrestateTrace, PrestateTracer, PrestateTracerConfig, Tracer}, - }; + use crate::evm::{PrestateTracer, PrestateTracerConfig, Tracer}; let (binary, _code_hash) = compile_module("self_destruct_by_syscall").unwrap(); struct TestCase { description: &'static str, create_tracer: Box Tracer>, - expected_trace_fn: Box) -> Trace>, - modify_trace_fn: Option Trace>>, + expected_trace_fn: Box) -> TraceV1>, + modify_trace_fn: Option TraceV1>>, } let test_cases = vec![ @@ -5498,19 +5500,19 @@ fn self_destruct_by_syscall_tracing_works() { description: "CallTracer", create_tracer: Box::new(|| Tracer::CallTracer(CallTracer::new(Default::default()))), expected_trace_fn: Box::new(|addr, _binary| { - Trace::Call(CallTrace { + TraceV1::Call(CallTraceV1 { from: ALICE_ADDR, to: addr, - call_type: CallType::Call, + call_type: CallTypeV1::Call, value: Some(U256::zero()), gas: 0, gas_used: 0, - calls: vec![CallTrace { + calls: vec![CallTraceV1 { from: addr, to: DJANGO_ADDR, gas: 0, - call_type: CallType::Selfdestruct, + call_type: CallTypeV1::Selfdestruct, value: Some(Pallet::::convert_native_to_evm(100_000u128)), ..Default::default() }], @@ -5518,7 +5520,7 @@ fn self_destruct_by_syscall_tracing_works() { }) }), modify_trace_fn: Some(Box::new(|mut actual_trace| { - if let Trace::Call(trace) = &mut actual_trace { + if let TraceV1::Call(trace) = &mut actual_trace { trace.gas = 0; trace.gas_used = 0; trace.calls[0].gas = 0; @@ -5584,8 +5586,8 @@ fn self_destruct_by_syscall_tracing_works() { .replace("{{DJANGO_BALANCE_POST}}", &format!("{:#x}", django_balance_post)) .replace("{{CONTRACT_CODE}}", &format!("0x{}", hex::encode(&binary))); - let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); - Trace::Prestate(expected) + let expected: PrestateTraceV1 = serde_json::from_str(&json).unwrap(); + TraceV1::Prestate(expected) }), modify_trace_fn: None, }, @@ -5629,8 +5631,8 @@ fn self_destruct_by_syscall_tracing_works() { .replace("{{DJANGO_BALANCE}}", &format!("{:#x}", django_balance)) .replace("{{CONTRACT_CODE}}", &format!("0x{}", hex::encode(&binary))); - let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); - Trace::Prestate(expected) + let expected: PrestateTraceV1 = serde_json::from_str(&json).unwrap(); + TraceV1::Prestate(expected) }), modify_trace_fn: None, }, @@ -5652,15 +5654,15 @@ fn self_destruct_by_syscall_tracing_works() { builder::call(addr).build().unwrap(); }); - let mut trace = tracer.collect_trace().unwrap(); + let mut trace = TraceV1::from(tracer.collect_trace().unwrap()); if let Some(modify_trace_fn) = modify_trace_fn { trace = modify_trace_fn(trace); } let trace_wrapped = match trace { - crate::evm::Trace::Call(ct) => Trace::Call(ct), - crate::evm::Trace::Prestate(pt) => Trace::Prestate(pt), - crate::evm::Trace::Execution(_) => panic!("Execution trace not expected"), + TraceV1::Call(ct) => TraceV1::Call(ct), + TraceV1::Prestate(pt) => TraceV1::Prestate(pt), + TraceV1::Execution(_) => panic!("Execution trace not expected"), }; assert_eq!(trace_wrapped, expected_trace, "Trace mismatch for: {}", description); diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 2b72c600ef09a..b11f52fda4c4d 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -20,7 +20,7 @@ use crate::{ PristineCode, assert_refcount, call_builder::VmBinaryModule, debug::DebugSettings, - evm::{PrestateTrace, PrestateTracer, PrestateTracerConfig}, + evm::{PrestateTracer, PrestateTracerConfig}, test_utils::{ALICE, ALICE_ADDR, BOB, builder::Contract}, tests::{ AllowEvmBytecode, DebugFlag, ExtBuilder, RuntimeOrigin, Test, builder, @@ -34,6 +34,7 @@ use frame_support::{ assert_err, assert_noop, assert_ok, dispatch::GetDispatchInfo, traits::fungible::Mutate, }; use pallet_revive_fixtures::{Fibonacci, FixtureType, NestedCounter, compile_module_with_type}; +use pallet_revive_types::runtime_api::*; use pretty_assertions::assert_eq; use sp_runtime::Weight; use test_case::test_case; @@ -557,9 +558,10 @@ fn prestate_diff_mode_tracing_works() { let instantiate_trace = tracer.collect_trace(); let expected_json = replace_placeholders(test_case.expected_instantiate_trace_json); - let expected_trace: PrestateTrace = serde_json::from_str(&expected_json).unwrap(); + let expected_trace: PrestateTraceV1 = serde_json::from_str(&expected_json).unwrap(); assert_eq!( - instantiate_trace, expected_trace, + PrestateTraceV1::from(instantiate_trace), + expected_trace, "unexpected instantiate trace for {:?}", test_case.config ); @@ -578,9 +580,10 @@ fn prestate_diff_mode_tracing_works() { let call_trace = tracer.collect_trace(); let expected_json = replace_placeholders(test_case.expected_call_trace_json); - let expected_trace: PrestateTrace = serde_json::from_str(&expected_json).unwrap(); + let expected_trace: PrestateTraceV1 = serde_json::from_str(&expected_json).unwrap(); assert_eq!( - call_trace, expected_trace, + PrestateTraceV1::from(call_trace), + expected_trace, "unexpected call trace for {:?}", test_case.config ); @@ -679,7 +682,7 @@ fn eth_substrate_call_tracks_weight_correctly() { #[test] fn execution_tracing_works() { use crate::{ - evm::{Bytes, ExecutionStepKind, ExecutionTrace, ExecutionTracer, ExecutionTracerConfig}, + evm::{Bytes, ExecutionTrace, ExecutionTracer, ExecutionTracerConfig}, tracing::trace, }; use pallet_revive_fixtures::{Callee, Caller}; @@ -785,7 +788,7 @@ fn execution_tracing_works() { ]; /// Normalizes trace by zeroing out all dynamic values for stable comparisons. - fn normalize_trace(trace: &ExecutionTrace) -> ExecutionTrace { + fn normalize_trace(trace: &ExecutionTraceV1) -> ExecutionTraceV1 { use frame_support::weights::Weight; let mut normalized = trace.clone(); @@ -799,33 +802,32 @@ fn execution_tracing_works() { step.weight_cost = Weight::zero(); match &mut step.kind { - ExecutionStepKind::EVMOpcode { stack, .. } => { + ExecutionStepKindV1::EVMOpcode { stack, .. } => { for val in stack.iter_mut() { *val = Bytes::from(vec![0u8]); } }, - ExecutionStepKind::PVMSyscall { op, args, returned, .. } => { + ExecutionStepKindV1::PVMSyscall { op, args, returned, .. } => { // Normalize call/delegate_call to their _evm variants so // the test passes regardless of which resolc version // compiled the fixtures (older emits call/delegate_call, // newer emits call_evm/delegate_call_evm). - use crate::vm::pvm::env::lookup_syscall_index; - let call_idx = lookup_syscall_index("call").unwrap(); - let call_evm_idx = lookup_syscall_index("call_evm").unwrap(); - let delegate_idx = lookup_syscall_index("delegate_call").unwrap(); - let delegate_evm_idx = lookup_syscall_index("delegate_call_evm").unwrap(); - if *op == call_idx || *op == call_evm_idx { - *op = call_evm_idx; - // Clear args since the two variants have compatible - // behavior but different argument layouts. - args.clear(); - } else if *op == delegate_idx || *op == delegate_evm_idx { - *op = delegate_evm_idx; - args.clear(); - } else { - for val in args.iter_mut() { - *val = 0; - } + match op { + PolkavmSyscallV1::Call | PolkavmSyscallV1::CallEvm => { + *op = PolkavmSyscallV1::CallEvm; + // Clear args since the two variants have compatible behavior but + // different argument layouts. + args.clear(); + }, + PolkavmSyscallV1::DelegateCall | PolkavmSyscallV1::DelegateCallEvm => { + *op = PolkavmSyscallV1::DelegateCallEvm; + args.clear(); + }, + _ => { + for val in args.iter_mut() { + *val = 0; + } + }, } if returned.is_some() { *returned = Some(0); @@ -879,12 +881,12 @@ fn execution_tracing_works() { } else { test_case.expected_pvm_trace }; - let expected: ExecutionTrace = serde_json::from_str(expected_json_str) + let expected: ExecutionTraceV1 = serde_json::from_str(expected_json_str) .unwrap_or_else(|e| { panic!("{name} ({vm_type}): failed to parse expected JSON: {e}") }); // Normalize both traces for comparison (zeroes out dynamic values) - let normalized_actual = normalize_trace(&actual_trace); + let normalized_actual = normalize_trace(&actual_trace.clone().into()); let normalized_expected = normalize_trace(&expected); assert_eq!( normalized_actual, normalized_expected, diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index 8bc3b523660cf..6f980f2402029 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -957,3 +957,29 @@ pub mod env { Err(TrapReason::Termination) } } + +#[cfg(test)] +mod tests { + use super::*; + use pallet_revive_types::runtime_api::*; + use strum::VariantArray; + + #[test] + fn wire_has_all_syscalls() { + let wire_syscalls = PolkavmSyscallV1::VARIANTS + .iter() + .copied() + .map(<&'static str>::from) + .collect::>(); + let canonical_syscalls = list_trace_ops() + .iter() + .map(|bytes| str::from_utf8(bytes).unwrap()) + .collect::>(); + + assert_eq!( + wire_syscalls, canonical_syscalls, + "There is a difference between the syscalls defined for the wire and the syscalls \ + available in the system. Wire = {wire_syscalls:?}. Available = {canonical_syscalls:?}" + ) + } +} diff --git a/substrate/frame/revive/types/Cargo.toml b/substrate/frame/revive/types/Cargo.toml new file mode 100644 index 0000000000000..628a06dec1608 --- /dev/null +++ b/substrate/frame/revive/types/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "pallet-revive-types" +version = "0.1.0" +authors.workspace = true +edition = "2024" +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "Wire types used by pallet-revive which have higher stability guarantees than internal pallet-revive types." + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +alloy-core = { workspace = true } +codec = { workspace = true, features = ["derive", "max-encoded-len"] } +derive_more = { workspace = true, features = ["from", "try_into"] } +num-traits = { workspace = true } +num_enum = { workspace = true } +revm-bytecode = { workspace = true, features = ["parse"] } +scale-info = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["alloc", "derive"] } +sp-core = { workspace = true, features = ["serde"] } +sp-weights = { workspace = true, features = ["serde"] } +strum = { workspace = true, features = ["derive"] } + +[features] +default = ["std"] +std = [ + "alloy-core/std", + "codec/std", + "num-traits/std", + "num_enum/std", + "revm-bytecode/std", + "scale-info/std", + "serde/std", + "sp-core/std", + "sp-weights/std", + "strum/std", +] + +[dev-dependencies] +serde_json = { workspace = true, features = ["std"] } diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/types/src/common/byte.rs similarity index 73% rename from substrate/frame/revive/src/evm/api/byte.rs rename to substrate/frame/revive/types/src/common/byte.rs index d598eaf385e5c..3469de19250d0 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/types/src/common/byte.rs @@ -14,9 +14,8 @@ // 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. -//! Define Byte wrapper types for encoding and decoding hex strings -use super::hex_serde::HexCodec; +use crate::common::hex_serde::HexCodec; use alloc::{vec, vec::Vec}; use alloy_core::hex; use codec::{Decode, DecodeWithMemTracking, Encode}; @@ -39,7 +38,7 @@ macro_rules! impl_hex { ($type:ident, $inner:ty, $default:expr) => { #[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, Clone, Serialize, Deserialize, Hash, DecodeWithMemTracking)] #[doc = concat!("`", stringify!($inner), "`", " wrapper type for encoding and decoding hex strings")] - pub struct $type(#[serde(with = "crate::evm::api::hex_serde")] pub $inner); + pub struct $type(#[serde(with = "crate::common::hex_serde")] pub $inner); impl Default for $type { fn default() -> Self { @@ -94,34 +93,39 @@ impl_hex!(Bytes8, [u8; 8], [0u8; 8]); impl_hex!(Bytes32, [u8; 32], [0u8; 32]); impl_hex!(Bytes256, [u8; 256], [0u8; 256]); -#[test] -fn test_to_short_hex() { - let bytes = Bytes(crate::U256::from(4).to_big_endian().to_vec()); - assert_eq!(bytes.to_short_hex(), "0x4"); -} +#[cfg(test)] +mod tests { + use super::*; -#[test] -fn test_to_hex_no_prefix() { - let bytes = Bytes(vec![0x12, 0x34, 0x56, 0x78]); - assert_eq!(bytes.to_hex_no_prefix(), "12345678"); -} + #[test] + fn test_to_short_hex() { + let bytes = Bytes(sp_core::U256::from(4).to_big_endian().to_vec()); + assert_eq!(bytes.to_short_hex(), "0x4"); + } -#[test] -fn serialize_works() { - let a = Byte(42); - let s = serde_json::to_string(&a).unwrap(); - assert_eq!(s, "\"0x2a\""); - let b = serde_json::from_str::(&s).unwrap(); - assert_eq!(a, b); - - let a = Bytes(b"bello world".to_vec()); - let s = serde_json::to_string(&a).unwrap(); - assert_eq!(s, "\"0x62656c6c6f20776f726c64\""); - let b = serde_json::from_str::(&s).unwrap(); - assert_eq!(a, b); - - let a = Bytes256([42u8; 256]); - let s = serde_json::to_string(&a).unwrap(); - let b = serde_json::from_str::(&s).unwrap(); - assert_eq!(a, b); + #[test] + fn test_to_hex_no_prefix() { + let bytes = Bytes(vec![0x12, 0x34, 0x56, 0x78]); + assert_eq!(bytes.to_hex_no_prefix(), "12345678"); + } + + #[test] + fn serialize_works() { + let a = Byte(42); + let s = serde_json::to_string(&a).unwrap(); + assert_eq!(s, "\"0x2a\""); + let b = serde_json::from_str::(&s).unwrap(); + assert_eq!(a, b); + + let a = Bytes(b"bello world".to_vec()); + let s = serde_json::to_string(&a).unwrap(); + assert_eq!(s, "\"0x62656c6c6f20776f726c64\""); + let b = serde_json::from_str::(&s).unwrap(); + assert_eq!(a, b); + + let a = Bytes256([42u8; 256]); + let s = serde_json::to_string(&a).unwrap(); + let b = serde_json::from_str::(&s).unwrap(); + assert_eq!(a, b); + } } diff --git a/substrate/frame/revive/types/src/common/mod.rs b/substrate/frame/revive/types/src/common/mod.rs new file mode 100644 index 0000000000000..06e6cadf727c2 --- /dev/null +++ b/substrate/frame/revive/types/src/common/mod.rs @@ -0,0 +1,22 @@ +// 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. + +mod byte; +mod serde_helpers; + +pub use byte::*; +pub use serde_helpers::*; diff --git a/substrate/frame/revive/types/src/common/serde_helpers.rs b/substrate/frame/revive/types/src/common/serde_helpers.rs new file mode 100644 index 0000000000000..7ec4098bc0b3e --- /dev/null +++ b/substrate/frame/revive/types/src/common/serde_helpers.rs @@ -0,0 +1,248 @@ +// 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 alloc::{collections::BTreeMap, format, string::String, vec::Vec}; +use alloy_core::hex; +use num_traits::Zero; +use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeMap}; + +use crate::common::Bytes; + +pub mod hex_serde { + use super::*; + + pub trait HexCodec: Sized { + type Error; + fn to_hex(&self) -> String; + fn from_hex(s: String) -> Result; + } + + macro_rules! impl_hex_codec { + ($($t:ty),*) => { + $( + impl HexCodec for $t { + type Error = core::num::ParseIntError; + fn to_hex(&self) -> String { + format!("0x{:x}", self) + } + fn from_hex(s: String) -> Result { + <$t>::from_str_radix(s.trim_start_matches("0x"), 16) + } + } + )* + }; + } + + impl_hex_codec!(u8, u32, u64); + + impl HexCodec for [u8; T] { + type Error = hex::FromHexError; + + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + + fn from_hex(s: String) -> Result { + let data = hex::decode(s.trim_start_matches("0x"))?; + data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) + } + } + + impl HexCodec for Vec { + type Error = hex::FromHexError; + + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + + fn from_hex(s: String) -> Result { + hex::decode(s.trim_start_matches("0x")) + } + } + + pub fn serialize(value: &T, serializer: S) -> Result + where + S: Serializer, + T: HexCodec, + { + let s = value.to_hex(); + serializer.serialize_str(&s) + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: HexCodec, + ::Error: core::fmt::Debug, + { + let s = String::deserialize(deserializer)?; + let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; + Ok(value) + } + + pub mod option { + use super::*; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + T: HexCodec, + { + match value { + Some(v) => serializer.serialize_str(&v.to_hex()), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: HexCodec, + ::Error: core::fmt::Debug, + { + let opt = Option::::deserialize(deserializer)?; + match opt { + Some(s) => T::from_hex(s) + .map(Some) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))), + None => Ok(None), + } + } + } + + pub mod vec { + use super::*; + use serde::ser::SerializeSeq; + + pub fn serialize(values: &Vec, serializer: S) -> Result + where + S: Serializer, + T: HexCodec, + { + let mut seq = serializer.serialize_seq(Some(values.len()))?; + for v in values { + seq.serialize_element(&v.to_hex())?; + } + seq.end() + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: HexCodec, + ::Error: core::fmt::Debug, + { + let strings = Vec::::deserialize(deserializer)?; + strings + .into_iter() + .map(|s| T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))) + .collect() + } + } +} + +pub fn serialize_stack_minimal(stack: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + let minimal_values: Vec = stack.iter().map(|bytes| bytes.to_short_hex()).collect(); + minimal_values.serialize(serializer) +} + +pub fn deserialize_stack_minimal<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let strings = Vec::::deserialize(deserializer)?; + strings + .into_iter() + .map(|s| { + let s = s.trim_start_matches("0x"); + let value = sp_core::U256::from_str_radix(s, 16) + .map_err(|e| serde::de::Error::custom(alloc::format!("{:?}", e)))?; + let bytes = value.to_big_endian(); + let trimmed = bytes + .iter() + .position(|&b| b != 0) + .map(|pos| bytes[pos..].to_vec()) + .unwrap_or_else(|| alloc::vec![0u8]); + Ok(Bytes::from(trimmed)) + }) + .collect() +} + +pub fn serialize_memory_no_prefix(memory: &Vec, serializer: S) -> Result +where + S: Serializer, +{ + let hex_values: Vec = memory.iter().map(|bytes| bytes.to_hex_no_prefix()).collect(); + hex_values.serialize(serializer) +} + +pub fn serialize_storage_no_prefix( + storage: &Option>, + serializer: S, +) -> Result +where + S: Serializer, +{ + match storage { + None => serializer.serialize_none(), + Some(map) => { + let mut ser_map = serializer.serialize_map(Some(map.len()))?; + for (key, value) in map { + ser_map.serialize_entry(&key.to_hex_no_prefix(), &value.to_hex_no_prefix())?; + } + ser_map.end() + }, + } +} + +pub mod option_value_map { + use super::*; + + pub fn is_empty<'a, K: 'a, V: 'a>( + iterator: impl IntoIterator)>, + ) -> bool { + !iterator.into_iter().any(|(_, value)| value.is_some()) + } + + pub fn serialize_skip_none<'a, K, V, S>( + iterator: impl IntoIterator)>, + serializer: S, + ) -> Result + where + K: Serialize + 'a, + V: Serialize + 'a, + S: Serializer, + { + serializer.collect_map(iterator.into_iter().filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))) + } +} + +pub fn zero_to_none<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Zero, +{ + let value = Option::::deserialize(deserializer)?; + match value { + Some(value) if value.is_zero() => Ok(None), + Some(value) => Ok(Some(value)), + None => Ok(None), + } +} diff --git a/substrate/frame/revive/types/src/lib.rs b/substrate/frame/revive/types/src/lib.rs new file mode 100644 index 0000000000000..492d8d13a2f78 --- /dev/null +++ b/substrate/frame/revive/types/src/lib.rs @@ -0,0 +1,23 @@ +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +pub mod common; +pub mod runtime_api; diff --git a/substrate/frame/revive/types/src/runtime_api/mod.rs b/substrate/frame/revive/types/src/runtime_api/mod.rs new file mode 100644 index 0000000000000..4a2a0dc98d8f3 --- /dev/null +++ b/substrate/frame/revive/types/src/runtime_api/mod.rs @@ -0,0 +1,22 @@ +// 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. + +mod payloads; +mod types; + +pub use payloads::*; +pub use types::*; diff --git a/substrate/frame/revive/types/src/runtime_api/payloads/mod.rs b/substrate/frame/revive/types/src/runtime_api/payloads/mod.rs new file mode 100644 index 0000000000000..d4967365601ed --- /dev/null +++ b/substrate/frame/revive/types/src/runtime_api/payloads/mod.rs @@ -0,0 +1,20 @@ +// 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. + +mod trace_block; + +pub use trace_block::*; diff --git a/substrate/frame/revive/types/src/runtime_api/payloads/trace_block.rs b/substrate/frame/revive/types/src/runtime_api/payloads/trace_block.rs new file mode 100644 index 0000000000000..3b29c9c3ad7d6 --- /dev/null +++ b/substrate/frame/revive/types/src/runtime_api/payloads/trace_block.rs @@ -0,0 +1,65 @@ +// 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 alloc::vec::Vec; +use codec::{Decode, Encode}; +use derive_more::{From, TryInto}; +use scale_info::TypeInfo; + +use crate::runtime_api::*; + +// ======= +// Inputs +// ======= + +#[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq)] +pub struct TraceBlockInputPayloadV1 { + pub block: Block, + pub config: TracerTypeV1, +} + +#[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq)] +pub struct TraceBlockInputPayloadV2 { + pub block: Block, + pub config: TracerTypeV1, +} + +#[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq, From, TryInto)] +pub enum TraceBlockVersionedInputPayload { + V1(TraceBlockInputPayloadV1), + V2(TraceBlockInputPayloadV2), +} + +// ======== +// Outputs +// ======== + +#[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq)] +pub struct TraceBlockOutputPayloadV1 { + pub traces: Vec<(u32, TraceV1)>, +} + +#[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq)] +pub struct TraceBlockOutputPayloadV2 { + pub traces: Vec<(u32, TraceV2)>, +} + +#[derive(TypeInfo, Debug, Clone, Encode, Decode, PartialEq, From, TryInto)] +pub enum TraceBlockVersionedOutputPayload { + V1(TraceBlockOutputPayloadV1), + V2(TraceBlockOutputPayloadV2), +} diff --git a/substrate/frame/revive/types/src/runtime_api/types/mod.rs b/substrate/frame/revive/types/src/runtime_api/types/mod.rs new file mode 100644 index 0000000000000..448eca8c8738d --- /dev/null +++ b/substrate/frame/revive/types/src/runtime_api/types/mod.rs @@ -0,0 +1,22 @@ +// 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. + +mod tracer; +mod traces; + +pub use tracer::*; +pub use traces::*; diff --git a/substrate/frame/revive/types/src/runtime_api/types/tracer.rs b/substrate/frame/revive/types/src/runtime_api/types/tracer.rs new file mode 100644 index 0000000000000..99c2f2c096922 --- /dev/null +++ b/substrate/frame/revive/types/src/runtime_api/types/tracer.rs @@ -0,0 +1,85 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use codec::{Decode, Encode}; +use derive_more::From; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; + +use crate::common::*; + +#[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, Deserialize, PartialEq, From)] +#[serde(tag = "tracer", content = "tracerConfig", rename_all = "camelCase")] +pub enum TracerTypeV1 { + CallTracer(Option), + PrestateTracer(Option), + ExecutionTracer(Option), +} + +impl Default for TracerTypeV1 { + fn default() -> Self { + TracerTypeV1::ExecutionTracer(Some(ExecutionTracerConfigV1::default())) + } +} + +#[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] +#[serde(default, rename_all = "camelCase")] +pub struct CallTracerConfigV1 { + pub with_logs: bool, + pub only_top_call: bool, +} + +impl Default for CallTracerConfigV1 { + fn default() -> Self { + Self { with_logs: true, only_top_call: false } + } +} + +#[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo, Default)] +#[serde(default, rename_all = "camelCase")] +pub struct PrestateTracerConfigV1 { + pub diff_mode: bool, + pub disable_storage: bool, + pub disable_code: bool, +} + +#[derive(Clone, Debug, Decode, Serialize, Deserialize, Encode, PartialEq, TypeInfo)] +#[serde(default, rename_all = "camelCase")] +pub struct ExecutionTracerConfigV1 { + pub enable_memory: bool, + pub disable_stack: bool, + pub disable_storage: bool, + pub enable_return_data: bool, + pub disable_syscall_details: bool, + #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "zero_to_none")] + pub limit: Option, + pub memory_word_limit: u32, +} + +impl Default for ExecutionTracerConfigV1 { + fn default() -> Self { + Self { + enable_memory: false, + disable_stack: false, + disable_storage: false, + enable_return_data: false, + disable_syscall_details: false, + limit: None, + memory_word_limit: 16, + } + } +} diff --git a/substrate/frame/revive/types/src/runtime_api/types/traces.rs b/substrate/frame/revive/types/src/runtime_api/types/traces.rs new file mode 100644 index 0000000000000..f1c6e6468ac15 --- /dev/null +++ b/substrate/frame/revive/types/src/runtime_api/types/traces.rs @@ -0,0 +1,354 @@ +// 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 alloc::{collections::BTreeMap, string::String, vec::Vec}; +use codec::{Decode, Encode}; +use derive_more::From; +use revm_bytecode::opcode::OpCode; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_core::{H160, H256, U256}; +use sp_weights::Weight; + +use crate::common::*; + +#[derive(TypeInfo, Deserialize, Serialize, From, Encode, Decode, Clone, Debug, Eq, PartialEq)] +#[serde(untagged)] +pub enum TraceV1 { + Call(CallTraceV1), + Prestate(PrestateTraceV1), + Execution(ExecutionTraceV1), +} + +#[derive(TypeInfo, Deserialize, Serialize, From, Encode, Decode, Clone, Debug, Eq, PartialEq)] +#[serde(untagged)] +pub enum TraceV2 { + Call(CallTraceV2), + Prestate(PrestateTraceV1), + Execution(ExecutionTraceV1), +} + +#[derive( + TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +#[serde(rename_all = "camelCase")] +pub struct CallTraceV1 { + pub from: H160, + #[serde(with = "hex_serde")] + pub gas: u64, + #[serde(with = "hex_serde")] + pub gas_used: u64, + pub to: H160, + pub input: Bytes, + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub output: Bytes, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub revert_reason: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub calls: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub logs: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(rename = "type")] + pub call_type: CallTypeV1, + #[serde(skip)] + pub child_call_count: u32, +} + +#[derive( + TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +#[serde(rename_all = "camelCase")] +pub struct CallTraceV2 { + pub from: H160, + #[serde(with = "hex_serde")] + pub gas: u64, + #[serde(with = "hex_serde")] + pub gas_used: u64, + pub to: H160, + pub input: Bytes, + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub output: Bytes, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub revert_reason: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub calls: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub logs: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(rename = "type")] + pub call_type: CallTypeV1, + #[serde(skip)] + pub child_call_count: u32, +} + +#[derive(TypeInfo, Encode, Serialize, Deserialize, Decode, Clone, Debug, Eq, PartialEq)] +#[serde(untagged, deny_unknown_fields)] +pub enum PrestateTraceV1 { + Prestate(BTreeMap), + DiffMode { pre: BTreeMap, post: BTreeMap }, +} + +#[derive( + Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +#[serde(default, rename_all = "camelCase")] +pub struct ExecutionTraceV1 { + pub gas: u64, + pub weight_consumed: Weight, + pub base_call_weight: Weight, + pub failed: bool, + pub return_value: Bytes, + pub struct_logs: Vec, +} + +#[derive( + Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq, +)] +pub struct CallLogV1 { + pub address: H160, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub topics: Vec, + pub data: Bytes, + #[serde(with = "hex_serde")] + pub position: u32, +} + +#[derive( + Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq, +)] +pub struct CallLogV2 { + pub address: H160, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub topics: Vec, + pub data: Bytes, + #[serde(with = "hex_serde")] + pub position: u32, + #[serde(with = "hex_serde")] + pub index: u32, +} + +#[derive( + Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Clone, Debug, +)] +#[serde(rename_all = "UPPERCASE")] +pub enum CallTypeV1 { + #[default] + Call, + StaticCall, + DelegateCall, + Create, + Create2, + Selfdestruct, +} + +#[derive( + TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +pub struct PrestateTraceInfoV1 { + #[serde(skip_serializing_if = "Option::is_none")] + pub balance: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + #[serde( + default, + skip_serializing_if = "option_value_map::is_empty", + serialize_with = "option_value_map::serialize_skip_none" + )] + pub storage: BTreeMap>, +} + +#[derive( + TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Default, +)] +#[serde(default, rename_all = "camelCase")] +pub struct ExecutionStepV1 { + #[codec(compact)] + pub gas: u64, + #[codec(compact)] + pub gas_cost: u64, + pub weight_cost: Weight, + pub depth: u16, + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub return_data: Bytes, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(flatten)] + pub kind: ExecutionStepKindV1, +} + +#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(untagged)] +pub enum ExecutionStepKindV1 { + EVMOpcode { + #[codec(compact)] + pc: u32, + op: EvmOpcodeV1, + #[serde( + default, + serialize_with = "serialize_stack_minimal", + deserialize_with = "deserialize_stack_minimal" + )] + stack: Vec, + #[serde( + default, + skip_serializing_if = "Vec::is_empty", + serialize_with = "serialize_memory_no_prefix" + )] + memory: Vec, + #[serde( + default, + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_storage_no_prefix" + )] + storage: Option>, + }, + PVMSyscall { + op: PolkavmSyscallV1, + #[serde(default, skip_serializing_if = "Vec::is_empty", with = "hex_serde::vec")] + args: Vec, + #[serde(default, skip_serializing_if = "Option::is_none", with = "hex_serde::option")] + returned: Option, + }, +} + +impl Default for ExecutionStepKindV1 { + fn default() -> Self { + Self::EVMOpcode { + pc: 0, + op: EvmOpcodeV1(0), + stack: Vec::new(), + memory: Vec::new(), + storage: None, + } + } +} + +#[derive(TypeInfo, Encode, Decode, Clone, Debug, Eq, PartialEq)] +pub struct EvmOpcodeV1(pub u8); + +impl Serialize for EvmOpcodeV1 { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + OpCode::new(self.0) + .map(|opcode| opcode.info().name()) + .unwrap_or("INVALID") + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for EvmOpcodeV1 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let opcode = <&str>::deserialize(deserializer)?; + OpCode::parse(opcode) + .ok_or_else(|| serde::de::Error::custom(alloc::format!("Invalid EVM Opcode: {opcode}"))) + .map(|opcode| Self(opcode.get())) + } +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Encode, + Decode, + TypeInfo, + Serialize, + num_enum::IntoPrimitive, + num_enum::TryFromPrimitive, + strum::Display, + strum::EnumString, + strum::IntoStaticStr, + strum::VariantArray, +)] +#[serde(into = "&'static str")] +#[strum(serialize_all = "snake_case")] +#[repr(u8)] +pub enum PolkavmSyscallV1 { + Noop, + SetStorage, + SetStorageOrClear, + GetStorage, + GetStorageOrZero, + Call, + CallEvm, + DelegateCall, + DelegateCallEvm, + Instantiate, + CallDataSize, + CallDataCopy, + CallDataLoad, + SealReturn, + Caller, + Origin, + CodeHash, + CodeSize, + Address, + GetImmutableData, + SetImmutableData, + Balance, + BalanceOf, + ChainId, + GasLimit, + ValueTransferred, + GasPrice, + BaseFee, + Now, + DepositEvent, + BlockNumber, + BlockHash, + BlockAuthor, + #[strum(serialize = "hash_keccak_256")] + HashKeccak256, + ReturnDataSize, + ReturnDataCopy, + RefTimeLeft, + ConsumeAllGas, + EcdsaToEthAddress, + Sr25519Verify, + Terminate, + PvmFuel, +} + +impl<'de> Deserialize<'de> for PolkavmSyscallV1 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + <&str>::deserialize(deserializer)? + .parse() + .map_err(|_| serde::de::Error::custom("invalid PolkaVM syscall")) + } +} diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 9717dad7ac210..9e094fb3dbc32 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -137,6 +137,7 @@ std = [ "pallet-recovery?/std", "pallet-referenda?/std", "pallet-remark?/std", + "pallet-revive-types?/std", "pallet-revive?/std", "pallet-root-offences?/std", "pallet-root-testing?/std", @@ -706,6 +707,7 @@ runtime-full = [ "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", + "pallet-revive-types", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", @@ -1715,6 +1717,11 @@ default-features = false optional = true path = "../substrate/frame/revive/proc-macro" +[dependencies.pallet-revive-types] +default-features = false +optional = true +path = "../substrate/frame/revive/types" + [dependencies.pallet-revive-uapi] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 45f32a9ec9b6e..68cd4079b1208 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -664,6 +664,10 @@ pub use pallet_revive; #[cfg(feature = "pallet-revive-proc-macro")] pub use pallet_revive_proc_macro; +/// Shared types used by pallet_revive and its companion crates. +#[cfg(feature = "pallet-revive-types")] +pub use pallet_revive_types; + /// Exposes all the host functions that a contract can import. #[cfg(feature = "pallet-revive-uapi")] pub use pallet_revive_uapi;