code/pallets/proxy/src/safe_tests.rs

safe_tests.rs

437 lines · 13,668 bytes · d967d13588RawGitHub
#![allow(clippy::unwrap_used, clippy::indexing_slicing)]

use super::*;
use frame::deps::{
    sp_io,
    sp_runtime::{AccountId32, traits::IdentityLookup},
};
use frame::testing_prelude::*;
use pallet_subtensor_utility as utility;

type Block = frame_system::mocking::MockBlock<Test>;
construct_runtime!(
    pub enum Test {
        System: frame_system,
        Balances: pallet_balances,
        Proxy: crate,
        Utility: utility,
    }
);

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
    type Block = Block;
    type AccountId = AccountId32;
    type Lookup = IdentityLookup<Self::AccountId>;
    type AccountData = pallet_balances::AccountData<u64>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
    type AccountStore = System;
}
impl utility::Config for Test {
    type RuntimeCall = RuntimeCall;
    type PalletsOrigin = OriginCaller;
    type WeightInfo = ();
    type DerivedAccountPolicy = ();
    type SafeDerivationWeight = ();
}

#[derive(
    Clone,
    Copy,
    Default,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Encode,
    Decode,
    DecodeWithMemTracking,
    Debug,
    MaxEncodedLen,
    TypeInfo,
)]
pub enum ProxyType {
    #[default]
    Any,
    UtilityOrProxy,
}
impl InstanceFilter<RuntimeCall> for ProxyType {
    fn filter(&self, call: &RuntimeCall) -> bool {
        *self == Self::Any || matches!(call, RuntimeCall::Utility(_) | RuntimeCall::Proxy(_))
    }
    fn is_superset(&self, other: &Self) -> bool {
        *self == Self::Any || self == other
    }
}

// Safe creation is explicit and is available even to protected parents.
pub struct ProtectedParent;
impl Contains<(AccountId32, AccountId32)> for ProtectedParent {
    fn contains(_: &(AccountId32, AccountId32)) -> bool {
        true
    }
}
impl ProxyAccountPolicy<AccountId32> for ProtectedParent {
    fn requires_protection(_: &AccountId32) -> bool {
        true
    }
}
impl Config for Test {
    type RuntimeCall = RuntimeCall;
    type Currency = Balances;
    type AccountPolicy = ProtectedParent;
    type ProxyType = ProxyType;
    type ProxyDepositBase = ConstU64<1>;
    type ProxyDepositFactor = ConstU64<1>;
    type MaxProxies = ConstU32<4>;
    type WeightInfo = ();
    type SafeDerivationWeight = ();
    type MaxPending = ConstU32<4>;
    type CallHasher = BlakeTwo256;
    type AnnouncementDepositBase = ConstU64<1>;
    type AnnouncementDepositFactor = ConstU64<1>;
    type BlockNumberProvider = System;
}

fn account(n: u8) -> AccountId32 {
    AccountId32::new([n; 32])
}
fn ext() -> sp_io::TestExternalities {
    let mut storage = frame_system::GenesisConfig::<Test>::default()
        .build_storage()
        .unwrap();
    pallet_balances::GenesisConfig::<Test> {
        balances: vec![(account(1), 100), (account(2), 100), (account(3), 100)],
        ..Default::default()
    }
    .assimilate_storage(&mut storage)
    .unwrap();
    let mut ext = sp_io::TestExternalities::new(storage);
    ext.execute_with(|| System::set_block_number(1));
    ext
}
fn kill(spawner: AccountId32, height: u64, ext_index: u32) -> Box<RuntimeCall> {
    Box::new(RuntimeCall::Proxy(Call::kill_safe_pure {
        spawner,
        proxy_type: ProxyType::Any,
        index: 7,
        height,
        ext_index,
    }))
}

#[test]
fn safe_pure_preserves_legacy_derivation_and_call_bytes() {
    ext().execute_with(|| {
        let parent = account(1);
        let legacy = Proxy::pure_account(&parent, &ProxyType::Any, 7, None).unwrap();
        assert_eq!(
            legacy,
            AccountId32::new([
                127, 213, 69, 74, 56, 92, 228, 66, 210, 7, 92, 2, 22, 112, 104, 103, 167, 55, 154,
                146, 183, 240, 241, 21, 202, 118, 227, 147, 47, 143, 120, 171,
            ])
        );
        let safe = Proxy::safe_pure_account(&parent, &ProxyType::Any, 7, None).unwrap();
        assert_ne!(safe, legacy);
        assert!(subtensor_hashed::mldsa::rejects_legacy_public_keys(
            safe.as_ref()
        ));
        assert_ne!(
            safe,
            Proxy::safe_pure_account(&parent, &ProxyType::Any, 7, Some((1, 1))).unwrap()
        );
        assert_ne!(
            safe,
            Proxy::safe_pure_account(&parent, &ProxyType::Any, 7, Some((2, 0))).unwrap()
        );
        let create = Call::<Test>::create_pure {
            proxy_type: ProxyType::Any,
            delay: 0,
            index: 7,
        };
        assert_eq!(
            create.encode(),
            [vec![4], (ProxyType::Any, 0u64, 7u16).encode()].concat()
        );
        let old_kill = Call::<Test>::kill_pure {
            spawner: parent.clone(),
            proxy_type: ProxyType::Any,
            index: 7,
            height: 1,
            ext_index: 0,
        };
        assert_eq!(
            old_kill.encode(),
            [
                vec![5],
                (
                    parent,
                    ProxyType::Any,
                    7u16,
                    codec::Compact(1u64),
                    codec::Compact(0u32)
                )
                    .encode()
            ]
            .concat()
        );
        assert_eq!(
            Call::<Test>::create_protected_pure {
                proxy_type: ProxyType::Any,
                delay: 0,
                index: 7
            }
            .encode()[0],
            12
        );
        assert_eq!(
            Call::<Test>::create_safe_pure {
                proxy_type: ProxyType::Any,
                delay: 0,
                index: 7
            }
            .encode()[0],
            13
        );
        assert_eq!(
            Call::<Test>::kill_safe_pure {
                spawner: account(1),
                proxy_type: ProxyType::Any,
                index: 7,
                height: 1,
                ext_index: 0
            }
            .encode()[0],
            14
        );
    });
}

#[test]
fn safe_pure_lifecycle_refunds_only_proxy_deposit_and_does_not_recreate_old_address() {
    ext().execute_with(|| {
        System::set_extrinsic_index(3);
        let parent = account(1);
        let pure = Proxy::safe_pure_account(&parent, &ProxyType::Any, 7, None).unwrap();
        assert_ok!(Proxy::create_safe_pure(
            RuntimeOrigin::signed(parent.clone()),
            ProxyType::Any,
            0,
            7
        ));
        assert_eq!(Balances::reserved_balance(&parent), 2);
        assert_eq!(Proxies::<Test>::get(&pure).1, 2);
        assert_noop!(
            Proxy::create_safe_pure(RuntimeOrigin::signed(parent.clone()), ProxyType::Any, 0, 7),
            Error::<Test>::Duplicate
        );
        assert_noop!(
            Proxy::kill_safe_pure(
                RuntimeOrigin::signed(pure.clone()),
                parent.clone(),
                ProxyType::Any,
                7,
                1,
                4
            ),
            Error::<Test>::NoPermission
        );
        assert_noop!(
            Proxy::kill_pure(
                RuntimeOrigin::signed(pure.clone()),
                parent.clone(),
                ProxyType::Any,
                7,
                1,
                3
            ),
            Error::<Test>::NoPermission
        );
        let _ = Balances::make_free_balance_be(&pure, 10);
        assert_ok!(Proxy::proxy(
            RuntimeOrigin::signed(parent.clone()),
            pure.clone(),
            None,
            kill(parent.clone(), 1, 3)
        ));
        assert!(!Proxies::<Test>::contains_key(&pure));
        assert_eq!(Balances::reserved_balance(&parent), 0);
        assert_eq!(Balances::free_balance(&parent), 100);
        assert_eq!(Balances::free_balance(&pure), 10);
        System::set_block_number(2);
        System::set_extrinsic_index(0);
        let next = Proxy::safe_pure_account(&parent, &ProxyType::Any, 7, None).unwrap();
        assert_ne!(next, pure);
        assert_ok!(Proxy::create_safe_pure(
            RuntimeOrigin::signed(parent.clone()),
            ProxyType::Any,
            0,
            7
        ));
        assert_noop!(
            Proxy::proxy(
                RuntimeOrigin::signed(parent),
                pure,
                None,
                Box::new(RuntimeCall::System(frame_system::Call::remark {
                    remark: vec![]
                }))
            ),
            Error::<Test>::NotProxy
        );
        assert!(Proxies::<Test>::contains_key(next));
    });
}

#[test]
fn safe_children_and_nested_proxies_keep_restricted_origin_filters() {
    ext().execute_with(|| {
        let parent = account(1);
        let delegate = account(2);
        let recipient = account(4);
        assert_ok!(Proxy::create_safe_pure(
            RuntimeOrigin::signed(parent.clone()),
            ProxyType::Any,
            0,
            7
        ));
        let pure = Proxy::safe_pure_account(&parent, &ProxyType::Any, 7, None).unwrap();
        let _ = Balances::make_free_balance_be(&pure, 100);
        assert_ok!(Proxy::add_proxy(
            RuntimeOrigin::signed(pure.clone()),
            delegate.clone(),
            ProxyType::UtilityOrProxy,
            0
        ));
        let child = Utility::safe_derivative_account_id(pure.clone(), 5).unwrap();
        let _ = Balances::make_free_balance_be(&child, 10);
        let transfer = Box::new(RuntimeCall::Balances(
            pallet_balances::Call::transfer_allow_death {
                dest: recipient.clone(),
                value: 3,
            },
        ));
        let derivative = Box::new(RuntimeCall::Utility(utility::Call::as_safe_derivative {
            index: 5,
            call: transfer.clone(),
        }));
        assert_ok!(Proxy::proxy(
            RuntimeOrigin::signed(delegate.clone()),
            pure.clone(),
            None,
            derivative
        ));
        assert_eq!(
            LastCallResult::<Test>::get(&pure),
            Some(Err(frame_system::Error::<Test>::CallFiltered.into()))
        );
        assert_eq!(Balances::free_balance(&child), 10);
        assert_eq!(Balances::free_balance(&recipient), 0);

        // The outer restricted filter also survives a nested proxy dispatch.
        assert_ok!(Proxy::create_safe_pure(
            RuntimeOrigin::signed(pure.clone()),
            ProxyType::Any,
            0,
            8
        ));
        let nested = Proxy::safe_pure_account(&pure, &ProxyType::Any, 8, None).unwrap();
        let _ = Balances::make_free_balance_be(&nested, 10);
        let nested_call = Box::new(RuntimeCall::Proxy(Call::proxy {
            real: nested.clone(),
            force_proxy_type: None,
            call: transfer.clone(),
        }));
        assert_ok!(Proxy::proxy(
            RuntimeOrigin::signed(delegate),
            pure.clone(),
            None,
            nested_call
        ));
        assert_eq!(
            LastCallResult::<Test>::get(&nested),
            Some(Err(frame_system::Error::<Test>::CallFiltered.into()))
        );
        assert_eq!(Balances::free_balance(&nested), 10);
        assert_ok!(Proxy::proxy(
            RuntimeOrigin::signed(parent),
            pure.clone(),
            None,
            Box::new(RuntimeCall::Proxy(Call::proxy {
                real: nested,
                force_proxy_type: None,
                call: transfer
            }))
        ));
        assert_eq!(Balances::free_balance(recipient), 3);
    });
}

#[test]
fn restricted_direct_and_announced_proxies_cannot_kill_safe_pure() {
    ext().execute_with(|| {
        let parent = account(1);
        let delegate = account(2);
        assert_ok!(Proxy::create_safe_pure(
            RuntimeOrigin::signed(parent.clone()),
            ProxyType::Any,
            0,
            7
        ));
        let pure = Proxy::safe_pure_account(&parent, &ProxyType::Any, 7, None).unwrap();
        let _ = Balances::make_free_balance_be(&pure, 100);
        assert_ok!(Proxy::add_proxy(
            RuntimeOrigin::signed(pure.clone()),
            delegate.clone(),
            ProxyType::UtilityOrProxy,
            0
        ));
        let call = kill(parent.clone(), 1, 0);
        assert_ok!(Proxy::proxy(
            RuntimeOrigin::signed(delegate.clone()),
            pure.clone(),
            None,
            call.clone()
        ));
        assert_eq!(
            LastCallResult::<Test>::get(&pure),
            Some(Err(frame_system::Error::<Test>::CallFiltered.into()))
        );
        assert!(Proxies::<Test>::contains_key(&pure));
        assert_eq!(Balances::reserved_balance(&parent), 2);
        assert_ok!(Proxy::remove_proxy(
            RuntimeOrigin::signed(pure.clone()),
            delegate.clone(),
            ProxyType::UtilityOrProxy,
            0
        ));
        assert_ok!(Proxy::add_proxy(
            RuntimeOrigin::signed(pure.clone()),
            delegate.clone(),
            ProxyType::UtilityOrProxy,
            2
        ));
        assert_ok!(Proxy::announce(
            RuntimeOrigin::signed(delegate.clone()),
            pure.clone(),
            BlakeTwo256::hash_of(&call)
        ));
        System::set_block_number(3);
        assert_ok!(Proxy::proxy_announced(
            RuntimeOrigin::signed(account(3)),
            delegate,
            pure.clone(),
            None,
            call
        ));
        assert_eq!(
            LastCallResult::<Test>::get(&pure),
            Some(Err(frame_system::Error::<Test>::CallFiltered.into()))
        );
        assert!(Proxies::<Test>::contains_key(pure));
        assert_eq!(Balances::reserved_balance(parent), 2);
    });
}