Charity

pallets/charity Try on playground View on GitHub

The Charity pallet represents a simple charitable organization that collects funds into a pot that it controls, and allocates those funds to the appropriate causes. It demonstrates two useful concepts in Substrate development:

  • A pallet-controlled shared pot of funds
  • Absorbing imbalances from the runtime

Instantiate a Pot

Our charity needs an account to hold its funds. Unlike other accounts, it will not be controlled by a user's cryptographic key pair, but directly by the pallet. To instantiate such a pool of funds, import ModuleId and AccountIdConversion from sp-runtime.

use sp-runtime::{ModuleId, traits::AccountIdConversion};

With these imports, a PALLET_ID constant can be generated as an identifier for the pool of funds. The PALLET_ID must be exactly eight characters long which is why we've included the exclamation point. (Well, that and Charity work is just so exciting!) This identifier can be converted into an AccountId with the into_account() method provided by the AccountIdConversion trait.

const PALLET_ID: ModuleId = ModuleId(*b"Charity!");

impl<T: Config> Module<T> {
	/// The account ID that holds the Charity's funds
	pub fn account_id() -> T::AccountId {
		PALLET_ID.into_account()
	}

	/// The Charity's balance
	fn pot() -> BalanceOf<T> {
		T::Currency::free_balance(&Self::account_id())
	}
}

Receiving Funds

Our charity can receive funds in two different ways.

Donations

The first and perhaps more familiar way is through charitable donations. Donations can be made through a standard donate extrinsic which accepts the amount to be donated as a parameter.

#[pallet::call]
impl<T: Config> Pallet<T> {
	/// Donate some funds to the charity
	#[pallet::weight(10_000)]
	pub fn donate(origin: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResultWithPostInfo {
		let donor = ensure_signed(origin)?;

		T::Currency::transfer(&donor, &Self::account_id(), amount, AllowDeath)
			.map_err(|_| DispatchError::Other("Can't make donation"))?;

			Self::deposit_event(Event::DonationReceived(donor, amount, Self::pot()));
			Ok(().into())
}

Imbalances

The second way the charity can receive funds is by absorbing imbalances created elsewhere in the runtime. An Imbalance is created whenever tokens are burned, or minted. Because our charity wants to collect funds, we are specifically interested in NegativeImbalances. Negative imbalances are created, for example, when a validator is slashed for violating consensus rules, transaction fees are collected, or another pallet burns funds as part of an incentive-alignment mechanism. To allow our pallet to absorb these imbalances, we implement the OnUnbalanced trait.

use frame_support::traits::{OnUnbalanced, Imbalance};
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;

impl<T: Config> OnUnbalanced<NegativeImbalanceOf<T>> for Module<T> {
	fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T>) {
		let numeric_amount = amount.peek();

		// Must resolve into existing but better to be safe.
		let _ = T::Currency::resolve_creating(&Self::account_id(), amount);

		Self::deposit_event(RawEvent::ImbalanceAbsorbed(numeric_amount, Self::pot()));
	}
}

Allocating Funds

In order for the charity to affect change with the funds it has collected it must be able to allocate those funds. Our charity pallet abstracts the governance of where funds will be allocated to the rest of the runtime. Funds can be allocated by a root call to the allocate extrinsic. One good example of a governance mechanism for such decisions is Substrate's own Democracy pallet.