【Ink入门】编写并部署智能合约ERC20 Token

【Ink入门】编写并部署智能合约ERC20 Token

文章内容持续更新……

本文目标

在本地节点部署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智能合约 | 登链社区 | 深入浅出区块链技术

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Proudly powered by WordPress | Theme: HoneyWaves by SpiceThemes