Mapping 数据结构 | 用 Rust 写智能合约(二)
TryTry Liquid,很不错!
在上一篇中,我们讲到了如下知识点: - 什么是`WASM`合约 - 标准`ink!`合约模板 - 用`ink!`实现值的读取 今天我们来讲讲用`Mapping`的方式进行值的存储与读取。 不过,我们这次不基于`Substrate`,而是基于`FISCO BCOS`新推出`Liquid`智能合约——`Liquid`智能合约同样也是基于`RUST`与`WASM`。 ## 1 Liquid 环境配置 如下内容来自官方文档: > https://liquid-doc.readthedocs.io/zh_CN/latest/docs/quickstart/prerequisite.html **部署 Rust 编译环境** Liquid 智能合约的构建过程主要依赖 Rust 语言编译器`rustc`及代码组织管理工具`cargo`,且均要求版本号大于或等与 1.50.0。如果此前从未安装过`rustc`及`cargo`,可参考下列步骤进行安装: - 对于 Mac 或 Linux 用户,请在终端中执行以下命令; ``` # 此命令将会自动安装 rustup,rustup 会自动安装 rustc 及 cargo curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` - 对于 32 位 Windows 用户,请从[此处](https://static.rust-lang.org/rustup/dist/i686-pc-windows-msvc/rustup-init.exe)下载安装 32 位版本安装程序。 - 对于 64 位 Windows 用户,请从[此处](https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe)下载安装 64 位版本安装程序。 如果此前安装过`rustc`及`cargo`,但是未能最低版本要求,则可在终端中执行以下命令进行更新: ``` rustup update ``` 安装完毕后,分别执行以下命令验证已安装正确版本的 `rustc` 及 `cargo`: ``` rustc --version cargo --version ``` 此外需要安装以下工具链组件: ``` rustup toolchain install nightly rustup target add wasm32-unknown-unknown --toolchain stable rustup target add wasm32-unknown-unknown --toolchain nightly rustup component add rust-src --toolchain stable rustup component add rust-src --toolchain nightly ``` 构建 Liquid 智能合约的过程中需要下载大量第三方依赖,若当前网络无法正常访问 crates.io 官方镜像源,则按照以下步骤为 `cargo` 更换镜像源: ``` # 编辑cargo配置文件,若没有则新建 vim $HOME/.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" ``` 最关键的是要安装`cargo-liquid`: ``` cargo install --git https://gitee.com/WeBankBlockchain/cargo-liquid --tag v1.0.0-rc1 --force ``` ## 2 创建一个 liquid 项目 执行如下命令: ``` cargo liquid new map_storer ``` 创建完成后进入文件夹: ``` cd map_storer ``` ## 3 替换代码 将`lib.rs`内容用如下代码替换: ```rust #![cfg_attr(not(feature = "std"), no_std)] use liquid::storage; use liquid_lang as liquid; #[liquid::contract] mod map_storer { use super::*; /// Defines the state variables of your contract. #[liquid(storage)] struct MapStorer { my_number_map: storage::Mapping<address, u32>, } /// Defines the methods of your contract. #[liquid(methods)] impl MapStorer { /// Defines the constructor which will be executed automatically when the contract is /// under deploying. Usually constructor is used to initialize state variables. /// /// # Note /// 1. The name of constructor must be `new`; /// 2. The receiver of constructor must be `&mut self`; /// 3. The visibility of constructor must be `pub`. /// 4. The constructor should return nothing. /// 5. If you forget to initialize state variables, you /// will be trapped in an runtime-error for attempting /// to visit uninitialized storage. /// Constructor that initializes the `my number map` Hashmap pub fn new(&mut self) { self.my_number_map.initialize(); } // Get the value for a given addr pub fn get(&self, of: address) -> u32 { self.my_number_or_zero(&of) } // Set the value for a given addr pub fn store(&mut self, payload: u32, of: address) { self.my_number_map.insert(&of, payload); } // Get the value for the calling addr pub fn get_my_number(&self) -> u32 { let caller = self.env().get_caller(); self.my_number_or_zero(&caller) } // Returns the number for an addr or 0 if it is not set. fn my_number_or_zero(&self, of: &address) -> u32 { let value = self.my_number_map.get(of).unwrap_or(&0); *value } } /// Unit tests in Rust are normally defined within such a `#[cfg(test)]` /// module and test functions are marked with a `#[test]` attribute. /// The below code is technically just normal Rust code. #[cfg(test)] mod tests { /// Imports all the definitions from the outer scope so we can use them here. use super::*; } } ``` ## 4 测试与编译 ``` cargo +nightly test cargo +nightly liquid build ``` ![image-20210403140149106](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf3zo21j30xs08wwgr.jpg) ABI 和 Solidity ABI 保持一致!这点很好: ![image-20210403140250505](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf6ikh8j313006qdj4.jpg) ## 5 部署 ### 5.1 安装 FISCO BCOS 区块链`liquid`分支 当前,FISCO BCOS 对 Wasm 虚拟机的支持尚未合入主干版本,仅开放了实验版本的源代码及可执行二进制文件供开发者体验,因此需要按照以下步骤手动搭建 FISCO BCOS 区块链: 1. 根据[依赖项说明](https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/installation.html#id2)中的要求安装依赖项; 2. 下载实验版本的建链工具 build_chain.sh: ``` cd ~ && mkdir -p fisco && cd fisco curl -#LO https://gitee.com/WeBankBlockchain/liquid/attach_files/651253/download/build_chain.sh && chmod u+x build_chain.sh ``` 3. 使用 build_chain.sh 在本地搭建一条单群组 1 节点的 FISCO BCOS 区块链并运行。更多 build_chain.sh 的使用方法可参考其[使用文档](https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/build_chain.html): **注:**可通过把`build_chain.sh`中令`Download_link=cdn_download_link`来提速 > ![image-20210403141933670](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf7fj0xj311802qdh3.jpg) ``` bash build_chain.sh -l 127.0.0.1:1 -p 30300,20200,8545 bash nodes/127.0.0.1/start_all.sh ``` ![image-20210403142038494](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf95pchj30hq0403yx.jpg) ### 5.2 部署 Node.js SDK 由于 Liquid 当前暂为实验项目,因此目前仅有 FISCO BCOS Node.js SDK 提供的 CLI 工具能够部署及调用 Liquid 智能合约。Node.js SDK 部署方式可参考其[官方文档](https://gitee.com/FISCO-BCOS/nodejs-sdk#fisco-bcos-nodejs-sdk)。但需要注意的是,Liquid 智能合约相关的功能目前同样未合入 Node.js SDK 的主干版本。因此当从 GitHub 克隆了 Node.js SDK 的源代码后,需要先手动切换至`liquid`分支并随后安装[SCALE](https://substrate.dev/docs/en/knowledgebase/advanced/codec)编解码器: ``` git clone https://gitee.com/FISCO-BCOS/nodejs-sdk.git cd nodejs-sdk && git checkout liquid npm install cd packages/cli/scale_codec && npm install ``` 返回`cli`目录: ``` cd .. ``` 将证书文件复制到`cli/conf/authentication`文件夹中: ``` cp ~/fisco/nodes/127.0.0.1/sdk/* ./ # 根据实际地址调整 ``` 测试`SDK`连通性: ``` ./cli.js exec getBlockNumber ``` ![image-20210403143224516](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf889e3j30jc024mxh.jpg) ### 5.3 将合约部署至区块链 使用 Node.js SDK CLI 工具提供的`deploy`子命令,我们可以将 Hello World 合约构建生成的 Wasm 格式字节码部署至真实的区块链上,`deploy`子命令的使用说明如下: ``` cli.js exec deploy <contract> [parameters..] Deploy a contract written in Solidity or Liquid Positionals: contract The path of the contract [string] [required] parameters The parameters(split by space) of constructor [array] [default: []] Options: --version Show version number [boolean] --abi, -a The path of the corresponding ABI file [string] --who, -w Who will do this operation [string] -h, --help Show help [boolean] ``` ```bash /cli.js exec deploy /Users/liaohua/substrate/contracts/liquid/map_storer/target/map_storer.wasm --abi /Users/liaohua/substrate/contracts/liquid/map_storer/target/map_storer.abi ``` 部署成功后,返回如下形式的结果,其中包含状态码、合约地址及交易哈希: ```json { "status": "0x0", "contractAddress": "0x039ced1cd5bea5ace04de8e74c66e312ba4a48af", "transactionHash": "0xf84811a5c7a5d3a4452a65e6929a49e69d9a55a0f03b5a03a3e8956f80e9ff41" } ``` ![image-20210403143449883](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf9sblvj316004idhs.jpg) ### 5.4 调用 使用 Node.js SDK CLI 工具提供的`call`子命令,我们可以调用已被部署到链上的智能合约,`call`子命令的使用方式如下: ``` cli.js exec call <contractName> <contractAddress> <function> [parameters..] Call a contract by a function and parameters Positionals: contractName The name of a contract [string] [required] contractAddress 20 Bytes - The address of a contract [string] [required] function The function of a contract [string] [required] parameters The parameters(split by space) of a function [array] [default: []] Options: --version Show version number [boolean] --who, -w Who will do this operation [string] -h, --help Show help [boolean] ``` 先调用`get_my_number`函数,这个参数无需输入值: ``` ./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e get_my_number ``` ![image-20210403144056078](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf4izsjj315i01ywfc.jpg) 再调用`store`函数,对地址`0x039ced1cd5bea5ace04de8e74c66e312ba4a48af`进行存值: ``` ./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e store 300 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af ``` ![image-20210403144440050](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf5v038j31ko01uta6.jpg) 调用`get`函数,对上述地址进行取值: ``` ./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e get 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af ``` ![image-20210403144516763](https://tva1.sinaimg.cn/large/008eGmZEly1gpmtf6yajfj31im01swfi.jpg) 成功~ ## 6 源码解读 ### 6.1 Mapping 类型 相对于上一篇的代码,本篇中的代码引入了新的类型——`Mapping`。 `Mapping`是一种很有用的类型,我们在`Solidity`合约中同样能见到它的身影: 如: ``` mapping(address=>bool) isStake; ``` 在`liquid`智能合约中,我们这样定义一个`Mapping`: ```rust my_number_map: storage::Mapping<address, u32>, ``` `Mapping`变量的`get`操作: ``` let value = self.my_number_map.get(of).unwrap_or(&0); ``` `Mapping`变量的`insert`操作: ``` self.my_number_map.insert(&of, payload); ``` ### 6.2 获取当前合约调用者 ``` let caller = self.env().get_caller(); ``` ### 6.3 unwrap_or `unwrap_or`是`Rust`错误捕捉方式的一种: ```rust fn unwrap_or<T>(option: Option<T>, default: T) -> T { match option { None => default, Some(value) => value, } } ``` unwrap_or提供了一个默认值`default`,当值为`None`时返回`default`。 因此,如下语句中,当`of`对应的值不存在时,便会返回`0`: ``` let value = self.my_number_map.get(of).unwrap_or(&0); ```
在上一篇中,我们讲到了如下知识点:
- 什么是
WASM
合约 - 标准
ink!
合约模板 - 用
ink!
实现值的读取
今天我们来讲讲用Mapping
的方式进行值的存储与读取。
不过,我们这次不基于Substrate
,而是基于FISCO BCOS
新推出Liquid
智能合约——Liquid
智能合约同样也是基于RUST
与WASM
。
1 Liquid 环境配置
如下内容来自官方文档:
https://liquid-doc.readthedocs.io/zh_CN/latest/docs/quickstart/prerequisite.html
部署 Rust 编译环境
Liquid 智能合约的构建过程主要依赖 Rust 语言编译器rustc
及代码组织管理工具cargo
,且均要求版本号大于或等与 1.50.0。如果此前从未安装过rustc
及cargo
,可参考下列步骤进行安装:
-
对于 Mac 或 Linux 用户,请在终端中执行以下命令;
# 此命令将会自动安装 rustup,rustup 会自动安装 rustc 及 cargo curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- 对于 32 位 Windows 用户,请从此处下载安装 32 位版本安装程序。
- 对于 64 位 Windows 用户,请从此处下载安装 64 位版本安装程序。
如果此前安装过rustc
及cargo
,但是未能最低版本要求,则可在终端中执行以下命令进行更新:
rustup update
安装完毕后,分别执行以下命令验证已安装正确版本的 rustc
及 cargo
:
rustc --version
cargo --version
此外需要安装以下工具链组件:
rustup toolchain install nightly
rustup target add wasm32-unknown-unknown --toolchain stable
rustup target add wasm32-unknown-unknown --toolchain nightly
rustup component add rust-src --toolchain stable
rustup component add rust-src --toolchain nightly
构建 Liquid 智能合约的过程中需要下载大量第三方依赖,若当前网络无法正常访问 crates.io 官方镜像源,则按照以下步骤为 cargo
更换镜像源:
# 编辑cargo配置文件,若没有则新建
vim $HOME/.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"
最关键的是要安装cargo-liquid
:
cargo install --git https://gitee.com/WeBankBlockchain/cargo-liquid --tag v1.0.0-rc1 --force
2 创建一个 liquid 项目
执行如下命令:
cargo liquid new map_storer
创建完成后进入文件夹:
cd map_storer
3 替换代码
将lib.rs
内容用如下代码替换:
#![cfg_attr(not(feature = "std"), no_std)]
use liquid::storage;
use liquid_lang as liquid;
#[liquid::contract]
mod map_storer {
use super::*;
/// Defines the state variables of your contract.
#[liquid(storage)]
struct MapStorer {
my_number_map: storage::Mapping<address, u32>,
}
/// Defines the methods of your contract.
#[liquid(methods)]
impl MapStorer {
/// Defines the constructor which will be executed automatically when the contract is
/// under deploying. Usually constructor is used to initialize state variables.
///
/// # Note
/// 1. The name of constructor must be `new`;
/// 2. The receiver of constructor must be `&mut self`;
/// 3. The visibility of constructor must be `pub`.
/// 4. The constructor should return nothing.
/// 5. If you forget to initialize state variables, you
/// will be trapped in an runtime-error for attempting
/// to visit uninitialized storage.
/// Constructor that initializes the `my number map` Hashmap
pub fn new(&mut self) {
self.my_number_map.initialize();
}
// Get the value for a given addr
pub fn get(&self, of: address) -> u32 {
self.my_number_or_zero(&of)
}
// Set the value for a given addr
pub fn store(&mut self, payload: u32, of: address) {
self.my_number_map.insert(&of, payload);
}
// Get the value for the calling addr
pub fn get_my_number(&self) -> u32 {
let caller = self.env().get_caller();
self.my_number_or_zero(&caller)
}
// Returns the number for an addr or 0 if it is not set.
fn my_number_or_zero(&self, of: &address) -> u32 {
let value = self.my_number_map.get(of).unwrap_or(&0);
*value
}
}
/// Unit tests in Rust are normally defined within such a `#[cfg(test)]`
/// module and test functions are marked with a `#[test]` attribute.
/// The below code is technically just normal Rust code.
#[cfg(test)]
mod tests {
/// Imports all the definitions from the outer scope so we can use them here.
use super::*;
}
}
4 测试与编译
cargo +nightly test
cargo +nightly liquid build
ABI 和 Solidity ABI 保持一致!这点很好:
5 部署
5.1 安装 FISCO BCOS 区块链liquid
分支
当前,FISCO BCOS 对 Wasm 虚拟机的支持尚未合入主干版本,仅开放了实验版本的源代码及可执行二进制文件供开发者体验,因此需要按照以下步骤手动搭建 FISCO BCOS 区块链:
- 根据依赖项说明中的要求安装依赖项;
-
下载实验版本的建链工具 build_chain.sh:
cd ~ && mkdir -p fisco && cd fisco curl -#LO https://gitee.com/WeBankBlockchain/liquid/attach_files/651253/download/build_chain.sh && chmod u+x build_chain.sh
-
使用 build_chain.sh 在本地搭建一条单群组 1 节点的 FISCO BCOS 区块链并运行。更多 build_chain.sh 的使用方法可参考其使用文档:
注:可通过把
build_chain.sh
中令Download_link=cdn_download_link
来提速bash build_chain.sh -l 127.0.0.1:1 -p 30300,20200,8545 bash nodes/127.0.0.1/start_all.sh
5.2 部署 Node.js SDK
由于 Liquid 当前暂为实验项目,因此目前仅有 FISCO BCOS Node.js SDK 提供的 CLI 工具能够部署及调用 Liquid 智能合约。Node.js SDK 部署方式可参考其官方文档。但需要注意的是,Liquid 智能合约相关的功能目前同样未合入 Node.js SDK 的主干版本。因此当从 GitHub 克隆了 Node.js SDK 的源代码后,需要先手动切换至liquid
分支并随后安装SCALE编解码器:
git clone https://gitee.com/FISCO-BCOS/nodejs-sdk.git
cd nodejs-sdk && git checkout liquid
npm install
cd packages/cli/scale_codec && npm install
返回cli
目录:
cd ..
将证书文件复制到cli/conf/authentication
文件夹中:
cp ~/fisco/nodes/127.0.0.1/sdk/* ./ # 根据实际地址调整
测试SDK
连通性:
./cli.js exec getBlockNumber
5.3 将合约部署至区块链
使用 Node.js SDK CLI 工具提供的deploy
子命令,我们可以将 Hello World 合约构建生成的 Wasm 格式字节码部署至真实的区块链上,deploy
子命令的使用说明如下:
cli.js exec deploy <contract> [parameters..]
Deploy a contract written in Solidity or Liquid
Positionals:
contract The path of the contract [string] [required]
parameters The parameters(split by space) of constructor
[array] [default: []]
Options:
--version Show version number [boolean]
--abi, -a The path of the corresponding ABI file [string]
--who, -w Who will do this operation [string]
-h, --help Show help [boolean]
/cli.js exec deploy /Users/liaohua/substrate/contracts/liquid/map_storer/target/map_storer.wasm --abi /Users/liaohua/substrate/contracts/liquid/map_storer/target/map_storer.abi
部署成功后,返回如下形式的结果,其中包含状态码、合约地址及交易哈希:
{
"status": "0x0",
"contractAddress": "0x039ced1cd5bea5ace04de8e74c66e312ba4a48af",
"transactionHash": "0xf84811a5c7a5d3a4452a65e6929a49e69d9a55a0f03b5a03a3e8956f80e9ff41"
}
5.4 调用
使用 Node.js SDK CLI 工具提供的call
子命令,我们可以调用已被部署到链上的智能合约,call
子命令的使用方式如下:
cli.js exec call <contractName> <contractAddress> <function> [parameters..]
Call a contract by a function and parameters
Positionals:
contractName The name of a contract [string] [required]
contractAddress 20 Bytes - The address of a contract [string] [required]
function The function of a contract [string] [required]
parameters The parameters(split by space) of a function
[array] [default: []]
Options:
--version Show version number [boolean]
--who, -w Who will do this operation [string]
-h, --help Show help [boolean]
先调用get_my_number
函数,这个参数无需输入值:
./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e get_my_number
再调用store
函数,对地址0x039ced1cd5bea5ace04de8e74c66e312ba4a48af
进行存值:
./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e store 300 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af
调用get
函数,对上述地址进行取值:
./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e get 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af
成功~
6 源码解读
6.1 Mapping 类型
相对于上一篇的代码,本篇中的代码引入了新的类型——Mapping
。
Mapping
是一种很有用的类型,我们在Solidity
合约中同样能见到它的身影:
如:
mapping(address=>bool) isStake;
在liquid
智能合约中,我们这样定义一个Mapping
:
my_number_map: storage::Mapping<address, u32>,
Mapping
变量的get
操作:
let value = self.my_number_map.get(of).unwrap_or(&0);
Mapping
变量的insert
操作:
self.my_number_map.insert(&of, payload);
6.2 获取当前合约调用者
let caller = self.env().get_caller();
6.3 unwrap_or
unwrap_or
是Rust
错误捕捉方式的一种:
fn unwrap_or<T>(option: Option<T>, default: T) -> T {
match option {
None => default,
Some(value) => value,
}
}
unwrap_or提供了一个默认值default
,当值为None
时返回default
。
因此,如下语句中,当of
对应的值不存在时,便会返回0
:
let value = self.my_number_map.get(of).unwrap_or(&0);
区块链技术网。
- 发表于 2021-04-06 15:46
- 阅读 ( 925 )
- 学分 ( 19 )
- 分类:FISCO BCOS
- 专栏:狗哥区块链精品内容集
评论