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]