Mapping 数据结构 | 用 Rust 写智能合约(二)
source link: https://learnblockchain.cn/article/2345
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
在上一篇中,我们讲到了如下知识点:
- 什么是
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.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);
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK