Skip to content

Commit

Permalink
update doc
Browse files Browse the repository at this point in the history
  • Loading branch information
vita-dounai committed Nov 20, 2020
1 parent 57d649a commit b7e9408
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 69 deletions.
83 changes: 74 additions & 9 deletions docs/in_depth/call_external.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,84 @@

## 外部合约声明

当您需要在Liquid项目中调用外部合约时,您需要在项目中声明所调用外部合约的接口。外部合约接口声明与合约均是一个合约的代表(尽管接口中没有实现),因此在语义上外部合约接口与合约时同级的,与合约类似,您也需要通过模块语法声明外部合约接口,并在该模块前添加`#[liquid::interface(...)]`属性:
当您需要调用外部合约时,您需要在代码中编写所调用外部合约的声明。同合约类似,声明外部合约需要使用`mod`模块语法,并模块前添加`#[liquid::interface(...)]`属性:

```rust
#[liquid::interface]
mod entry {
// ...
}
```eval_rst
.. code-block:: rust
:linenos:
#[liquid::interface(name = auto)]
mod entry {
// ...
}
```

一个项目中可以声明多个外部合约
在外部合约声明中,可以声明以下内容:

- 符号引入:使用`use ... as ...`语法,用于在外部合约声明所使用的模块中引入定义在外部的符号,如:

```eval_rst
.. code-block:: rst
:linenos:
#[liquid::interface(name = auto)]
mod kv_table {
use super::entry::*;
// ...
}
```

- 自定义数据结构:使用`struct`结构体语法,用于声明外部合约中所使用到自定义数据结构,如:

外部合约声明(interface)的描述对象与合约(contract)相同,两者均用于对智能合约的行为进行表述,只是外部合约声明中并不包含其行为的具体实现,因此两者在语义上属于同等地位。当合约项目中包含外部合约声明时,较好的代码组织方式是将两者放置于同级的命名空间中,而不是在合约内部放置外部合约声明:
```eval_rst
.. code-block:: rst
:linenos:
#[liquid::interface(name = auto)]
mod kv_table {
use super::entry::*;
struct Result {
success: bool,
value: Entry,
}
// ...
}
```

所声明的自定义数据结构中,不允许为成员指定可见性。同时,由于外部合约声明中的自定义数据结构一般会用作外部合约方法的参数及返回值类型,因此Liquid会自动为这些数据结构添加`#[derive(liquid_lang::InOut)]`属性。

- 合约方法:使用`extern`语法声明要调用的外部合约接口,如:

```eval_rst
.. code-block:: rst
:linenos:
#[liquid::interface(name = auto)]
mod entry {
extern {
fn getInt(&self, key: String) -> i256;
fn getUint(&self, key: String) -> u256;
fn getAddress(&self, key: String) -> address;
fn getString(&self, key: String) -> String;
fn set(&mut self, key: String, value: i256);
fn set(&mut self, key: String, value: u256);
fn set(&mut self, key: String, value: address);
fn set(&mut self, key: String, value: String);
}
}
```

由于在执行外部合约调用时需要计算目标方法的选择器,因此需要所声明的接口签名(包括接口名称及参数类型)与实际的被调用方法的签名完全一致,即使接口名称并不满足Rust编程规范中“函数名必须为snake_case形式”的要求。为避免Rust编译器给出警告,Liquid会自动为所声明的方法添加`#[allow(non_snake_case)]`属性。

在外部合约声明中,必须有且只能有一个`extern`代码块,且该代码块中需要有至少一个接口的声明。接口的声明中,只能包含相应外部方法的签名,而不能包含其实现。每个外部合约方法的第一个参数均需要是接收器,可以为`&self``&mut self`,用于表示相应接口是否为只读接口。所声明的接口的只读性需要和实际被调用方法的只读性一致,否则可能会导致调用失败。接口的声明中无需包含构造函数的声明。接口声明中所使用的参数及返回值类型均需要是ABI编解码器能够编解码的类型。

由于Solidity语言支持重载语法,因此Solidity合约中可能会出现名称相同但参数不同的合约方法。为支持调用这些方法,Liquid允许您在接口声明中声明名称相同但参数类型不同的接口,Liquid将会使用特殊的方法对这些重载方法进行支持。但是需要注意的是,您只能在外部合约声明中使用这种语法。由于Rust本身并不支持重载语法,因此在合约的定义中您仍然无法使用方法重载。

`extern`关键字后无需指定任何ABI类型,但是为防止某些代码补全工具自动将`extern`补全为`extern "C"`,您也可以写作`extern "liquid"`,此处的“"liquid"”仅仅是一个占位符,没有任何实际意义。

外部合约声明的描述对象与合约相同,两者均是对智能合约行为的表述,只是外部合约声明中并不包含其行为的具体实现,因此两者在语义上属于同等地位。当合约项目中包含外部合约声明时,较好的代码组织方式是将两者放置于同级的命名空间中,而不是在合约内部放置外部合约声明:

```eval_rst
.. code-block:: rust
Expand All @@ -40,7 +106,6 @@ mod entry {
mod entry {
// ...
}
// ...
}
```
70 changes: 36 additions & 34 deletions docs/quick_start/basic_dev_process.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# 基本开发流程

本节主要介绍Liquid合约的开发生命周期,涵盖合约的创建、测试、构建、部署及调用。
本节主要介绍Liquid项目的开发生命周期,涵盖项目的创建、测试、构建、部署及调用。

## 新建合约项目
## 新建项目

当您进入到您的工作目录后,您就可以开始着手创建您的合约项目了。在命令行中执行以下命令
在命令行中执行以下命令创建Liquid项目

```bash
cargo liquid new hello_world
```

`cargo liquid`是调用命令行工具`cargo-liquid`的另一种记法,这种记法使得`liquid`看上去像是`cargo`子命令。上述命令将会在当前目录下新建一个名为“hello_world”的项目,您此时应该能够看到当面目录下多出来一个名为“hello_world”的目录
`cargo liquid`是调用命令行工具`cargo-liquid`的另一种写法,这种记法使得`liquid`看上去像是`cargo`子命令。上述命令将会在当前目录下新建一个名为hello_world的项目,您此时应该能够看到当面目录下新生成了一个名为hello_world的目录

```bash
cd ./hello_world
```

此时hello_world项目内的目录结构如下所示
hello_world项目内的目录结构如下所示

```bash
hello_world/
Expand All @@ -29,20 +29,18 @@ hello_world/
└── lib.rs
```

从上往下依次介绍各个文件的作用
其中各文件的功能如下

1. `.gitignore`:隐藏文件,用于在告诉Git哪些文件或目录不需要被添加到版本管理中。Liquid会默认将某些项目(如临时文件等)排除在版本管理之外,如果您不需要使用到Git,可以忽略该文件
2. `.liquid\`:隐藏目录,用于实现Liquid的某些内部功能,其中`abi_gen`目录下包含了ABI生成器的实现,该目录下的编译配置及代码逻辑是固定的,您不应当修改该目录下的任何文件,否则可能会造成ABI生成出错
3. `Cargo.toml`项目元信息,主要包括项目信息、外部包依赖、编译配置等,一般而言您无需修改该文件,除非您有特殊的需求(引用新的第三方包、调整优化等级等)
4. `lib.rs`Liquid合约源代码,您可以在此文件中进行Liquid合约的开发
1. `.gitignore`:隐藏文件,用于在告诉Git哪些文件或目录不需要被添加到版本管理中。Liquid会默认将某些项目(如临时文件等)排除在版本管理之外,如果您不使用Git管理您的项目版本,可以忽略该文件
2. `.liquid\`:隐藏目录,用于实现Liquid的某些内部功能,其中`abi_gen`目录下包含了ABI生成器的实现,该目录下的编译配置及代码逻辑是固定的,您不需要也不应该修改该目录下的任何文件,否则可能会造成ABI生成出错
3. `Cargo.toml`项目配置清单,主要包括项目信息、外部库依赖、编译配置等,一般而言您无需修改该文件,除非您有特殊的需求(引用额外的第三方库、调整优化等级等)
4. `lib.rs`Liquid项目根文件

当执行`cargo liquid new`命令时,Liquid会以我们在第一节中介绍过的HelloWorld合约为模板创建新的项目,因此创建出的`lib.rs`文件中包含了HelloWorld合约的代码实现,以及大量的解释每个程序语句的目的及功能的注释,这是一个能直接编译运行的“最简单”的合约。HelloWorld合约我们学习Liquid的起点,随着学习的深入,我们可以基于它开发出功能更为强大的合约!
当执行`cargo liquid new`命令时,Liquid会以上一节中介绍的Hello World合约为模板创建新的项目,因此创建出的`lib.rs`文件中包含了Hello World合约的代码实现,这是一个能立即编译运行的最简Liquid项目。尽管简单,但Hello World合约仍然是我们学习Liquid的起点,接下来我们将主要使用它来对Liquid项目的开发过程进行讲解。

本节更加关注Liquid合约的开发步骤,因此接下来都我们将围绕这个自动生成的HelloWorld合约进行讲解:)
## 测试

## 测试合约

在HelloWorld合约代码的底部,我们为HelloWorld合约撰写了了两个分别检验`get`方法与`set`方法功能正确性的基本单元测试。Liquid内置了对区块链链上环境的模拟,因此即使不将合约部署上链,我们也能够在本地方便地执行合约并观察合约的运行状态,仿佛合约真的已经部署在链上一般。在`hello_world`目录下执行以下命令便可快速执行单元测试:
在Hello World合约代码的底部,我们为Hello World合约编写了两个分别用于检查`get`方法与`set`方法功能正确性的单元测试用例。Liquid内置了对区块链链上环境的模拟,因此即使不将项目部署上链,我们也能够在本地方便地执行单元测试。在hello_world目录下执行以下命令可快速执行单元测试:

```bash
cargo +nightly test
Expand All @@ -51,7 +49,7 @@ cargo +nightly test
```eval_rst
.. admonition:: 注意
上述命令与创建合约项目时的命令有两点不同:1、命令中**并不**包含``liquid``字样,因为我们使用的是标准cargo单元测试框架来执行单元测试,因此不需要执行``cargo-liquid``;2、执行时需要加上``+nightly``选项以启动nightly版本的rust工具链。这些差别在构建合约的命令中也存在,因此请务必注意。
上述命令与创建合约项目时的命令有两点不同:1、命令中并不包含liquid子命令,因为Liquid使用标准cargo单元测试框架来执行单元测试,因此不需要调用cargo-liquid;2、执行时需要加上+nightly”选项以启用nightly版本的Rust编译工具。这些差别在构建合约的命令中也存在,因此请务必注意。
```

执行结束后,命令行中显示如下内容:
Expand All @@ -70,25 +68,25 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

从结果上看,我们通过了所有的单元测试,因此我们可以很有信心地认为我们的合约逻辑是正确无误的!我们接下来可以开始着手构建HelloWorld合约,并把它部署到真正的区块链上。
从结果中可以看出,我们通过了所有的单元测试,因此我们可以有信心地认为合约的实现逻辑是符合预期的。我们接下来可以开始着手构建Hello World合约项目,并把它部署到真正的区块链上。

## 构建合约
## 构建

构建合约的目的是将合约编译为能在区块链上运行的Wasm格式字节码。在命令行中执行以下命令构建合约
构建的目的是将Liquid项目编译为能在区块链系统上运行的Wasm格式字节码。在命令行中执行以下命令构建Liquid项目

```bash
cargo +nightly liquid build
```

该命令会引导编译器以`wasm32`为目标编译合约并生成合约的ABI。执行完成后,命令行中会显示以下内容:
该命令会引导编译器以`wasm32`为目标编译项目并生成ABI。命令执行完成后,命令行中会显示以下内容:

```bash
Your contract is ready:
binary: <your working root directory>\hello_world\target\hello_world.wasm
ABI : <your working root directory>\hello_world\target\hello_world.abi
:-) Done in 35 seconds, your contract is ready now:
Binary: <path>\hello_world\target\hello_world.wasm
ABI: <path>\hello_world\target\hello_world.abi
```

`hello_world`项目下的`target`目录中回包含一个同名的`.wasm`文件及`.abi`文件,分别存放了合约字节码及ABI。其中合约ABI文件的内容如下
hello_world项目下的target目录中回包含一个同名的`.wasm`文件及`.abi`文件,分别存放了Hello World合约的字节码及ABI,其中ABI文件的内容如下

```json
[
Expand Down Expand Up @@ -135,9 +133,15 @@ Your contract is ready:
]
```

## 部署合约
```eval_rst
.. admonition:: 注意
使用Node.js SDK CLI工具提供的`deployWasm`命令,我们可以将编译出的合约字节码部署至真实的区块链上,`deployWasm`命令的使用方式如下:
如果您希望构建出的Wasm格式字节码能够在国密版FISCO BCOS区块链节点上运行,请在使用构建命令时添加-g选项。
```

## 部署

使用Node.js SDK CLI工具提供的`deployWasm`命令,我们可以将编译出的Hello World合约字节码部署至实际的区块链系统中,`deployWasm`命令的使用方式如下:

```bash
cli.js deployWasm <bin> <abi> [parameters...]
Expand All @@ -153,10 +157,10 @@ Positionals:
执行该命令时需要依次传入字节码文件的路径、ABI文件的路径及构造函数的参数。当配置好CLI工具后(见[配置手册](https://github.com/FISCO-BCOS/nodejs-sdk#22-%E9%85%8D%E7%BD%AE),我们可以使用以下命令部署HelloWorld合约(其构造函数参数为空,因此我们在命令中没有提供任何构造函数参数):
```bash
node .\cli.js deployWasm <your working root directory>\hello_world\target\hello_world.wasm <your working root directory>\hello_world\target\hello_world.abi
node .\cli.js deployWasm <path of hello_word.wasm> <path of hello_world.abi>
```
部署成功后,命令返回如下结果
部署成功后,返回格式如下结果,其中包含状态码、合约地址及交易哈希
```json
{
Expand All @@ -166,9 +170,7 @@ node .\cli.js deployWasm <your working root directory>\hello_world\target\hello_
}
```
其中包含状态码、合约地址及交易哈希。
## 调用合约
## 调用
使用Node.js SDK CLI工具提供的`call`命令,我们可以调用已被部署到区块链上的智能合约,`call`命令的使用方式如下:
Expand All @@ -184,7 +186,7 @@ Positionals:
parameters The parameters(splitted by space) of a function [array] [default: []]
```
执行该命令时需要依次传入合约名(对于Liquid合约,是合约项目名)、合约地址、要调用的合约方法名及传递给合约方法的参数。以调用HelloWorld合约的`get`方法为例,我们可以使用以下命令调用该方法(其方法参数为空,因此我们在命令中没有提供任何方法参数):
执行该命令时需要依次传入合约名(对于Liquid合约,是合约项目名)、合约地址、要调用的合约方法名及传递给合约方法的参数。以调用Hello World合约的`get`方法为例,我们可以使用以下命令调用该方法(其方法参数为空,因此我们在命令中不提供任何方法参数):
```bash
node .\cli.js call hello_world 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af get
Expand All @@ -198,10 +200,10 @@ node .\cli.js call hello_world 0x039ced1cd5bea5ace04de8e74c66e312ba4a48af get
"output": {
"function": "get()",
"result": [
"Hello, Alice!"
"Alice"
]
}
}
```
其中`output.result`字段中包含了`get`方法的返回值。可以看到,正如我们所预期的那样,`get`方法返回了字符串“Hello, Alice!”。
其中`output.result`字段中包含了`get`方法的返回值。可以看到,正如我们所预期的那样,`get`方法返回了字符串“Alice”。

0 comments on commit b7e9408

Please sign in to comment.