-
Notifications
You must be signed in to change notification settings - Fork 26
token module
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,
}
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)
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.
Substrate use decl_module! macro the define the runtime module, which can be called by any blockchain frontend
- 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.
- 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:
- ensure token exists
ensure!(token.is_some(), "no matching token found");
- ensure sender have free balance of the token
ensure!(
<FreeBalanceOf<T>>::exists((sender.clone(), hash)),
"sender does not have the token"
);
- 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"
);
- 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.
-
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 {
...
}
}
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 thatsome_method()
will return variant Ok(()) of Result -
assert_err!(some_method(), e)
says thatsome_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
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"
);
Anything unclear or inaccurate? Please let us know at [email protected].