3

Mapping 数据结构 | 用 Rust 写智能合约(二)

 3 years ago
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智能合约同样也是基于RUSTWASM

1 Liquid 环境配置

如下内容来自官方文档:

https://liquid-doc.readthedocs.io/zh_CN/latest/docs/quickstart/prerequisite.html

部署 Rust 编译环境

Liquid 智能合约的构建过程主要依赖 Rust 语言编译器rustc及代码组织管理工具cargo,且均要求版本号大于或等与 1.50.0。如果此前从未安装过rustccargo,可参考下列步骤进行安装:

  • 对于 Mac 或 Linux 用户,请在终端中执行以下命令;

    # 此命令将会自动安装 rustup,rustup 会自动安装 rustc 及 cargo
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
  • 对于 32 位 Windows 用户,请从此处下载安装 32 位版本安装程序。

  • 对于 64 位 Windows 用户,请从此处下载安装 64 位版本安装程序。

如果此前安装过rustccargo,但是未能最低版本要求,则可在终端中执行以下命令进行更新:

rustup update

安装完毕后,分别执行以下命令验证已安装正确版本的 rustccargo

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

image-20210403140149106

ABI 和 Solidity ABI 保持一致!这点很好:

image-20210403140250505

5.1 安装 FISCO BCOS 区块链liquid分支

当前,FISCO BCOS 对 Wasm 虚拟机的支持尚未合入主干版本,仅开放了实验版本的源代码及可执行二进制文件供开发者体验,因此需要按照以下步骤手动搭建 FISCO BCOS 区块链:

  1. 根据依赖项说明中的要求安装依赖项;

  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 的使用方法可参考其使用文档

    **注:**可通过把build_chain.sh中令Download_link=cdn_download_link来提速

    image-20210403141933670

    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

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

image-20210403143224516

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"
}

image-20210403143449883

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

再调用store函数,对地址0x039ced1cd5bea5ace04de8e74c66e312ba4a48af进行存值:

./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e store 300 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af

image-20210403144440050

调用get函数,对上述地址进行取值:

./cli.js exec call map_storer 0xf5736213670d32f63b1a598e55753014f710344e get 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af

image-20210403144516763

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_orRust错误捕捉方式的一种:

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);

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK