substrate学习笔记10:开发erc20合约
1 学习内容#
本节学习写一个 ERC20 合约,主要包括:
- 初始 token 设置;
- 支持 transfer;
- 通过 substrate 触发事件。
2 ERC20 标准#
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);
}
3 创建一个 ERC20 模板#
使用如下命令:
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)
}
}
4 实现转账功能#
代码如下:
#[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
}
5 创建事件#
定义事件如下:
#[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,
});
6 支持 approval 和 transfer_from#
添加完后代码如下:
#![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)
}
}
}
7 添加测试#
添加测试代码如下:
#[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
);
}
}
8 参考资料#
docs.substrate.io/tutorials/v3/ink...
完整源码地址#
github.com/anonymousGiga/substrate...
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: