//! Falcon uses the same Executive, nonce, dispatch and composite paths as MS. use super::*; use frame_support::assert_ok; use pallet_hashed_accounts::{DerivedAuthorities, EvmAliases, MlDsaAccounts}; use sp_runtime::traits::TransactionExtension; use subtensor_hashed::{falcon, mldsa}; #[derive(Clone)] pub(super) struct Wallet { pub(super) seed: [u8; 32], pub(super) descriptor: mldsa::Descriptor, pub(super) account: AccountId, } impl Wallet { pub(super) fn new(identity: u8, profile: u8) -> Self { for attempt in 0u32..256 { let mut seed = [identity; 32]; seed[..4].copy_from_slice(&attempt.to_le_bytes()); let public = falcon::public_key_from_seed(profile, &seed).unwrap(); let descriptor = mldsa::Descriptor { version: falcon::VERSION, algorithm: profile, initial_commitment: falcon::commitment(profile, &public).unwrap(), }; let account = mldsa::account_id(&descriptor); if !falcon::is_fixed_profile(profile) || mldsa::rejects_legacy_public_keys(&account) { return Self { seed, descriptor, account: AccountId::new(account), }; } } panic!("safe Falcon fixture exhausted") } fn generation_seed(&self, generation: u64) -> [u8; 32] { if generation == 0 { return self.seed; } let mut seed = self.seed; seed[8..16].copy_from_slice(&generation.to_le_bytes()); seed } pub(super) fn prepare(&self) { setup(); if self.descriptor.is_rotating() { let sponsor = AccountId::from(pair(70).public()); fund(&sponsor); assert_ok!(HashedAccounts::register( RuntimeOrigin::signed(sponsor), self.descriptor.into() )); } fund(&self.account); } pub(super) fn sign(&self, call: RuntimeCall) -> UncheckedExtrinsic { self.sign_nonce(call, System::account_nonce(&self.account)) } fn sign_nonce(&self, call: RuntimeCall, nonce: u32) -> UncheckedExtrinsic { let generation = MlDsaAccounts::::get(&self.account).map_or(0, |record| record.generation); self.sign_with_extra(call, extra(nonce), generation) } fn sign_with_extra( &self, call: RuntimeCall, extra: TxExtension, generation: u64, ) -> UncheckedExtrinsic { let profile = self.descriptor.algorithm; let seed = self.generation_seed(generation); let next_commitment = if self.descriptor.is_stateless() { self.descriptor.initial_commitment } else { falcon::commitment( profile, &falcon::public_key_from_seed(profile, &self.generation_seed(generation + 1)) .unwrap(), ) .unwrap() }; let header = falcon::Header { account: *self.account.as_ref(), version: falcon::VERSION, algorithm: profile, generation, public_key: falcon::public_key_from_seed(profile, &seed).unwrap(), next_commitment, }; let payload = (&call, &extra, extra.implicit().unwrap()).encode(); let proof = header.sign(&seed, &payload, &[1; 32]).unwrap(); UncheckedExtrinsic::new_signed( call, Address::Raw(proof.encode()), Signature::Ed25519(sp_core::ed25519::Signature::from_raw( mldsa::CARRIER_SIGNATURE, )), extra, ) } } fn setup() { System::set_block_number(1); pallet_admin_utils::HashedAccountsEnabled::::put(true); hashed_auth::TestVerificationWeight::set(Weight::from_parts(10_000_000, 0)); } fn fund(account: &AccountId) { let _ = Balances::make_free_balance_be(account, TaoBalance::new(1_000_000_000_000_000)); } fn transfer(destination: &AccountId, amount: u64) -> RuntimeCall { RuntimeCall::Balances(BalancesCall::transfer_keep_alive { dest: destination.clone().into(), value: TaoBalance::new(amount), }) } fn assert_registrationless(account: &AccountId) { assert!(!MlDsaAccounts::::contains_key(account)); assert!(!pallet_hashed_accounts::Accounts::::contains_key( account )); assert!(!DerivedAuthorities::::contains_key(account)); assert!(!EvmAliases::::contains_key( HashedAccounts::evm_alias(account) )); assert_eq!(Balances::reserved_balance(account), TaoBalance::new(0)); } #[test] fn every_falcon_profile_roundtrips_legacy_v4_and_executes_transfers_once() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(151, profile); wallet.prepare(); let destination = AccountId::new([152; 32]); let tx = wallet.sign(transfer(&destination, 1_000_000_000)); let wire = tx.encode(); assert_eq!( LegacyUnchecked::decode(&mut wire.as_slice()) .unwrap() .encode(), wire ); assert_eq!( UncheckedExtrinsic::decode(&mut wire.as_slice()).unwrap(), tx ); assert_ok!(Executive::apply_extrinsic(tx.clone()).unwrap()); assert_eq!( Balances::free_balance(&destination), TaoBalance::new(1_000_000_000) ); let before = System::account_nonce(&wallet.account); assert!(Executive::apply_extrinsic(tx).is_err()); assert_eq!(System::account_nonce(&wallet.account), before); assert_ok!(Executive::apply_extrinsic(wallet.sign(call())).unwrap()); if wallet.descriptor.is_rotating() { assert_eq!( MlDsaAccounts::::get(&wallet.account) .unwrap() .generation, 2 ); } else { assert_registrationless(&wallet.account); } }); } } #[test] fn falcon_pause_and_unmeasured_weight_gate_reject_before_dispatch() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(153, profile); wallet.prepare(); let tx = wallet.sign(call()); let before = System::account_nonce(&wallet.account); pallet_admin_utils::HashedAccountsEnabled::::put(false); assert!(Executive::apply_extrinsic(tx.clone()).is_err()); pallet_admin_utils::HashedAccountsEnabled::::put(true); hashed_auth::TestVerificationWeight::set(Weight::MAX); assert!(Executive::apply_extrinsic(tx.clone()).is_err()); assert_eq!(System::account_nonce(&wallet.account), before); hashed_auth::TestVerificationWeight::set(Weight::from_parts(10_000_000, 0)); assert_ok!(Executive::apply_extrinsic(tx).unwrap()); }); } } #[test] fn falcon_rejects_other_genesis_nonce_and_call_without_consuming_authority() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(155, profile); wallet.prepare(); let tx = wallet.sign(call()); let before = frame_system::Account::::get(&wallet.account); let authority = MlDsaAccounts::::get(&wallet.account); frame_system::BlockHash::::insert(0, H256::repeat_byte(0x42)); assert!(Executive::apply_extrinsic(tx.clone()).is_err()); frame_system::BlockHash::::insert(0, H256::zero()); let mut wrong_nonce = tx.clone(); let UncheckedExtrinsic::Legacy(legacy) = &mut wrong_nonce; let sp_runtime::generic::Preamble::Signed(_, _, extension) = &mut legacy.0.preamble else { panic!("Falcon fixture must be signed"); }; extension.0.0.5 = check_nonce::CheckNonce::from(before.nonce + 1); assert!(Executive::apply_extrinsic(wrong_nonce).is_err()); let mut wrong_call = tx.clone(); let UncheckedExtrinsic::Legacy(legacy) = &mut wrong_call; legacy.0.function = transfer(&AccountId::new([156; 32]), 1_000_000_000); assert!(Executive::apply_extrinsic(wrong_call).is_err()); assert_eq!( frame_system::Account::::get(&wallet.account), before ); assert_eq!(MlDsaAccounts::::get(&wallet.account), authority); assert_ok!(Executive::apply_extrinsic(tx).unwrap()); }); } } #[test] fn falcon_included_failure_consumes_nonce_and_only_rotating_authority_advances() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(157, profile); wallet.prepare(); let destination = AccountId::new([158; 32]); let nonce = System::account_nonce(&wallet.account); let tx = wallet.sign(transfer(&destination, u64::MAX)); assert!(Executive::apply_extrinsic(tx.clone()).unwrap().is_err()); assert_eq!(Balances::free_balance(&destination), TaoBalance::new(0)); assert_eq!(System::account_nonce(&wallet.account), nonce + 1); if wallet.descriptor.is_rotating() { assert_eq!( MlDsaAccounts::::get(&wallet.account) .unwrap() .generation, 1 ); } else { assert_registrationless(&wallet.account); } assert!(Executive::apply_extrinsic(tx).is_err()); assert_eq!(System::account_nonce(&wallet.account), nonce + 1); assert_ok!(Executive::apply_extrinsic(wallet.sign(call())).unwrap()); assert_eq!(System::account_nonce(&wallet.account), nonce + 2); }); } } #[test] fn falcon_unaffordable_tip_preserves_authority_and_allows_the_same_proof_after_funding() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(160, profile); wallet.prepare(); let nonce = System::account_nonce(&wallet.account); let mut extension = extra(nonce); extension.1.0 = transaction_payment_wrapper::ChargeTransactionPaymentWrapper::new( TaoBalance::new(2_000_000_000_000_000), ); let tx = wallet.sign_with_extra(call(), extension, 0); let before = frame_system::Account::::get(&wallet.account); let authority = MlDsaAccounts::::get(&wallet.account); assert_eq!( Executive::apply_extrinsic(tx.clone()), Err(InvalidTransaction::Payment.into()) ); assert_eq!( frame_system::Account::::get(&wallet.account), before ); assert_eq!(MlDsaAccounts::::get(&wallet.account), authority); let _ = Balances::make_free_balance_be( &wallet.account, TaoBalance::new(4_000_000_000_000_000), ); assert_ok!(Executive::apply_extrinsic(tx).unwrap()); assert_eq!(System::account_nonce(&wallet.account), nonce + 1); if wallet.descriptor.is_rotating() { assert_eq!( MlDsaAccounts::::get(&wallet.account) .unwrap() .generation, 1 ); } else { assert_registrationless(&wallet.account); } }); } } #[test] fn falcon_exhausted_native_nonce_cannot_wrap_charge_or_rotate_any_profile() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(161, profile); wallet.prepare(); frame_system::Account::::mutate(&wallet.account, |account| { account.nonce = u32::MAX; }); let before = frame_system::Account::::get(&wallet.account); let authority = MlDsaAccounts::::get(&wallet.account); let tx = wallet.sign_nonce(call(), u32::MAX); assert!( Executive::validate_transaction( TransactionSource::External, tx.clone(), System::parent_hash(), ) .is_err() ); assert_eq!( frame_system::Account::::get(&wallet.account), before ); assert_eq!(MlDsaAccounts::::get(&wallet.account), authority); assert!(Executive::apply_extrinsic(tx).is_err()); assert_eq!( frame_system::Account::::get(&wallet.account), before ); assert_eq!(MlDsaAccounts::::get(&wallet.account), authority); }); } } #[test] fn falcon_malformed_envelopes_cannot_charge_fees_or_consume_authority() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(159, profile); wallet.prepare(); let tx = wallet.sign(call()); let before = frame_system::Account::::get(&wallet.account); let authority = MlDsaAccounts::::get(&wallet.account); for mutation in 0..6 { let mut bad = tx.clone(); let UncheckedExtrinsic::Legacy(legacy) = &mut bad; let sp_runtime::generic::Preamble::Signed(Address::Raw(bytes), signature, _) = &mut legacy.0.preamble else { panic!("Falcon fixture must carry a raw proof"); }; match mutation { 0 => { bytes.pop(); } 1 => bytes.push(0), 2 => *bytes.last_mut().unwrap() ^= 1, 3 => { *signature = Signature::Ed25519(sp_core::ed25519::Signature::from_raw([1; 64])); } 4 => { *signature = Signature::Sr25519(sp_core::sr25519::Signature::from_raw([0; 64])); } _ => bytes[33] = u8::MAX, } assert!(Executive::apply_extrinsic(bad).is_err()); assert_eq!( frame_system::Account::::get(&wallet.account), before ); assert_eq!(MlDsaAccounts::::get(&wallet.account), authority); } assert_ok!(Executive::apply_extrinsic(tx).unwrap()); }); } } #[test] fn draining_and_refunding_cannot_reset_consumed_native_nonces() { for profile in [falcon::FIXED_ALGORITHM_512, falcon::FIXED_ALGORITHM_1024] { ext().execute_with(|| { setup(); let wallet = Wallet::new(85, profile); fund(&wallet.account); let first = wallet.sign(call()); assert_ok!(Executive::apply_extrinsic(first.clone()).unwrap()); let drain = wallet.sign(RuntimeCall::Balances(BalancesCall::transfer_all { dest: AccountId::new([86; 32]).into(), keep_alive: false, })); assert_ok!(Executive::apply_extrinsic(drain.clone()).unwrap()); // Remove any fee refund too, exercising balances' complete reap path. assert_ok!(Balances::force_set_balance( RuntimeOrigin::root(), wallet.account.clone().into(), TaoBalance::new(0), )); assert_eq!(Balances::free_balance(&wallet.account), TaoBalance::new(0)); assert_eq!(System::account_nonce(&wallet.account), 2); assert!(frame_system::Account::::contains_key( &wallet.account )); fund(&wallet.account); let refunded = Balances::free_balance(&wallet.account); for consumed in [first, drain] { assert!(Executive::apply_extrinsic(consumed).is_err()); assert_eq!(System::account_nonce(&wallet.account), 2); assert_eq!(Balances::free_balance(&wallet.account), refunded); } assert_ok!(Executive::apply_extrinsic(wallet.sign(call())).unwrap()); assert_eq!(System::account_nonce(&wallet.account), 3); assert_registrationless(&wallet.account); }); } } #[test] fn consecutive_fixed_transactions_have_independent_pool_nonce_tags() { for profile in [falcon::FIXED_ALGORITHM_512, falcon::FIXED_ALGORITHM_1024] { ext().execute_with(|| { setup(); let wallet = Wallet::new(104, profile); fund(&wallet.account); let first = wallet.sign_nonce(call(), 0); let second = wallet.sign_nonce(call(), 1); let validate = |tx| { Executive::validate_transaction( sp_runtime::transaction_validity::TransactionSource::External, tx, System::parent_hash(), ) .unwrap() }; let first_validity = validate(first.clone()); let second_validity = validate(second.clone()); let first_tag = (&wallet.account, 0u32).encode(); let second_tag = (&wallet.account, 1u32).encode(); let obsolete_generation_tag = (b"hashed-generation", &wallet.account, 0u64).encode(); assert!(first_validity.provides.contains(&first_tag)); assert!(second_validity.provides.contains(&second_tag)); assert!(second_validity.requires.contains(&first_tag)); assert!(!first_validity.provides.contains(&obsolete_generation_tag)); assert!(!second_validity.provides.contains(&obsolete_generation_tag)); assert_eq!(System::account_nonce(&wallet.account), 0); let before = frame_system::Account::::get(&wallet.account); assert!(Executive::apply_extrinsic(second.clone()).is_err()); // Authorization prepares a sufficient reference before CheckNonce rejects // this future transaction. The failed application must roll it all back. assert_eq!( frame_system::Account::::get(&wallet.account), before ); assert_registrationless(&wallet.account); assert_ok!(Executive::apply_extrinsic(first).unwrap()); assert_ok!(Executive::apply_extrinsic(second).unwrap()); assert_eq!(System::account_nonce(&wallet.account), 2); assert_registrationless(&wallet.account); }); } } #[test] fn ordinary_classical_proxy_can_be_granted_and_execute() { for profile in [falcon::FIXED_ALGORITHM_512, falcon::FIXED_ALGORITHM_1024] { ext().execute_with(|| { setup(); let wallet = Wallet::new(90, profile); fund(&wallet.account); let delegate = pair(91); let delegate_account = AccountId::from(delegate.public()); fund(&delegate_account); assert_ok!( Executive::apply_extrinsic(wallet.sign(RuntimeCall::Proxy( pallet_proxy::Call::add_proxy { delegate: delegate_account.clone().into(), proxy_type: ProxyType::Any, delay: 0, }, ))) .unwrap() ); let destination = AccountId::new([92; 32]); assert_ok!( Executive::apply_extrinsic(legacy( &delegate, RuntimeCall::Proxy(pallet_proxy::Call::proxy { real: wallet.account.clone().into(), force_proxy_type: Some(ProxyType::Any), call: Box::new(transfer(&destination, 1_000_000_000)), }), )) .unwrap() ); assert_eq!( Balances::free_balance(destination), TaoBalance::new(1_000_000_000) ); assert_eq!(System::account_nonce(&wallet.account), 1); assert!(!MlDsaAccounts::::contains_key(&wallet.account)); assert!(!HashedAccounts::is_protected(&wallet.account)); }); } } #[test] fn safe_falcon_ms_multisig_requires_the_quorum_without_registration() { for profile in [falcon::FIXED_ALGORITHM_512, falcon::FIXED_ALGORITHM_1024] { ext().execute_with(|| { setup(); let first = Wallet::new(121, profile); let second = super::mldsa::stateless::Wallet::new(122); fund(&first.account); fund(&second.account); let mut members = vec![first.account.clone(), second.account.clone()]; members.sort(); let composite = Multisig::safe_multi_account_id(&members, 2).unwrap(); assert!(mldsa::rejects_legacy_public_keys(composite.as_ref())); assert_ne!(composite, Multisig::multi_account_id(&members, 2)); fund(&composite); let destination = AccountId::new([123; 32]); let inner = transfer(&destination, 1_000_000_000); let hash = sp_io::hashing::blake2_256(&inner.encode()); let max_weight = inner.get_dispatch_info().total_weight(); assert_ok!( Executive::apply_extrinsic(first.sign(RuntimeCall::Multisig( pallet_multisig::Call::approve_as_multi_safe { threshold: 2, other_signatories: vec![second.account.clone()], maybe_timepoint: None, call_hash: hash, max_weight, }, ))) .unwrap() ); let pending = pallet_multisig::Multisigs::::get(&composite, hash).unwrap(); assert_eq!( pending.approvals.as_slice(), core::slice::from_ref(&first.account) ); assert_eq!(Balances::free_balance(&destination), TaoBalance::new(0)); assert_eq!(Balances::reserved_balance(&first.account), pending.deposit); assert_ok!( Executive::apply_extrinsic(second.sign(RuntimeCall::Multisig( pallet_multisig::Call::as_multi_safe { threshold: 2, other_signatories: vec![first.account.clone()], maybe_timepoint: Some(pending.when), call: Box::new(inner), max_weight, }, ))) .unwrap() ); assert_eq!( Balances::free_balance(&destination), TaoBalance::new(1_000_000_000) ); assert!(!pallet_multisig::Multisigs::::contains_key( &composite, hash )); for account in [&first.account, &second.account, &composite] { assert_registrationless(account); } }); } } #[test] fn safe_falcon_derivative_and_pure_proxy_use_full_width_authority() { for profile in [falcon::FIXED_ALGORITHM_512, falcon::FIXED_ALGORITHM_1024] { ext().execute_with(|| { setup(); let wallet = Wallet::new(124, profile); fund(&wallet.account); let child = Utility::safe_derivative_account_id(wallet.account.clone(), 7).unwrap(); assert!(mldsa::rejects_legacy_public_keys(child.as_ref())); fund(&child); let destination = AccountId::new([125; 32]); assert_ok!( Executive::apply_extrinsic(wallet.sign(RuntimeCall::Utility( pallet_utility::Call::as_safe_derivative { index: 7, call: Box::new(transfer(&destination, 1_000_000_000)), }, ))) .unwrap() ); assert_eq!( Balances::free_balance(&destination), TaoBalance::new(1_000_000_000) ); assert_registrationless(&child); let height = System::block_number(); let ext_index = System::extrinsic_index().unwrap_or_default(); let pure = Proxy::safe_pure_account( &wallet.account, &ProxyType::Any, 8, Some((height, ext_index)), ) .unwrap(); assert!(mldsa::rejects_legacy_public_keys(pure.as_ref())); assert_ok!( Executive::apply_extrinsic(wallet.sign(RuntimeCall::Proxy( pallet_proxy::Call::create_safe_pure { proxy_type: ProxyType::Any, delay: 0, index: 8, }, ))) .unwrap() ); let (delegates, deposit) = Proxy::proxies(pure.clone()); assert_eq!(delegates[0].delegate, wallet.account); assert_eq!(Balances::reserved_balance(&wallet.account), deposit); assert!(!DerivedAuthorities::::contains_key(&pure)); assert!(!EvmAliases::::contains_key( HashedAccounts::evm_alias(&pure) )); fund(&pure); assert_ok!( Executive::apply_extrinsic(wallet.sign(RuntimeCall::Proxy( pallet_proxy::Call::proxy { real: pure.clone().into(), force_proxy_type: Some(ProxyType::Any), call: Box::new(transfer(&destination, 1_000_000_000)), }, ))) .unwrap() ); assert_eq!( Balances::free_balance(&destination), TaoBalance::new(2_000_000_000) ); // Move funds before killing the pure, just as for the legacy lifecycle. assert_ok!( Executive::apply_extrinsic(wallet.sign(RuntimeCall::Proxy( pallet_proxy::Call::proxy { real: pure.clone().into(), force_proxy_type: Some(ProxyType::Any), call: Box::new(RuntimeCall::Balances(BalancesCall::transfer_all { dest: wallet.account.clone().into(), keep_alive: false, })), }, ))) .unwrap() ); assert_ok!( Executive::apply_extrinsic(wallet.sign(RuntimeCall::Proxy( pallet_proxy::Call::proxy { real: pure.clone().into(), force_proxy_type: Some(ProxyType::Any), call: Box::new(RuntimeCall::Proxy(pallet_proxy::Call::kill_safe_pure { spawner: wallet.account.clone().into(), proxy_type: ProxyType::Any, index: 8, height, ext_index, })), }, ))) .unwrap() ); assert!(Proxy::proxies(pure.clone()).0.is_empty()); assert_registrationless(&wallet.account); assert_registrationless(&pure); }); } } #[test] fn native_evm_keeps_ordinary_separate_backing_balance_and_nonce() { use pallet_evm::{AddressMapping, FeeCalculator}; use sp_core::{H160, U256}; for profile in [falcon::FIXED_ALGORITHM_512, falcon::FIXED_ALGORITHM_1024] { ext().execute_with(|| { setup(); let wallet = Wallet::new(98, profile); fund(&wallet.account); let source = H160::from_slice(&AsRef::<[u8; 32]>::as_ref(&wallet.account)[..20]); let backing = evm_origin::HashedAddressMapping::into_account_id(source); assert_eq!( backing, pallet_evm::HashedAddressMapping::::into_account_id( source ) ); assert_ne!(backing, wallet.account); // Ordinary native funding of the historical EVM backing address. assert_ok!( Executive::apply_extrinsic(wallet.sign(transfer(&backing, 10_000_000_000))) .unwrap() ); assert_eq!(System::account_nonce(&wallet.account), 1); assert_eq!(EVM::account_basic(&source).0.nonce, U256::zero()); let target = H160::repeat_byte(0x42); let destination = evm_origin::HashedAddressMapping::into_account_id(target); let before = Balances::free_balance(&backing); pallet_evm::DisableWhitelistCheck::::put(true); assert_ok!( Executive::apply_extrinsic(wallet.sign(RuntimeCall::EVM(pallet_evm::Call::call { source, target, input: vec![], value: U256::from(1_000_000_000_u64) * U256::from(EVM_TO_SUBSTRATE_DECIMALS), gas_limit: 100_000, max_fee_per_gas: ::FeeCalculator::min_gas_price().0, max_priority_fee_per_gas: None, nonce: Some(U256::zero()), access_list: vec![], authorization_list: vec![], },))) .unwrap() ); assert_eq!( Balances::free_balance(destination), TaoBalance::new(1_000_000_000) ); assert!(Balances::free_balance(&backing) < before - TaoBalance::new(1_000_000_000)); assert_eq!(System::account_nonce(&wallet.account), 2); assert_eq!(EVM::account_basic(&source).0.nonce, U256::one()); assert!(evm_origin::HashedAddressMapping::is_ethereum_authority_allowed(source)); assert_registrationless(&wallet.account); }); } } #[test] fn falcon_shield_carrier_and_inner_preserve_nonce_and_rotation_order() { for profile in [6, 7, 8, 9] { ext().execute_with(|| { let wallet = Wallet::new(154, profile); wallet.prepare(); frame_system::BlockHash::::insert(1, H256::repeat_byte(1)); let nonce = System::account_nonce(&wallet.account); let mut outer_extra = extra(nonce); outer_extra.0.4 = check_mortality::CheckMortality::from(Era::mortal(8, 1)); let mut inner_extra = extra(nonce + 1); inner_extra.0.4 = check_mortality::CheckMortality::from(Era::mortal(8, 1)); let inner = wallet.sign_with_extra( call(), inner_extra, >::from(wallet.descriptor.is_rotating()), ); let mut ciphertext = vec![0xab; 16]; ciphertext.extend_from_slice(&1088u16.to_le_bytes()); ciphertext.extend_from_slice(&[0xcc; 1088]); ciphertext.extend_from_slice(&[0xdd; 24]); ciphertext.extend_from_slice(&inner.encode()); ciphertext.extend_from_slice(&[0xee; 16]); let carrier = RuntimeCall::MevShield(pallet_shield::Call::submit_encrypted { ciphertext: ciphertext.try_into().unwrap(), }); let outer = wallet.sign_with_extra(carrier, outer_extra, 0); assert!(hashed_extrinsic::shielded_transaction(outer.clone()).is_some()); assert!(Executive::apply_extrinsic(inner.clone()).is_err()); assert_ok!(Executive::apply_extrinsic(outer.clone()).unwrap()); assert_ok!(Executive::apply_extrinsic(inner.clone()).unwrap()); assert_eq!(System::account_nonce(&wallet.account), nonce + 2); assert!(Executive::apply_extrinsic(outer).is_err()); assert!(Executive::apply_extrinsic(inner).is_err()); if wallet.descriptor.is_rotating() { assert_eq!( MlDsaAccounts::::get(&wallet.account) .unwrap() .generation, 2 ); } else { assert_registrationless(&wallet.account); } }); } }