#![cfg(test)]
use core::{cell::RefCell, marker::PhantomData};
use sp_runtime::{
traits::{BadOrigin, Dispatchable, IdentityLookup},
BuildStorage,
};
use frame_support::{
assert_err_ignore_postinfo, assert_noop, assert_ok, derive_impl,
pallet_prelude::Pays,
parameter_types,
traits::{
tokens::{ConversionFromAssetBalance, PaymentStatus},
ConstU32, ConstU64, OnInitialize,
},
PalletId,
};
use super::*;
use crate as treasury;
type Block = frame_system::mocking::MockBlock<Test>;
type UtilityCall = pallet_utility::Call<Test>;
type TreasuryCall = crate::Call<Test>;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Treasury: treasury,
Utility: pallet_utility,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type AccountId = u128; type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type AccountData = pallet_balances::AccountData<u64>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
}
impl pallet_utility::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}
thread_local! {
pub static PAID: RefCell<BTreeMap<(u128, u32), u64>> = RefCell::new(BTreeMap::new());
pub static STATUS: RefCell<BTreeMap<u64, PaymentStatus>> = RefCell::new(BTreeMap::new());
pub static LAST_ID: RefCell<u64> = RefCell::new(0u64);
#[cfg(feature = "runtime-benchmarks")]
pub static TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR: RefCell<bool> = RefCell::new(false);
}
fn paid(who: u128, asset_id: u32) -> u64 {
PAID.with(|p| p.borrow().get(&(who, asset_id)).cloned().unwrap_or(0))
}
fn unpay(who: u128, asset_id: u32, amount: u64) {
PAID.with(|p| p.borrow_mut().entry((who, asset_id)).or_default().saturating_reduce(amount))
}
fn set_status(id: u64, s: PaymentStatus) {
STATUS.with(|m| m.borrow_mut().insert(id, s));
}
fn go_to_block(n: u64) {
<Test as Config>::BlockNumberProvider::set_block_number(n);
<Treasury as OnInitialize<u64>>::on_initialize(n);
}
pub struct TestPay;
impl Pay for TestPay {
type Beneficiary = u128;
type Balance = u64;
type Id = u64;
type AssetKind = u32;
type Error = ();
fn pay(
who: &Self::Beneficiary,
asset_kind: Self::AssetKind,
amount: Self::Balance,
) -> Result<Self::Id, Self::Error> {
PAID.with(|paid| *paid.borrow_mut().entry((*who, asset_kind)).or_default() += amount);
Ok(LAST_ID.with(|lid| {
let x = *lid.borrow();
lid.replace(x + 1);
x
}))
}
fn check_payment(id: Self::Id) -> PaymentStatus {
STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown))
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) {}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_concluded(id: Self::Id) {
set_status(id, PaymentStatus::Failure)
}
}
parameter_types! {
pub const Burn: Permill = Permill::from_percent(50);
pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
pub TreasuryAccount: u128 = Treasury::account_id();
pub const SpendPayoutPeriod: u64 = 5;
}
pub struct TestSpendOrigin;
impl frame_support::traits::EnsureOrigin<RuntimeOrigin> for TestSpendOrigin {
type Success = u64;
fn try_origin(outer: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
Result::<frame_system::RawOrigin<_>, RuntimeOrigin>::from(outer.clone()).and_then(|o| {
match o {
frame_system::RawOrigin::Root => Ok(u64::max_value()),
frame_system::RawOrigin::Signed(10) => Ok(5),
frame_system::RawOrigin::Signed(11) => Ok(10),
frame_system::RawOrigin::Signed(12) => Ok(20),
frame_system::RawOrigin::Signed(13) => Ok(50),
frame_system::RawOrigin::Signed(14) => Ok(500),
_ => Err(outer),
}
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
if TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR.with(|i| *i.borrow()) {
Err(())
} else {
Ok(frame_system::RawOrigin::Root.into())
}
}
}
pub struct MulBy<N>(PhantomData<N>);
impl<N: Get<u64>> ConversionFromAssetBalance<u64, u32, u64> for MulBy<N> {
type Error = ();
fn from_asset_balance(balance: u64, _asset_id: u32) -> Result<u64, Self::Error> {
return balance.checked_mul(N::get()).ok_or(());
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful(_: u32) {}
}
impl Config for Test {
type PalletId = TreasuryPalletId;
type Currency = pallet_balances::Pallet<Test>;
type RejectOrigin = frame_system::EnsureRoot<u128>;
type RuntimeEvent = RuntimeEvent;
type SpendPeriod = ConstU64<2>;
type Burn = Burn;
type BurnDestination = (); type WeightInfo = ();
type SpendFunds = ();
type MaxApprovals = ConstU32<100>;
type SpendOrigin = TestSpendOrigin;
type AssetKind = u32;
type Beneficiary = u128;
type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
type Paymaster = TestPay;
type BalanceConverter = MulBy<ConstU64<2>>;
type PayoutPeriod = SpendPayoutPeriod;
type BlockNumberProvider = System;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = ();
}
pub struct ExtBuilder {}
impl Default for ExtBuilder {
fn default() -> Self {
#[cfg(feature = "runtime-benchmarks")]
TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR.with(|i| *i.borrow_mut() = false);
Self {}
}
}
impl ExtBuilder {
#[cfg(feature = "runtime-benchmarks")]
pub fn spend_origin_succesful_origin_err(self) -> Self {
TEST_SPEND_ORIGIN_TRY_SUCCESFUL_ORIGIN_ERR.with(|i| *i.borrow_mut() = true);
self
}
pub fn build(self) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(0, 100), (1, 98), (2, 1)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
crate::GenesisConfig::<Test>::default().assimilate_storage(&mut t).unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
fn get_payment_id(i: SpendIndex) -> Option<u64> {
let spend = Spends::<Test, _>::get(i).expect("no spend");
match spend.status {
PaymentState::Attempted { id } => Some(id),
_ => None,
}
}
#[test]
fn genesis_config_works() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(Treasury::pot(), 0);
assert_eq!(ProposalCount::<Test>::get(), 0);
});
}
#[test]
fn spend_local_origin_permissioning_works() {
#[allow(deprecated)]
ExtBuilder::default().build().execute_with(|| {
assert_noop!(Treasury::spend_local(RuntimeOrigin::signed(1), 1, 1), BadOrigin);
assert_noop!(
Treasury::spend_local(RuntimeOrigin::signed(10), 6, 1),
Error::<Test>::InsufficientPermission
);
assert_noop!(
Treasury::spend_local(RuntimeOrigin::signed(11), 11, 1),
Error::<Test>::InsufficientPermission
);
assert_noop!(
Treasury::spend_local(RuntimeOrigin::signed(12), 21, 1),
Error::<Test>::InsufficientPermission
);
assert_noop!(
Treasury::spend_local(RuntimeOrigin::signed(13), 51, 1),
Error::<Test>::InsufficientPermission
);
});
}
#[docify::export]
#[test]
fn spend_local_origin_works() {
#[allow(deprecated)]
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 102);
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(11), 10, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(12), 20, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(13), 50, 6));
go_to_block(1);
assert_eq!(Balances::free_balance(6), 0);
go_to_block(2);
assert_eq!(Balances::free_balance(6), 100);
assert_eq!(Treasury::pot(), 0);
});
}
#[test]
fn minting_works() {
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 101);
assert_eq!(Treasury::pot(), 100);
});
}
#[test]
fn accepted_spend_proposal_ignored_outside_spend_period() {
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 101);
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3));
}
go_to_block(1);
assert_eq!(Balances::free_balance(3), 0);
assert_eq!(Treasury::pot(), 100);
});
}
#[test]
fn unused_pot_should_diminish() {
ExtBuilder::default().build().execute_with(|| {
let init_total_issuance = pallet_balances::TotalIssuance::<Test>::get();
Balances::make_free_balance_be(&Treasury::account_id(), 101);
assert_eq!(pallet_balances::TotalIssuance::<Test>::get(), init_total_issuance + 100);
go_to_block(2);
assert_eq!(Treasury::pot(), 50);
assert_eq!(pallet_balances::TotalIssuance::<Test>::get(), init_total_issuance + 50);
});
}
#[test]
fn accepted_spend_proposal_enacted_on_spend_period() {
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 101);
assert_eq!(Treasury::pot(), 100);
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3));
}
go_to_block(2);
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Treasury::pot(), 0);
});
}
#[test]
fn pot_underflow_should_not_diminish() {
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 101);
assert_eq!(Treasury::pot(), 100);
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 150, 3));
}
go_to_block(2);
assert_eq!(Treasury::pot(), 100);
let _ = Balances::deposit_into_existing(&Treasury::account_id(), 100).unwrap();
go_to_block(4);
assert_eq!(Balances::free_balance(3), 150); assert_eq!(Treasury::pot(), 25); });
}
#[test]
fn treasury_account_doesnt_get_deleted() {
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 101);
assert_eq!(Treasury::pot(), 100);
let treasury_balance = Balances::free_balance(&Treasury::account_id());
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), treasury_balance, 3));
<Treasury as OnInitialize<u64>>::on_initialize(2);
assert_eq!(Treasury::pot(), 100);
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), treasury_balance, 3));
go_to_block(2);
assert_eq!(Treasury::pot(), 100);
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), Treasury::pot(), 3));
}
go_to_block(4);
assert_eq!(Treasury::pot(), 0); assert_eq!(Balances::free_balance(Treasury::account_id()), 1); });
}
#[test]
fn inexistent_account_works() {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(0, 100), (1, 99), (2, 1)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
let mut t: sp_io::TestExternalities = t.into();
t.execute_with(|| {
assert_eq!(Balances::free_balance(Treasury::account_id()), 0); assert_eq!(Treasury::pot(), 0);
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 99, 3));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 1, 3));
}
go_to_block(2);
assert_eq!(Treasury::pot(), 0); assert_eq!(Balances::free_balance(3), 0);
Balances::make_free_balance_be(&Treasury::account_id(), 100);
assert_eq!(Treasury::pot(), 99); assert_eq!(Balances::free_balance(Treasury::account_id()), 100);
go_to_block(4);
assert_eq!(Treasury::pot(), 0); assert_eq!(Balances::free_balance(3), 99); });
}
#[test]
fn genesis_funding_works() {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let initial_funding = 100;
pallet_balances::GenesisConfig::<Test> {
balances: vec![(0, 100), (Treasury::account_id(), initial_funding)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
crate::GenesisConfig::<Test>::default().assimilate_storage(&mut t).unwrap();
let mut t: sp_io::TestExternalities = t.into();
t.execute_with(|| {
assert_eq!(Balances::free_balance(Treasury::account_id()), initial_funding);
assert_eq!(Treasury::pot(), initial_funding - Balances::minimum_balance());
});
}
#[test]
fn max_approvals_limited() {
#[allow(deprecated)]
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), u64::MAX);
Balances::make_free_balance_be(&0, u64::MAX);
for _ in 0..<Test as Config>::MaxApprovals::get() {
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3));
}
assert_noop!(
Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3),
Error::<Test, _>::TooManyApprovals
);
});
}
#[test]
fn remove_already_removed_approval_fails() {
#[allow(deprecated)]
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 101);
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3));
assert_eq!(Approvals::<Test>::get(), vec![0]);
assert_ok!(Treasury::remove_approval(RuntimeOrigin::root(), 0));
assert_eq!(Approvals::<Test>::get(), vec![]);
assert_noop!(
Treasury::remove_approval(RuntimeOrigin::root(), 0),
Error::<Test, _>::ProposalNotApproved
);
});
}
#[test]
fn spending_local_in_batch_respects_max_total() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(RuntimeCall::from(UtilityCall::batch_all {
calls: vec![
RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }),
RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 101 })
]
})
.dispatch(RuntimeOrigin::signed(10)));
assert_err_ignore_postinfo!(
RuntimeCall::from(UtilityCall::batch_all {
calls: vec![
RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }),
RuntimeCall::from(TreasuryCall::spend_local { amount: 4, beneficiary: 101 })
]
})
.dispatch(RuntimeOrigin::signed(10)),
Error::<Test, _>::InsufficientPermission
);
})
}
#[test]
fn spending_in_batch_respects_max_total() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(RuntimeCall::from(UtilityCall::batch_all {
calls: vec![
RuntimeCall::from(TreasuryCall::spend {
asset_kind: Box::new(1),
amount: 1,
beneficiary: Box::new(100),
valid_from: None,
}),
RuntimeCall::from(TreasuryCall::spend {
asset_kind: Box::new(1),
amount: 1,
beneficiary: Box::new(101),
valid_from: None,
})
]
})
.dispatch(RuntimeOrigin::signed(10)));
assert_err_ignore_postinfo!(
RuntimeCall::from(UtilityCall::batch_all {
calls: vec![
RuntimeCall::from(TreasuryCall::spend {
asset_kind: Box::new(1),
amount: 2,
beneficiary: Box::new(100),
valid_from: None,
}),
RuntimeCall::from(TreasuryCall::spend {
asset_kind: Box::new(1),
amount: 2,
beneficiary: Box::new(101),
valid_from: None,
})
]
})
.dispatch(RuntimeOrigin::signed(10)),
Error::<Test, _>::InsufficientPermission
);
})
}
#[test]
fn spend_origin_works() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 1, Box::new(6), None));
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_noop!(
Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 3, Box::new(6), None),
Error::<Test, _>::InsufficientPermission
);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(11), Box::new(1), 5, Box::new(6), None));
assert_noop!(
Treasury::spend(RuntimeOrigin::signed(11), Box::new(1), 6, Box::new(6), None),
Error::<Test, _>::InsufficientPermission
);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(12), Box::new(1), 10, Box::new(6), None));
assert_noop!(
Treasury::spend(RuntimeOrigin::signed(12), Box::new(1), 11, Box::new(6), None),
Error::<Test, _>::InsufficientPermission
);
assert_eq!(SpendCount::<Test, _>::get(), 4);
assert_eq!(Spends::<Test, _>::iter().count(), 4);
});
}
#[test]
fn spend_works() {
ExtBuilder::default().build().execute_with(|| {
System::set_block_number(1);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_eq!(SpendCount::<Test, _>::get(), 1);
assert_eq!(
Spends::<Test, _>::get(0).unwrap(),
SpendStatus {
asset_kind: 1,
amount: 2,
beneficiary: 6,
valid_from: 1,
expire_at: 6,
status: PaymentState::Pending,
}
);
System::assert_last_event(
Event::<Test, _>::AssetSpendApproved {
index: 0,
asset_kind: 1,
amount: 2,
beneficiary: 6,
valid_from: 1,
expire_at: 6,
}
.into(),
);
});
}
#[test]
fn spend_expires() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
System::set_block_number(1);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
System::set_block_number(6);
assert_noop!(Treasury::payout(RuntimeOrigin::signed(1), 0), Error::<Test, _>::SpendExpired);
assert_noop!(
Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), Some(0)),
Error::<Test, _>::SpendExpired
);
});
}
#[docify::export]
#[test]
fn spend_payout_works() {
ExtBuilder::default().build().execute_with(|| {
System::set_block_number(1);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
assert_eq!(paid(6, 1), 2);
assert_eq!(SpendCount::<Test, _>::get(), 1);
let payment_id = get_payment_id(0).expect("no payment attempt");
System::assert_last_event(Event::<Test, _>::Paid { index: 0, payment_id }.into());
set_status(payment_id, PaymentStatus::Success);
assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 0 }.into());
assert_noop!(Treasury::payout(RuntimeOrigin::signed(1), 0), Error::<Test, _>::InvalidIndex);
});
}
#[test]
fn payout_extends_expiry() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
System::set_block_number(1);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
System::set_block_number(4);
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
assert_eq!(paid(6, 1), 2);
let payment_id = get_payment_id(0).expect("no payment attempt");
set_status(payment_id, PaymentStatus::Failure);
unpay(6, 1, 2);
assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
System::assert_last_event(Event::<Test, _>::PaymentFailed { index: 0, payment_id }.into());
System::set_block_number(7);
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
assert_eq!(paid(6, 1), 2);
});
}
#[test]
fn payout_retry_works() {
ExtBuilder::default().build().execute_with(|| {
System::set_block_number(1);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
assert_eq!(paid(6, 1), 2);
let payment_id = get_payment_id(0).expect("no payment attempt");
set_status(payment_id, PaymentStatus::Failure);
unpay(6, 1, 2);
assert_noop!(
Treasury::payout(RuntimeOrigin::signed(1), 0),
Error::<Test, _>::AlreadyAttempted
);
assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
System::assert_last_event(Event::<Test, _>::PaymentFailed { index: 0, payment_id }.into());
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
assert_eq!(paid(6, 1), 2);
});
}
#[test]
fn spend_valid_from_works() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
System::set_block_number(1);
assert_ok!(Treasury::spend(
RuntimeOrigin::signed(10),
Box::new(1),
2,
Box::new(6),
Some(2)
));
assert_noop!(Treasury::payout(RuntimeOrigin::signed(1), 0), Error::<Test, _>::EarlyPayout);
System::set_block_number(2);
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
System::set_block_number(5);
assert_ok!(Treasury::spend(
RuntimeOrigin::signed(10),
Box::new(1),
2,
Box::new(6),
Some(4)
));
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 1));
});
}
#[test]
fn void_spend_works() {
ExtBuilder::default().build().execute_with(|| {
System::set_block_number(1);
assert_ok!(Treasury::spend(
RuntimeOrigin::signed(10),
Box::new(1),
2,
Box::new(6),
Some(1)
));
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
assert_noop!(
Treasury::void_spend(RuntimeOrigin::root(), 0),
Error::<Test, _>::AlreadyAttempted
);
assert_ok!(Treasury::spend(
RuntimeOrigin::signed(10),
Box::new(1),
2,
Box::new(6),
Some(10)
));
assert_ok!(Treasury::void_spend(RuntimeOrigin::root(), 1));
assert_eq!(Spends::<Test, _>::get(1), None);
});
}
#[test]
fn check_status_works() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
System::set_block_number(1);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
System::set_block_number(7);
let info = Treasury::check_status(RuntimeOrigin::signed(1), 0).unwrap();
assert_eq!(info.pays_fee, Pays::No);
System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 0 }.into());
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_noop!(
Treasury::check_status(RuntimeOrigin::signed(1), 1),
Error::<Test, _>::NotAttempted
);
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 1));
let payment_id = get_payment_id(1).expect("no payment attempt");
set_status(payment_id, PaymentStatus::Failure);
System::set_block_number(13);
let info = Treasury::check_status(RuntimeOrigin::signed(1), 1).unwrap();
assert_eq!(info.pays_fee, Pays::Yes);
System::assert_last_event(Event::<Test, _>::PaymentFailed { index: 1, payment_id }.into());
let info = Treasury::check_status(RuntimeOrigin::signed(1), 1).unwrap();
assert_eq!(info.pays_fee, Pays::No);
System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 1 }.into());
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 2));
let payment_id = get_payment_id(2).expect("no payment attempt");
set_status(payment_id, PaymentStatus::Success);
let info = Treasury::check_status(RuntimeOrigin::signed(1), 2).unwrap();
assert_eq!(info.pays_fee, Pays::No);
System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 2 }.into());
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 3));
let payment_id = get_payment_id(3).expect("no payment attempt");
set_status(payment_id, PaymentStatus::InProgress);
assert_noop!(
Treasury::check_status(RuntimeOrigin::signed(1), 3),
Error::<Test, _>::Inconclusive
);
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 4));
let payment_id = get_payment_id(4).expect("no payment attempt");
set_status(payment_id, PaymentStatus::Unknown);
let info = Treasury::check_status(RuntimeOrigin::signed(1), 4).unwrap();
assert_eq!(info.pays_fee, Pays::No);
System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 4 }.into());
});
}
#[test]
fn try_state_proposals_invariant_1_works() {
ExtBuilder::default().build().execute_with(|| {
use frame_support::pallet_prelude::DispatchError::Other;
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 1, 3));
}
assert_eq!(Proposals::<Test>::iter().count(), 1);
assert_eq!(ProposalCount::<Test>::get(), 1);
assert!(ProposalCount::<Test>::get() as usize >= Proposals::<Test>::iter().count());
ProposalCount::<Test>::put(0);
assert_eq!(
Treasury::do_try_state(),
Err(Other("Actual number of proposals exceeds `ProposalCount`."))
);
});
}
#[test]
fn try_state_proposals_invariant_2_works() {
ExtBuilder::default().build().execute_with(|| {
use frame_support::pallet_prelude::DispatchError::Other;
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 1, 3));
}
assert_eq!(Proposals::<Test>::iter().count(), 1);
assert_eq!(Approvals::<Test>::get().len(), 1);
let current_proposal_count = ProposalCount::<Test>::get();
assert_eq!(current_proposal_count, 1);
assert!(
Proposals::<Test>::iter_keys()
.all(|proposal_index| {
proposal_index < current_proposal_count
})
);
let proposal = Proposals::<Test>::take(0).unwrap();
Proposals::<Test>::insert(1, proposal);
assert_eq!(
Treasury::do_try_state(),
Err(Other("`ProposalCount` should by strictly greater than any ProposalIndex used as a key for `Proposals`."))
);
});
}
#[test]
fn try_state_proposals_invariant_3_works() {
ExtBuilder::default().build().execute_with(|| {
use frame_support::pallet_prelude::DispatchError::Other;
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 10, 3));
}
assert_eq!(Proposals::<Test>::iter().count(), 1);
assert_eq!(Approvals::<Test>::get().len(), 1);
assert!(Approvals::<Test>::get()
.iter()
.all(|proposal_index| { Proposals::<Test>::contains_key(proposal_index) }));
let mut approvals_modified = Approvals::<Test>::get();
approvals_modified.try_push(2).unwrap();
Approvals::<Test>::put(approvals_modified);
assert_eq!(
Treasury::do_try_state(),
Err(Other("Proposal indices in `Approvals` must also be contained in `Proposals`."))
);
});
}
#[test]
fn try_state_spends_invariant_1_works() {
ExtBuilder::default().build().execute_with(|| {
use frame_support::pallet_prelude::DispatchError::Other;
assert_ok!({
Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 1, Box::new(6), None)
});
assert_eq!(Spends::<Test>::iter().count(), 1);
assert_eq!(SpendCount::<Test>::get(), 1);
assert!(SpendCount::<Test>::get() as usize >= Spends::<Test>::iter().count());
SpendCount::<Test>::put(0);
assert_eq!(
Treasury::do_try_state(),
Err(Other("Actual number of spends exceeds `SpendCount`."))
);
});
}
#[test]
fn try_state_spends_invariant_2_works() {
ExtBuilder::default().build().execute_with(|| {
use frame_support::pallet_prelude::DispatchError::Other;
assert_ok!({
Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 1, Box::new(6), None)
});
assert_eq!(Spends::<Test>::iter().count(), 1);
let current_spend_count = SpendCount::<Test>::get();
assert_eq!(current_spend_count, 1);
assert!(
Spends::<Test>::iter_keys()
.all(|spend_index| {
spend_index < current_spend_count
})
);
let spend = Spends::<Test>::take(0).unwrap();
Spends::<Test>::insert(1, spend);
assert_eq!(
Treasury::do_try_state(),
Err(Other("`SpendCount` should by strictly greater than any SpendIndex used as a key for `Spends`."))
);
});
}
#[test]
fn try_state_spends_invariant_3_works() {
ExtBuilder::default().build().execute_with(|| {
use frame_support::pallet_prelude::DispatchError::Other;
assert_ok!({
Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 1, Box::new(6), None)
});
assert_eq!(Spends::<Test>::iter().count(), 1);
let current_spend_count = SpendCount::<Test>::get();
assert_eq!(current_spend_count, 1);
assert!(Spends::<Test>::iter_values()
.all(|SpendStatus { valid_from, expire_at, .. }| { valid_from < expire_at }));
let spend = Spends::<Test>::take(0).unwrap();
Spends::<Test>::insert(
0,
SpendStatus { valid_from: spend.expire_at, expire_at: spend.valid_from, ..spend },
);
assert_eq!(
Treasury::do_try_state(),
Err(Other("Spend cannot expire before it becomes valid."))
);
});
}
#[test]
fn multiple_spend_periods_work() {
ExtBuilder::default().build().execute_with(|| {
Balances::make_free_balance_be(&Treasury::account_id(), 100 + 1024 + 1);
#[allow(deprecated)]
{
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(11), 10, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(12), 20, 6));
assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(13), 50, 6));
}
go_to_block(1);
assert_eq!(Balances::free_balance(6), 0);
go_to_block(2);
assert_eq!(Balances::free_balance(6), 100);
assert_eq!(Treasury::pot(), 512);
go_to_block(2 + (3 * 2) + 1);
assert_eq!(Treasury::pot(), 64);
assert_eq!(LastSpendPeriod::<Test>::get(), Some(8));
});
}