内容简介:本文参考官方教程(本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
本文参考官方教程( https://substrate.dev/substrate-contracts-workshop ) ,带你上手使用Ink!开发Substrate ERC20智能合约
使用Ink!开发Substrate ERC20智能合约
jasonruan 2020.07.14
1 环境搭建
1.1 安装Substrate节点
$ git clone git@github.com:paritytech/substrate.git $ cd substrate (master)$ git checkout -b v2.0.0-rc4 v2.0.0-rc4 切换到一个新分支 'v2.0.0-rc4' (v2.0.0-rc4)$ cargo build --release
1.2 安装cargo contract插件
- 安装命令
$ cargo install cargo-contract --vers 0.6.1 --force
- 帮助手册
$ cargo contract --help cargo-contract 0.6.1 Utilities to develop Wasm smart contracts USAGE: cargo contract <SUBCOMMAND> OPTIONS: -h, --help Prints help information -V, --version Prints version information SUBCOMMANDS: new Setup and create a new smart contract project build Compiles the smart contract generate-metadata Generate contract metadata artifacts test Test the smart contract off-chain help Prints this message or the help of the given subcommand(s)
2 ERC20合约介绍
2.1 什么是ERC20标准
ERC20 通证标准(ERC20 Token Standard)是通过以太坊创建通证时的一种规范。按照 ERC20 的规范可以编写一个智能合约,创建“可互换通证”。它并非强制要求,但遵循这个标准,所创建的通证可以与众多交易所、钱包等进行交互,它现在已被行业普遍接受。
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); }
- 功能介绍:
| 函数名 | 功能 | | -------------- | ------------------------------------------------------------ | | totalSupply | 返回存在于流通中的通证(Token)总量 | | balanceOf | 返回指定账户地址的通证余额 | | transfer | 让调用方将指定数量的通证发送到另一个地址,即转账 | | transferFrom | 允许智能合约自动执行转账流程并代表所有者发送给定数量的通证 | | approve | 调用方授权给定的地址可以从其地址中提款 | | allowance | 返回被允许转移的余额数量 | | event Transfer | 事件通知,当token被转移时, 必须 调用触发,类似回调,当事件发生时,会得到通知 | | event Approval | 事件通知,当任何成功调用 approve
后, 必须 调用触发 |
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] 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); } } }
4 ERC20合约部署
4.1 启动substrate链
[Jason@RUAN:~/Blockchain/substrate] (v2.0.0-rc4)$ ./target/release/substrate purge-chain --dev Are you sure to remove "/root/.local/share/substrate/chains/dev/db"? [y/N]: y "/root/.local/share/substrate/chains/dev/db" did not exist. [Jason@RUAN:~/Blockchain/substrate] (v2.0.0-rc4)$ ./target/release/substrate --dev --ws-external --rpc-external --rpc-cors=all 2020-07-13 23:07:17 Substrate Node 2020-07-13 23:07:17 :v: version 2.0.0-rc4-00768a1-x86_64-linux-gnu 2020-07-13 23:07:17 :heart: by Parity Technologies <admin@parity.io>, 2017-2020 2020-07-13 23:07:17 :clipboard: Chain specification: Development 2020-07-13 23:07:17 Node name: ill-hen-8567 2020-07-13 23:07:17 :bust_in_silhouette: Role: AUTHORITY 2020-07-13 23:07:17 :floppy_disk: Database: RocksDb at /root/.local/share/substrate/chains/dev/db 2020-07-13 23:07:17 ⛓ Native runtime: node-254 (substrate-node-0.tx1.au10) 2020-07-13 23:07:17 :money_with_wings: new validator set of size 1 has been elected via ElectionCompute::OnChain for era 0 2020-07-13 23:07:17 :hammer: Initializing Genesis block/state (state: 0xc720…bb8a, header-hash: 0x6ea2…1245) 2020-07-13 23:07:17 :older_man: Loading GRANDPA authority set from genesis on what appears to be first startup. 2020-07-13 23:07:17 ⏱ Loaded block-time = 3000 milliseconds from genesis on first-launch 2020-07-13 23:07:17 :baby: Creating empty BABE epoch changes on what appears to be first startup. 2020-07-13 23:07:17 :package: Highest known block at #0 2020-07-13 23:07:17 Using default protocol ID "sup" because none is configured in the chain specs 2020-07-13 23:07:17 Local node identity is: 12D3KooWQUQtujJ5SGCdCcheuExioC81R5W4E3RFGhmhx3MT8iqy (legacy representation: QmX71wUqWKy7FQX8PEHKoQLaiBLLTfK8TL25mFXxKhMWGw) 2020-07-13 23:07:17 〽 Prometheus server started at 127.0.0.1:9615 2020-07-13 23:07:17 :baby: Starting BABE Authorship worker 2020-07-13 23:07:18 :raised_hands: Starting consensus session on top of parent 0x6ea2a97a8da973976a82f053a8b909aff5e0659ca6d51b6c9d6947b4dc3d1245 2020-07-13 23:07:18 :gift: Prepared block for proposing at 1 [hash: 0x3b99b664d0a21fbc72bfed709700b5bba05564c8d62e9ddd677412896f25de31; parent_hash: 0x6ea2…1245; extrinsics (1): [0xdcda…fb8d]] 2020-07-13 23:07:18 :bookmark: Pre-sealed block for proposal at 1. Hash now 0x3081484a5cbe82a9b4a4aea4d360fd69219a43d18182c6fd297e2ffac71feff2, previously 0x3b99b664d0a21fbc72bfed709700b5bba05564c8d62e9ddd677412896f25de31. 2020-07-13 23:07:18 :baby: New epoch 0 launching at block 0x3081…eff2 (block slot 531550946 >= start slot 531550946). 2020-07-13 23:07:18 :baby: Next epoch starts at slot 531551146 2020-07-13 23:07:18 :sparkles: Imported #1 (0x3081…eff2) 2020-07-13 23:07:21 :raised_hands: Starting consensus session on top of parent 0x3081484a5cbe82a9b4a4aea4d360fd69219a43d18182c6fd297e2ffac71feff2 2020-07-13 23:07:21 :gift: Prepared block for proposing at 2 [hash: 0x346204e0b46b86dc4ec85b18cf2fdf0f0e818b24208e56217e6f44c135e3aef3; parent_hash: 0x3081…eff2; extrinsics (1): [0xfdbb…bdd0]] 2020-07-13 23:07:21 :bookmark: Pre-sealed block for proposal at 2. Hash now 0x906f64c7a6139ad0819f6c31d776404573e72f3f155bab486a9aeca7c89df810, previously 0x346204e0b46b86dc4ec85b18cf2fdf0f0e818b24208e56217e6f44c135e3aef3. 2020-07-13 23:07:21 :sparkles: Imported #2 (0x906f…f810)
4.2 合约编译
$ cargo contract build [1/4] Collecting crate metadata [2/4] Building cargo project Finished release [optimized] target(s) in 0.05s [3/4] Post processing wasm file [4/4] Optimizing wasm file wasm-opt is not installed. Install this tool on your system in order to reduce the size of your contract's Wasm binary. See https://github.com/WebAssembly/binaryen#tools Your contract is ready. You can find it here: ./erc20/target/erc20.wasm
4.3 metadata生成
以便通过 polkadot.js.org
与合约进行交互
$ cargo contract generate-metadata Generating metadata Updating git repository `https://github.com/paritytech/ink` Updating crates.io index Updating git repository `https://github.com/type-metadata/type-metadata.git` Finished release [optimized] target(s) in 3.38s Running `target/release/abi-gen` Your metadata file is ready. You can find it here: ./erc20/target/metadata.json
4.4 上传WASM
4.5 部署合约
5 ERC20合约执行
5.1 执行合约
注:右下角开关
打开开关:作为RPC调用发送,只能查看链上状态
关闭开关:作为交易发送,对链上状态有更改
5.2 查询发行总量
5.3 查询Alice账户余额
5.4 Alice给Bob转账1000
5.5 分别查询Alice和Bob余额
5.6 Alice授权Eve可以消费自己的2000代币
5.7 Eve给Ferdie转账Alice的500代币
5.8 查看到Ferdie的代币数
5.9 查看Eve剩余Alice的授权额度
6 参考资料
https://substrate.dev/substrate-contracts-workshop/#/
使用Ink!开发Substrate ERC20智能合约
jasonruan 2020.07.14
1 环境搭建
1.1 安装Substrate节点
$ git clone git@github.com:paritytech/substrate.git $ cd substrate (master)$ git checkout -b v2.0.0-rc4 v2.0.0-rc4 切换到一个新分支 'v2.0.0-rc4' (v2.0.0-rc4)$ cargo build --release
1.2 安装cargo contract插件
- 安装命令
$ cargo install cargo-contract --vers 0.6.1 --force
- 帮助手册
$ cargo contract --help cargo-contract 0.6.1 Utilities to develop Wasm smart contracts USAGE: cargo contract <SUBCOMMAND> OPTIONS: -h, --help Prints help information -V, --version Prints version information SUBCOMMANDS: new Setup and create a new smart contract project build Compiles the smart contract generate-metadata Generate contract metadata artifacts test Test the smart contract off-chain help Prints this message or the help of the given subcommand(s)
2 ERC20合约介绍
2.1 什么是ERC20标准
ERC20 通证标准(ERC20 Token Standard)是通过以太坊创建通证时的一种规范。按照 ERC20 的规范可以编写一个智能合约,创建“可互换通证”。它并非强制要求,但遵循这个标准,所创建的通证可以与众多交易所、钱包等进行交互,它现在已被行业普遍接受。
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); }
- 功能介绍:
函数名 | 功能 |
---|---|
totalSupply | 返回存在于流通中的通证(Token)总量 |
balanceOf | 返回指定账户地址的通证余额 |
transfer | 让调用方将指定数量的通证发送到另一个地址,即转账 |
transferFrom | 允许智能合约自动执行转账流程并代表所有者发送给定数量的通证 |
approve | 调用方授权给定的地址可以从其地址中提款 |
allowance | 返回被允许转移的余额数量 |
event Transfer | 事件通知,当token被转移时, 必须 调用触发,类似回调,当事件发生时,会得到通知 |
event Approval | 事件通知,当任何成功调用 approve 后, 必须 调用触发 |
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] 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); } } }
4 ERC20合约部署
4.1 启动substrate链
[Jason@RUAN:~/Blockchain/substrate] (v2.0.0-rc4)$ ./target/release/substrate purge-chain --dev Are you sure to remove "/root/.local/share/substrate/chains/dev/db"? [y/N]: y "/root/.local/share/substrate/chains/dev/db" did not exist. [Jason@RUAN:~/Blockchain/substrate] (v2.0.0-rc4)$ ./target/release/substrate --dev --ws-external --rpc-external --rpc-cors=all 2020-07-13 23:07:17 Substrate Node 2020-07-13 23:07:17 :v: version 2.0.0-rc4-00768a1-x86_64-linux-gnu 2020-07-13 23:07:17 :heart: by Parity Technologies <admin@parity.io>, 2017-2020 2020-07-13 23:07:17 :clipboard: Chain specification: Development 2020-07-13 23:07:17 Node name: ill-hen-8567 2020-07-13 23:07:17 :bust_in_silhouette: Role: AUTHORITY 2020-07-13 23:07:17 :floppy_disk: Database: RocksDb at /root/.local/share/substrate/chains/dev/db 2020-07-13 23:07:17 ⛓ Native runtime: node-254 (substrate-node-0.tx1.au10) 2020-07-13 23:07:17 :money_with_wings: new validator set of size 1 has been elected via ElectionCompute::OnChain for era 0 2020-07-13 23:07:17 :hammer: Initializing Genesis block/state (state: 0xc720…bb8a, header-hash: 0x6ea2…1245) 2020-07-13 23:07:17 :older_man: Loading GRANDPA authority set from genesis on what appears to be first startup. 2020-07-13 23:07:17 ⏱ Loaded block-time = 3000 milliseconds from genesis on first-launch 2020-07-13 23:07:17 :baby: Creating empty BABE epoch changes on what appears to be first startup. 2020-07-13 23:07:17 :package: Highest known block at #0 2020-07-13 23:07:17 Using default protocol ID "sup" because none is configured in the chain specs 2020-07-13 23:07:17 Local node identity is: 12D3KooWQUQtujJ5SGCdCcheuExioC81R5W4E3RFGhmhx3MT8iqy (legacy representation: QmX71wUqWKy7FQX8PEHKoQLaiBLLTfK8TL25mFXxKhMWGw) 2020-07-13 23:07:17 〽 Prometheus server started at 127.0.0.1:9615 2020-07-13 23:07:17 :baby: Starting BABE Authorship worker 2020-07-13 23:07:18 :raised_hands: Starting consensus session on top of parent 0x6ea2a97a8da973976a82f053a8b909aff5e0659ca6d51b6c9d6947b4dc3d1245 2020-07-13 23:07:18 :gift: Prepared block for proposing at 1 [hash: 0x3b99b664d0a21fbc72bfed709700b5bba05564c8d62e9ddd677412896f25de31; parent_hash: 0x6ea2…1245; extrinsics (1): [0xdcda…fb8d]] 2020-07-13 23:07:18 :bookmark: Pre-sealed block for proposal at 1. Hash now 0x3081484a5cbe82a9b4a4aea4d360fd69219a43d18182c6fd297e2ffac71feff2, previously 0x3b99b664d0a21fbc72bfed709700b5bba05564c8d62e9ddd677412896f25de31. 2020-07-13 23:07:18 :baby: New epoch 0 launching at block 0x3081…eff2 (block slot 531550946 >= start slot 531550946). 2020-07-13 23:07:18 :baby: Next epoch starts at slot 531551146 2020-07-13 23:07:18 :sparkles: Imported #1 (0x3081…eff2) 2020-07-13 23:07:21 :raised_hands: Starting consensus session on top of parent 0x3081484a5cbe82a9b4a4aea4d360fd69219a43d18182c6fd297e2ffac71feff2 2020-07-13 23:07:21 :gift: Prepared block for proposing at 2 [hash: 0x346204e0b46b86dc4ec85b18cf2fdf0f0e818b24208e56217e6f44c135e3aef3; parent_hash: 0x3081…eff2; extrinsics (1): [0xfdbb…bdd0]] 2020-07-13 23:07:21 :bookmark: Pre-sealed block for proposal at 2. Hash now 0x906f64c7a6139ad0819f6c31d776404573e72f3f155bab486a9aeca7c89df810, previously 0x346204e0b46b86dc4ec85b18cf2fdf0f0e818b24208e56217e6f44c135e3aef3. 2020-07-13 23:07:21 :sparkles: Imported #2 (0x906f…f810)
4.2 合约编译
$ cargo contract build [1/4] Collecting crate metadata [2/4] Building cargo project Finished release [optimized] target(s) in 0.05s [3/4] Post processing wasm file [4/4] Optimizing wasm file wasm-opt is not installed. Install this tool on your system in order to reduce the size of your contract's Wasm binary. See https://github.com/WebAssembly/binaryen#tools Your contract is ready. You can find it here: ./erc20/target/erc20.wasm
4.3 metadata生成
以便通过 polkadot.js.org
与合约进行交互
$ cargo contract generate-metadata Generating metadata Updating git repository `https://github.com/paritytech/ink` Updating crates.io index Updating git repository `https://github.com/type-metadata/type-metadata.git` Finished release [optimized] target(s) in 3.38s Running `target/release/abi-gen` Your metadata file is ready. You can find it here: ./erc20/target/metadata.json
4.4 上传WASM
4.5 部署合约
5 ERC20合约执行
5.1 执行合约
注:右下角开关
打开开关:作为RPC调用发送,只能查看链上状态
关闭开关:作为交易发送,对链上状态有更改
5.2 查询发行总量
5.3 查询Alice账户余额
5.4 Alice给Bob转账1000
5.5 分别查询Alice和Bob余额
5.6 Alice授权Eve可以消费自己的2000代币
5.7 Eve给Ferdie转账Alice的500代币
5.8 查看到Ferdie的代币数
5.9 查看Eve剩余Alice的授权额度
6 参考资料
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 11分钟前
- 阅读 ( 12 )
- 学分 ( 5 )
- 分类:Polkadot
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。