内容简介:本文参考官方教程(本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
本文参考官方教程( 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
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Servlet & JSP Cookbook
Bruce W. Perry / O'Reilly Media / 2003-12-1 / USD 49.99
With literally hundreds of examples and thousands of lines of code, the Java Servlet and JSP Cookbook yields tips and techniques that any Java web developer who uses JavaServer Pages or servlets will ......一起来看看 《Java Servlet & JSP Cookbook》 这本书的介绍吧!