// This file is part of Substrate. // Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Tests for Proxy Pallet #![cfg(test)] use super::*; use frame_support::{ assert_ok, assert_noop, parameter_types, RuntimeDebug, dispatch::DispatchError, traits::Filter, }; use codec::{Encode, Decode}; use sp_core::H256; use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; use crate as proxy; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Proxy: proxy::{Pallet, Call, Storage, Event}, Utility: pallet_utility::{Pallet, Call, Event}, } ); parameter_types! { pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(1024); } impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; type BlockWeights = (); type BlockLength = (); type DbWeight = (); type Origin = Origin; type Index = u64; type BlockNumber = u64; type Hash = H256; type Call = Call; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Header = Header; type Event = Event; type BlockHashCount = BlockHashCount; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = (); } parameter_types! { pub const ExistentialDeposit: u64 = 1; } impl pallet_balances::Config for Test { type MaxLocks = (); type Balance = u64; type Event = Event; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); } impl pallet_utility::Config for Test { type Event = Event; type Call = Call; type WeightInfo = (); } parameter_types! { pub const ProxyDepositBase: u64 = 1; pub const ProxyDepositFactor: u64 = 1; pub const MaxProxies: u16 = 4; pub const MaxPending: u32 = 2; pub const AnnouncementDepositBase: u64 = 1; pub const AnnouncementDepositFactor: u64 = 1; } #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug)] pub enum ProxyType { Any, JustTransfer, JustUtility, } impl Default for ProxyType { fn default() -> Self { Self::Any } } impl InstanceFilter for ProxyType { fn filter(&self, c: &Call) -> bool { match self { ProxyType::Any => true, ProxyType::JustTransfer => matches!(c, Call::Balances(pallet_balances::Call::transfer(..))), ProxyType::JustUtility => matches!(c, Call::Utility(..)), } } fn is_superset(&self, o: &Self) -> bool { self == &ProxyType::Any || self == o } } pub struct BaseFilter; impl Filter for BaseFilter { fn filter(c: &Call) -> bool { match *c { // Remark is used as a no-op call in the benchmarking Call::System(SystemCall::remark(_)) => true, Call::System(_) => false, _ => true, } } } impl Config for Test { type Event = Event; type Call = Call; type Currency = Balances; type ProxyType = ProxyType; type ProxyDepositBase = ProxyDepositBase; type ProxyDepositFactor = ProxyDepositFactor; type MaxProxies = MaxProxies; type WeightInfo = (); type CallHasher = BlakeTwo256; type MaxPending = MaxPending; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; } use frame_system::Call as SystemCall; use pallet_balances::Call as BalancesCall; use pallet_balances::Error as BalancesError; use pallet_balances::Event as BalancesEvent; use pallet_utility::Call as UtilityCall; use pallet_utility::Event as UtilityEvent; use super::Event as ProxyEvent; use super::Call as ProxyCall; pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], }.assimilate_storage(&mut t).unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext } fn last_event() -> Event { system::Pallet::::events().pop().expect("Event expected").event } fn expect_event>(e: E) { assert_eq!(last_event(), e.into()); } fn last_events(n: usize) -> Vec { system::Pallet::::events().into_iter().rev().take(n).rev().map(|e| e.event).collect() } fn expect_events(e: Vec) { assert_eq!(last_events(e.len()), e); } #[test] fn announcement_works() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); assert_eq!(Balances::reserved_balance(3), 0); assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); assert_eq!(Announcements::::get(3), (vec![Announcement { real: 1, call_hash: [1; 32].into(), height: 1, }], 2)); assert_eq!(Balances::reserved_balance(3), 2); assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); assert_eq!(Announcements::::get(3), (vec![ Announcement { real: 1, call_hash: [1; 32].into(), height: 1, }, Announcement { real: 2, call_hash: [2; 32].into(), height: 1, }, ], 3)); assert_eq!(Balances::reserved_balance(3), 3); assert_noop!(Proxy::announce(Origin::signed(3), 2, [3; 32].into()), Error::::TooMany); }); } #[test] fn remove_announcement_works() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); let e = Error::::NotFound; assert_noop!(Proxy::remove_announcement(Origin::signed(3), 1, [0; 32].into()), e); assert_ok!(Proxy::remove_announcement(Origin::signed(3), 1, [1; 32].into())); assert_eq!(Announcements::::get(3), (vec![Announcement { real: 2, call_hash: [2; 32].into(), height: 1, }], 2)); assert_eq!(Balances::reserved_balance(3), 2); }); } #[test] fn reject_announcement_works() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); assert_ok!(Proxy::announce(Origin::signed(3), 1, [1; 32].into())); assert_ok!(Proxy::announce(Origin::signed(3), 2, [2; 32].into())); let e = Error::::NotFound; assert_noop!(Proxy::reject_announcement(Origin::signed(1), 3, [0; 32].into()), e); let e = Error::::NotFound; assert_noop!(Proxy::reject_announcement(Origin::signed(4), 3, [1; 32].into()), e); assert_ok!(Proxy::reject_announcement(Origin::signed(1), 3, [1; 32].into())); assert_eq!(Announcements::::get(3), (vec![Announcement { real: 2, call_hash: [2; 32].into(), height: 1, }], 2)); assert_eq!(Balances::reserved_balance(3), 2); }); } #[test] fn announcer_must_be_proxy() { new_test_ext().execute_with(|| { assert_noop!(Proxy::announce(Origin::signed(2), 1, H256::zero()), Error::::NotProxy); }); } #[test] fn delayed_requires_pre_announcement() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 1)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); let e = Error::::Unannounced; assert_noop!(Proxy::proxy(Origin::signed(2), 1, None, call.clone()), e); let e = Error::::Unannounced; assert_noop!(Proxy::proxy_announced(Origin::signed(0), 2, 1, None, call.clone()), e); let call_hash = BlakeTwo256::hash_of(&call); assert_ok!(Proxy::announce(Origin::signed(2), 1, call_hash)); system::Pallet::::set_block_number(2); assert_ok!(Proxy::proxy_announced(Origin::signed(0), 2, 1, None, call.clone())); }); } #[test] fn proxy_announced_removes_announcement_and_returns_deposit() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 1)); assert_ok!(Proxy::add_proxy(Origin::signed(2), 3, ProxyType::Any, 1)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); let call_hash = BlakeTwo256::hash_of(&call); assert_ok!(Proxy::announce(Origin::signed(3), 1, call_hash)); assert_ok!(Proxy::announce(Origin::signed(3), 2, call_hash)); // Too early to execute announced call let e = Error::::Unannounced; assert_noop!(Proxy::proxy_announced(Origin::signed(0), 3, 1, None, call.clone()), e); system::Pallet::::set_block_number(2); assert_ok!(Proxy::proxy_announced(Origin::signed(0), 3, 1, None, call.clone())); assert_eq!(Announcements::::get(3), (vec![Announcement { real: 2, call_hash, height: 1, }], 2)); assert_eq!(Balances::reserved_balance(3), 2); }); } #[test] fn filtering_works() { new_test_ext().execute_with(|| { assert!(Balances::mutate_account(&1, |a| a.free = 1000).is_ok()); assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::JustTransfer, 0)); assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Ok(()))); assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Ok(()))); assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); let derivative_id = Utility::derivative_account_id(1, 0); assert!(Balances::mutate_account(&derivative_id, |a| a.free = 1000).is_ok()); let inner = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); let call = Box::new(Call::Utility(UtilityCall::as_derivative(0, inner.clone()))); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Ok(()))); assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); let call = Box::new(Call::Utility(UtilityCall::batch(vec![*inner]))); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); expect_events(vec![UtilityEvent::BatchCompleted.into(), ProxyEvent::ProxyExecuted(Ok(())).into()]); assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); expect_events(vec![ UtilityEvent::BatchInterrupted(0, DispatchError::BadOrigin).into(), ProxyEvent::ProxyExecuted(Ok(())).into(), ]); let inner = Box::new(Call::Proxy(ProxyCall::add_proxy(5, ProxyType::Any, 0))); let call = Box::new(Call::Utility(UtilityCall::batch(vec![*inner]))); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); expect_events(vec![UtilityEvent::BatchCompleted.into(), ProxyEvent::ProxyExecuted(Ok(())).into()]); assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); expect_events(vec![ UtilityEvent::BatchInterrupted(0, DispatchError::BadOrigin).into(), ProxyEvent::ProxyExecuted(Ok(())).into(), ]); let call = Box::new(Call::Proxy(ProxyCall::remove_proxies())); assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); assert_ok!(Proxy::proxy(Origin::signed(4), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); expect_events(vec![BalancesEvent::::Unreserved(1, 5).into(), ProxyEvent::ProxyExecuted(Ok(())).into()]); }); } #[test] fn add_remove_proxies_works() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); assert_noop!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::Any, 0), Error::::Duplicate); assert_eq!(Balances::reserved_balance(1), 2); assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_eq!(Balances::reserved_balance(1), 3); assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 4); assert_ok!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); assert_eq!(Balances::reserved_balance(1), 5); assert_noop!(Proxy::add_proxy(Origin::signed(1), 4, ProxyType::Any, 0), Error::::TooMany); assert_noop!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::JustTransfer, 0), Error::::NotFound); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); assert_eq!(Balances::reserved_balance(1), 4); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 3); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 2); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_eq!(Balances::reserved_balance(1), 0); assert_noop!(Proxy::add_proxy(Origin::signed(1), 1, ProxyType::Any, 0), Error::::NoSelfProxy); }); } #[test] fn cannot_add_proxy_without_balance() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(5), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(5), 2); assert_noop!( Proxy::add_proxy(Origin::signed(5), 4, ProxyType::Any, 0), BalancesError::::InsufficientBalance ); }); } #[test] fn proxying_works() { new_test_ext().execute_with(|| { assert_ok!(Proxy::add_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_ok!(Proxy::add_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); assert_noop!(Proxy::proxy(Origin::signed(4), 1, None, call.clone()), Error::::NotProxy); assert_noop!( Proxy::proxy(Origin::signed(2), 1, Some(ProxyType::Any), call.clone()), Error::::NotProxy ); assert_ok!(Proxy::proxy(Origin::signed(2), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Ok(()))); assert_eq!(Balances::free_balance(6), 1); let call = Box::new(Call::System(SystemCall::set_code(vec![]))); assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); let call = Box::new(Call::Balances(BalancesCall::transfer_keep_alive(6, 1))); assert_ok!(Call::Proxy(super::Call::proxy(1, None, call.clone())).dispatch(Origin::signed(2))); expect_event(ProxyEvent::ProxyExecuted(Err(DispatchError::BadOrigin))); assert_ok!(Proxy::proxy(Origin::signed(3), 1, None, call.clone())); expect_event(ProxyEvent::ProxyExecuted(Ok(()))); assert_eq!(Balances::free_balance(6), 2); }); } #[test] fn anonymous_works() { new_test_ext().execute_with(|| { assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); let anon = Proxy::anonymous_account(&1, &ProxyType::Any, 0, None); expect_event(ProxyEvent::AnonymousCreated(anon.clone(), 1, ProxyType::Any, 0)); // other calls to anonymous allowed as long as they're not exactly the same. assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::JustTransfer, 0, 0)); assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 1)); let anon2 = Proxy::anonymous_account(&2, &ProxyType::Any, 0, None); assert_ok!(Proxy::anonymous(Origin::signed(2), ProxyType::Any, 0, 0)); assert_noop!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0), Error::::Duplicate); System::set_extrinsic_index(1); assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); System::set_extrinsic_index(0); System::set_block_number(2); assert_ok!(Proxy::anonymous(Origin::signed(1), ProxyType::Any, 0, 0)); let call = Box::new(Call::Balances(BalancesCall::transfer(6, 1))); assert_ok!(Balances::transfer(Origin::signed(3), anon, 5)); assert_ok!(Proxy::proxy(Origin::signed(1), anon, None, call)); expect_event(ProxyEvent::ProxyExecuted(Ok(()))); assert_eq!(Balances::free_balance(6), 1); let call = Box::new(Call::Proxy(ProxyCall::kill_anonymous(1, ProxyType::Any, 0, 1, 0))); assert_ok!(Proxy::proxy(Origin::signed(2), anon2, None, call.clone())); let de = DispatchError::from(Error::::NoPermission).stripped(); expect_event(ProxyEvent::ProxyExecuted(Err(de))); assert_noop!( Proxy::kill_anonymous(Origin::signed(1), 1, ProxyType::Any, 0, 1, 0), Error::::NoPermission ); assert_eq!(Balances::free_balance(1), 0); assert_ok!(Proxy::proxy(Origin::signed(1), anon, None, call.clone())); assert_eq!(Balances::free_balance(1), 2); assert_noop!(Proxy::proxy(Origin::signed(1), anon, None, call.clone()), Error::::NotProxy); }); }