Charity
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.
fn donate(
origin,
amount: BalanceOf<T>
) -> DispatchResult {
let donor = ensure_signed(origin)?;
let _ = T::Currency::transfer(&donor, &Self::account_id(), amount, AllowDeath);
Self::deposit_event(RawEvent::DonationReceived(donor, amount, Self::pot()));
Ok(())
}
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
NegativeImbalance
s.
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.