Test a transfer function
Please read Substrate to Polkadot SDK page first.
Testing each function is an important part of developing pallets for production.
This guide steps you through best practices for writing test cases for a basic transfer function.
Outline the transfer function
A transfer function has two key elements: subtracting a balance from an account and adding that balance to another account. Here, we'll start by outlining this function:
#[pallet::weight(10_000)]
pub (super) fn transfer(
origin: OriginFor<T>,
to: T::AccountId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
Accounts::<T>::mutate(&sender, |bal| {
*bal = bal.saturating_sub(amount);
});
Accounts::<T>::mutate(&to, |bal| {
*bal = bal.saturating_add(amount);
});
Self::deposit_event(Event::<T>::Transferred(sender, to, amount))
Ok(().into())
}Check that the sender has enough balance
The first thing to verify, is whether the sender has enough balance.
In a separate tests.rs file, write out this first test case:
#[test]
fn transfer_works() {
new_test_ext().execute_with(|| {
MetaDataStore::<Test>::put(MetaData {
issuance: 0,
minter: 1,
burner: 1,
});
// Mint 42 coins to account 2.
assert_ok!(RewardCoin::mint(RuntimeOrigin::signed(1), 2, 42));
// Send 50 coins to account 3.
asset_noop!(RewardCoin::transfer(RuntimeOrigin::signed(2), 3, 50), Error::<T>::InsufficientBalance);Configure error handling
To implement some error check, replace mutate with try_mutate to use ensure!.
This will check whether bal is greater or equal to amount and throw an error message if not:
Accounts::<T>::try_mutate(&sender, |bal| {
ensure!(bal >= amount, Error::<T>::InsufficientBalance);
*bal = bal.saturating_sub(amount);
Ok(())
});Run cargo test from your pallet's directory.
Check that sending account doesn't go below minimum balance
Inside your transfer_works function:
assert_noop!(RewardCoin::transfer(RuntimeOrigin::signed(2), 3, 50), Error::<Test>::InsufficientBalance);Check that both tests work together
Use #[transactional] to generate a wrapper around both checks:
#[transactional]
pub(super) fn transfer(
/*--snip--*/Handle dust accounts
Make sure that sending and receiving accounts aren't dust accounts. Use T::MinBalance::get():
/*--snip--*/
let new_bal = bal.checked_sub(&amount).ok_or(Error::<T>::InsufficientBalance)?;
ensure!(new_bal >= T::MinBalance::get(), Error::<T>::BelowMinBalance);
/*--snip--*/Examples
- Tests in the
reward-coinexample pallet.
