Hello World!

提示

为了能够更好地使用Liquid进行智能合约开发,我们强烈建议提前参考 Rust语言官方教程 ,掌握Rust语言的基础知识,尤其借用、生命周期、属性等关键概念。

本节将以简单的 HelloWorld 合约为示例,帮助读者快速建立对 Liquid 合约的直观认识。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#![cfg_attr(not(feature = "std"), no_std)]

use liquid::storage;
use liquid_lang as liquid;

#[liquid::contract]
mod hello_world {
    use super::*;

    #[liquid(storage)]
    struct HelloWorld {
        name: storage::Value<String>,
    }

    #[liquid(methods)]
    impl HelloWorld {
        pub fn new(&mut self) {
            self.name.initialize(String::from("Alice"));
        }

        pub fn get(&self) -> String {
            self.name.clone()
        }

        pub fn set(&mut self, name: String) {
            self.name.set(name)
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        #[test]
        fn get_works() {
            let contract = HelloWorld::new();
            assert_eq!(contract.get(), "Alice");
        }

        #[test]
        fn set_works() {
            let mut contract = HelloWorld::new();

            let new_name = String::from("Bob");
            contract.set(new_name.clone());
            assert_eq!(contract.get(), "Bob");
        }
    }
}

上述智能合约代码中所使用的各种语法的详细说明可参阅“普通合约”一章,在本节中我们先进行初步的认识:

  • 第 1 行:

    1
    #![cfg_attr(not(feature = "std"), no_std)]
    

    cfg_attr是 Rust 语言中的内置属性之一。此行代码用于向编译器告知,若编译时没有启用std特性,则在全局范围内启用no_std属性,所有 Liquid 智能合约项目都需要以此行代码为首行。当在本地运行单元测试用例时,Liquid 会自动启用std特性;反之,当构建为可在区块链底层平台部署及运行的 Wasm 格式字节码时,std特性将被关闭,此时no_std特性将被自动启用。

    由于 Wasm 虚拟机的运行时环境较为特殊,对 Rust 语言标准库的支持并不完整,因此需要启用no_std特性以保证智能合约代码能够被 Wasm 虚拟机执行。相反的,当在本地运行单元测试用例时,Liquid 并不生成 Wasm 格式字节码,而是生成可在本地直接运行的可执行二进制文件,因此并不受前述限制。

  • 第 2~3 行:

    2
    3
    use liquid::storage;
    use liquid_lang as liquid;
    

    上述代码用于导入liquid_lang库并将其重命名为liquid,同时一并导入liquid_lang库中的storage模块。liquid_lang库是 Liquid 的核心组成部分,Liquid 中的诸多特性均由该库实现,而storage模块对区块链状态读写接口进行了封装,是定义智能合约状态变量所必须依赖的模块。

  • 第 10~13 行:

    10
    11
    12
    13
    #[liquid(storage)]
    struct HelloWorld {
        name: storage::Value<String>,
    }
    

    上述代码用于定义 HelloWorld 合约中的状态变量,状态变量中的内容会在区块链底层存储中永久保存。可以看出,HelloWorld 合约中只包含一个名为“name”的状态变量,且其类型为字符串类型String。但是注意到在声明状态变量类型时并没有直接写为String,而是将其置于单值容器storage::Value中,更多关于容器的说明可参阅状态变量与容器一节。

  • 第 15~28 行:

    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #[liquid(methods)]
    impl HelloWorld {
        pub fn new(&mut self) {
            self.name.initialize(String::from("Alice"));
        }
    
        pub fn get(&self) -> String {
            self.name.clone()
        }
    
        pub fn set(&mut self, name: String) {
            self.name.set(name)
        }
    }
    

    上述代码用于定义 HelloWorld 合约的合约方法。示例中的合约方法均为外部方法,即可被外界直接调用,其中:

    • new方法为 HelloWorld 合约的构造函数,构造函数会在合约部署时自动执行。示例中new方法会在初始时将状态变量name的内容初始化为字符串“Alice”;
    • get方法用于将状态变量name中的内容返回至调用者
    • set方法要求调用者向其传递一个字符串参数,并将状态变量name的内容修改为该参数。
  • 第 30~48 行:

    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn get_works() {
            let contract = HelloWorld::new();
            assert_eq!(contract.get(), "Alice");
        }
    
        #[test]
        fn set_works() {
            let mut contract = HelloWorld::new();
    
            let new_name = String::from("Bob");
            contract.set(new_name.clone());
            assert_eq!(contract.get(), "Bob");
        }
    }
    

    上述代码用于编写 HelloWorld 合约的单元测试用例。首行#[cfg(test)]用于告知编译器,只有启用test编译标志时,才编译其后跟随的模块,否则直接从代码中剔除。当将 Liquid 智能合约编译为 Wasm 格式字节码时,不会启用test编译标志,因此最终的字节码中不会包含任何与测试相关的代码。代码中的剩余部分则是包含了单元测试用例的具体实现,示例中的用例分别用于测试get方法及set方法的逻辑正确性,其中每一个测试用例均由#[test]属性进行标注。