Developers Home»how to guides»Testing a Transfer Function

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

Resources

Rust docs

Last edit: on

Was This Guide Helpful?
Help us improve