Testing a Transfer Function
Goal
Learn how to write tests and improve the correctness of a transfer
function.
Use Cases
Testing a custom transfer function.
Overview
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.
Steps
1. 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())
}
2. 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(Origin::signed(1), 2, 42));
// Send 50 coins to account 3.
asset_noop!(RewardCoin::transfer(Origin::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.
3. Check that sending account doesn't go below minimum balance
Inside your transfer_works
function:
assert_noop!(RewardCoin::transfer(Origin::signed(2), 3, 50), Error::<Test>::InsufficientBalance);
4. Check that both tests work together
Use #[transactional]
to generate a wrapper around both checks:
#[transactional]
pub(super) fn transfer(
/*--snip--*/
5. 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-coin
example pallet.