本節學習寫一個ERC20合約,主要包括:
- 初始token設定;
- 支援transfer;
- 透過substrate觸發事件。
ERC20標準定義了最流行的智慧合約的介面,可以用來實現ERC20 token。
主要如下:
// ----------------------------------------------------------------------------
// ERC Token Standard #20 Interface
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
// ----------------------------------------------------------------------------
contract ERC20Interface {
// Storage Getters
function totalSupply() public view returns (uint);
function balanceOf(address tokenOwner) public view returns (uint balance);
function allowance(address tokenOwner, address spender) public view returns (uint remaining);
// Public Functions
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
// Contract Events
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
使用如下命令:
cargo contract new erc20
接下來我們就將編輯lib.rs的內容來實現我們的功能。我們根據上面的ERC20的標準的定義,我們可以分析出我們需要的儲存如下:
total_supply
: storage_value,代表合約中token的總量;balances
:hashMap,代表每個賬戶的餘額。
下面我們可以寫出部分合約程式碼如下:
#[ink(storage)]
pub struct Erc20 {
total_supply: Balance,
balances: ink_storage::collections::HashMap<AccountId, Balance>
}
impl Erc20 {
#[ink(constructor)]
pub fn new(init_supply: Balance) -> Self {
let mut balances = ink_storage::collections::HashMap::new();
balances.insert(Self::env().caller(), init_supply);
Self {
total_supply: init_supply,
balances
}
}
#[ink(message)]
pub fn total_supply(&self) -> Balance {
self.total_supply
}
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balance_of_or_zero(&owner)
}
fn balance_of_or_zero(&self, owner: &AccountId) -> Balance {
*self.balances.get(owner).unwrap_or(&0)
}
}
程式碼如下:
#[ink(message)]
pub fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
self.transfer_from_to(self.env().caller(), to, value)
}
fn transfer_from_to(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
let from_balance = self.balance_of_or_zero(&from);
if from_balance < value {
return false;
}
self.balances.insert(from, from_balance - value);
let to_balance = self.balance_of_or_zero(&to);
self.balances.insert(to, to_balance + value);
true
}
定義事件如下:
#[ink(event)]
pub struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
#[ink(topic)]
value: Balance,
}
觸發事件如下:
Self::env().emit_event(Transfer{
from: Some(from),
to: Some(to),
value,
});
新增完後程式碼如下:
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod erc20 {
#[ink(storage)]
pub struct Erc20 {
total_supply: Balance,
balances: ink_storage::collections::HashMap<AccountId, Balance>,
allowances: ink_storage::collections::HashMap<(AccountId, AccountId), Balance>,
}
#[ink(event)]
pub struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
#[ink(topic)]
value: Balance,
}
#[ink(event)]
pub struct Approval {
#[ink(topic)]
owner: AccountId,
#[ink(topic)]
spender: AccountId,
#[ink(topic)]
value: Balance,
}
impl Erc20 {
#[ink(constructor)]
pub fn new(init_supply: Balance) -> Self {
let caller = Self::env().caller();
let mut balances = ink_storage::collections::HashMap::new();
balances.insert(caller, init_supply);
Self::env().emit_event(Transfer {
from: None,
to: Some(caller),
value: init_supply,
});
Self {
total_supply: init_supply,
balances,
allowances: ink_storage::collections::HashMap::new(),
}
}
#[ink(message)]
pub fn total_supply(&self) -> Balance {
self.total_supply
}
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balance_of_or_zero(&owner)
}
#[ink(message)]
pub fn approve(&mut self, spender: AccountId, value: Balance) -> bool {
let owner = self.env().caller();
self.allowances.insert((owner, spender), value);
self.env().emit_event(Approval {
owner,
spender,
value,
});
true
}
#[ink(message)]
pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
self.allowance_of_or_zero(&owner, &spender)
}
#[ink(message)]
pub fn transfer_from(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
let caller = self.env().caller();
let allowance = self.allowance_of_or_zero(&from, &caller);
if allowance < value {
return false;
}
let transfer_result = self.transfer_from_to(from, to, value);
if !transfer_result {
return false;
}
self.allowances.insert((from, caller), allowance - value);
true
}
#[ink(message)]
pub fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
self.transfer_from_to(self.env().caller(), to, value)
}
fn transfer_from_to(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
let from_balance = self.balance_of_or_zero(&from);
if from_balance < value {
return false;
}
self.balances.insert(from, from_balance - value);
let to_balance = self.balance_of_or_zero(&to);
self.balances.insert(to, to_balance + value);
Self::env().emit_event(Transfer {
from: Some(from),
to: Some(to),
value,
});
true
}
fn balance_of_or_zero(&self, owner: &AccountId) -> Balance {
*self.balances.get(owner).unwrap_or(&0)
}
fn allowance_of_or_zero(&self, owner: &AccountId, spender: &AccountId) -> Balance {
*self.allowances.get(&(*owner, *spender)).unwrap_or(&0)
}
}
}
新增測試程式碼如下:
#[cfg(test)]
mod tests {
use super::*;
use ink_lang as ink;
#[ink::test]
fn new_works() {
let contract = Erc20::new(888);
assert_eq!(contract.total_supply(), 888);
}
#[ink::test]
fn balance_works() {
let contract = Erc20::new(100);
assert_eq!(contract.total_supply(), 100);
assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 0);
}
#[ink::test]
fn transfer_works() {
let mut contract = Erc20::new(100);
assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
assert!(contract.transfer(AccountId::from([0x0; 32]), 10));
assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);
assert!(!contract.transfer(AccountId::from([0x0; 32]), 100));
}
#[ink::test]
fn transfer_from_works() {
let mut contract = Erc20::new(100);
assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
contract.approve(AccountId::from([0x1; 32]), 20);
contract.transfer_from(AccountId::from([0x1; 32]), AccountId::from([0x0; 32]), 10);
assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);
}
#[ink::test]
fn allowances_works() {
let mut contract = Erc20::new(100);
assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
contract.approve(AccountId::from([0x1; 32]), 200);
assert_eq!(
contract.allowance(AccountId::from([0x1; 32]), AccountId::from([0x1; 32])),
200
);
assert!(contract.transfer_from(
AccountId::from([0x1; 32]),
AccountId::from([0x0; 32]),
50
));
assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 50);
assert_eq!(
contract.allowance(AccountId::from([0x1; 32]), AccountId::from([0x1; 32])),
150
);
assert!(!contract.transfer_from(
AccountId::from([0x1; 32]),
AccountId::from([0x0; 32]),
100
));
assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 50);
assert_eq!(
contract.allowance(AccountId::from([0x1; 32]), AccountId::from([0x1; 32])),
150
);
}
}
docs.substrate.io/tutorials/v3/ink...
github.com/anonymousGiga/substrate...
本作品採用《CC 協議》,轉載必須註明作者和本文連結