Skip to content

[pallet-revive] Make Runtime call dispatchable as eth transaction #8883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions prdoc/pr_8883.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
title: '[pallet-revive] Make Runtime call dispatchable as eth transaction'
doc:
- audience: Runtime Dev
description: |-
Make RuntimeCall dispatchable as eth transaction.

By sending a transaction to `0x6d6f646c70792f70616464720000000000000000`, using the encoded runtime call as input, the call will be executed by this given origin.

see https://github.com/paritytech/foundry-polkadot/issues/130

e.g sending a remark_with_event
```
cast wallet import dev-account --private-key 5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133
cast send --account dev-account 0x6d6f646c70792f70616464720000000000000000 0x0007143132333435
```
crates:
- name: pallet-revive
bump: patch
18 changes: 9 additions & 9 deletions substrate/frame/revive/rpc/src/fee_history_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const CACHE_SIZE: u32 = 1024;

#[derive(Default, Clone)]
struct FeeHistoryCacheItem {
base_fee: u64,
base_fee: u128,
gas_used_ratio: f64,
rewards: Vec<u64>,
rewards: Vec<u128>,
}

/// Manages the fee history cache.
Expand All @@ -47,17 +47,17 @@ impl FeeHistoryProvider {
let block_number: SubstrateBlockNumber =
block.number.try_into().expect("Block number is always valid");

let base_fee = block.base_fee_per_gas.unwrap_or_default().as_u64();
let gas_used = block.gas_used.as_u64();
let gas_used_ratio = (gas_used as f64) / (block.gas_limit.as_u64() as f64);
let base_fee = block.base_fee_per_gas.unwrap_or_default().as_u128();
let gas_used = block.gas_used.as_u128();
let gas_used_ratio = (gas_used as f64) / (block.gas_limit.as_u128() as f64);
let mut result = FeeHistoryCacheItem { base_fee, gas_used_ratio, rewards: vec![] };

let mut receipts = receipts
.iter()
.map(|receipt| {
let gas_used = receipt.gas_used.as_u64();
let gas_used = receipt.gas_used.as_u128();
let effective_reward =
receipt.effective_gas_price.as_u64().saturating_sub(base_fee);
receipt.effective_gas_price.as_u128().saturating_sub(base_fee);
(gas_used, effective_reward)
})
.collect::<Vec<_>>();
Expand All @@ -67,8 +67,8 @@ impl FeeHistoryProvider {
result.rewards = reward_percentiles
.into_iter()
.filter_map(|p| {
let target_gas = (p * gas_used as f64 / 100f64) as u64;
let mut sum_gas = 0u64;
let target_gas = (p * gas_used as f64 / 100f64) as u128;
let mut sum_gas = 0u128;
for (gas_used, reward) in &receipts {
sum_gas += gas_used;
if target_gas <= sum_gas {
Expand Down
74 changes: 55 additions & 19 deletions substrate/frame/revive/src/evm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ use crate::{
api::{GenericTransaction, TransactionSigned},
GasEncoder,
},
AccountIdOf, AddressMapper, BalanceOf, Config, ConversionPrecision, MomentOf, Pallet,
LOG_TARGET,
AccountIdOf, AddressMapper, BalanceOf, Config, ConversionPrecision, MomentOf,
OnChargeTransactionBalanceOf, Pallet, LOG_TARGET, RUNTIME_PALLETS_ADDR,
};
use alloc::vec::Vec;
use codec::{Decode, DecodeWithMemTracking, Encode};
use codec::{Decode, DecodeLimit, DecodeWithMemTracking, Encode};
use frame_support::{
dispatch::{DispatchInfo, GetDispatchInfo},
traits::{InherentBuilder, IsSubType, SignedTransactionBuilder},
MAX_EXTRINSIC_DEPTH,
};
use pallet_transaction_payment::OnChargeTransaction;
use scale_info::{StaticTypeInfo, TypeInfo};
use sp_core::{Get, H256, U256};
use sp_runtime::{
Expand Down Expand Up @@ -118,8 +118,6 @@ impl<Address: TypeInfo, Signature: TypeInfo, E: EthExtra> ExtrinsicCall
}
}

type OnChargeTransactionBalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;

impl<LookupSource, Signature, E, Lookup> Checkable<Lookup>
for UncheckedExtrinsic<LookupSource, Signature, E>
where
Expand Down Expand Up @@ -333,12 +331,31 @@ pub trait EthExtra {
})?;

let call = if let Some(dest) = to {
crate::Call::call::<Self::Config> {
dest,
value,
gas_limit,
storage_deposit_limit,
data,
if dest == RUNTIME_PALLETS_ADDR {
let call = CallOf::<Self::Config>::decode_all_with_depth_limit(
MAX_EXTRINSIC_DEPTH,
&mut &data[..],
)
.map_err(|_| {
log::debug!(target: LOG_TARGET, "Failed to decode data as Call");
InvalidTransaction::Call
})?;

if value != 0u32.into() {
log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value");
return Err(InvalidTransaction::Call)
}

call
} else {
crate::Call::call::<Self::Config> {
dest,
value,
gas_limit,
storage_deposit_limit,
data,
}
.into()
}
} else {
let blob = match polkavm::ProgramBlob::blob_length(&data) {
Expand All @@ -359,10 +376,10 @@ pub trait EthExtra {
code: code.to_vec(),
data: data.to_vec(),
}
.into()
};

let mut info = call.get_dispatch_info();
let function: CallOf<Self::Config> = call.into();
let nonce = nonce.unwrap_or_default().try_into().map_err(|_| {
log::debug!(target: LOG_TARGET, "Failed to convert nonce");
InvalidTransaction::Call
Expand All @@ -373,7 +390,7 @@ pub trait EthExtra {
.map_err(|_| InvalidTransaction::Call)?;

// Fees calculated from the extrinsic, without the tip.
info.extension_weight = Self::get_eth_extension(nonce, 0u32.into()).weight(&function);
info.extension_weight = Self::get_eth_extension(nonce, 0u32.into()).weight(&call);
let actual_fee: BalanceOf<Self::Config> =
pallet_transaction_payment::Pallet::<Self::Config>::compute_fee(
encoded_len as u32,
Expand Down Expand Up @@ -403,7 +420,7 @@ pub trait EthExtra {
log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}");
Ok(CheckedExtrinsic {
format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)),
function,
function: call,
})
}
}
Expand Down Expand Up @@ -474,14 +491,21 @@ mod test {
}
}

fn data(mut self, data: Vec<u8>) -> Self {
self.tx.input = Bytes(data).into();
self
}

fn estimate_gas(&mut self) {
let dry_run = crate::Pallet::<Test>::dry_run_eth_transact(
self.tx.clone(),
Weight::MAX,
|call, mut info| {
let call = RuntimeCall::Contracts(call);
info.extension_weight = Extra::get_eth_extension(0, 0u32.into()).weight(&call);
let uxt: Ex = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into();
|eth_call, dispatch_call| {
let mut info = dispatch_call.get_dispatch_info();
info.extension_weight =
Extra::get_eth_extension(0, 0u32.into()).weight(&dispatch_call);
let uxt: Ex =
sp_runtime::generic::UncheckedExtrinsic::new_bare(eth_call).into();
pallet_transaction_payment::Pallet::<Test>::compute_fee(
uxt.encoded_size() as u32,
&info,
Expand Down Expand Up @@ -698,4 +722,16 @@ mod test {
let expected_tip = crate::Pallet::<Test>::evm_gas_to_fee(tx.gas.unwrap(), diff).unwrap();
assert_eq!(extra.1.tip(), expected_tip);
}

#[test]
fn check_runtime_pallets_addr_works() {
let remark: CallOf<Test> =
frame_system::Call::remark { remark: b"Hello, world!".to_vec() }.into();

let builder =
UncheckedExtrinsicBuilder::call_with(RUNTIME_PALLETS_ADDR).data(remark.encode());
let (call, _, _) = builder.check().unwrap();

assert_eq!(call, remark);
}
}
Loading
Loading