mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-23 00:21:08 +00:00
Treasury spends various asset kinds (#1333)
### Summary This PR introduces new dispatchables to the treasury pallet, allowing spends of various asset types. The enhanced features of the treasury pallet, in conjunction with the asset-rate pallet, are set up and enabled for Westend and Rococo. ### Westend and Rococo runtimes. Polkadot/Kusams/Rococo Treasury can accept proposals for `spends` of various asset kinds by specifying the asset's location and ID. #### Treasury Instance New Dispatchables: - `spend(AssetKind, AssetBalance, Beneficiary, Option<ValidFrom>)` - propose and approve a spend; - `payout(SpendIndex)` - payout an approved spend or retry a failed payout - `check_payment(SpendIndex)` - check the status of a payout; - `void_spend(SpendIndex)` - void previously approved spend; > existing spend dispatchable renamed to spend_local in this context, the `AssetKind` parameter contains the asset's location and it's corresponding `asset_id`, for example: `USDT` on `AssetHub`, ``` rust location = MultiLocation(0, X1(Parachain(1000))) asset_id = MultiLocation(0, X2(PalletInstance(50), GeneralIndex(1984))) ``` the `Beneficiary` parameter is a `MultiLocation` in the context of the asset's location, for example ``` rust // the Fellowship salary pallet's location / account FellowshipSalaryPallet = MultiLocation(1, X2(Parachain(1001), PalletInstance(64))) // or custom `AccountId` Alice = MultiLocation(0, AccountId32(network: None, id: [1,...])) ``` the `AssetBalance` represents the amount of the `AssetKind` to be transferred to the `Beneficiary`. For permission checks, the asset amount is converted to the native amount and compared against the maximum spendable amount determined by the commanding spend origin. the `spend` dispatchable allows for batching spends with different `ValidFrom` arguments, enabling milestone-based spending. If the expectations tied to an approved spend are not met, it is possible to void the spend later using the `void_spend` dispatchable. Asset Rate Pallet provides the conversion rate from the `AssetKind` to the native balance. #### Asset Rate Instance Dispatchables: - `create(AssetKind, Rate)` - initialize a conversion rate to the native balance for the given asset - `update(AssetKind, Rate)` - update the conversion rate to the native balance for the given asset - `remove(AssetKind)` - remove an existing conversion rate to the native balance for the given asset the pallet's dispatchables can be executed by the Root or Treasurer origins. ### Treasury Pallet Treasury Pallet can accept proposals for `spends` of various asset kinds and pay them out through the implementation of the `Pay` trait. New Dispatchables: - `spend(Config::AssetKind, AssetBalance, Config::Beneficiary, Option<ValidFrom>)` - propose and approve a spend; - `payout(SpendIndex)` - payout an approved spend or retry a failed payout; - `check_payment(SpendIndex)` - check the status of a payout; - `void_spend(SpendIndex)` - void previously approved spend; > existing spend dispatchable renamed to spend_local The parameters' types of the `spend` dispatchable exposed via the pallet's `Config` and allows to propose and accept a spend of a certain amount. An approved spend can be claimed via the `payout` within the `Config::SpendPeriod`. Clients provide an implementation of the `Pay` trait which can pay an asset of the `AssetKind` to the `Beneficiary` in `AssetBalance` units. The implementation of the Pay trait might not have an immediate final payment status, for example if implemented over `XCM` and the actual transfer happens on a remote chain. The `check_status` dispatchable can be executed to update the spend's payment state and retry the `payout` if the payment has failed. --------- Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: command-bot <>
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use core::{cell::RefCell, marker::PhantomData};
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
traits::{BadOrigin, BlakeTwo256, Dispatchable, IdentityLookup},
|
||||
@@ -26,8 +27,13 @@ use sp_runtime::{
|
||||
};
|
||||
|
||||
use frame_support::{
|
||||
assert_err_ignore_postinfo, assert_noop, assert_ok, parameter_types,
|
||||
traits::{ConstU32, ConstU64, OnInitialize},
|
||||
assert_err_ignore_postinfo, assert_noop, assert_ok,
|
||||
pallet_prelude::Pays,
|
||||
parameter_types,
|
||||
traits::{
|
||||
tokens::{ConversionFromAssetBalance, PaymentStatus},
|
||||
ConstU32, ConstU64, OnInitialize,
|
||||
},
|
||||
PalletId,
|
||||
};
|
||||
|
||||
@@ -96,10 +102,64 @@ impl pallet_utility::Config for Test {
|
||||
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);
|
||||
}
|
||||
|
||||
/// paid balance for a given account and asset ids
|
||||
fn paid(who: u128, asset_id: u32) -> u64 {
|
||||
PAID.with(|p| p.borrow().get(&(who, asset_id)).cloned().unwrap_or(0))
|
||||
}
|
||||
|
||||
/// reduce paid balance for a given account and asset ids
|
||||
fn unpay(who: u128, asset_id: u32, amount: u64) {
|
||||
PAID.with(|p| p.borrow_mut().entry((who, asset_id)).or_default().saturating_reduce(amount))
|
||||
}
|
||||
|
||||
/// set status for a given payment id
|
||||
fn set_status(id: u64, s: PaymentStatus) {
|
||||
STATUS.with(|m| m.borrow_mut().insert(id, s));
|
||||
}
|
||||
|
||||
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 ProposalBond: Permill = Permill::from_percent(5);
|
||||
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 {
|
||||
@@ -120,6 +180,16 @@ impl frame_support::traits::EnsureOrigin<RuntimeOrigin> for TestSpendOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
@@ -137,6 +207,14 @@ impl Config for Test {
|
||||
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;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
@@ -151,6 +229,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
t.into()
|
||||
}
|
||||
|
||||
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() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -160,46 +246,49 @@ fn genesis_config_works() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_origin_permissioning_works() {
|
||||
fn spend_local_origin_permissioning_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Treasury::spend(RuntimeOrigin::signed(1), 1, 1), BadOrigin);
|
||||
assert_noop!(Treasury::spend_local(RuntimeOrigin::signed(1), 1, 1), BadOrigin);
|
||||
assert_noop!(
|
||||
Treasury::spend(RuntimeOrigin::signed(10), 6, 1),
|
||||
Treasury::spend_local(RuntimeOrigin::signed(10), 6, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Treasury::spend(RuntimeOrigin::signed(11), 11, 1),
|
||||
Treasury::spend_local(RuntimeOrigin::signed(11), 11, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Treasury::spend(RuntimeOrigin::signed(12), 21, 1),
|
||||
Treasury::spend_local(RuntimeOrigin::signed(12), 21, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Treasury::spend(RuntimeOrigin::signed(13), 51, 1),
|
||||
Treasury::spend_local(RuntimeOrigin::signed(13), 51, 1),
|
||||
Error::<Test>::InsufficientPermission
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn spend_origin_works() {
|
||||
fn spend_local_origin_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Check that accumulate works when we have Some value in Dummy already.
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(11), 10, 6));
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(12), 20, 6));
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(13), 50, 6));
|
||||
|
||||
// approve spend of some amount to beneficiary `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(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));
|
||||
// free balance of `6` is zero, spend period has not passed.
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(1);
|
||||
assert_eq!(Balances::free_balance(6), 0);
|
||||
|
||||
// free balance of `6` is `100`, spend period has passed.
|
||||
<Treasury as OnInitialize<u64>>::on_initialize(2);
|
||||
assert_eq!(Balances::free_balance(6), 100);
|
||||
// `100` spent, `1` burned.
|
||||
assert_eq!(Treasury::pot(), 0);
|
||||
});
|
||||
}
|
||||
@@ -579,13 +668,13 @@ fn remove_already_removed_approval_fails() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spending_in_batch_respects_max_total() {
|
||||
fn spending_local_in_batch_respects_max_total() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Respect the `max_total` for the given origin.
|
||||
assert_ok!(RuntimeCall::from(UtilityCall::batch_all {
|
||||
calls: vec![
|
||||
RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }),
|
||||
RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 101 })
|
||||
RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }),
|
||||
RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 101 })
|
||||
]
|
||||
})
|
||||
.dispatch(RuntimeOrigin::signed(10)));
|
||||
@@ -593,8 +682,8 @@ fn spending_in_batch_respects_max_total() {
|
||||
assert_err_ignore_postinfo!(
|
||||
RuntimeCall::from(UtilityCall::batch_all {
|
||||
calls: vec![
|
||||
RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }),
|
||||
RuntimeCall::from(TreasuryCall::spend { amount: 4, beneficiary: 101 })
|
||||
RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }),
|
||||
RuntimeCall::from(TreasuryCall::spend_local { amount: 4, beneficiary: 101 })
|
||||
]
|
||||
})
|
||||
.dispatch(RuntimeOrigin::signed(10)),
|
||||
@@ -602,3 +691,296 @@ fn spending_in_batch_respects_max_total() {
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spending_in_batch_respects_max_total() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Respect the `max_total` for the given origin.
|
||||
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() {
|
||||
new_test_ext().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() {
|
||||
new_test_ext().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() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
|
||||
|
||||
// spend `0` expires in 5 blocks after the creating.
|
||||
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);
|
||||
|
||||
// spend cannot be approved since its already expired.
|
||||
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() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
// approve a `2` coins spend of asset `1` to beneficiary `6`, the spend valid from now.
|
||||
assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
|
||||
// payout the spend.
|
||||
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
|
||||
// beneficiary received `2` coins of asset `1`.
|
||||
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);
|
||||
// the payment succeed.
|
||||
assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
|
||||
System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 0 }.into());
|
||||
// cannot payout the same spend twice.
|
||||
assert_noop!(Treasury::payout(RuntimeOrigin::signed(1), 0), Error::<Test, _>::InvalidIndex);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payout_retry_works() {
|
||||
new_test_ext().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");
|
||||
// spend payment is failed
|
||||
set_status(payment_id, PaymentStatus::Failure);
|
||||
unpay(6, 1, 2);
|
||||
// cannot payout a spend in the attempted state
|
||||
assert_noop!(
|
||||
Treasury::payout(RuntimeOrigin::signed(1), 0),
|
||||
Error::<Test, _>::AlreadyAttempted
|
||||
);
|
||||
// check status and update it to retry the payout again
|
||||
assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
|
||||
System::assert_last_event(Event::<Test, _>::PaymentFailed { index: 0, payment_id }.into());
|
||||
// the payout can be retried now
|
||||
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
|
||||
assert_eq!(paid(6, 1), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_valid_from_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
|
||||
System::set_block_number(1);
|
||||
|
||||
// spend valid from block `2`.
|
||||
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);
|
||||
// spend approved even if `valid_from` in the past since the payout period has not passed.
|
||||
assert_ok!(Treasury::spend(
|
||||
RuntimeOrigin::signed(10),
|
||||
Box::new(1),
|
||||
2,
|
||||
Box::new(6),
|
||||
Some(4)
|
||||
));
|
||||
// spend paid.
|
||||
assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn void_spend_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
// spend cannot be voided if already attempted.
|
||||
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
|
||||
);
|
||||
|
||||
// void spend.
|
||||
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() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
|
||||
System::set_block_number(1);
|
||||
|
||||
// spend `0` expired and can be removed.
|
||||
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());
|
||||
|
||||
// spend `1` payment failed and expired hence can be removed.
|
||||
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);
|
||||
// spend expired.
|
||||
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());
|
||||
|
||||
// spend `2` payment succeed.
|
||||
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());
|
||||
|
||||
// spend `3` payment in process.
|
||||
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
|
||||
);
|
||||
|
||||
// spend `4` removed since the payment status is unknown.
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user