1#![doc = docify::embed!("src/tests.rs", spend_local_origin_works)]
53#![doc = docify::embed!("src/tests.rs", spend_payout_works)]
56#![cfg_attr(not(feature = "std"), no_std)]
74
75mod benchmarking;
76pub mod migration;
77#[cfg(test)]
78mod tests;
79pub mod weights;
80use core::marker::PhantomData;
81
82#[cfg(feature = "runtime-benchmarks")]
83pub use benchmarking::ArgumentsFactory;
84
85extern crate alloc;
86
87use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
88use scale_info::TypeInfo;
89
90use alloc::{boxed::Box, collections::btree_map::BTreeMap};
91use sp_runtime::{
92 traits::{
93 AccountIdConversion, BlockNumberProvider, CheckedAdd, One, Saturating, StaticLookup,
94 UniqueSaturatedInto, Zero,
95 },
96 PerThing, Permill, RuntimeDebug,
97};
98
99use frame_support::{
100 dispatch::{DispatchResult, DispatchResultWithPostInfo},
101 ensure, print,
102 traits::{
103 tokens::Pay, Currency, ExistenceRequirement::KeepAlive, Get, Imbalance, OnUnbalanced,
104 ReservableCurrency, WithdrawReasons,
105 },
106 weights::Weight,
107 BoundedVec, PalletId,
108};
109use frame_system::pallet_prelude::BlockNumberFor as SystemBlockNumberFor;
110
111pub use pallet::*;
112pub use weights::WeightInfo;
113
114pub type BalanceOf<T, I = ()> =
115 <<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
116pub type AssetBalanceOf<T, I> = <<T as Config<I>>::Paymaster as Pay>::Balance;
117pub type PositiveImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currency<
118 <T as frame_system::Config>::AccountId,
119>>::PositiveImbalance;
120pub type NegativeImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currency<
121 <T as frame_system::Config>::AccountId,
122>>::NegativeImbalance;
123type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
124type BeneficiaryLookupOf<T, I> = <<T as Config<I>>::BeneficiaryLookup as StaticLookup>::Source;
125pub type BlockNumberFor<T, I = ()> =
126 <<T as Config<I>>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
127
128#[impl_trait_for_tuples::impl_for_tuples(30)]
140pub trait SpendFunds<T: Config<I>, I: 'static = ()> {
141 fn spend_funds(
142 budget_remaining: &mut BalanceOf<T, I>,
143 imbalance: &mut PositiveImbalanceOf<T, I>,
144 total_weight: &mut Weight,
145 missed_any: &mut bool,
146 );
147}
148
149pub type ProposalIndex = u32;
151
152#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
154#[derive(
155 Encode,
156 Decode,
157 DecodeWithMemTracking,
158 Clone,
159 PartialEq,
160 Eq,
161 MaxEncodedLen,
162 RuntimeDebug,
163 TypeInfo,
164)]
165pub struct Proposal<AccountId, Balance> {
166 pub proposer: AccountId,
168 pub value: Balance,
170 pub beneficiary: AccountId,
172 pub bond: Balance,
174}
175
176#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
178#[derive(
179 Encode,
180 Decode,
181 DecodeWithMemTracking,
182 Clone,
183 PartialEq,
184 Eq,
185 MaxEncodedLen,
186 RuntimeDebug,
187 TypeInfo,
188)]
189pub enum PaymentState<Id> {
190 Pending,
192 Attempted { id: Id },
194 Failed,
196}
197
198#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
200#[derive(
201 Encode,
202 Decode,
203 DecodeWithMemTracking,
204 Clone,
205 PartialEq,
206 Eq,
207 MaxEncodedLen,
208 RuntimeDebug,
209 TypeInfo,
210)]
211pub struct SpendStatus<AssetKind, AssetBalance, Beneficiary, BlockNumber, PaymentId> {
212 pub asset_kind: AssetKind,
214 pub amount: AssetBalance,
216 pub beneficiary: Beneficiary,
218 pub valid_from: BlockNumber,
220 pub expire_at: BlockNumber,
222 pub status: PaymentState<PaymentId>,
224}
225
226pub type SpendIndex = u32;
228
229#[frame_support::pallet]
230pub mod pallet {
231 use super::*;
232 use frame_support::{
233 dispatch_context::with_context,
234 pallet_prelude::*,
235 traits::tokens::{ConversionFromAssetBalance, PaymentStatus},
236 };
237 use frame_system::pallet_prelude::{ensure_signed, OriginFor};
238
239 #[pallet::pallet]
240 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
241
242 #[pallet::config]
243 pub trait Config<I: 'static = ()>: frame_system::Config {
244 type Currency: Currency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
246
247 type RejectOrigin: EnsureOrigin<Self::RuntimeOrigin>;
249
250 #[allow(deprecated)]
252 type RuntimeEvent: From<Event<Self, I>>
253 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
254
255 #[pallet::constant]
257 type SpendPeriod: Get<BlockNumberFor<Self, I>>;
258
259 #[pallet::constant]
261 type Burn: Get<Permill>;
262
263 #[pallet::constant]
265 type PalletId: Get<PalletId>;
266
267 type BurnDestination: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
269
270 type WeightInfo: WeightInfo;
272
273 type SpendFunds: SpendFunds<Self, I>;
275
276 #[pallet::constant]
283 type MaxApprovals: Get<u32>;
284
285 type SpendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = BalanceOf<Self, I>>;
289
290 type AssetKind: Parameter + MaxEncodedLen;
292
293 type Beneficiary: Parameter + MaxEncodedLen;
295
296 type BeneficiaryLookup: StaticLookup<Target = Self::Beneficiary>;
298
299 type Paymaster: Pay<Beneficiary = Self::Beneficiary, AssetKind = Self::AssetKind>;
301
302 type BalanceConverter: ConversionFromAssetBalance<
306 <Self::Paymaster as Pay>::Balance,
307 Self::AssetKind,
308 BalanceOf<Self, I>,
309 >;
310
311 #[pallet::constant]
313 type PayoutPeriod: Get<BlockNumberFor<Self, I>>;
314
315 #[cfg(feature = "runtime-benchmarks")]
317 type BenchmarkHelper: ArgumentsFactory<Self::AssetKind, Self::Beneficiary>;
318
319 type BlockNumberProvider: BlockNumberProvider;
321 }
322
323 #[pallet::extra_constants]
324 impl<T: Config<I>, I: 'static> Pallet<T, I> {
325 fn pot_account() -> T::AccountId {
327 Self::account_id()
328 }
329 }
330
331 #[pallet::storage]
336 pub type ProposalCount<T, I = ()> = StorageValue<_, ProposalIndex, ValueQuery>;
337
338 #[pallet::storage]
343 pub type Proposals<T: Config<I>, I: 'static = ()> = StorageMap<
344 _,
345 Twox64Concat,
346 ProposalIndex,
347 Proposal<T::AccountId, BalanceOf<T, I>>,
348 OptionQuery,
349 >;
350
351 #[pallet::storage]
353 pub type Deactivated<T: Config<I>, I: 'static = ()> =
354 StorageValue<_, BalanceOf<T, I>, ValueQuery>;
355
356 #[pallet::storage]
361 pub type Approvals<T: Config<I>, I: 'static = ()> =
362 StorageValue<_, BoundedVec<ProposalIndex, T::MaxApprovals>, ValueQuery>;
363
364 #[pallet::storage]
366 pub type SpendCount<T, I = ()> = StorageValue<_, SpendIndex, ValueQuery>;
367
368 #[pallet::storage]
371 pub type Spends<T: Config<I>, I: 'static = ()> = StorageMap<
372 _,
373 Twox64Concat,
374 SpendIndex,
375 SpendStatus<
376 T::AssetKind,
377 AssetBalanceOf<T, I>,
378 T::Beneficiary,
379 BlockNumberFor<T, I>,
380 <T::Paymaster as Pay>::Id,
381 >,
382 OptionQuery,
383 >;
384
385 #[pallet::storage]
387 pub type LastSpendPeriod<T, I = ()> = StorageValue<_, BlockNumberFor<T, I>, OptionQuery>;
388
389 #[pallet::genesis_config]
390 #[derive(frame_support::DefaultNoBound)]
391 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
392 #[serde(skip)]
393 _config: core::marker::PhantomData<(T, I)>,
394 }
395
396 #[pallet::genesis_build]
397 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
398 fn build(&self) {
399 let account_id = Pallet::<T, I>::account_id();
401 let min = T::Currency::minimum_balance();
402 if T::Currency::free_balance(&account_id) < min {
403 let _ = T::Currency::make_free_balance_be(&account_id, min);
404 }
405 }
406 }
407
408 #[pallet::event]
409 #[pallet::generate_deposit(pub(super) fn deposit_event)]
410 pub enum Event<T: Config<I>, I: 'static = ()> {
411 Spending { budget_remaining: BalanceOf<T, I> },
413 Awarded { proposal_index: ProposalIndex, award: BalanceOf<T, I>, account: T::AccountId },
415 Burnt { burnt_funds: BalanceOf<T, I> },
417 Rollover { rollover_balance: BalanceOf<T, I> },
419 Deposit { value: BalanceOf<T, I> },
421 SpendApproved {
423 proposal_index: ProposalIndex,
424 amount: BalanceOf<T, I>,
425 beneficiary: T::AccountId,
426 },
427 UpdatedInactive { reactivated: BalanceOf<T, I>, deactivated: BalanceOf<T, I> },
429 AssetSpendApproved {
431 index: SpendIndex,
432 asset_kind: T::AssetKind,
433 amount: AssetBalanceOf<T, I>,
434 beneficiary: T::Beneficiary,
435 valid_from: BlockNumberFor<T, I>,
436 expire_at: BlockNumberFor<T, I>,
437 },
438 AssetSpendVoided { index: SpendIndex },
440 Paid { index: SpendIndex, payment_id: <T::Paymaster as Pay>::Id },
442 PaymentFailed { index: SpendIndex, payment_id: <T::Paymaster as Pay>::Id },
444 SpendProcessed { index: SpendIndex },
447 }
448
449 #[pallet::error]
451 pub enum Error<T, I = ()> {
452 InvalidIndex,
454 TooManyApprovals,
456 InsufficientPermission,
459 ProposalNotApproved,
461 FailedToConvertBalance,
463 SpendExpired,
465 EarlyPayout,
467 AlreadyAttempted,
469 PayoutError,
471 NotAttempted,
473 Inconclusive,
475 }
476
477 #[pallet::hooks]
478 impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
479 fn on_initialize(_do_not_use_local_block_number: SystemBlockNumberFor<T>) -> Weight {
482 let block_number = T::BlockNumberProvider::current_block_number();
483 let pot = Self::pot();
484 let deactivated = Deactivated::<T, I>::get();
485 if pot != deactivated {
486 T::Currency::reactivate(deactivated);
487 T::Currency::deactivate(pot);
488 Deactivated::<T, I>::put(&pot);
489 Self::deposit_event(Event::<T, I>::UpdatedInactive {
490 reactivated: deactivated,
491 deactivated: pot,
492 });
493 }
494
495 let last_spend_period = LastSpendPeriod::<T, I>::get()
497 .unwrap_or_else(|| Self::update_last_spend_period());
501 let blocks_since_last_spend_period = block_number.saturating_sub(last_spend_period);
502 let safe_spend_period = T::SpendPeriod::get().max(BlockNumberFor::<T, I>::one());
503
504 let (spend_periods_passed, extra_blocks) = (
506 blocks_since_last_spend_period / safe_spend_period,
507 blocks_since_last_spend_period % safe_spend_period,
508 );
509 let new_last_spend_period = block_number.saturating_sub(extra_blocks);
510 if spend_periods_passed > BlockNumberFor::<T, I>::zero() {
511 Self::spend_funds(spend_periods_passed, new_last_spend_period)
512 } else {
513 Weight::zero()
514 }
515 }
516
517 #[cfg(feature = "try-runtime")]
518 fn try_state(_: SystemBlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
519 Self::do_try_state()?;
520 Ok(())
521 }
522 }
523
524 #[derive(Default)]
525 struct SpendContext<Balance> {
526 spend_in_context: BTreeMap<Balance, Balance>,
527 }
528
529 #[pallet::call]
530 impl<T: Config<I>, I: 'static> Pallet<T, I> {
531 #[pallet::call_index(3)]
549 #[pallet::weight(T::WeightInfo::spend_local())]
550 #[deprecated(
551 note = "The `spend_local` call will be removed by May 2025. Migrate to the new flow and use the `spend` call."
552 )]
553 #[allow(deprecated)]
554 pub fn spend_local(
555 origin: OriginFor<T>,
556 #[pallet::compact] amount: BalanceOf<T, I>,
557 beneficiary: AccountIdLookupOf<T>,
558 ) -> DispatchResult {
559 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
560 ensure!(amount <= max_amount, Error::<T, I>::InsufficientPermission);
561
562 with_context::<SpendContext<BalanceOf<T, I>>, _>(|v| {
563 let context = v.or_default();
564
565 let spend = context.spend_in_context.entry(max_amount).or_default();
570
571 if spend.checked_add(&amount).map(|s| s > max_amount).unwrap_or(true) {
573 Err(Error::<T, I>::InsufficientPermission)
574 } else {
575 *spend = spend.saturating_add(amount);
576
577 Ok(())
578 }
579 })
580 .unwrap_or(Ok(()))?;
581
582 let beneficiary = T::Lookup::lookup(beneficiary)?;
583 #[allow(deprecated)]
584 let proposal_index = ProposalCount::<T, I>::get();
585 #[allow(deprecated)]
586 Approvals::<T, I>::try_append(proposal_index)
587 .map_err(|_| Error::<T, I>::TooManyApprovals)?;
588 let proposal = Proposal {
589 proposer: beneficiary.clone(),
590 value: amount,
591 beneficiary: beneficiary.clone(),
592 bond: Default::default(),
593 };
594 #[allow(deprecated)]
595 Proposals::<T, I>::insert(proposal_index, proposal);
596 #[allow(deprecated)]
597 ProposalCount::<T, I>::put(proposal_index + 1);
598
599 Self::deposit_event(Event::SpendApproved { proposal_index, amount, beneficiary });
600 Ok(())
601 }
602
603 #[pallet::call_index(4)]
625 #[pallet::weight((T::WeightInfo::remove_approval(), DispatchClass::Operational))]
626 #[deprecated(
627 note = "The `remove_approval` call will be removed by May 2025. It associated with the deprecated `spend_local` call."
628 )]
629 #[allow(deprecated)]
630 pub fn remove_approval(
631 origin: OriginFor<T>,
632 #[pallet::compact] proposal_id: ProposalIndex,
633 ) -> DispatchResult {
634 T::RejectOrigin::ensure_origin(origin)?;
635
636 #[allow(deprecated)]
637 Approvals::<T, I>::try_mutate(|v| -> DispatchResult {
638 if let Some(index) = v.iter().position(|x| x == &proposal_id) {
639 v.remove(index);
640 Ok(())
641 } else {
642 Err(Error::<T, I>::ProposalNotApproved.into())
643 }
644 })?;
645
646 Ok(())
647 }
648
649 #[pallet::call_index(5)]
676 #[pallet::weight(T::WeightInfo::spend())]
677 pub fn spend(
678 origin: OriginFor<T>,
679 asset_kind: Box<T::AssetKind>,
680 #[pallet::compact] amount: AssetBalanceOf<T, I>,
681 beneficiary: Box<BeneficiaryLookupOf<T, I>>,
682 valid_from: Option<BlockNumberFor<T, I>>,
683 ) -> DispatchResult {
684 let max_amount = T::SpendOrigin::ensure_origin(origin)?;
685 let beneficiary = T::BeneficiaryLookup::lookup(*beneficiary)?;
686
687 let now = T::BlockNumberProvider::current_block_number();
688 let valid_from = valid_from.unwrap_or(now);
689 let expire_at = valid_from.saturating_add(T::PayoutPeriod::get());
690 ensure!(expire_at > now, Error::<T, I>::SpendExpired);
691
692 let native_amount =
693 T::BalanceConverter::from_asset_balance(amount, *asset_kind.clone())
694 .map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
695
696 ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
697
698 with_context::<SpendContext<BalanceOf<T, I>>, _>(|v| {
699 let context = v.or_default();
700 let spend = context.spend_in_context.entry(max_amount).or_default();
705
706 if spend.checked_add(&native_amount).map(|s| s > max_amount).unwrap_or(true) {
708 Err(Error::<T, I>::InsufficientPermission)
709 } else {
710 *spend = spend.saturating_add(native_amount);
711 Ok(())
712 }
713 })
714 .unwrap_or(Ok(()))?;
715
716 let index = SpendCount::<T, I>::get();
717 Spends::<T, I>::insert(
718 index,
719 SpendStatus {
720 asset_kind: *asset_kind.clone(),
721 amount,
722 beneficiary: beneficiary.clone(),
723 valid_from,
724 expire_at,
725 status: PaymentState::Pending,
726 },
727 );
728 SpendCount::<T, I>::put(index + 1);
729
730 Self::deposit_event(Event::AssetSpendApproved {
731 index,
732 asset_kind: *asset_kind,
733 amount,
734 beneficiary,
735 valid_from,
736 expire_at,
737 });
738 Ok(())
739 }
740
741 #[pallet::call_index(6)]
761 #[pallet::weight(T::WeightInfo::payout())]
762 pub fn payout(origin: OriginFor<T>, index: SpendIndex) -> DispatchResult {
763 ensure_signed(origin)?;
764 let mut spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
765 let now = T::BlockNumberProvider::current_block_number();
766 ensure!(now >= spend.valid_from, Error::<T, I>::EarlyPayout);
767 ensure!(spend.expire_at > now, Error::<T, I>::SpendExpired);
768 ensure!(
769 matches!(spend.status, PaymentState::Pending | PaymentState::Failed),
770 Error::<T, I>::AlreadyAttempted
771 );
772
773 let id = T::Paymaster::pay(&spend.beneficiary, spend.asset_kind.clone(), spend.amount)
774 .map_err(|_| Error::<T, I>::PayoutError)?;
775
776 spend.status = PaymentState::Attempted { id };
777 spend.expire_at = now.saturating_add(T::PayoutPeriod::get());
778 Spends::<T, I>::insert(index, spend);
779
780 Self::deposit_event(Event::<T, I>::Paid { index, payment_id: id });
781
782 Ok(())
783 }
784
785 #[pallet::call_index(7)]
805 #[pallet::weight(T::WeightInfo::check_status())]
806 pub fn check_status(origin: OriginFor<T>, index: SpendIndex) -> DispatchResultWithPostInfo {
807 use PaymentState as State;
808 use PaymentStatus as Status;
809
810 ensure_signed(origin)?;
811 let mut spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
812 let now = T::BlockNumberProvider::current_block_number();
813
814 if now > spend.expire_at && !matches!(spend.status, State::Attempted { .. }) {
815 Spends::<T, I>::remove(index);
817 Self::deposit_event(Event::<T, I>::SpendProcessed { index });
818 return Ok(Pays::No.into())
819 }
820
821 let payment_id = match spend.status {
822 State::Attempted { id } => id,
823 _ => return Err(Error::<T, I>::NotAttempted.into()),
824 };
825
826 match T::Paymaster::check_payment(payment_id) {
827 Status::Failure => {
828 spend.status = PaymentState::Failed;
829 Spends::<T, I>::insert(index, spend);
830 Self::deposit_event(Event::<T, I>::PaymentFailed { index, payment_id });
831 },
832 Status::Success | Status::Unknown => {
833 Spends::<T, I>::remove(index);
834 Self::deposit_event(Event::<T, I>::SpendProcessed { index });
835 return Ok(Pays::No.into())
836 },
837 Status::InProgress => return Err(Error::<T, I>::Inconclusive.into()),
838 }
839 return Ok(Pays::Yes.into())
840 }
841
842 #[pallet::call_index(8)]
859 #[pallet::weight(T::WeightInfo::void_spend())]
860 pub fn void_spend(origin: OriginFor<T>, index: SpendIndex) -> DispatchResult {
861 T::RejectOrigin::ensure_origin(origin)?;
862 let spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
863 ensure!(
864 matches!(spend.status, PaymentState::Pending | PaymentState::Failed),
865 Error::<T, I>::AlreadyAttempted
866 );
867
868 Spends::<T, I>::remove(index);
869 Self::deposit_event(Event::<T, I>::AssetSpendVoided { index });
870 Ok(())
871 }
872 }
873}
874
875impl<T: Config<I>, I: 'static> Pallet<T, I> {
876 pub fn account_id() -> T::AccountId {
883 T::PalletId::get().into_account_truncating()
884 }
885
886 fn update_last_spend_period() -> BlockNumberFor<T, I> {
890 let block_number = T::BlockNumberProvider::current_block_number();
891 let spend_period = T::SpendPeriod::get().max(BlockNumberFor::<T, I>::one());
892 let time_since_last_spend = block_number % spend_period;
893 let last_spend_period = if time_since_last_spend.is_zero() {
896 block_number.saturating_sub(spend_period)
897 } else {
898 block_number.saturating_sub(time_since_last_spend)
900 };
901 LastSpendPeriod::<T, I>::put(last_spend_period);
902 last_spend_period
903 }
904
905 #[deprecated(
907 note = "This function will be removed by May 2025. Configure pallet to use PayFromAccount for Paymaster type instead"
908 )]
909 pub fn proposal_count() -> ProposalIndex {
910 #[allow(deprecated)]
911 ProposalCount::<T, I>::get()
912 }
913
914 #[deprecated(
916 note = "This function will be removed by May 2025. Configure pallet to use PayFromAccount for Paymaster type instead"
917 )]
918 pub fn proposals(index: ProposalIndex) -> Option<Proposal<T::AccountId, BalanceOf<T, I>>> {
919 #[allow(deprecated)]
920 Proposals::<T, I>::get(index)
921 }
922
923 #[deprecated(
925 note = "This function will be removed by May 2025. Configure pallet to use PayFromAccount for Paymaster type instead"
926 )]
927 #[allow(deprecated)]
928 pub fn approvals() -> BoundedVec<ProposalIndex, T::MaxApprovals> {
929 Approvals::<T, I>::get()
930 }
931
932 pub fn spend_funds(
934 spend_periods_passed: BlockNumberFor<T, I>,
935 new_last_spend_period: BlockNumberFor<T, I>,
936 ) -> Weight {
937 LastSpendPeriod::<T, I>::put(new_last_spend_period);
938 let mut total_weight = Weight::zero();
939
940 let mut budget_remaining = Self::pot();
941 Self::deposit_event(Event::Spending { budget_remaining });
942 let account_id = Self::account_id();
943
944 let mut missed_any = false;
945 let mut imbalance = PositiveImbalanceOf::<T, I>::zero();
946 #[allow(deprecated)]
947 let proposals_len = Approvals::<T, I>::mutate(|v| {
948 let proposals_approvals_len = v.len() as u32;
949 v.retain(|&index| {
950 if let Some(p) = Proposals::<T, I>::get(index) {
952 if p.value <= budget_remaining {
953 budget_remaining -= p.value;
954 Proposals::<T, I>::remove(index);
955
956 let err_amount = T::Currency::unreserve(&p.proposer, p.bond);
958 debug_assert!(err_amount.is_zero());
959
960 imbalance.subsume(T::Currency::deposit_creating(&p.beneficiary, p.value));
962
963 Self::deposit_event(Event::Awarded {
964 proposal_index: index,
965 award: p.value,
966 account: p.beneficiary,
967 });
968 false
969 } else {
970 missed_any = true;
971 true
972 }
973 } else {
974 false
975 }
976 });
977 proposals_approvals_len
978 });
979
980 total_weight += T::WeightInfo::on_initialize_proposals(proposals_len);
981
982 T::SpendFunds::spend_funds(
984 &mut budget_remaining,
985 &mut imbalance,
986 &mut total_weight,
987 &mut missed_any,
988 );
989
990 if !missed_any && !T::Burn::get().is_zero() {
991 let one_minus_burn = T::Burn::get().left_from_one();
994 let percent_left =
995 one_minus_burn.saturating_pow(spend_periods_passed.unique_saturated_into());
996 let new_budget_remaining = percent_left * budget_remaining;
997 let burn = budget_remaining.saturating_sub(new_budget_remaining);
998 budget_remaining = new_budget_remaining;
999
1000 let (debit, credit) = T::Currency::pair(burn);
1001 imbalance.subsume(debit);
1002 T::BurnDestination::on_unbalanced(credit);
1003 Self::deposit_event(Event::Burnt { burnt_funds: burn })
1004 }
1005
1006 if let Err(problem) =
1011 T::Currency::settle(&account_id, imbalance, WithdrawReasons::TRANSFER, KeepAlive)
1012 {
1013 print("Inconsistent state - couldn't settle imbalance for funds spent by treasury");
1014 drop(problem);
1016 }
1017
1018 Self::deposit_event(Event::Rollover { rollover_balance: budget_remaining });
1019
1020 total_weight
1021 }
1022
1023 pub fn pot() -> BalanceOf<T, I> {
1026 T::Currency::free_balance(&Self::account_id())
1027 .saturating_sub(T::Currency::minimum_balance())
1029 }
1030
1031 #[cfg(any(feature = "try-runtime", test))]
1033 fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
1034 Self::try_state_proposals()?;
1035 Self::try_state_spends()?;
1036
1037 Ok(())
1038 }
1039
1040 #[cfg(any(feature = "try-runtime", test))]
1048 fn try_state_proposals() -> Result<(), sp_runtime::TryRuntimeError> {
1049 let current_proposal_count = ProposalCount::<T, I>::get();
1050 ensure!(
1051 current_proposal_count as usize >= Proposals::<T, I>::iter().count(),
1052 "Actual number of proposals exceeds `ProposalCount`."
1053 );
1054
1055 Proposals::<T, I>::iter_keys().try_for_each(|proposal_index| -> DispatchResult {
1056 ensure!(
1057 current_proposal_count as u32 > proposal_index,
1058 "`ProposalCount` should by strictly greater than any ProposalIndex used as a key for `Proposals`."
1059 );
1060 Ok(())
1061 })?;
1062
1063 Approvals::<T, I>::get()
1064 .iter()
1065 .try_for_each(|proposal_index| -> DispatchResult {
1066 ensure!(
1067 Proposals::<T, I>::contains_key(proposal_index),
1068 "Proposal indices in `Approvals` must also be contained in `Proposals`."
1069 );
1070 Ok(())
1071 })?;
1072
1073 Ok(())
1074 }
1075
1076 #[cfg(any(feature = "try-runtime", test))]
1084 fn try_state_spends() -> Result<(), sp_runtime::TryRuntimeError> {
1085 let current_spend_count = SpendCount::<T, I>::get();
1086 ensure!(
1087 current_spend_count as usize >= Spends::<T, I>::iter().count(),
1088 "Actual number of spends exceeds `SpendCount`."
1089 );
1090
1091 Spends::<T, I>::iter_keys().try_for_each(|spend_index| -> DispatchResult {
1092 ensure!(
1093 current_spend_count > spend_index,
1094 "`SpendCount` should by strictly greater than any SpendIndex used as a key for `Spends`."
1095 );
1096 Ok(())
1097 })?;
1098
1099 Spends::<T, I>::iter().try_for_each(|(_index, spend)| -> DispatchResult {
1100 ensure!(
1101 spend.valid_from < spend.expire_at,
1102 "Spend cannot expire before it becomes valid."
1103 );
1104 Ok(())
1105 })?;
1106
1107 Ok(())
1108 }
1109}
1110
1111impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
1112 fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
1113 let numeric_amount = amount.peek();
1114
1115 let _ = T::Currency::resolve_creating(&Self::account_id(), amount);
1117
1118 Self::deposit_event(Event::Deposit { value: numeric_amount });
1119 }
1120}
1121
1122pub struct TreasuryAccountId<R>(PhantomData<R>);
1124impl<R> sp_runtime::traits::TypedGet for TreasuryAccountId<R>
1125where
1126 R: crate::Config,
1127{
1128 type Type = <R as frame_system::Config>::AccountId;
1129 fn get() -> Self::Type {
1130 crate::Pallet::<R>::account_id()
1131 }
1132}