code/runtime/src/falcon_tests.rs

falcon_tests.rs

781 lines · 33,382 bytes · d967d13588RawGitHub
//! 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::<Runtime>::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::<Runtime>::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::<Runtime>::contains_key(account));
    assert!(!pallet_hashed_accounts::Accounts::<Runtime>::contains_key(
        account
    ));
    assert!(!DerivedAuthorities::<Runtime>::contains_key(account));
    assert!(!EvmAliases::<Runtime>::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::<Runtime>::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::<Runtime>::put(false);
            assert!(Executive::apply_extrinsic(tx.clone()).is_err());
            pallet_admin_utils::HashedAccountsEnabled::<Runtime>::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::<Runtime>::get(&wallet.account);
            let authority = MlDsaAccounts::<Runtime>::get(&wallet.account);

            frame_system::BlockHash::<Runtime>::insert(0, H256::repeat_byte(0x42));
            assert!(Executive::apply_extrinsic(tx.clone()).is_err());
            frame_system::BlockHash::<Runtime>::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::<Runtime>::get(&wallet.account),
                before
            );
            assert_eq!(MlDsaAccounts::<Runtime>::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::<Runtime>::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::<Runtime>::get(&wallet.account);
            let authority = MlDsaAccounts::<Runtime>::get(&wallet.account);
            assert_eq!(
                Executive::apply_extrinsic(tx.clone()),
                Err(InvalidTransaction::Payment.into())
            );
            assert_eq!(
                frame_system::Account::<Runtime>::get(&wallet.account),
                before
            );
            assert_eq!(MlDsaAccounts::<Runtime>::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::<Runtime>::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::<Runtime>::mutate(&wallet.account, |account| {
                account.nonce = u32::MAX;
            });
            let before = frame_system::Account::<Runtime>::get(&wallet.account);
            let authority = MlDsaAccounts::<Runtime>::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::<Runtime>::get(&wallet.account),
                before
            );
            assert_eq!(MlDsaAccounts::<Runtime>::get(&wallet.account), authority);
            assert!(Executive::apply_extrinsic(tx).is_err());
            assert_eq!(
                frame_system::Account::<Runtime>::get(&wallet.account),
                before
            );
            assert_eq!(MlDsaAccounts::<Runtime>::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::<Runtime>::get(&wallet.account);
            let authority = MlDsaAccounts::<Runtime>::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::<Runtime>::get(&wallet.account),
                    before
                );
                assert_eq!(MlDsaAccounts::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::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::<Runtime>::contains_key(&pure));
            assert!(!EvmAliases::<Runtime>::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::<sp_runtime::traits::BlakeTwo256>::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::<Runtime>::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:
                        <Runtime as pallet_evm::Config>::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::<Runtime>::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,
                <u64 as From<bool>>::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::<Runtime>::get(&wallet.account)
                        .unwrap()
                        .generation,
                    2
                );
            } else {
                assert_registrationless(&wallet.account);
            }
        });
    }
}