diff --git a/.github/workflows/command-inform.yml b/.github/workflows/command-inform.yml index 97346395319362b0455bcbcfbb490fa23e6b3b07..3431eadf70608d2a7465f57718689f4acfe96952 100644 --- a/.github/workflows/command-inform.yml +++ b/.github/workflows/command-inform.yml @@ -8,7 +8,7 @@ jobs: comment: runs-on: ubuntu-latest # Temporary disable the bot until the new command bot works properly - if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') && false # disabled for now, until tested + if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') steps: - name: Inform that the new command exist uses: actions/github-script@v7 @@ -18,5 +18,5 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'We have migrated the command bot to GHA<br/><br/>Please, see the new usage instructions <a href="https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/commands-readme.md">here</a>. Soon the old commands will be disabled.' - }) \ No newline at end of file + body: 'We have migrated the command bot to GHA<br/><br/>Please, see the new usage instructions <a href="https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/commands-readme.md">here</a> or <a href="https://forum.parity.io/t/streamlining-weight-generation-and-more-the-new-cmd-bot/2411">here</a>. Soon the old commands will be disabled.' + }) diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 3dd5b1114813dbb2e151319293ade7ce44f4aae9..27c6162a0fc20cddbde29324a9626f3f1e8b7973 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -29,7 +29,7 @@ jobs: with: artifact-name: pr_number - name: "Evaluates PR reviews and assigns reviewers" - uses: paritytech/review-bot@v2.6.0 + uses: paritytech/review-bot@v2.7.0 with: repo-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }} diff --git a/Cargo.lock b/Cargo.lock index 42ed88fb0d06de36c1ceec61e00e4df20a5065a7..c9a139f30744617460685f294a5c2ea203801ef5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4640,6 +4640,8 @@ dependencies = [ "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", + "cumulus-test-client", + "cumulus-test-relay-sproof-builder 0.7.0", "futures", "parity-scale-codec", "parking_lot 0.12.3", @@ -4664,6 +4666,7 @@ dependencies = [ "sp-consensus-aura 0.32.0", "sp-core 28.0.0", "sp-inherents 26.0.0", + "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", @@ -14839,6 +14842,7 @@ dependencies = [ "assert_matches", "derive_more 0.99.17", "environmental", + "ethabi-decode 2.0.0", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", "frame-support 28.0.0", @@ -14857,7 +14861,7 @@ dependencies = [ "pallet-utility 28.0.0", "parity-scale-codec", "paste", - "polkavm 0.18.0", + "polkavm 0.19.0", "pretty_assertions", "rlp 0.6.1", "scale-info", @@ -14946,7 +14950,7 @@ name = "pallet-revive-fixtures" version = "0.1.0" dependencies = [ "anyhow", - "polkavm-linker 0.18.0", + "polkavm-linker 0.19.0", "sp-core 28.0.0", "sp-io 30.0.0", "toml 0.8.19", @@ -15061,7 +15065,7 @@ dependencies = [ "pallet-revive-proc-macro 0.1.0", "parity-scale-codec", "paste", - "polkavm-derive 0.18.0", + "polkavm-derive 0.19.0", "scale-info", ] @@ -19933,6 +19937,19 @@ dependencies = [ "polkavm-linux-raw 0.18.0", ] +[[package]] +name = "polkavm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8379bb48ff026aa8ae0645ea45f27920bfd21c82b2e82ed914224bb233d59f83" +dependencies = [ + "libc", + "log", + "polkavm-assembler 0.19.0", + "polkavm-common 0.19.0", + "polkavm-linux-raw 0.19.0", +] + [[package]] name = "polkavm-assembler" version = "0.9.0" @@ -19960,6 +19977,15 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-assembler" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57513b596cf0bafb052dab48e9c168f473c35f7522e17f70cc9f96603012d9b7" +dependencies = [ + "log", +] + [[package]] name = "polkavm-common" version = "0.9.0" @@ -19989,6 +20015,16 @@ dependencies = [ "polkavm-assembler 0.18.0", ] +[[package]] +name = "polkavm-common" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a972bd305ba8cbf0de79951d6d49d2abfad47c277596be5a2c6a0924a163abbd" +dependencies = [ + "log", + "polkavm-assembler 0.19.0", +] + [[package]] name = "polkavm-derive" version = "0.9.1" @@ -20016,6 +20052,15 @@ dependencies = [ "polkavm-derive-impl-macro 0.18.0", ] +[[package]] +name = "polkavm-derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d866972a7532d82d05c26b4516563660dd6676d7ab9e64e681d8ef0e29255c" +dependencies = [ + "polkavm-derive-impl-macro 0.19.0", +] + [[package]] name = "polkavm-derive-impl" version = "0.9.0" @@ -20052,6 +20097,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cffca9d51b21153395a192b65698457687bc51daa41026629895542ccaa65c2" +dependencies = [ + "polkavm-common 0.19.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + [[package]] name = "polkavm-derive-impl-macro" version = "0.9.0" @@ -20082,6 +20139,16 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0dc0cf2e8f4d30874131eccfa36bdabd4a52cfb79c15f8630508abaf06a2a6" +dependencies = [ + "polkavm-derive-impl 0.19.0", + "syn 2.0.87", +] + [[package]] name = "polkavm-linker" version = "0.9.2" @@ -20128,6 +20195,22 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "polkavm-linker" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caec2308f1328b5a667da45322c04fad7ff97ad8b36817d18c7635ea4dd6c6f4" +dependencies = [ + "dirs", + "gimli 0.31.1", + "hashbrown 0.14.5", + "log", + "object 0.36.1", + "polkavm-common 0.19.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + [[package]] name = "polkavm-linux-raw" version = "0.9.0" @@ -20146,6 +20229,12 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23eff02c070c70f31878a3d915e88a914ecf3e153741e2fb572dde28cce20fde" +[[package]] +name = "polkavm-linux-raw" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136ae072ab6fa38e584a06d12b1b216cff19f54d5cd202a8f8c5ec2e92e7e4bb" + [[package]] name = "polling" version = "2.8.0" diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 7022309386455923c812a6e16c1bf2dd0823ab4e..8637133a5f5cb0de115dcb9c21a19240f4a910b8 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -59,6 +59,11 @@ polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +[dev-dependencies] +cumulus-test-client = { workspace = true } +cumulus-test-relay-sproof-builder = { workspace = true } +sp-keyring = { workspace = true } + [features] # Allows collator to use full PoV size for block building full-pov-size = [] diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 031fa963ba6ae825264d8d1f0264660125f68ea8..66c6086eaf9ee3b2410ad389ae67a603b9311a6d 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -179,12 +179,19 @@ where let authorities = runtime_api.authorities(parent_hash).ok()?; let author_pub = aura_internal::claim_slot::<P>(para_slot, &authorities, keystore).await?; - let Ok(Some(api_version)) = - runtime_api.api_version::<dyn AuraUnincludedSegmentApi<Block>>(parent_hash) - else { - return (parent_hash == included_block) - .then(|| SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp)); - }; + // This function is typically called when we want to build block N. At that point, the + // unincluded segment in the runtime is unaware of the hash of block N-1. If the unincluded + // segment in the runtime is full, but block N-1 is the included block, the unincluded segment + // should have length 0 and we can build. Since the hash is not available to the runtime + // however, we need this extra check here. + if parent_hash == included_block { + return Some(SlotClaim::unchecked::<P>(author_pub, para_slot, timestamp)); + } + + let api_version = runtime_api + .api_version::<dyn AuraUnincludedSegmentApi<Block>>(parent_hash) + .ok() + .flatten()?; let slot = if api_version > 1 { relay_slot } else { para_slot }; @@ -243,3 +250,116 @@ where .max_by_key(|a| a.depth) .map(|parent| (included_block, parent)) } + +#[cfg(test)] +mod tests { + use crate::collators::can_build_upon; + use codec::Encode; + use cumulus_primitives_aura::Slot; + use cumulus_primitives_core::BlockT; + use cumulus_relay_chain_interface::PHash; + use cumulus_test_client::{ + runtime::{Block, Hash}, + Client, DefaultTestClientBuilderExt, InitBlockBuilder, TestClientBuilder, + TestClientBuilderExt, + }; + use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; + use polkadot_primitives::HeadData; + use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; + use sp_consensus::BlockOrigin; + use sp_keystore::{Keystore, KeystorePtr}; + use sp_timestamp::Timestamp; + use std::sync::Arc; + + async fn import_block<I: BlockImport<Block>>( + importer: &I, + block: Block, + origin: BlockOrigin, + import_as_best: bool, + ) { + let (header, body) = block.deconstruct(); + + let mut block_import_params = BlockImportParams::new(origin, header); + block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(import_as_best)); + block_import_params.body = Some(body); + importer.import_block(block_import_params).await.unwrap(); + } + + fn sproof_with_parent_by_hash(client: &Client, hash: PHash) -> RelayStateSproofBuilder { + let header = client.header(hash).ok().flatten().expect("No header for parent block"); + let included = HeadData(header.encode()); + let mut builder = RelayStateSproofBuilder::default(); + builder.para_id = cumulus_test_client::runtime::PARACHAIN_ID.into(); + builder.included_para_head = Some(included); + + builder + } + async fn build_and_import_block(client: &Client, included: Hash) -> Block { + let sproof = sproof_with_parent_by_hash(client, included); + + let block_builder = client.init_block_builder(None, sproof).block_builder; + + let block = block_builder.build().unwrap().block; + + let origin = BlockOrigin::NetworkInitialSync; + import_block(client, block.clone(), origin, true).await; + block + } + + fn set_up_components() -> (Arc<Client>, KeystorePtr) { + let keystore = Arc::new(sp_keystore::testing::MemoryKeystore::new()) as Arc<_>; + for key in sp_keyring::Sr25519Keyring::iter() { + Keystore::sr25519_generate_new( + &*keystore, + sp_application_crypto::key_types::AURA, + Some(&key.to_seed()), + ) + .expect("Can insert key into MemoryKeyStore"); + } + (Arc::new(TestClientBuilder::new().build()), keystore) + } + + /// This tests a special scenario where the unincluded segment in the runtime + /// is full. We are calling `can_build_upon`, passing the last built block as the + /// included one. In the runtime we will not find the hash of the included block in the + /// unincluded segment. The `can_build_upon` runtime API would therefore return `false`, but + /// we are ensuring on the node side that we are are always able to build on the included block. + #[tokio::test] + async fn test_can_build_upon() { + let (client, keystore) = set_up_components(); + + let genesis_hash = client.chain_info().genesis_hash; + let mut last_hash = genesis_hash; + + // Fill up the unincluded segment tracker in the runtime. + while can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>( + Slot::from(u64::MAX), + Slot::from(u64::MAX), + Timestamp::default(), + last_hash, + genesis_hash, + &*client, + &keystore, + ) + .await + .is_some() + { + let block = build_and_import_block(&client, genesis_hash).await; + last_hash = block.header().hash(); + } + + // Blocks were built with the genesis hash set as included block. + // We call `can_build_upon` with the last built block as the included block. + let result = can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>( + Slot::from(u64::MAX), + Slot::from(u64::MAX), + Timestamp::default(), + last_hash, + last_hash, + &*client, + &keystore, + ) + .await; + assert!(result.is_some()); + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 3ef5e87f24c47fa8375ced87be441ee025d8b3aa..41f29fe2c56a098dd64adfaa39f2c28bfb968e01 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1077,7 +1077,6 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned<Self::AccountId>; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = pallet_xcm::Pallet<Self>; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. diff --git a/docs/sdk/packages/guides/first-pallet/Cargo.toml b/docs/sdk/packages/guides/first-pallet/Cargo.toml index a1411580119da456e389715c0f47bc2df8691c8f..e6325c31781a6571055983607879f0c920a13f0f 100644 --- a/docs/sdk/packages/guides/first-pallet/Cargo.toml +++ b/docs/sdk/packages/guides/first-pallet/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } docify = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { workspace = true } [features] diff --git a/docs/sdk/packages/guides/first-runtime/Cargo.toml b/docs/sdk/packages/guides/first-runtime/Cargo.toml index 303d5c5e7f5fc8a11c52d48d0105f4872e77bceb..8ed17dea1b71ec90295a2a75d6fa064e289ed6ca 100644 --- a/docs/sdk/packages/guides/first-runtime/Cargo.toml +++ b/docs/sdk/packages/guides/first-runtime/Cargo.toml @@ -18,7 +18,7 @@ scale-info = { workspace = true } serde_json = { workspace = true } # this is a frame-based runtime, thus importing `frame` with runtime feature enabled. -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } # pallets that we want to use pallet-balances = { workspace = true } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 8a5771fe7cc08f7750951304ef63325f458cf20b..a9ba0778fe0eff7b4801df2b045f038752390113 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1087,6 +1087,7 @@ pub enum ProxyType { CancelProxy, Auction, NominationPools, + ParaRegistration, } impl Default for ProxyType { fn default() -> Self { @@ -1183,6 +1184,15 @@ impl InstanceFilter<RuntimeCall> for ProxyType { RuntimeCall::Registrar(..) | RuntimeCall::Slots(..) ), + ProxyType::ParaRegistration => matches!( + c, + RuntimeCall::Registrar(paras_registrar::Call::reserve { .. }) | + RuntimeCall::Registrar(paras_registrar::Call::register { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch_all { .. }) | + RuntimeCall::Utility(pallet_utility::Call::force_batch { .. }) | + RuntimeCall::Proxy(pallet_proxy::Call::remove_proxy { .. }) + ), } } fn is_superset(&self, o: &Self) -> bool { diff --git a/polkadot/xcm/docs/Cargo.toml b/polkadot/xcm/docs/Cargo.toml index 6fa7ea9a23a92c2e3f86082a630ca3994bfce1d1..c3bda50619c1572202db2a3334bc14d4d11c6b4b 100644 --- a/polkadot/xcm/docs/Cargo.toml +++ b/polkadot/xcm/docs/Cargo.toml @@ -18,7 +18,7 @@ xcm-simulator = { workspace = true, default-features = true } # For building FRAME runtimes codec = { workspace = true, default-features = true } -frame = { features = ["experimental", "runtime"], workspace = true, default-features = true } +frame = { features = ["runtime"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } diff --git a/prdoc/pr_6995.prdoc b/prdoc/pr_6995.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ffdb4738a6fd5b1841d07ddd8e5e462c42abd704 --- /dev/null +++ b/prdoc/pr_6995.prdoc @@ -0,0 +1,14 @@ +title: added new proxy ParaRegistration to Westend +doc: +- audience: Runtime User + description: |- + This adds a new Proxy type to Westend Runtime called ParaRegistration. This is related to: https://github.com/polkadot-fellows/runtimes/pull/520. + + This new proxy allows: + 1. Reserve paraID + 2. Register Parachain + 3. Leverage Utilites pallet + 4. Remove proxy. +crates: +- name: westend-runtime + bump: major diff --git a/prdoc/pr_7177.prdoc b/prdoc/pr_7177.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9ab0be1f20a936efe9feadf81c79a5f74f3531f6 --- /dev/null +++ b/prdoc/pr_7177.prdoc @@ -0,0 +1,20 @@ +title: Make frame crate not experimental +doc: +- audience: Runtime Dev + description: |- + Frame crate may still be unstable, but it is no longer feature gated by the feature `experimental`. +crates: +- name: polkadot-sdk-frame + bump: minor +- name: pallet-salary + bump: patch +- name: pallet-multisig + bump: patch +- name: pallet-proxy + bump: patch +- name: pallet-atomic-swap + bump: patch +- name: pallet-mixnet + bump: patch +- name: pallet-node-authorization + bump: patch diff --git a/prdoc/pr_7203.prdoc b/prdoc/pr_7203.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..96a3d19472e9f77189eaaf0f9ad3c829aa27e8a4 --- /dev/null +++ b/prdoc/pr_7203.prdoc @@ -0,0 +1,13 @@ +title: 'pallet_revive: Bump PolkaVM' +doc: +- audience: Runtime Dev + description: Update to PolkaVM `0.19`. This version renumbers the opcodes in order + to be in-line with the grey paper. Hopefully, for the last time. This means that + it breaks existing contracts. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: patch diff --git a/prdoc/pr_7205.prdoc b/prdoc/pr_7205.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..758beb0b6313c764f161c02ba927868a233a419a --- /dev/null +++ b/prdoc/pr_7205.prdoc @@ -0,0 +1,10 @@ +title: 'Collator: Fix `can_build_upon` by always allowing to build on included block' +doc: +- audience: Node Dev + description: |- + Fixes a bug introduced in #6825. + We should always allow building on the included block of parachains. In situations where the unincluded segment + is full, but the included block moved to the most recent block, building was wrongly disallowed. +crates: +- name: cumulus-client-consensus-aura + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 117d306e3060eab232744aa7b4103ad819c6e40d..26f4dacf9a1e3039d3cd8e8d0f79415e4727be07 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1491,7 +1491,6 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned<Self::AccountId>; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml index 785bfee71b683653fba9f3f9c8be1e531927e01f..05a38ded91c516ac995d90cc54d92d688619d2f4 100644 --- a/substrate/frame/atomic-swap/Cargo.toml +++ b/substrate/frame/atomic-swap/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } [dev-dependencies] diff --git a/substrate/frame/examples/frame-crate/Cargo.toml b/substrate/frame/examples/frame-crate/Cargo.toml index f174c6b9054b52d1f88d5d9853ffecd2df6f8014..46db1afc34643cef8b90a639e39aa2070a16919e 100644 --- a/substrate/frame/examples/frame-crate/Cargo.toml +++ b/substrate/frame/examples/frame-crate/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } [features] diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index 0ae3b3938c608b6ad10a0b6a06f256b463c50564..33bf7146980d5e32a46093483494b73279f012e4 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["derive"], workspace = true } diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml index 0d175617c9c23dedd5f2e72e806f187dc1e5e119..e18e14f2626bfcae1ed509005e1108913e42568e 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } # third party diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index 7e55ad178091ffc42d858c80d4729a6f6d86d362..86a78e6e361535ef4556527e6a84890e50cd3a4f 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } diff --git a/substrate/frame/proxy/Cargo.toml b/substrate/frame/proxy/Cargo.toml index a36b2c1cb9c3af5dc545f35d4788d8a043f1a77e..3f2565abac88d2653781046d111c1f50fb757393 100644 --- a/substrate/frame/proxy/Cargo.toml +++ b/substrate/frame/proxy/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["max-encoded-len"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } [dev-dependencies] diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 1284f5ee8947b6ff8d0d6990ce45ba8710ab2711..0959cc50638ba51a671c3ad8311d7e5745ef086f 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -20,12 +20,13 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive", "max-encoded-len"], workspace = true } derive_more = { workspace = true } environmental = { workspace = true } +ethabi = { workspace = true } ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } hex = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.18.0", default-features = false } +polkavm = { version = "0.19.0", default-features = false } rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = [ @@ -75,6 +76,7 @@ default = ["std"] std = [ "codec/std", "environmental/std", + "ethabi/std", "ethereum-types/std", "frame-benchmarking?/std", "frame-support/std", diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index e17bc88a3847a660500671d85c234dfb6d5e0b80..a6f25cc26f3c040ed9db6b56b9b402d8319ee3b1 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -21,7 +21,7 @@ sp-io = { workspace = true, default-features = true, optional = true } [build-dependencies] anyhow = { workspace = true, default-features = true } -polkavm-linker = { version = "0.18.0" } +polkavm-linker = { version = "0.19.0" } toml = { workspace = true } [features] diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index bfb9aaedd6f5cfa2535aa023f4ca68ba5f835cee..1a0a635420ad5acf02c55302f26c904cceb0f1b4 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -14,7 +14,8 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.18.0" } +hex-literal = { version = "0.4.1", default-features = false } +polkavm-derive = { version = "0.19.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/fixtures/contracts/tracing.rs b/substrate/frame/revive/fixtures/contracts/tracing.rs new file mode 100644 index 0000000000000000000000000000000000000000..9cbef3bbc84355dc89968347248d4daed68aedad --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/tracing.rs @@ -0,0 +1,75 @@ +// 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. + +//! This fixture calls itself as many times as passed as argument. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(calls_left: u32, callee_addr: &[u8; 20],); + if calls_left == 0 { + return + } + + let next_input = (calls_left - 1).to_le_bytes(); + api::deposit_event(&[], b"before"); + + // Call the callee, ignore revert. + let _ = api::call( + uapi::CallFlags::empty(), + callee_addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &next_input, + None, + ); + + api::deposit_event(&[], b"after"); + + // own address + let mut addr = [0u8; 20]; + api::address(&mut addr); + let mut input = [0u8; 24]; + + input[..4].copy_from_slice(&next_input); + input[4..24].copy_from_slice(&callee_addr[..20]); + + // recurse + api::call( + uapi::CallFlags::ALLOW_REENTRY, + &addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/tracing_callee.rs b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs new file mode 100644 index 0000000000000000000000000000000000000000..d44771e417f9df5fd8c13277a2b4f5c0d7afe8f3 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs @@ -0,0 +1,45 @@ +// 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. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(id: u32, ); + + match id { + // Revert with message "This function always fails" + 2 => { + let data = hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ); + api::return_value(uapi::ReturnFlags::REVERT, &data) + }, + 1 => { + panic!("booum"); + }, + _ => api::return_value(uapi::ReturnFlags::empty(), &id.to_le_bytes()), + }; +} diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm index 77de4ff3b1b3fe1f378ae31bbba24ddb38cc6300..48de6e0aa0c6cc1604008ba4e65c237dd557675b 100644 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm index 6dbc5ca8b108c1ad04cc248b735b2d7d4f43f2a4..cea22e46adcad0dc9bf6375e676dd4923c624c89 100644 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm index 488ee684f0c4aee5d64f8b691d048ebefdd51044..67f11e68f117309169a317d415ba547020c1f568 100644 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index 38a1098fe3a767aa0af74764bf7247e59f6110b7..29efafd8722db556b949b04c47c88eeec07535a6 100644 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm differ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index d0082db90e5e398832e4a32a9ec86dce83d16dd5..78455fcdd7c64a3a1f5e93b6d62cd03b46eb5953 100644 Binary files a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm and b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm differ diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index cd0effe7faf2f16084e665edd77eaa8449c8973b..c61c5871f76aeda6ae808d5215b5243f112ebba5 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -27,8 +27,9 @@ use crate::{ use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pallet_revive::{ evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptInfo, - SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, + extract_revert_message, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, + GenericTransaction, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, + H256, U256, }, EthTransactError, EthTransactInfo, }; @@ -83,47 +84,6 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option<ErrorObjectOwned> { } } -/// Extract the revert message from a revert("msg") solidity statement. -fn extract_revert_message(exec_data: &[u8]) -> Option<String> { - let error_selector = exec_data.get(0..4)?; - - match error_selector { - // assert(false) - [0x4E, 0x48, 0x7B, 0x71] => { - let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; - - // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require - let msg = match panic_code { - 0x00 => "generic panic", - 0x01 => "assert(false)", - 0x11 => "arithmetic underflow or overflow", - 0x12 => "division or modulo by zero", - 0x21 => "enum overflow", - 0x22 => "invalid encoded storage byte array accessed", - 0x31 => "out-of-bounds array access; popping on an empty array", - 0x32 => "out-of-bounds access of an array or bytesN", - 0x41 => "out of memory", - 0x51 => "uninitialized function", - code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), - }; - - Some(format!("execution reverted: {msg}")) - }, - // revert(string) - [0x08, 0xC3, 0x79, 0xA0] => { - let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; - if let Some(ethabi::Token::String(msg)) = decoded.first() { - return Some(format!("execution reverted: {msg}")) - } - Some("execution reverted".to_string()) - }, - _ => { - log::debug!(target: LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); - Some("execution reverted".to_string()) - }, - } -} - /// The error type for the client. #[derive(Error, Debug)] pub enum ClientError { diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 18d7bb0afc31ab56c71115078ec84b1f34a0a8ef..16bdd6d1a18a034922b5eff47c190d76776f4d23 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -772,7 +772,7 @@ mod benchmarks { let mut setup = CallSetup::<T>::default(); let input = setup.data(); let (mut ext, _) = setup.ext(); - ext.override_export(crate::debug::ExportedFunction::Constructor); + ext.override_export(crate::exec::ExportedFunction::Constructor); let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input); diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs deleted file mode 100644 index d1fc0823e03dff6719018ee60f0c0668aca37d7f..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/src/debug.rs +++ /dev/null @@ -1,109 +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. - -pub use crate::{ - exec::{ExecResult, ExportedFunction}, - primitives::ExecReturnValue, -}; -use crate::{Config, LOG_TARGET}; -use sp_core::H160; - -/// Umbrella trait for all interfaces that serves for debugging. -pub trait Debugger<T: Config>: Tracing<T> + CallInterceptor<T> {} - -impl<T: Config, V> Debugger<T> for V where V: Tracing<T> + CallInterceptor<T> {} - -/// Defines methods to capture contract calls, enabling external observers to -/// measure, trace, and react to contract interactions. -pub trait Tracing<T: Config> { - /// The type of [`CallSpan`] that is created by this trait. - type CallSpan: CallSpan; - - /// Creates a new call span to encompass the upcoming contract execution. - /// - /// This method should be invoked just before the execution of a contract and - /// marks the beginning of a traceable span of execution. - /// - /// # Arguments - /// - /// * `contract_address` - The address of the contract that is about to be executed. - /// * `entry_point` - Describes whether the call is the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - fn new_call_span( - contract_address: &H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> Self::CallSpan; -} - -/// Defines a span of execution for a contract call. -pub trait CallSpan { - /// Called just after the execution of a contract. - /// - /// # Arguments - /// - /// * `output` - The raw output of the call. - fn after_call(self, output: &ExecReturnValue); -} - -impl<T: Config> Tracing<T> for () { - type CallSpan = (); - - fn new_call_span(contract_address: &H160, entry_point: ExportedFunction, input_data: &[u8]) { - log::trace!(target: LOG_TARGET, "call {entry_point:?} address: {contract_address:?}, input_data: {input_data:?}") - } -} - -impl CallSpan for () { - fn after_call(self, output: &ExecReturnValue) { - log::trace!(target: LOG_TARGET, "call result {output:?}") - } -} - -/// Provides an interface for intercepting contract calls. -pub trait CallInterceptor<T: Config> { - /// Allows to intercept contract calls and decide whether they should be executed or not. - /// If the call is intercepted, the mocked result of the call is returned. - /// - /// # Arguments - /// - /// * `contract_address` - The address of the contract that is about to be executed. - /// * `entry_point` - Describes whether the call is the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - /// - /// # Expected behavior - /// - /// This method should return: - /// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call - /// is returned. - /// * `None` - otherwise, i.e. the call should be executed normally. - fn intercept_call( - contract_address: &H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> Option<ExecResult>; -} - -impl<T: Config> CallInterceptor<T> for () { - fn intercept_call( - _contract_address: &H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option<ExecResult> { - None - } -} diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index c8c967fbe091bb2af5bfaacaefbe854d4f9d0043..33660a36aa6ea57f713ccf57a62f8ba013ac4ef9 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,6 +19,51 @@ mod api; pub use api::*; +mod tracing; +pub use tracing::*; mod gas_encoder; pub use gas_encoder::*; pub mod runtime; + +use crate::alloc::{format, string::*}; + +/// Extract the revert message from a revert("msg") solidity statement. +pub fn extract_revert_message(exec_data: &[u8]) -> Option<String> { + let error_selector = exec_data.get(0..4)?; + + match error_selector { + // assert(false) + [0x4E, 0x48, 0x7B, 0x71] => { + let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; + + // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require + let msg = match panic_code { + 0x00 => "generic panic", + 0x01 => "assert(false)", + 0x11 => "arithmetic underflow or overflow", + 0x12 => "division or modulo by zero", + 0x21 => "enum overflow", + 0x22 => "invalid encoded storage byte array accessed", + 0x31 => "out-of-bounds array access; popping on an empty array", + 0x32 => "out-of-bounds access of an array or bytesN", + 0x41 => "out of memory", + 0x51 => "uninitialized function", + code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), + }; + + Some(format!("execution reverted: {msg}")) + }, + // revert(string) + [0x08, 0xC3, 0x79, 0xA0] => { + let decoded = ethabi::decode(&[ethabi::ParamKind::String], &exec_data[4..]).ok()?; + if let Some(ethabi::Token::String(msg)) = decoded.first() { + return Some(format!("execution reverted: {}", String::from_utf8_lossy(msg))) + } + Some("execution reverted".to_string()) + }, + _ => { + log::debug!(target: crate::LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); + Some("execution reverted".to_string()) + }, + } +} diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index fe18c8735bed4d8dc435f8517d86f67aa9c70393..7a34fdc83f9a5140cc459f4be0da188bd81b5b7b 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -16,6 +16,8 @@ // limitations under the License. //! JSON-RPC methods and types, for Ethereum. +mod hex_serde; + mod byte; pub use byte::*; @@ -25,6 +27,9 @@ pub use rlp; mod type_id; pub use type_id::*; +mod debug_rpc_types; +pub use debug_rpc_types::*; + mod rpc_types; mod rpc_types_gen; pub use rpc_types_gen::*; diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index c2d64f8e5e424b2086e0247e5ccea0e08d4350b0..f11966d0072cf64d674bd2925862720192c43924 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -15,79 +15,16 @@ // 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 alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use core::{ fmt::{Debug, Display, Formatter, Result as FmtResult}, str::FromStr, }; -use hex_serde::HexCodec; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -mod hex_serde { - #[cfg(not(feature = "std"))] - use alloc::{format, string::String, vec::Vec}; - use serde::{Deserialize, Deserializer, Serializer}; - - pub trait HexCodec: Sized { - type Error; - fn to_hex(&self) -> String; - fn from_hex(s: String) -> Result<Self, Self::Error>; - } - - impl HexCodec for u8 { - type Error = core::num::ParseIntError; - fn to_hex(&self) -> String { - format!("0x{:x}", self) - } - fn from_hex(s: String) -> Result<Self, Self::Error> { - u8::from_str_radix(s.trim_start_matches("0x"), 16) - } - } - - impl<const T: usize> HexCodec for [u8; T] { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result<Self, Self::Error> { - let data = hex::decode(s.trim_start_matches("0x"))?; - data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) - } - } - - impl HexCodec for Vec<u8> { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result<Self, Self::Error> { - hex::decode(s.trim_start_matches("0x")) - } - } - - pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - T: HexCodec, - { - let s = value.to_hex(); - serializer.serialize_str(&s) - } - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error> - where - D: Deserializer<'de>, - T: HexCodec, - <T as 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) - } -} - impl FromStr for Bytes { type Err = hex::FromHexError; fn from_str(s: &str) -> Result<Self, Self::Err> { @@ -100,7 +37,7 @@ macro_rules! impl_hex { ($type:ident, $inner:ty, $default:expr) => { #[derive(Encode, Decode, Eq, PartialEq, TypeInfo, Clone, Serialize, Deserialize)] #[doc = concat!("`", stringify!($inner), "`", " wrapper type for encoding and decoding hex strings")] - pub struct $type(#[serde(with = "hex_serde")] pub $inner); + pub struct $type(#[serde(with = "crate::evm::api::hex_serde")] pub $inner); impl Default for $type { fn default() -> Self { @@ -131,6 +68,13 @@ macro_rules! impl_hex { }; } +impl Bytes { + /// See `Vec::is_empty` + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + impl_hex!(Byte, u8, 0u8); impl_hex!(Bytes, Vec<u8>, vec![]); impl_hex!(Bytes8, [u8; 8], [0u8; 8]); diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs new file mode 100644 index 0000000000000000000000000000000000000000..0857a59fbf3b650707069e1a0a8387acad49fe99 --- /dev/null +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -0,0 +1,219 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::evm::{Bytes, CallTracer}; +use alloc::{fmt, string::String, vec::Vec}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{ + de::{self, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use sp_core::{H160, H256, U256}; + +/// Tracer configuration used to trace calls. +#[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, PartialEq)] +#[serde(tag = "tracer", content = "tracerConfig")] +pub enum TracerConfig { + /// A tracer that captures call traces. + #[serde(rename = "callTracer")] + CallTracer { + /// Whether or not to capture logs. + #[serde(rename = "withLog")] + with_logs: bool, + }, +} + +impl TracerConfig { + /// Build the tracer associated to this config. + pub fn build<G>(self, gas_mapper: G) -> CallTracer<U256, G> { + match self { + Self::CallTracer { with_logs } => CallTracer::new(with_logs, gas_mapper), + } + } +} + +/// Custom deserializer to support the following JSON format: +/// +/// ```json +/// { "tracer": "callTracer", "tracerConfig": { "withLogs": false } } +/// ``` +/// +/// ```json +/// { "tracer": "callTracer" } +/// ``` +impl<'de> Deserialize<'de> for TracerConfig { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct TracerConfigVisitor; + + impl<'de> Visitor<'de> for TracerConfigVisitor { + type Value = TracerConfig; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map with tracer and optional tracerConfig") + } + + fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> + where + M: MapAccess<'de>, + { + let mut tracer_type: Option<String> = None; + let mut with_logs = None; + + while let Some(key) = map.next_key::<String>()? { + match key.as_str() { + "tracer" => { + tracer_type = map.next_value()?; + }, + "tracerConfig" => { + #[derive(Deserialize)] + struct CallTracerConfig { + #[serde(rename = "withLogs")] + with_logs: Option<bool>, + } + let inner: CallTracerConfig = map.next_value()?; + with_logs = inner.with_logs; + }, + _ => {}, + } + } + + match tracer_type.as_deref() { + Some("callTracer") => + Ok(TracerConfig::CallTracer { with_logs: with_logs.unwrap_or(true) }), + _ => Err(de::Error::custom("Unsupported or missing tracer type")), + } + } + } + + deserializer.deserialize_map(TracerConfigVisitor) + } +} + +#[test] +fn test_tracer_config_serialization() { + let tracers = vec![ + (r#"{"tracer": "callTracer"}"#, TracerConfig::CallTracer { with_logs: true }), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": true }}"#, + TracerConfig::CallTracer { with_logs: true }, + ), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": false }}"#, + TracerConfig::CallTracer { with_logs: false }, + ), + ]; + + for (json_data, expected) in tracers { + let result: TracerConfig = + serde_json::from_str(json_data).expect("Deserialization should succeed"); + assert_eq!(result, expected); + } +} + +impl Default for TracerConfig { + fn default() -> Self { + TracerConfig::CallTracer { with_logs: false } + } +} + +/// The type of call that was executed. +#[derive( + Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Clone, Debug, +)] +#[serde(rename_all = "UPPERCASE")] +pub enum CallType { + /// A regular call. + #[default] + Call, + /// A read-only call. + StaticCall, + /// A delegate call. + DelegateCall, +} + +/// A smart contract execution call trace. +#[derive( + TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +pub struct CallTrace<Gas = U256> { + /// Address of the sender. + pub from: H160, + /// Address of the receiver. + pub to: H160, + /// Call input data. + pub input: Vec<u8>, + /// Amount of value transferred. + #[serde(skip_serializing_if = "U256::is_zero")] + pub value: U256, + /// Type of call. + #[serde(rename = "type")] + pub call_type: CallType, + /// Amount of gas provided for the call. + pub gas: Gas, + /// Amount of gas used. + #[serde(rename = "gasUsed")] + pub gas_used: Gas, + /// Return data. + #[serde(flatten, 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<String>, + /// The revert reason, if the call reverted. + #[serde(rename = "revertReason")] + pub revert_reason: Option<String>, + /// List of sub-calls. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub calls: Vec<CallTrace<Gas>>, + /// List of logs emitted during the call. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub logs: Vec<CallLog>, +} + +/// A log emitted during a call. +#[derive( + Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq, +)] +pub struct CallLog { + /// The address of the contract that emitted the log. + pub address: H160, + /// The log's data. + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub data: Bytes, + /// The topics used to index the log. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub topics: Vec<H256>, + /// Position of the log relative to subcalls within the same trace + /// See <https://github.com/ethereum/go-ethereum/pull/28389> for details + #[serde(with = "super::hex_serde")] + pub position: u32, +} + +/// A transaction trace +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TransactionTrace { + /// The transaction hash. + #[serde(rename = "txHash")] + pub tx_hash: H256, + /// The trace of the transaction. + #[serde(rename = "result")] + pub trace: CallTrace, +} diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs new file mode 100644 index 0000000000000000000000000000000000000000..ba07b36fa4be6e5cba82bba719c3893cd32aafc5 --- /dev/null +++ b/substrate/frame/revive/src/evm/api/hex_serde.rs @@ -0,0 +1,84 @@ +// 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 serde::{Deserialize, Deserializer, Serializer}; + +pub trait HexCodec: Sized { + type Error; + fn to_hex(&self) -> String; + fn from_hex(s: String) -> Result<Self, Self::Error>; +} + +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<Self, Self::Error> { + <$t>::from_str_radix(s.trim_start_matches("0x"), 16) + } + } + )* + }; +} + +impl_hex_codec!(u8, u32); + +impl<const T: usize> HexCodec for [u8; T] { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result<Self, Self::Error> { + let data = hex::decode(s.trim_start_matches("0x"))?; + data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) + } +} + +impl HexCodec for Vec<u8> { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result<Self, Self::Error> { + hex::decode(s.trim_start_matches("0x")) + } +} + +pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error> +where + S: Serializer, + T: HexCodec, +{ + let s = value.to_hex(); + serializer.serialize_str(&s) +} + +pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error> +where + D: Deserializer<'de>, + T: HexCodec, + <T as 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) +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index d4b344e20eb850e6c42d84bdf870204c1382cb67..0e5fc3da545b5eddc4363676877f721473c8c42a 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,7 +20,7 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, GasEncoder, }, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode}; @@ -72,6 +72,18 @@ where } } +/// Convert a `Weight` into a gas value, using the fixed `GAS_PRICE`. +/// and the `Config::WeightPrice` to compute the fee. +/// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. +pub fn gas_from_weight<T: Config>(weight: Weight) -> U256 +where + BalanceOf<T>: Into<U256>, +{ + use sp_runtime::traits::Convert; + let fee: BalanceOf<T> = T::WeightPrice::convert(weight); + gas_from_fee(fee) +} + /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs new file mode 100644 index 0000000000000000000000000000000000000000..7466ec1de487738bcb662e92b9127852098e304d --- /dev/null +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -0,0 +1,134 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{ + evm::{extract_revert_message, CallLog, CallTrace, CallType}, + primitives::ExecReturnValue, + tracing::Tracer, + DispatchError, Weight, +}; +use alloc::{format, string::ToString, vec::Vec}; +use sp_core::{H160, H256, U256}; + +/// A Tracer that reports logs and nested call traces transactions. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct CallTracer<Gas, GasMapper> { + /// Map Weight to Gas equivalent. + gas_mapper: GasMapper, + /// Store all in-progress CallTrace instances. + traces: Vec<CallTrace<Gas>>, + /// Stack of indices to the current active traces. + current_stack: Vec<usize>, + /// whether or not to capture logs. + with_log: bool, +} + +impl<Gas, GasMapper> CallTracer<Gas, GasMapper> { + /// Create a new [`CallTracer`] instance. + pub fn new(with_log: bool, gas_mapper: GasMapper) -> Self { + Self { gas_mapper, traces: Vec::new(), current_stack: Vec::new(), with_log } + } + + /// Collect the traces and return them. + pub fn collect_traces(&mut self) -> Vec<CallTrace<Gas>> { + core::mem::take(&mut self.traces) + } +} + +impl<Gas: Default, GasMapper: Fn(Weight) -> Gas> Tracer for CallTracer<Gas, GasMapper> { + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas_left: Weight, + ) { + let call_type = if is_read_only { + CallType::StaticCall + } else if is_delegate_call { + CallType::DelegateCall + } else { + CallType::Call + }; + + self.traces.push(CallTrace { + from, + to, + value, + call_type, + input: input.to_vec(), + gas: (self.gas_mapper)(gas_left), + ..Default::default() + }); + + // Push the index onto the stack of the current active trace + self.current_stack.push(self.traces.len() - 1); + } + + fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) { + if !self.with_log { + return; + } + + let current_index = self.current_stack.last().unwrap(); + let position = self.traces[*current_index].calls.len() as u32; + let log = + CallLog { address, topics: topics.to_vec(), data: data.to_vec().into(), position }; + + let current_index = *self.current_stack.last().unwrap(); + self.traces[current_index].logs.push(log); + } + + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.output = output.data.clone().into(); + trace.gas_used = (self.gas_mapper)(gas_used); + + if output.did_revert() { + trace.revert_reason = extract_revert_message(&output.data); + trace.error = Some("execution reverted".to_string()); + } + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.gas_used = (self.gas_mapper)(gas_used); + + trace.error = match error { + DispatchError::Module(sp_runtime::ModuleError { message, .. }) => + Some(message.unwrap_or_default().to_string()), + _ => Some(format!("{:?}", error)), + }; + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index f696f75a4a138fe27255e9e334d309b3db3f72fe..d2ef6c9c7ba6ce2e79f2b1608702b1340ae8db1b 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -17,12 +17,12 @@ use crate::{ address::{self, AddressMapper}, - debug::{CallInterceptor, CallSpan, Tracing}, gas::GasMeter, limits, primitives::{ExecReturnValue, StorageDeposit}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, + tracing::if_tracing, transient_storage::TransientStorage, BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, @@ -773,7 +773,25 @@ where )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { - Self::transfer_from_origin(&origin, &origin, &dest, value) + if_tracing(|t| { + let address = + origin.account_id().map(T::AddressMapper::to_address).unwrap_or_default(); + let dest = T::AddressMapper::to_address(&dest); + t.enter_child_span(address, dest, false, false, value, &input_data, Weight::zero()); + }); + + let result = Self::transfer_from_origin(&origin, &origin, &dest, value); + match result { + Ok(ref output) => { + if_tracing(|t| { + t.exit_child_span(&output, Weight::zero()); + }); + }, + Err(e) => { + if_tracing(|t| t.exit_child_span_with_error(e.error.into(), Weight::zero())); + }, + } + result } } @@ -1018,6 +1036,7 @@ where fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; + let is_delegate_call = frame.delegate.is_some(); let delegated_code_hash = if frame.delegate.is_some() { Some(*executable.code_hash()) } else { None }; @@ -1038,6 +1057,9 @@ where let do_transaction = || -> ExecResult { let caller = self.caller(); let frame = top_frame_mut!(self); + let read_only = frame.read_only; + let value_transferred = frame.value_transferred; + let account_id = &frame.account_id.clone(); // We need to charge the storage deposit before the initial transfer so that // it can create the account in case the initial transfer is < ed. @@ -1045,10 +1067,11 @@ where // Root origin can't be used to instantiate a contract, so it is safe to assume that // if we reached this point the origin has an associated account. let origin = &self.origin.account_id()?; + frame.nested_storage.charge_instantiate( origin, - &frame.account_id, - frame.contract_info.get(&frame.account_id), + &account_id, + frame.contract_info.get(&account_id), executable.code_info(), self.skip_transfer, )?; @@ -1069,15 +1092,34 @@ where )?; } - let contract_address = T::AddressMapper::to_address(&top_frame!(self).account_id); - - let call_span = T::Debug::new_call_span(&contract_address, entry_point, &input_data); + let contract_address = T::AddressMapper::to_address(account_id); + let maybe_caller_address = caller.account_id().map(T::AddressMapper::to_address); + + if_tracing(|tracer| { + tracer.enter_child_span( + maybe_caller_address.unwrap_or_default(), + contract_address, + is_delegate_call, + read_only, + value_transferred, + &input_data, + frame.nested_gas.gas_left(), + ); + }); - let output = T::Debug::intercept_call(&contract_address, entry_point, &input_data) - .unwrap_or_else(|| executable.execute(self, entry_point, input_data)) - .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; + let output = executable.execute(self, entry_point, input_data).map_err(|e| { + if_tracing(|tracer| { + tracer.exit_child_span_with_error( + e.error, + top_frame_mut!(self).nested_gas.gas_consumed(), + ); + }); + ExecError { error: e.error, origin: ErrorOrigin::Callee } + })?; - call_span.after_call(&output); + if_tracing(|tracer| { + tracer.exit_child_span(&output, top_frame_mut!(self).nested_gas.gas_consumed()); + }); // Avoid useless work that would be reverted anyways. if output.did_revert() { @@ -1353,7 +1395,7 @@ where &mut self, gas_limit: Weight, deposit_limit: U256, - dest: &H160, + dest_addr: &H160, value: U256, input_data: Vec<u8>, allows_reentry: bool, @@ -1369,7 +1411,7 @@ where *self.last_frame_output_mut() = Default::default(); let try_call = || { - let dest = T::AddressMapper::to_account_id(dest); + let dest = T::AddressMapper::to_account_id(dest_addr); if !self.allows_reentry(&dest) { return Err(<Error<T>>::ReentranceDenied.into()); } @@ -1661,11 +1703,11 @@ where } fn deposit_event(&mut self, topics: Vec<H256>, data: Vec<u8>) { - Contracts::<Self::T>::deposit_event(Event::ContractEmitted { - contract: T::AddressMapper::to_address(self.account_id()), - data, - topics, + let contract = T::AddressMapper::to_address(self.account_id()); + if_tracing(|tracer| { + tracer.log_event(contract, &topics, &data); }); + Contracts::<Self::T>::deposit_event(Event::ContractEmitted { contract, data, topics }); } fn block_number(&self) -> U256 { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index a9f2842c35f6a2fc3fe151879f70f99993865a14..c36cb3f47caed988ddefbd20a1f1caef680f2c75 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -35,9 +35,9 @@ mod wasm; mod tests; pub mod chain_extension; -pub mod debug; pub mod evm; pub mod test_utils; +pub mod tracing; pub mod weights; use crate::{ @@ -83,7 +83,6 @@ use sp_runtime::{ pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, - debug::Tracing, exec::{MomentOf, Origin}, pallet::*, }; @@ -118,7 +117,6 @@ const LOG_TARGET: &str = "runtime::revive"; #[frame_support::pallet] pub mod pallet { use super::*; - use crate::debug::Debugger; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use sp_core::U256; @@ -255,12 +253,6 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>; - /// Debugging utilities for contracts. - /// For production chains, it's recommended to use the `()` implementation of this - /// trait. - #[pallet::no_default_bounds] - type Debug: Debugger<Self>; - /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and /// execute XCM programs. #[pallet::no_default_bounds] @@ -367,7 +359,6 @@ pub mod pallet { type InstantiateOrigin = EnsureSigned<AccountId>; type WeightInfo = (); type WeightPrice = Self; - type Debug = (); type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; @@ -1146,7 +1137,6 @@ where DepositLimit::Unchecked }; - // TODO remove once we have revisited how we encode the gas limit. if tx.nonce.is_none() { tx.nonce = Some(<System<T>>::account_nonce(&origin).into()); } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 8398bc2cb66ffbdcfbb8cf14207e4b61a5de8f26..90b9f053a03fbf05fd180be81c23da63955134ef 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -16,12 +16,8 @@ // limitations under the License. mod pallet_dummy; -mod test_debug; -use self::{ - test_debug::TestDebug, - test_utils::{ensure_stored, expected_deposit}, -}; +use self::test_utils::{ensure_stored, expected_deposit}; use crate::{ self as pallet_revive, address::{create1, create2, AddressMapper}, @@ -29,13 +25,14 @@ use crate::{ ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, }, - evm::{runtime::GAS_PRICE, GenericTransaction}, + evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, exec::Key, limits, primitives::CodeUploadReturnValue, storage::DeletionQueueManager, test_utils::*, tests::test_utils::{get_contract, get_contract_checked}, + tracing::trace, wasm::Memory, weights::WeightInfo, AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf, @@ -523,7 +520,6 @@ impl Config for Test { type UploadOrigin = EnsureAccount<Self, UploadAccount>; type InstantiateOrigin = EnsureAccount<Self, InstantiateAccount>; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = TestDebug; type ChainId = ChainId; } @@ -4554,3 +4550,151 @@ fn unstable_interface_rejected() { assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result); }); } + +#[test] +fn tracing_works_for_transfers() { + ExtBuilder::default().build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 100_000_000); + let mut tracer = CallTracer::new(false, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(BOB_ADDR).value(10_000_000).build_and_unwrap_result(); + }); + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: BOB_ADDR, + value: U256::from(10_000_000), + call_type: CallType::Call, + ..Default::default() + },] + ) + }); +} + +#[test] +fn tracing_works() { + use crate::evm::*; + use CallType::*; + let (code, _code_hash) = compile_module("tracing").unwrap(); + let (wasm_callee, _) = compile_module("tracing_callee").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let tracer_options = vec![ + ( false , vec![]), + ( + true , + vec![ + CallLog { + address: addr, + topics: Default::default(), + data: b"before".to_vec().into(), + position: 0, + }, + CallLog { + address: addr, + topics: Default::default(), + data: b"after".to_vec().into(), + position: 1, + }, + ], + ), + ]; + + // Verify that the first trace report the same weight reported by bare_call + let mut tracer = CallTracer::new(false, |w| w); + let gas_used = trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed + }); + let traces = tracer.collect_traces(); + assert_eq!(&traces[0].gas_used, &gas_used); + + // Discarding gas usage, check that traces reported are correct + for (with_logs, logs) in tracer_options { + let mut tracer = CallTracer::new(with_logs, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build() + }); + + + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: addr, + input: (3u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 2u32.encode(), + output: hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ).to_vec().into(), + revert_reason: Some( + "execution reverted: This function always fails".to_string() + ), + error: Some("execution reverted".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (2u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 1u32.encode(), + output: Default::default(), + error: Some("ContractTrapped".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (1u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 0u32.encode(), + output: 0u32.to_le_bytes().to_vec().into(), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (0u32, addr_callee).encode(), + call_type: Call, + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + },] + ); + } + }); +} diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs deleted file mode 100644 index b1fdb2d47441eff1335a4f9b800e04c9489bd94b..0000000000000000000000000000000000000000 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ /dev/null @@ -1,235 +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 super::*; - -use crate::{ - debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, - primitives::ExecReturnValue, - test_utils::*, - DepositLimit, -}; -use frame_support::traits::Currency; -use pretty_assertions::assert_eq; -use sp_core::H160; -use std::cell::RefCell; - -#[derive(Clone, PartialEq, Eq, Debug)] -struct DebugFrame { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec<u8>, - result: Option<Vec<u8>>, -} - -thread_local! { - static DEBUG_EXECUTION_TRACE: RefCell<Vec<DebugFrame>> = RefCell::new(Vec::new()); - static INTERCEPTED_ADDRESS: RefCell<Option<sp_core::H160>> = RefCell::new(None); -} - -pub struct TestDebug; -pub struct TestCallSpan { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec<u8>, -} - -impl Tracing<Test> for TestDebug { - type CallSpan = TestCallSpan; - - fn new_call_span( - contract_address: &crate::H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> TestCallSpan { - DEBUG_EXECUTION_TRACE.with(|d| { - d.borrow_mut().push(DebugFrame { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - result: None, - }) - }); - TestCallSpan { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - } - } -} - -impl CallInterceptor<Test> for TestDebug { - fn intercept_call( - contract_address: &sp_core::H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option<ExecResult> { - INTERCEPTED_ADDRESS.with(|i| { - if i.borrow().as_ref() == Some(contract_address) { - Some(Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![] })) - } else { - None - } - }) - } -} - -impl CallSpan for TestCallSpan { - fn after_call(self, output: &ExecReturnValue) { - DEBUG_EXECUTION_TRACE.with(|d| { - d.borrow_mut().push(DebugFrame { - contract_address: self.contract_address, - call: self.call, - input: self.input, - result: Some(output.data.clone()), - }) - }); - } -} - -#[test] -fn debugging_works() { - let (wasm_caller, _) = compile_module("call").unwrap(); - let (wasm_callee, _) = compile_module("store_call").unwrap(); - - fn current_stack() -> Vec<DebugFrame> { - DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) - } - - fn deploy(wasm: Vec<u8>) -> H160 { - Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - DepositLimit::Balance(deposit_limit::<Test>()), - Code::Upload(wasm), - vec![], - Some([0u8; 32]), - ) - .result - .unwrap() - .addr - } - - fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Constructor, - input: vec![], - result: if after { Some(vec![]) } else { None }, - } - } - - fn call_frame(contract_address: &H160, args: Vec<u8>, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Call, - input: args, - result: if after { Some(vec![]) } else { None }, - } - } - - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - - assert_eq!(current_stack(), vec![]); - - let addr_caller = deploy(wasm_caller); - let addr_callee = deploy(wasm_callee); - - assert_eq!( - current_stack(), - vec![ - constructor_frame(&addr_caller, false), - constructor_frame(&addr_caller, true), - constructor_frame(&addr_callee, false), - constructor_frame(&addr_callee, true), - ] - ); - - let main_args = (100u32, &addr_callee.clone()).encode(); - let inner_args = (100u32).encode(); - - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr_caller, - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - main_args.clone() - )); - - let stack_top = current_stack()[4..].to_vec(); - assert_eq!( - stack_top, - vec![ - call_frame(&addr_caller, main_args.clone(), false), - call_frame(&addr_callee, inner_args.clone(), false), - call_frame(&addr_callee, inner_args, true), - call_frame(&addr_caller, main_args, true), - ] - ); - }); -} - -#[test] -fn call_interception_works() { - let (wasm, _) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - - let account_id = Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - deposit_limit::<Test>().into(), - Code::Upload(wasm), - vec![], - // some salt to ensure that the address of this contract is unique among all tests - Some([0x41; 32]), - ) - .result - .unwrap() - .addr; - - // no interception yet - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - vec![], - )); - - // intercept calls to this contract - INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id)); - - assert_err_ignore_postinfo!( - Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::<Test>(), - vec![], - ), - <Error<Test>>::ContractReverted, - ); - }); -} diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs new file mode 100644 index 0000000000000000000000000000000000000000..e9c05f8cb5058ee74b4fbb29cb9fe5aea30faac2 --- /dev/null +++ b/substrate/frame/revive/src/tracing.rs @@ -0,0 +1,64 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{primitives::ExecReturnValue, DispatchError, Weight}; +use environmental::environmental; +use sp_core::{H160, H256, U256}; + +environmental!(tracer: dyn Tracer + 'static); + +/// Trace the execution of the given closure. +/// +/// # Warning +/// +/// Only meant to be called from off-chain code as its additional resource usage is +/// not accounted for in the weights or memory envelope. +pub fn trace<R, F: FnOnce() -> R>(tracer: &mut (dyn Tracer + 'static), f: F) -> R { + tracer::using_once(tracer, f) +} + +/// Run the closure when tracing is enabled. +/// +/// This is safe to be called from on-chain code as tracing will never be activated +/// there. Hence the closure is not executed in this case. +pub(crate) fn if_tracing<F: FnOnce(&mut (dyn Tracer + 'static))>(f: F) { + tracer::with(f); +} + +/// Defines methods to trace contract interactions. +pub trait Tracer { + /// Called before a contract call is executed + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas: Weight, + ); + + /// Record a log event + fn log_event(&mut self, event: H160, topics: &[H256], data: &[u8]); + + /// Called after a contract call is executed + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_left: Weight); + + /// Called when a contract call terminates with an error + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_left: Weight); +} diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 7241d667fcdc75c4dbd6e2c5f3a6a02efd1835aa..cf006941cfd0a6477d35b9b0d992060105f6f3c1 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -22,7 +22,7 @@ paste = { workspace = true } scale-info = { features = ["derive"], optional = true, workspace = true } [target.'cfg(target_arch = "riscv64")'.dependencies] -polkavm-derive = { version = "0.18.0" } +polkavm-derive = { version = "0.19.0" } [package.metadata.docs.rs] features = ["unstable-hostfn"] diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 626993a0547b58bee5a125edcec1ffce9ed5b779..84c55b110c8c2b3ebc1b948791311e551094dadf 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } pallet-ranked-collective = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index e3e58fc01b5fac66b5908daee1e17fd3f6e1b011..18c7bd1239443643a9f86762c542f25f23086a75 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -106,7 +106,7 @@ //! [dependencies] //! codec = { features = ["max-encoded-len"], workspace = true } //! scale-info = { features = ["derive"], workspace = true } -//! frame = { workspace = true, features = ["experimental", "runtime"] } +//! frame = { workspace = true, features = ["runtime"] } //! //! [features] //! default = ["std"] @@ -150,7 +150,6 @@ //! * `runtime::apis` should expose all common runtime APIs that all FRAME-based runtimes need. #![cfg_attr(not(feature = "std"), no_std)] -#![cfg(feature = "experimental")] #[doc(no_inline)] pub use frame_support::pallet; diff --git a/substrate/frame/support/test/stg_frame_crate/Cargo.toml b/substrate/frame/support/test/stg_frame_crate/Cargo.toml index f627d29cd563067cdad9b48e4dc5a7ac9bdefbd8..157361dbd5d6dc1df062ebd2e4632da830b7362b 100644 --- a/substrate/frame/support/test/stg_frame_crate/Cargo.toml +++ b/substrate/frame/support/test/stg_frame_crate/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } scale-info = { features = ["derive"], workspace = true } [features]