Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
07dd930
WIP: add dispatch_batch_all_evm to pallet dispatcher
F3Joule Apr 11, 2025
838d389
WIP: make wrapper extrinsic do single EVM call
F3Joule Apr 15, 2025
83f9c18
Refactor batch to single evm call wrap
F3Joule May 2, 2025
873d251
Merge remote-tracking branch 'origin/master' into feat/batch-evm
F3Joule May 2, 2025
deea0a8
Merge remote-tracking branch 'origin/master' into feat/batch-evm
F3Joule May 9, 2025
c697c1d
Implement pallet_evm::Call::call fail check in evm::runner
F3Joule May 19, 2025
84d3e8c
Merge remote-tracking branch 'origin/master' into feat/batch-evm
F3Joule May 19, 2025
388bbe8
Revert redundant changes
F3Joule May 19, 2025
27273c2
Add test for dispatch_evm_call
F3Joule May 19, 2025
66c88ec
Change test case not to fail
F3Joule May 19, 2025
6c40500
Add test for NotEvmCall error
F3Joule May 19, 2025
e33d6ee
Fix formatting
F3Joule May 19, 2025
a26795b
Fix tests build
F3Joule May 20, 2025
45db02f
Apply suggestions from code review
F3Joule May 30, 2025
46f65ae
Merge branch 'master' into feat/batch-evm
F3Joule May 30, 2025
51d5cdf
Complete tests after logic changes
F3Joule May 30, 2025
e9c6a4b
Merge remote-tracking branch 'origin/master' into feat/batch-evm
F3Joule Jun 13, 2025
c04a733
Revert unneeded changes after merge
F3Joule Jul 3, 2025
7213fa0
Merge branch 'master' into feat/batch-evm
F3Joule Jul 3, 2025
32546ab
Refactor dispatcher to store evm ExitReason instead of bool
F3Joule Jul 4, 2025
503b693
Fix tests
F3Joule Jul 4, 2025
ab3fa7d
Fix formatting
F3Joule Jul 4, 2025
82d6b26
Merge remote-tracking branch 'origin/master' into feat/batch-evm
F3Joule Jul 7, 2025
6863834
Clean up a code for a more neat look
F3Joule Jul 7, 2025
a416730
Fix code formatting
F3Joule Jul 7, 2025
47df584
Fix test
F3Joule Jul 8, 2025
705da1a
Merge branch 'master' into feat/batch-evm
F3Joule Jul 8, 2025
8f69cb5
Merge branch 'master' into feat/batch-evm
F3Joule Jul 11, 2025
de1d2f6
Add benchmark for dispatch_evm_call
F3Joule Jul 17, 2025
2dc39be
Merge remote-tracking branch 'origin/master' into feat/batch-evm
F3Joule Jul 17, 2025
a9618fa
Make EVM origin guard accept complete and truncated account ids
F3Joule Jul 17, 2025
018e7bd
Merge branch 'master' into feat/batch-evm
F3Joule Jul 22, 2025
cb41a0a
bump crates versions
F3Joule Jul 22, 2025
f653a25
Update pallets weights [ignore benchmarks]
F3Joule Jul 24, 2025
844d02a
Partially revert committed weights
F3Joule Jul 25, 2025
941d92c
Merge remote-tracking branch 'origin/master' into feat/batch-evm
F3Joule Jul 25, 2025
154c05e
Merge branch 'master' into feat/batch-evm
mrq1911 Jul 29, 2025
05e5a3a
Merge branch 'master' into feat/batch-evm
F3Joule Aug 12, 2025
452306a
Fix integration tests after merge
F3Joule Aug 12, 2025
0ecb44e
Merge branch 'master' into feat/batch-evm
mrq1911 Aug 13, 2025
b525d69
runtime 338
mrq1911 Aug 13, 2025
826b23a
lock
mrq1911 Aug 13, 2025
25d0583
Optimize EnsureAddressTruncated
F3Joule Aug 15, 2025
b396f0e
Merge branch 'master' into feat/batch-evm
F3Joule Aug 15, 2025
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
116 changes: 115 additions & 1 deletion integration-tests/src/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::evm::MockHandle;
use crate::polkadot_test_net::*;
use fp_evm::PrecompileSet;
use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
use frame_support::dispatch::{
extract_actual_pays_fee, extract_actual_weight, GetDispatchInfo, Pays, PostDispatchInfo,
};
use frame_support::{assert_err, assert_noop, assert_ok};
use hydradx_runtime::evm::precompiles::HydraDXPrecompiles;
use hydradx_runtime::evm::WethAssetId;
Expand All @@ -14,6 +16,7 @@ use sp_core::Encode;
use sp_core::Get;
use sp_core::{ByteArray, U256};
use sp_runtime::traits::SignedExtension;
use sp_runtime::DispatchErrorWithPostInfo;
use test_utils::last_events;
use xcm_emulator::TestExt;

Expand Down Expand Up @@ -438,3 +441,114 @@ fn dispatch_with_extra_gas_should_charge_extra_gas_when_calls_fail() {
);
});
}

#[test]
fn dispatch_evm_call_should_work_when_evm_call_succeeds() {
TestNet::reset();
Hydra::execute_with(|| {
// Arrange: Deploy a valid contract to interact with
let contract = crate::utils::contracts::deploy_contract("HydraToken", crate::contracts::deployer());

assert_ok!(hydradx_runtime::Tokens::set_balance(
hydradx_runtime::RuntimeOrigin::root(),
evm_account(),
WethAssetId::get(),
1_000_000_000_000_000_000u128,
0
));

// Create a valid EVM call that will succeed
let call = Box::new(RuntimeCall::EVM(pallet_evm::Call::call {
source: evm_address(),
target: contract,
input: hex!["06fdde03"].to_vec(), // name() function selector
value: U256::zero(),
gas_limit: 100_000,
max_fee_per_gas: U256::from(233_460_000),
max_priority_fee_per_gas: None,
nonce: None,
access_list: vec![],
}));

// Act: Dispatch the EVM call
assert_ok!(Dispatcher::dispatch_evm_call(evm_signed_origin(evm_address()), call));

// Assert: LastEvmCallFailed storage is clean
assert_eq!(Dispatcher::last_evm_call_failed(), false);
});
}

#[test]
fn dispatch_evm_call_should_fail_with_evm_call_failed_error() {
TestNet::reset();
Hydra::execute_with(|| {
assert_eq!(Dispatcher::last_evm_call_failed(), false);

assert_ok!(hydradx_runtime::Tokens::set_balance(
hydradx_runtime::RuntimeOrigin::root(),
evm_account(),
WethAssetId::get(),
1_000_000_000_000_000_000u128,
0
));

// Arrange: Create an EVM call that will fail (invalid target address)
let invalid_target = EvmAddress::from_slice(&[0x11; 20]);
let call = RuntimeCall::EVM(pallet_evm::Call::call {
source: evm_address(),
target: invalid_target,
input: hex!["12345678"].to_vec(), // Invalid function selector
gas_limit: 100_000,
value: U256::zero(),
max_fee_per_gas: U256::from(233_460_000),
max_priority_fee_per_gas: None,
nonce: None,
access_list: vec![],
});
let call_data = call.get_dispatch_info();
let boxed_call = Box::new(call);

// Act & Assert: The dispatch should fail with EvmCallFailed error
let result = Dispatcher::dispatch_evm_call(evm_signed_origin(evm_address()), boxed_call);
assert_eq!(
result,
Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(extract_actual_weight(&result, &call_data)),
pays_fee: extract_actual_pays_fee(&result, &call_data),
},
error: pallet_dispatcher::Error::<Runtime>::EvmCallFailed.into(),
})
);

// Assert: LastEvmCallFailed storage is cleaned after the execution
assert_eq!(Dispatcher::last_evm_call_failed(), false);
});
}

#[test]
fn dispatch_evm_call_should_fail_with_not_evm_call_error() {
TestNet::reset();
Hydra::execute_with(|| {
// Arrange: Create a non-EVM call
let call = RuntimeCall::Currencies(pallet_currencies::Call::transfer {
dest: BOB.into(),
currency_id: 1234,
amount: 100,
});
let boxed_call = Box::new(call.clone());

// Act & Assert: The dispatch should fail with NotEvmCall error
let result = Dispatcher::dispatch_evm_call(evm_signed_origin(evm_address()), boxed_call);
assert_eq!(
result,
Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: None,
pays_fee: Pays::Yes,
},
error: pallet_dispatcher::Error::<Runtime>::NotEvmCall.into(),
})
);
})
}
10 changes: 6 additions & 4 deletions pallets/dispatcher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ readme = "README.md"
codec = { workspace = true, features = ["derive", "max-encoded-len"] }
scale-info = { workspace = true }

pallet-evm = { workspace = true }
hydradx-traits = { workspace = true }

# primitives
sp-runtime = { workspace = true }
sp-std = { workspace = true }
Expand All @@ -26,12 +23,17 @@ sp-core = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }

# HydraDX dependencies
hydradx-traits = { workspace = true }

# EVM dependencies
pallet-evm = { workspace = true }

# Optional imports for benchmarking
frame-benchmarking = { workspace = true, optional = true }

[dev-dependencies]
sp-io = { workspace = true }
hydradx-traits = { workspace = true }
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
test-utils = { workspace = true }
Expand Down
64 changes: 64 additions & 0 deletions pallets/dispatcher/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod benchmarking;
pub mod weights;

use frame_support::dispatch::PostDispatchInfo;
use hydradx_traits::evm::MaybeEvmCall;
use pallet_evm::GasWeightMapping;
use sp_runtime::{traits::Dispatchable, DispatchResultWithInfo};
pub use weights::WeightInfo;
Expand Down Expand Up @@ -76,6 +77,9 @@ pub mod pallet {
+ From<frame_system::Call<Self>>
+ Parameter;

/// The trait to check whether RuntimeCall is [pallet_evm::Call::call].
type EvmCallIdentifier: MaybeEvmCall<<Self as Config>::RuntimeCall>;

type TreasuryManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type AaveManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;

Expand All @@ -101,6 +105,17 @@ pub mod pallet {
#[pallet::getter(fn extra_gas)]
pub type ExtraGas<T: Config> = StorageValue<_, u64, ValueQuery>;

#[pallet::storage]
#[pallet::whitelist_storage]
#[pallet::getter(fn last_evm_call_failed)]
pub type LastEvmCallFailed<T: Config> = StorageValue<_, bool, ValueQuery>;
Comment thread
F3Joule marked this conversation as resolved.
Outdated

#[pallet::error]
pub enum Error<T> {
EvmCallFailed,
NotEvmCall,
Comment thread
F3Joule marked this conversation as resolved.
}

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Expand Down Expand Up @@ -236,6 +251,47 @@ pub mod pallet {
}),
}
}

/// Execute a single EVM call.
/// This extrinsic will fail if the EVM call returns any other ExitReason than `ExitSucceed(Returned)`.
Comment thread
F3Joule marked this conversation as resolved.
Outdated
/// Look the [hydradx_runtime::evm::runner::WrapRunner] implementation for details.
///
/// Parameters:
/// - `origin`: Signed origin.
/// - `call`: presumably `pallet_evm::Call::call` as boxed `RuntimeCall`.
///
/// Emits `EvmCallFailed` event when failed.
#[pallet::call_index(4)]
#[pallet::weight(call.get_dispatch_info().weight)]
Comment thread
F3Joule marked this conversation as resolved.
Outdated
pub fn dispatch_evm_call(
origin: OriginFor<T>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
Comment thread
mrq1911 marked this conversation as resolved.
ensure!(T::EvmCallIdentifier::is_evm_call(&call), Error::<T>::NotEvmCall);

let (result, actual_weight) = Self::do_dispatch(origin, *call);

match result {
Comment thread
F3Joule marked this conversation as resolved.
Ok(_) if Self::last_evm_call_failed() => Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight,
pays_fee: Pays::Yes,
},
error: Error::<T>::EvmCallFailed.into(),
}),
Ok(_) => Ok(PostDispatchInfo {
actual_weight,
pays_fee: Pays::Yes,
}),
Err(err) => Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight,
pays_fee: Pays::Yes,
},
error: err.error,
}),
}
}
}
}

Expand Down Expand Up @@ -271,4 +327,12 @@ impl<T: Config> Pallet<T> {
ExtraGas::<T>::set(new_value);
}
}

pub fn set_last_evm_call_failed(status: bool) {
Comment thread
F3Joule marked this conversation as resolved.
Outdated
if !status {
LastEvmCallFailed::<T>::kill();
} else {
LastEvmCallFailed::<T>::put(status);
}
Comment thread
F3Joule marked this conversation as resolved.
Outdated
}
}
9 changes: 9 additions & 0 deletions pallets/dispatcher/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ impl pallet_evm::GasWeightMapping for MockGasWeightMapping {
}
}

pub struct EvmCallIdentifier;
impl MaybeEvmCall<RuntimeCall> for EvmCallIdentifier {
fn is_evm_call(_call: &RuntimeCall) -> bool {
false
}
}

impl dispatcher::Config for Test {
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
Expand All @@ -112,6 +119,7 @@ impl dispatcher::Config for Test {
type TreasuryAccount = TreasuryAccount;
type DefaultAaveManagerAccount = TreasuryAccount;
type WeightInfo = ();
type EvmCallIdentifier = EvmCallIdentifier;
type GasWeightMapping = MockGasWeightMapping;
}

Expand Down Expand Up @@ -207,6 +215,7 @@ impl<T: Config> Inspect for DummyRegistry<T> {
}
}

use hydradx_traits::evm::MaybeEvmCall;
#[cfg(feature = "runtime-benchmarks")]
use hydradx_traits::Create as CreateRegistry;
#[cfg(feature = "runtime-benchmarks")]
Expand Down
16 changes: 12 additions & 4 deletions runtime/hydradx/src/evm/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ use fp_evm::{Account, TransactionValidationError};
use frame_support::traits::Get;
use hydradx_traits::AccountFeeCurrencyBalanceInCurrency;
use pallet_evm::runner::Runner;
use pallet_evm::{AccountProvider, AddressMapping, CallInfo, Config, CreateInfo, FeeCalculator, RunnerError};
use pallet_evm::{
AccountProvider, AddressMapping, CallInfo, Config, CreateInfo, ExitReason, ExitSucceed, FeeCalculator, RunnerError,
};
use pallet_genesis_history::migration::Weight;
use primitive_types::{H160, H256, U256};
use primitives::{AssetId, Balance};
Expand All @@ -39,7 +41,7 @@ pub struct WrapRunner<T, R, B>(sp_std::marker::PhantomData<(T, R, B)>);

impl<T, R, B> Runner<T> for WrapRunner<T, R, B>
where
T: Config,
T: Config + pallet_dispatcher::Config,
R: Runner<T>,
<R as pallet_evm::Runner<T>>::Error: core::convert::From<TransactionValidationError>,
B: AccountFeeCurrencyBalanceInCurrency<AssetId, T::AccountId, Output = (Balance, Weight)>,
Expand Down Expand Up @@ -143,7 +145,7 @@ where
)?;
}
// Validated, flag set to false
R::call(
let result = R::call(
source,
target,
input,
Expand All @@ -158,7 +160,13 @@ where
weight_limit,
proof_size_base_cost,
config,
)
)?;

let CallInfo { exit_reason, .. } = &result;
let call_succeeded = matches!(exit_reason, ExitReason::Succeed(ExitSucceed::Returned));
pallet_dispatcher::Pallet::<T>::set_last_evm_call_failed(!call_succeeded);

Ok(result)
}

fn create(
Expand Down
17 changes: 15 additions & 2 deletions runtime/hydradx/src/governance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use frame_support::{
PalletId,
};
use frame_system::{EnsureRoot, EnsureRootWithSuccess};
use hydradx_traits::evm::MaybeEvmCall;
use pallet_collective::EnsureProportionAtLeast;
use primitives::constants::{currency::DOLLARS, time::DAYS};
use sp_arithmetic::Perbill;
Expand Down Expand Up @@ -236,13 +237,25 @@ parameter_types! {
pub const AaveManagerAccount: AccountId = AccountId::new(hex!("aa7e0000000000000000000000000000000aa7e0000000000000000000000000"));
}

pub struct EvmCallChecker;
impl MaybeEvmCall<RuntimeCall> for EvmCallChecker {
fn is_evm_call(call: &RuntimeCall) -> bool {
if let RuntimeCall::EVM(pallet_evm::Call::call { .. }) = call {
true
} else {
false
}
Comment thread
F3Joule marked this conversation as resolved.
Outdated
}
}

impl pallet_dispatcher::Config for Runtime {
type WeightInfo = weights::pallet_dispatcher::HydraWeight<Runtime>;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type EvmCallIdentifier = EvmCallChecker;
type TreasuryManagerOrigin = EitherOf<EnsureRoot<AccountId>, Treasurer>;
type AaveManagerOrigin = EitherOf<EnsureRoot<AccountId>, EconomicParameters>;
type TreasuryAccount = TreasuryAccount;
type DefaultAaveManagerAccount = AaveManagerAccount;
type GasWeightMapping = evm::FixedHydraGasWeightMapping<Runtime>;
type WeightInfo = weights::pallet_dispatcher::HydraWeight<Runtime>;
}
4 changes: 4 additions & 0 deletions traits/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ pub trait EVM<EvmResult> {
fn view(context: CallContext, data: Vec<u8>, gas: u64) -> EvmResult;
}

pub trait MaybeEvmCall<RuntimeCall> {
fn is_evm_call(call: &RuntimeCall) -> bool;
}

/// ERC20 interface adapter
pub trait ERC20 {
type Balance;
Expand Down