Skip to content

token module

Yang Xu edited this page Dec 20, 2019 · 3 revisions

Token Module

Token Entity

Everyone can issue a token, with a token symbol, and total supply.

We define the token object as:

struct Token<Hash, Balance> {
    pub hash: Hash,
    pub symbol: Vec<u8>,
    pub total_supply: Balance,
}

Events

Because substrate runtime can deposit event to let the client know what happen in the blockchain, we need define some events related to token.

Issued(AccountId, Hash, Balance)
Transferred(AccountId, AccountId, Hash, Balance)
Freezed(AccountId, Hash, Balance)
UnFreezed(AccountId, Hash, Balance)

Storage

Tokens get(token): map T::Hash => Option<Token<T::Hash, T::Balance>>;
Owners get(owner): map T::Hash => Option<T::AccountId>;
BalanceOf get(balance_of): map (T::AccountId, T::Hash) => T::Balance;
FreeBalanceOf get(free_balance_of): map (T::AccountId, T::Hash) => T::Balance;
FreezedBalanceOf get(freezed_balance_of): map (T::AccountId, T::Hash) => T::Balance;

OwnedTokens get(owned_token): map (T::AccountId, u64) => Option<T::Hash>;
OwnedTokensIndex get(owned_token_index): map T::AccountId => u64;

Nonce: u64;
  • We use Tokens map key/value pair to store token's Hash (Key) and token entity (value)
  • We use Owners map key/value pair to store token's Hash and token's creator's account ID
  • We use BalanceOf to keep the user's total balance, pay attention that the key is a tuple with AccountId + Token_Hash. Any token's holding by any user, meet the equation:
BalanceOf (User_U, Token_T) = FreeBalanceOf(User_U, Token_U) + FreezedBalanceOf(User_U, Token_U)
  • Also, we use OwnedTokens and OwnedTokensIndex to store all token's Hash owned/created by each user.

Module

Substrate use decl_module! macro the define the runtime module, which can be called by any blockchain frontend

issue token

  • Everyone can issue token as his will, by providing the symbol and total_supply.
  • When generate the Hash of the token, beside using random_seed of system module and sender's ID, we also use a auto-increment nonce to provide extra noise.

transfer token

  • When we build a blockchain business logic, we need do verification & assertion as detailed as we can.
  • Substrate blockchain runtime module logic is: verify first, write last.
  • We do the verification such like:
  1. ensure token exists
ensure!(token.is_some(), "no matching token found");
  1. ensure sender have free balance of the token
ensure!(
    <FreeBalanceOf<T>>::exists((sender.clone(), hash)),
    "sender does not have the token"
);
  1. ensure sender have enough amount
ensure!(from_amount >= amount, "sender does not have enough balance");

ensure!(
    from_free_amount >= amount,
    "sender does not have enough free balance"
);
  1. ensure not overflow after the transfer
ensure!(
    new_to_amount <= T::Balance::max_value(),
    "to amount overflow"
);

ensure!(
    new_to_free_amount <= T::Balance::max_value(),
    "to free amount overflow"
);
  • Seems there is no need to do some of the verifications, BUT, we think verifications are never enough, the more, the better.

freeze & unfreeze

  • As a basic token module, there is no need to provide freeze & unfreeze functions at all. However, we need to provide these capabilities to other's module, trade module.

  • As you can see in the source code, we put the main business code in a separate section. By doing so other module can call this method for free.

impl<T: Trait> Module<T> {
    pub fn do_unfreeze(sender: T::AccountId, hash: T::Hash, amount: T::Balance) -> Result {
        ...
    }
    
    pub fn do_unfreeze(sender: T::AccountId, hash: T::Hash, amount: T::Balance) -> Result {
        ...
    }
}

test case

We need write some test code to ensure everything is ok. Basically there are five assertion method we can use

  • assert_ok!(some_method()) says that some_method() will return variant Ok(()) of Result
  • assert_err!(some_method(), e) says that some_method() will return variant Err(E) of Result and the value of E is equal to e
  • assert_eq!(a, b) says that a is equal to b
  • assert_ne!(a, b) says that a is NOT equal to b
  • assert!(a) says that a is equal to true

The first two assert macros are provide by substrate support package. The last three are provided by rust std

test code

assert_ok!(TokenModule::issue(
    Origin::signed(alice),
    b"6688".to_vec(),
    21000000
));
assert_eq!(TokenModule::owned_token_index(alice), 1);

let token_hash = TokenModule::owned_token((alice, 0));
assert!(token_hash.is_some());
let token_hash = token_hash.unwrap();
let token = TokenModule::token(token_hash);
assert!(token.is_some());
let token = token.unwrap();

assert_eq!(TokenModule::balance_of((alice, token.hash)), 21000000);
assert_eq!(TokenModule::free_balance_of((alice, token.hash)), 21000000);
assert_eq!(TokenModule::freezed_balance_of((alice, token.hash)), 0);

assert_ok!(TokenModule::transfer(
    Origin::signed(alice),
    token.hash,
    bob,
    100
));
assert_eq!(TokenModule::balance_of((alice, token.hash)), 20999900);
assert_eq!(TokenModule::free_balance_of((alice, token.hash)), 20999900);
assert_eq!(TokenModule::freezed_balance_of((alice, token.hash)), 0);
assert_eq!(TokenModule::balance_of((bob, token.hash)), 100);
assert_eq!(TokenModule::free_balance_of((bob, token.hash)), 100);
assert_eq!(TokenModule::freezed_balance_of((bob, token.hash)), 0);

assert_err!(
    TokenModule::transfer(Origin::signed(bob), H256::from_low_u64_be(0), charlie, 101),
    "no matching token found"
);
assert_err!(
    TokenModule::transfer(Origin::signed(charlie), token.hash, bob, 101),
    "sender does not have the token"
);
assert_err!(
    TokenModule::transfer(Origin::signed(bob), token.hash, charlie, 101),
    "sender does not have enough balance"
);