文章内容持续更新……
本文目标
在本地节点部署ERC20智能合约,发布一个Token。
操作步骤
安装必备组件创建智能合约项目测试并编译智能合约部署智能合约—在本地网络部署智能合约—在线测试网发布智能合约Gas消耗情况统计
一、安装必备组件
本文的操作系统环境为:ubuntu20.04
1、修改rust镜像源
编辑~/.cargo/config文件,内容如下:
[source.crates-io]
registry = “https://github.com/rust-lang/crates.io-index”
replace-with = ustc
[source.ustc]
registry = “git://mirrors.ustc.edu.cn/crates.io-index”
2、安装rust-src
rustup component add rust-src –toolchain nightly
rustup target add wasm32-unknown-unknown –toolchain stable
3、安装canvas-node(可选安装)
cargo install canvas-node –git https://github.com/paritytech/canvas-node.git –tag v0.1.6 –force –locked
canvas-node是一条使用substrate构建的智能合约链,专用于测试部署智能合约。
如果没有安装canvas-node,也可以使用substrate-node-template,集成pallet-contract,然后编译出节点程序,通过polkadot.js apps来部署智能合约。
4、安装ink智能合约工具cargo-contract
cargo install cargo-contract –vers ^0.12 –force –locked
这个工具主要可以帮助我们编译wasm和生成metadata文件,使得我们开发Substrate智能合约项目更加容易,更多帮助可以查看cargo contract –help
5、安装Binaryen工具
Binaryen是用C ++编写的WebAssembly的编译器和工具链基础结构库。它旨在使对WebAssembly的编译变得容易,快速和有效。
仓库地址:WebAssembly/binaryen
cd /opt/ //可以是任意目录
sudo git clone https://github.com/WebAssembly/binaryen.git
cd ./binaryen
sudo cmake . && sudo make //编译
./bin/wasm-opt –version
wasm-opt version 101 (version_101-30-g5387a0b1f) //如果能够看到版本号,说明安装成功
cd /usr/local/bin
ln -s /opt/binaryen/bin/wasm-opt wasm-opt
wasm-opt –version //再次检查一下
如果没有安装Binaryen工具,将会遇到如下错误:
[3/5] Optimizing wasm file
ERROR: wasm-opt not found! Make sure the binary is in your PATH environment.
We use this tool to optimize the size of your contracts Wasm binary.
wasm-opt is part of the binaryen package. You can find detailed
installation instructions on https://github.com/WebAssembly/binaryen#tools.
There are ready-to-install packages for many platforms:
* Debian/Ubuntu: apt-get install binaryen
* Homebrew: brew install binaryen
* Arch Linux: pacman -S binaryen
* Windows: binary releases at https://github.com/WebAssembly/binaryen/releases
二、创建智能合约项目
1、在~/workspace/rustproject/目录(可以任意位置)下我们创建并初始化项目:
cd ~/workspace/rustproject/
cargo contract new erc20
其实官方已经为我们提供了常用的智能合约示例,包括ERC20、ERC721标准的合约,为了简化开发,我们这里直接使用官方合约代码。
如果是自己纯手工编写,请参考官方教程。
2、克隆ink仓库
cd ~/polkadot
git clone -b v3.0.0-rc3 –depth 1 https://github.com/paritytech/ink.git
这里我们选择3.0版本,笔者之前在尝试v2.x时,出现各种坑,目前官方也没有维护v2.x了。
3、我们来查看一下目录结构:
cd ~/workspace/rustproject/ink/examples/
tree erc20/
erc20/
├── Cargo.toml <=== Rust依赖和ink配置
└── src
└── lib.rs <=== 智能合约源代码
更多请见笔者前文《【Ink入门】ERC20 Token代码解读》。
三、测试并编译智能合约
1、执行单元测试
cd ~/workspace/rustproject/ink/examples/erc20
cargo +nightly-2021-03-01 test
Compiling erc20 v3.0.0-rc3 (/home/simon/workspace/rustproject/ink/examples/erc20)
Finished test [unoptimized + debuginfo] target(s) in 59.35s
Running unittests (target/debug/deps/erc20-7dc430012368e36c)
running 7 tests
test erc20::tests::invalid_transfer_should_fail … ok
test erc20::tests::new_works … ok
test erc20::tests::total_supply_works … ok
test erc20::tests::balance_of_works … ok
test erc20::tests::allowance_must_not_change_on_failed_transfer … ok
test erc20::tests::transfer_works … ok
test erc20::tests::transfer_from_works … ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
官方的合约自带了单元测试代码,通过单元测试后,我们可以自信地进行下一步操作——将这份合约编译成Wasm。
2、编译智能合约
cargo +nightly contract build
如果一切顺利,我们应该看到一个target目录新创建了以下文件:
target/
├── debug
│ ├── build
│ ├── deps
│ ├── examples
│ └── incremental
└── ink
├── CACHEDIR.TAG
├── erc20.contract
├── erc20.wasm
├── metadata.json
├── release
└── wasm32-unknown-unknown
编译后项目转换为Wasm二进制文件,erc20.contract文件内容是由metadata.json和erc20.wasm的合并而来,部署合约时,可以只上传erc20.contract这一个文件,或分别上传metadata.json和erc20.wasm文件,操作二选一即可。
让我们看一下meatadata.json的结构:
{
“metadataVersion”: “0.1.0”,
“source”: { … },
“contract”: { … },
“spec”: { … },
“storage”: { … },
“types”: [ … ]
}
我们可以看到该文件描述了可用于与合同进行交互的所有接口。
source 对象记录了合约代码的哈希、ink语言版本、编译器版本;contract 对象定义了合约模块名称、版本、作者信息;spec 对象定义了构造函数、事件、消息;storage 对象定义了合约管理的所有存储项目以及如何最终访问它们;types 对象提供了JSON中使用的字符串和自定义类型。
合约存储有关可调用函数的信息,例如构造函数和用户可以调用以与合约进行交互的消息。还具有一些有用的信息,例如合约或任何文档发出的事件。
如果仔细查看构造函数constructors,还会注意到selector是函数名称的4个字节哈希(这一点继承了以太坊solidity的做法),这个哈希值的作用等价于合约的函数名称。
四、部署智能合约
在本地网络部署智能合约
1、启动本地节点
canvas –dev –tmp
我们可以看到节点开始出块,同时也了解到RPC端口为9944:
2021-01-13 10:39:20 Running in –dev mode, RPC CORS has been disabled.
2021-01-13 10:39:20 Canvas Node
2021-01-13 10:39:20 ✌️ version 0.1.0-258b0fa-x86_64-linux-gnu
2021-01-13 10:39:20 ❤️ by Canvas, 2020-2021
2021-01-13 10:39:20 Chain specification: Development
2021-01-13 10:39:20 Node name: melodic-wrist-3532
2021-01-13 10:39:20 Role: AUTHORITY
2021-01-13 10:39:20 Database: RocksDb at /tmp/substratePSIROw/chains/dev/db
2021-01-13 10:39:20 ⛓ Native runtime: canvas-8 (canvas-0.tx1.au1)
2021-01-13 10:39:21 Initializing Genesis block/state (state: 0x7563…8316, header-hash: 0x745b…ed07)
2021-01-13 10:39:21 Loading GRANDPA authority set from genesis on what appears to be first startup.
2021-01-13 10:39:21 ⏱ Loaded block-time = 6000 milliseconds from genesis on first-launch
2021-01-13 10:39:21 Using default protocol ID “sup” because none is configured in the chain specs
2021-01-13 10:39:21 Local node identity is: 12D3KooWPQ42yE12r7hbLSeg13unZHmogwWjPj8c2ebmWGjv3pnR
2021-01-13 10:39:21 Highest known block at #0
2021-01-13 10:39:21 〽️ Prometheus server started at 127.0.0.1:9615
2021-01-13 10:39:21 Listening for new connections on 127.0.0.1:9944.
2021-01-13 10:39:24 Starting consensus session on top of parent 0x745bd60d4105499c0100bb94f9f5e9c643f48a46b9cdf88235e0295d5818ed07
2021-01-13 10:39:24 Prepared block for proposing at 1 [hash: 0xa9580042dcc0630f499e3138794312e9938f07432ed80b34effa6e34316f0be2; parent_hash: 0x745b…ed07; extrinsics (1): [0xfdb7…cdef]]
2021-01-13 10:39:24 Pre-sealed block for proposal at 1. Hash now 0x85e0f7918dee5635274cb46146830d9ead9e0fafcf76209d1ab22d29e79eec03, previously 0xa9580042dcc0630f499e3138794312e9938f07432ed80b34effa6e34316f0be2.
2021-01-13 10:39:24 ✨ Imported #1 (0x85e0…ec03)
2021-01-13 10:39:26 Idle (0 peers), best: #1 (0x85e0…ec03), finalized #0 (0x745b…ed07), ⬇ 0 ⬆ 0
2021-01-13 10:39:30 Starting consensus session on top of parent 0x85e0f7918dee5635274cb46146830d9ead9e0fafcf76209d1ab22d29e79eec03
2021-01-13 10:39:30 Prepared block for proposing at 2 [hash: 0x85196a3d48e664c3374b9b8640fb0b6ae80fbc821d62290d9b28dd306feb4301; parent_hash: 0x85e0…ec03; extrinsics (1): [0x5e8d…69b3]]
2021-01-13 10:39:30 Pre-sealed block for proposal at 2. Hash now 0x07f6e88b6b1f7c73cc3f567b90670f21da7e2e52c7b68a86b06415be7e055976, previously 0x85196a3d48e664c3374b9b8640fb0b6ae80fbc821d62290d9b28dd306feb4301.
2、我们使用官方提供的Canvas UI与本地节点进行交互,以完成合约部署。在浏览器地址栏打开Polkadot/Substrate Portal,并切换到本地节点网络:
3、上传和存储合约代码,填写Token名称,这里我们分别上传metadata.json和erc20.wasm文件:
4、在Deploy栏目中,可以看到我们刚才提交的合约,点击部署按钮:
在将合约部署到链上前,需要我们提供一些初始化信息,比如Token的发行总量、存储租赁费用(在后面小节进行解释)、Gas Limit等:
填写完成后,需要我们确认签名:
5、调用合约
我们来查询一下Token的发行总量,发行总量为1 kUnit(换算面值在后面小节补充):
再来查询一下合约管理员(Alice)的余额,从构造函数的代码中,我们可以了解到,合约一部署,所有余额都会转Alice名下,所以等于发行总量:
下面我们用Alice账号给Charlie转账,Charlie账号当前余额为0:
签名并提交:
我们再来查询一下Charlie的余额:
通过查询,我们可以知道Charlie的账号余额为666666666000000000000,即666666666个unit,Alice账号余额为:333333334000000000000,即333333334个unit。其余调用操作就不再展开了,可以自行操作学习,并了解gas消耗或余额情况。
在线测试网发布智能合约
简单介绍一下在线发布途径:
智能合约一旦编译完成,我们就可以使用metadata.json和erc20.wasm两个文件(或erc20.contract文件)在不同网络上传部署了,比如我们可以通过Polkadot JS APPs来部署合约,也可以使用Canvas UI,通过这两个工具,可以连接到本地节点,也可以连接到线上测试网节点,比如Canvas网络(wss://canvas-rpc.parity.io)、Plasm测试网(wss://rpc.rococo.plasmnet.io),由于篇幅问题这里我们不再展开介绍,可以自行研究,下面介绍一些使用过程中的注意事项。
在线上部署智能合约的平行链有很多选择,截止到当前发稿时间,Plasm测试网已经拿到了Rococo V1上的插槽(有关资讯),成为一条智能合约平行链,如果在Rococo V1平行链测试环境运行没有问题,很有可能接入到Polkadot主网,所以我们可以先行在Plasm网络上发布。
我们知道部署token,需要消耗gas,在Plasm网络上需要使用PLM来支付gas,当前PLM还没有获取途径(现在唯一可做的就是通过质押DOT去支持这个项目,等Plasm拿到插槽上Polkadot主网后,官方会按照贡献值兑换PLM给用户)。
这样我们就没办法了吗?Plasm还提供了一条测试网Dusty(别晕,就是测试网的测试网),它的token为PLD,可以加入官方水龙头获取。
五、Gas消耗情况统计
其实本文还有一个重要目的,就是统计出当前合约的Gas消耗情况。
先公布一组本地网络测试数据(仅作参考):
存储租赁gas:0.350,277,579,997,977
合约实例化:0.001,756,886,952,250
查询余额(balance_of)消耗的gas:0
查询授权额度(allowance)消耗的gas:0
转账(transfer)消耗的gas:0.002,025,170,217,271
授权审批额度(approve)消耗的gas:0.002,028,024,396,649
从授权额度中转账(transfer_from)消耗的gas:0.002,367,571,262,970
Dusty测试网测试数据:
部署合约消耗的gas:xxx unit
查询余额(balance_of)消耗的gas:0 unit
转账(transfer)消耗的gas:xxx unit
授权审批额度(approve)消耗的gas:xxx unit
查询授权额度(allowance)消耗的gas:xxx unit
从授权额度中转账(transfer_from)消耗的gas:xxx unit
笔者注:
相比在以太坊1.0部署合约,在Polkadot中除了交易要消耗的gas外,还多了一种存储租赁费用,个人理解这种设计是为了避免垃圾数据的存储,做过以太坊开发的朋友都知道,以太坊1.0主网发布合约,只要我们有ETH,就可以往上存储合约,仅收取一笔发布时的交易费用,而且在以太坊上很多合约在发布后并没有交易流量,成了僵尸合约,给主网区块链制造了很多垃圾,而在Polkadot改变了这一点,合约有了一种tombstone(死亡)状态。合约存储到链上,需要一直支付存储空间租赁费用,而且在发布合约时通过Endowment项就得预存一部分进去(后续补充这一块细节和原理),如果预存过少,发布合约时可能会部署失败,如果是在临界值(待补充),有可能合约一发布就是tombstone(死亡)状态,无法调用合约,或后续租金消耗完了,长时间没有续费而转成tombstone(死亡)状态,链就会将我们的合约存储空间释放掉。
见官方文档:
https://substrate.dev/substrate-contracts-workshop/#/0/deploying-your-contract
附换算面值
1 MUnit = 1,000,000,000,000,000,000
1 kUnit = 1,000,000,000,000,000
1 Unit = 1,000,000,000,000
1 mUnit = 1,000,000,000
1 uUnit = 1,000,000
1 nUnit = 1,000
1 pUnit = 1
附坑集
1、2.x版本执行cargo +nightly test时,报错:
Compiling ink_lang v2.1.0 (/home/simon/polkadot/ink/lang)
error[E0658]: function pointers cannot appear in constant functions
–> /home/simon/polkadot/ink/lang/src/dispatcher.rs:186:30
|
186 | pub const fn new(dispatchable: $dispatchable_fn<Msg, S>) -> Self {
| ^^^^^^^^^^^^
…
237 | / impl_dispatcher_for! {
238 | | /// Dispatcher for storage preserving messages.
239 | | struct Dispatcher(DispatchableFn);
240 | | }
| |_- in this macro invocation
|
= note: see issue #57563 <https://github.com/rust-lang/rust/issues/57563> for more information
= help: add `#![feature(const_fn_fn_ptr_basics)]` to the crate attributes to enable
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
解决办法:直接使用ink仓库的3.0版本。
参考:
https://substrate.dev/substrate-contracts-workshop/#/0/introduction
使用Ink!开发Substrate ERC20智能合约 | 登链社区 | 深入浅出区块链技术
发表回复