售前客服二维码
文章均源于网络收集编辑侵删
提示:仅接受技术开发咨询!
ERC20定义了一些标准的接口函数:balanceOf 、 totalSupply 、transfer 、transferFrom 、approve和allowance 。以及一些可选的字段,例如通证名称、符号以及小数保留位数等。
详见:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
2.2 ERC20接口
contract ERC20 {
function totalSupply() constant returns (uint theTotalSupply);
function balanceOf(address _owner) constant returns (uint balance);
function transfer(address _to, uint _value) returns (bool success);
function transferFrom(address _from, address _to, uint _value) returns (bool success);
function approve(address _spender, uint _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint remaining);
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
•功能介绍:
3 ERC20合约开发
3.1 创建合约工程
执行命令后,会生成2个文件,其中lib.rs会包括一些基础框架,我们可以在此基础上开发我们的合约。
$ cargo contract new erc20
Created contract erc20
$ tree erc20/
erc20/
├── Cargo.toml
└── lib.rs
3.2 合约存储创建
#[ink(storage)]
struct Erc20 {
/// 代币发行总量
total_supply: storage::Value<Balance>,
/// 用户及余额映射
balances: storage::HashMap<AccountId, Balance>,
}
3.3 合约构造方法创建
#[ink(constructor)]
fn new(&mut self, initial_supply: Balance) {
// 获取合约创建者
let caller = self.env().caller();
// 设置发行总量
self.total_supply.set(initial_supply);
// 合约创建者拥有所有发行代币
self.balances.insert(caller, initial_supply);
}
3.4 合约接口方法创建
(1)查询代币发行总量接口
#[ink(message)]
fn total_supply(&self) -> Balance {
*self.total_supply
}
(2)查询用户代币余额接口
#[ink(message)]
fn balance_of(&self, owner: AccountId) -> Balance {
self.balance_of_or_zero(&owner)
}
// 工具方法:若用户未被初始化,代币余额置为0
fn balance_of_or_zero(&self, owner: &AccountId) -> Balance {
*self.balances.get(owner).unwrap_or(&0)
(3)转账接口
#[ink(message)]
fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
// 获取合约接口调用者地址
let from = self.env().caller();
// 给接收地址转出指定金额代币
self.transfer_from_to(from, 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
}
// 获取合约接受者账户余额(代币接收者账户可能未被初始化,通过此方法将其余额初始化为0)
let to_balance = self.balance_of_or_zero(&to);
// 发送者余额减少指定数量
self.balances.insert(from, from_balance - value);
// 接收者余额增加指定数量
self.balances.insert(to, to_balance + value);
true
}
我们注意到,在进行余额的增减时,并未像以太坊的solidity智能合约,使用额外的SafeMath接口,这是因为ink!提供了内置防溢出保护,通过在Cargo.toml 配置文件中,添加如下配置来提供该安全机制:
[profile.release]
panic = "abort" <-- Panics shall be treated as aborts: reduces binary size
lto = true <-- enable link-time-optimization: more efficient codegen
opt-level = "z" <-- Optimize for small binary output
overflow-checks = true <-- Arithmetic overflow protection
(4)授权转账——授权接口
通过授权转账,调用方可以授权指定账户,从其地址中安全的消费指定数量的代币。
需完善合约存储:
#[ink(storage)]
struct Erc20 {
......
// (代币所有者, 代币授权使用者) -> 代币授权使用者可支配余额
allowances: storage::HashMap<(AccountId, AccountId), Balance>,
}
#[ink(message)]
fn approve(&mut self, spender: AccountId, value: Balance) -> bool {
let owner = self.env().caller();
// 代币所有者(owner)授权代币使用者(spender)可支配余额(value)
self.allowances.insert((owner, spender), value);
true
}
(5)授权转账——余额查询
获取代币授权使用者剩余被允许转移的代币数量。
#[ink(message)]
fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
self.allowance_of_or_zero(&owner, &spender)
}
(6)授权转账——转账接口
允许智能合约自动执行转账流程并代表所有者发送给定数量的代币
#[ink(message)]
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
}
self.allowances.insert((from, caller), allowance - value);
self.transfer_from_to(from, to, value)
}
fn allowance_of_or_zero(&self, owner: &AccountId, spender: &AccountId) -> Balance {
*self.allowances.get(&(*owner, *spender)).unwrap_or(&0)
}
3.5 合约事件创建
•事件定义
#[ink(event)]
struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
#[ink(topic)]
value: Balance,
}
#[ink(event)]
struct Approval {
#[ink(topic)]
owner: AccountId,
#[ink(topic)]
spender: AccountId,
#[ink(topic)]
value: Balance,
}
•合约构造事件
self.env().emit_event(Transfer {
from: None,
to: Some(caller),
value: initial_supply,
});
•转账事件
self.env().emit_event(Transfer {
from: Some(from),
to: Some(to),
value,
});
•授权事件
self.env().emit_event(Approval {
owner,
spender,
value,
});
3.6 单元测试用例编写
#[test]
fn new_works() {
let contract = Erc20::new(777);
assert_eq!(contract.total_supply(), 777);
}
#[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);
}
#[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));
}
#[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);
}
跑测试用例:
$ cargo +nightly test
3.7 完整代码
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract(version = "0.1.0")]
mod erc20 {
use ink_core::storage;
#[ink(storage)]
struct Erc20 {
/// The total supply.
total_supply: storage::Value<Balance>,
/// The balance of each user.
balances: storage::HashMap<AccountId, Balance>,
/// Approval spender on behalf of the message's sender.
allowances: storage::HashMap<(AccountId, AccountId), Balance>,
}
#[ink(event)]
struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
#[ink(topic)]
value: Balance,
}
#[ink(event)]
struct Approval {
#[ink(topic)]
owner: AccountId,
#[ink(topic)]
spender: AccountId,
#[ink(topic)]
value: Balance,
}
impl Erc20 {
#[ink(constructor)]
fn new(&mut self, initial_supply: Balance) {
let caller = self.env().caller();
self.total_supply.set(initial_supply);
self.balances.insert(caller, initial_supply);
self.env().emit_event(Transfer {
from: None,
to: Some(caller),
value: initial_supply,
});
}
#[ink(message)]
fn total_supply(&self) -> Balance {
*self.total_supply
}
#[ink(message)]
fn balance_of(&self, owner: AccountId) -> Balance {
self.balance_of_or_zero(&owner)
}
#[ink(message)]
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)]
fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
self.allowance_of_or_zero(&owner, &spender)
}
#[ink(message)]
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
}
self.allowances.insert((from, caller), allowance - value);
self.transfer_from_to(from, to, value)
}
#[ink(message)]
fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
let from = self.env().caller();
self.transfer_from_to(from, 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
}
let to_balance = self.balance_of_or_zero(&to);
self.balances.insert(from, from_balance - value);
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::*;
#[test]
fn new_works() {
let contract = Erc20::new(777);
assert_eq!(contract.total_supply(), 777);
}
#[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);
}
#[test]