Skip to content

Commit d48ba20

Browse files
Merge branch 'master' into fix/hsm-arb-speed
2 parents a4e77ab + 6ddd821 commit d48ba20

18 files changed

Lines changed: 428 additions & 32 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration-tests/src/dispatcher.rs

Lines changed: 223 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1+
use crate::evm::{create_dispatch_handle, gas_price};
12
use crate::polkadot_test_net::*;
3+
use fp_evm::PrecompileSet;
24
use frame_support::assert_ok;
3-
use frame_support::dispatch::GetDispatchInfo;
5+
use frame_support::dispatch::{
6+
extract_actual_pays_fee, extract_actual_weight, GetDispatchInfo, Pays, PostDispatchInfo,
7+
};
8+
use hydradx_runtime::evm::precompiles::HydraDXPrecompiles;
49
use hydradx_runtime::evm::WethAssetId;
510
use hydradx_runtime::*;
611
use orml_traits::MultiCurrency;
12+
use pallet_evm::{ExitReason, ExitSucceed};
713
use pallet_transaction_payment::ChargeTransactionPayment;
14+
use precompile_utils::prelude::PrecompileOutput;
815
use primitives::EvmAddress;
916
use sp_core::Encode;
1017
use sp_core::Get;
1118
use sp_core::{ByteArray, U256};
1219
use sp_runtime::traits::SignedExtension;
20+
use sp_runtime::DispatchErrorWithPostInfo;
1321
use test_utils::last_events;
1422
use xcm_emulator::TestExt;
1523

@@ -425,3 +433,217 @@ fn dispatch_with_extra_gas_should_charge_extra_gas_when_calls_fail() {
425433
);
426434
});
427435
}
436+
437+
#[test]
438+
fn dispatch_evm_call_should_work_when_evm_call_succeeds() {
439+
TestNet::reset();
440+
Hydra::execute_with(|| {
441+
// Verify that LastEvmCallExitReason storage is cleaned before execution
442+
assert_eq!(Dispatcher::last_evm_call_exit_reason(), None);
443+
444+
// Arrange: Deploy a valid contract to interact with
445+
let contract = crate::utils::contracts::deploy_contract("HydraToken", crate::contracts::deployer());
446+
let stop_code_contract = crate::utils::contracts::deploy_contract_code(
447+
hex!["608080604052346013576067908160188239f35b5f80fdfe6004361015600b575f80fd5b5f3560e01c6306fdde0314601d575f80fd5b34602d575f366003190112602d57005b5f80fdfea264697066735822122072cd2025c9922b7f29b4174f1e2d766386a8ecbaab35dc5921cda0fa301dcb3e64736f6c634300081e0033"].to_vec(),
448+
crate::contracts::deployer(),
449+
); // name() function selector returns "stopped"
450+
451+
assert_ok!(hydradx_runtime::Tokens::set_balance(
452+
hydradx_runtime::RuntimeOrigin::root(),
453+
evm_account(),
454+
WethAssetId::get(),
455+
1_000_000_000_000_000_000u128,
456+
0
457+
));
458+
459+
// Helper function to create EVM calls with common parameters
460+
let create_evm_call = |target| {
461+
Box::new(RuntimeCall::EVM(pallet_evm::Call::call {
462+
source: evm_address(),
463+
target,
464+
input: hex!["06fdde03"].to_vec(), // name() function selector
465+
value: U256::zero(),
466+
gas_limit: 1_000_000,
467+
max_fee_per_gas: gas_price(),
468+
max_priority_fee_per_gas: None,
469+
nonce: None,
470+
access_list: vec![],
471+
}))
472+
};
473+
474+
// Create test cases with different targets
475+
let call_succeed_returned = create_evm_call(contract);
476+
let call_succeed_stopped = create_evm_call(stop_code_contract);
477+
478+
// Act: Dispatch the EVM calls
479+
assert_ok!(Dispatcher::dispatch_evm_call(
480+
evm_signed_origin(evm_address()),
481+
call_succeed_returned
482+
));
483+
484+
// Verify that LastEvmCallExitReason storage has expected Returned value
485+
assert_eq!(
486+
Dispatcher::last_evm_call_exit_reason(),
487+
Some(ExitReason::Succeed(ExitSucceed::Returned))
488+
);
489+
490+
assert_ok!(Dispatcher::dispatch_evm_call(
491+
evm_signed_origin(evm_address()),
492+
call_succeed_stopped
493+
));
494+
495+
// Verify that LastEvmCallExitReason storage has expected Stopped value
496+
assert_eq!(
497+
Dispatcher::last_evm_call_exit_reason(),
498+
Some(ExitReason::Succeed(ExitSucceed::Stopped))
499+
);
500+
501+
// Produce the next block and ensure the key is gone at the next block
502+
hydradx_run_to_next_block();
503+
assert_eq!(
504+
Dispatcher::last_evm_call_exit_reason(),
505+
None,
506+
"Storage key should stay empty in subsequent blocks"
507+
);
508+
});
509+
}
510+
511+
#[test]
512+
fn dispatch_evm_call_should_fail_with_invalid_function_selector() {
513+
TestNet::reset();
514+
Hydra::execute_with(|| {
515+
// Verify that LastEvmCallExitReason storage is cleaned before execution
516+
assert_eq!(Dispatcher::last_evm_call_exit_reason(), None);
517+
518+
// Arrange
519+
assert_ok!(hydradx_runtime::Tokens::set_balance(
520+
hydradx_runtime::RuntimeOrigin::root(),
521+
evm_account(),
522+
WethAssetId::get(),
523+
1_000_000_000_000_000_000u128,
524+
0
525+
));
526+
527+
// Deploy a contract to test with
528+
let contract = crate::utils::contracts::deploy_contract("HydraToken", crate::contracts::deployer());
529+
530+
// Create an EVM call with an invalid function selector
531+
let call = RuntimeCall::EVM(pallet_evm::Call::call {
532+
source: evm_address(),
533+
target: contract,
534+
input: hex!["12345678"].to_vec(), // Invalid function selector
535+
gas_limit: 1_000_000,
536+
value: U256::zero(),
537+
max_fee_per_gas: gas_price(),
538+
max_priority_fee_per_gas: None,
539+
nonce: None,
540+
access_list: vec![],
541+
});
542+
let call_data = call.get_dispatch_info();
543+
let boxed_call = Box::new(call);
544+
545+
// Act
546+
let result = Dispatcher::dispatch_evm_call(evm_signed_origin(evm_address()), boxed_call);
547+
548+
// Assert
549+
// The dispatch should fail with EvmCallFailed error
550+
assert_eq!(
551+
result,
552+
Err(DispatchErrorWithPostInfo {
553+
post_info: PostDispatchInfo {
554+
actual_weight: Some(extract_actual_weight(&result, &call_data)),
555+
pays_fee: extract_actual_pays_fee(&result, &call_data),
556+
},
557+
error: pallet_dispatcher::Error::<Runtime>::EvmCallFailed.into(),
558+
})
559+
);
560+
561+
// Verify that LastEvmCallExitReason storage is cleaned after faulty execution
562+
assert_eq!(Dispatcher::last_evm_call_exit_reason(), None);
563+
});
564+
}
565+
566+
#[test]
567+
fn dispatch_evm_call_should_fail_with_not_evm_call_error() {
568+
TestNet::reset();
569+
Hydra::execute_with(|| {
570+
// Arrange: Create a non-EVM call
571+
let call = RuntimeCall::Currencies(pallet_currencies::Call::transfer {
572+
dest: BOB.into(),
573+
currency_id: 1234,
574+
amount: 100,
575+
});
576+
let boxed_call = Box::new(call.clone());
577+
578+
// Act & Assert: The dispatch should fail with NotEvmCall error
579+
let result = Dispatcher::dispatch_evm_call(evm_signed_origin(evm_address()), boxed_call);
580+
assert_eq!(
581+
result,
582+
Err(DispatchErrorWithPostInfo {
583+
post_info: PostDispatchInfo {
584+
actual_weight: None,
585+
pays_fee: Pays::Yes,
586+
},
587+
error: pallet_dispatcher::Error::<Runtime>::NotEvmCall.into(),
588+
})
589+
);
590+
})
591+
}
592+
593+
#[test]
594+
fn dispatch_evm_call_via_precompile_should_work() {
595+
TestNet::reset();
596+
Hydra::execute_with(|| {
597+
// Arrange
598+
let stop_code_contract = crate::utils::contracts::deploy_contract_code(
599+
hex!["608080604052346013576067908160188239f35b5f80fdfe6004361015600b575f80fd5b5f3560e01c6306fdde0314601d575f80fd5b34602d575f366003190112602d57005b5f80fdfea264697066735822122072cd2025c9922b7f29b4174f1e2d766386a8ecbaab35dc5921cda0fa301dcb3e64736f6c634300081e0033"].to_vec(),
600+
crate::contracts::deployer(),
601+
); // name() function selector returns "stopped"
602+
603+
assert_ok!(hydradx_runtime::Tokens::set_balance(
604+
hydradx_runtime::RuntimeOrigin::root(),
605+
evm_account(),
606+
WethAssetId::get(),
607+
1_000_000_000_000_000_000u128,
608+
0
609+
));
610+
611+
let inner_runtime_call = RuntimeCall::EVM(pallet_evm::Call::call {
612+
source: evm_address(),
613+
target: stop_code_contract,
614+
input: hex!["06fdde03"].to_vec(), // name() function selector
615+
value: U256::zero(),
616+
gas_limit: 100_000,
617+
max_fee_per_gas: U256::from(233_460_000),
618+
max_priority_fee_per_gas: None,
619+
nonce: None,
620+
access_list: vec![],
621+
});
622+
623+
let outer_call = RuntimeCall::Dispatcher(pallet_dispatcher::Call::dispatch_evm_call {
624+
call: Box::new(inner_runtime_call),
625+
});
626+
627+
// SCALE‑encode the entire outer call for precompile
628+
let data = outer_call.encode();
629+
630+
// Build a mocked EVM precompile handle which basically simulates a MetaMask
631+
// transaction calling the Frontier dispatch precompile (`DISPATCH_ADDR`)
632+
// from the default test EVM account.
633+
let mut handle = create_dispatch_handle(data);
634+
635+
// Execute all HydraDX precompiles (this includes the standard
636+
// Frontier Dispatch precompile wired at `DISPATCH_ADDR`).
637+
let precompiles = HydraDXPrecompiles::<hydradx_runtime::Runtime>::new();
638+
let result = precompiles.execute(&mut handle);
639+
640+
// The dispatch precompile should succeed and stop.
641+
assert_eq!(
642+
result.unwrap(),
643+
Ok(PrecompileOutput {
644+
exit_status: ExitSucceed::Stopped,
645+
output: Default::default(),
646+
})
647+
);
648+
});
649+
}

integration-tests/src/polkadot_test_net.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ pub fn hydradx_run_to_next_block() {
763763
hydradx_runtime::MultiTransactionPayment::on_finalize(b);
764764
hydradx_runtime::CircuitBreaker::on_finalize(b);
765765
hydradx_runtime::DCA::on_finalize(b);
766+
hydradx_runtime::Dispatcher::on_finalize(b);
766767
hydradx_runtime::EmaOracle::on_finalize(b);
767768
hydradx_runtime::EVM::on_finalize(b);
768769
hydradx_runtime::Ethereum::on_finalize(b);

pallets/dispatcher/Cargo.toml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pallet-dispatcher"
3-
version = "1.1.1"
3+
version = "1.2.0"
44
authors = ['GalacticCouncil']
55
edition = "2021"
66
license = "Apache-2.0"
@@ -14,9 +14,6 @@ readme = "README.md"
1414
codec = { workspace = true, features = ["derive", "max-encoded-len"] }
1515
scale-info = { workspace = true }
1616

17-
pallet-evm = { workspace = true }
18-
hydradx-traits = { workspace = true }
19-
2017
# primitives
2118
sp-runtime = { workspace = true }
2219
sp-std = { workspace = true }
@@ -26,12 +23,17 @@ sp-core = { workspace = true }
2623
frame-support = { workspace = true }
2724
frame-system = { workspace = true }
2825

26+
# HydraDX dependencies
27+
hydradx-traits = { workspace = true }
28+
29+
# EVM dependencies
30+
pallet-evm = { workspace = true }
31+
2932
# Optional imports for benchmarking
3033
frame-benchmarking = { workspace = true, optional = true }
3134

3235
[dev-dependencies]
3336
sp-io = { workspace = true }
34-
hydradx-traits = { workspace = true }
3537
orml-tokens = { workspace = true }
3638
orml-traits = { workspace = true }
3739
test-utils = { workspace = true }
@@ -50,7 +52,6 @@ std = [
5052
'orml-tokens/std',
5153
'orml-traits/std',
5254
"pallet-evm/std",
53-
"hydradx-traits/std",
5455
]
5556

5657
runtime-benchmarks = [

pallets/dispatcher/src/benchmarking.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,13 @@ benchmarks! {
6262

6363
}: _(RawOrigin::Signed(caller), Box::new(call), 50_000)
6464

65+
dispatch_evm_call {
66+
let n in 1 .. 10_000;
67+
let remark = sp_std::vec![1u8; n as usize];
68+
69+
let call: <T as pallet::Config>::RuntimeCall = frame_system::Call::remark { remark }.into();
70+
let caller: T::AccountId = account("caller", 0, 1);
71+
}: _(RawOrigin::Signed(caller), Box::new(call))
72+
6573
impl_benchmark_test_suite!(Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Test);
6674
}

0 commit comments

Comments
 (0)