特殊数据类型及宏

ContractId 类型

ContractId是 Liquid 提供的原生数据类型,在协作中用于存储合同 ID,无需导入即可直接使用。ContractId是一种泛型数据类型,其类型定义如下:

1
2
3
 pub struct ContractId<T> {
     ...
 }

其中类型参数T必须为合同模板类型,因此下列代码会导致编译时报错:

1
let something: ContractId<u8> = ...;

ContractId类型实现了标准库中的CopyClonePartialEq trait,因此可以方便地进行值拷贝及相等性比较:

1
2
3
let a: ContractId<Ballot> = ...;
let b = a;
assert_eq!(a, b);

ContractId类型能够在协作中的任何地方使用,但是当应用于定义合同模板成员、权利入参或返回值的类型时,其对外表现与u32类型一致。例如,假设协作中存在如下合同模板的定义:

1
2
3
4
5
6
#[liquid(contract)]
pub struct Offer {
    #[liquid(signers)]
    owner: address,
    item_id: ContractId<Item>,
}

通过 Node.js CLI 工具的sign命令签署生成Offer合同时,可以按照如下方式执行:

node ./cli.js sign Offer 0x144d5ca47de35194b019b6f11a56028b964585c9 1

注意在上述命令中,对第二项参数(即item_id)直接赋予整数 1。待交易被执行时,整数 1 会被 Liquid 自动转换为对应的ContractId类型变量,权利入参的传参及转换过程类似。当权利的返回值类型包括ContractId类型时,Liquid 会将其自动转换为整数并返回。

ContractId类型不仅用于存储合同 ID,更是对应合同的全权代表,当需要在权利代码中行使其他合同的权利或查询其他合同的数据内容时,均需要通过ContractId才能实现。例如在下列示例代码中,Offer合同模板中的settle权利能够直接通过合同自身的item_id成员(ContractId<Item>类型)直接调用Item合同模板中定义的transfer_item权利:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#[liquid(rights_belong_to = "owner")]
impl Item {
    pub fn transfer_item(self, new_owner: address) -> ContractId<Item> {
        ...
    }
}

#[liquid(rights_belong_to = "owner")]
impl Offer {
    pub fn settle(self, buyer: address) -> ContractId<Item> {
        self.item_id.transfer_item(buyer)
    }
}

需要注意的是,在上述示例中,transfer_item权利的接收器会使被行权的Item合同在执行结束后作废,因此若试图继续通过同样的item_id行权时,会引发“合同已作废”的运行时错误。

除了能够执行所指向合同中的权利,ContractId类型还实现了名为fetch的特殊方法,用于在代码中获取所指向合同中的数据类容,例如:

1
2
3
4
5
pub fn buy_item(&self, offer_id: ContractId<Offer>) {
    let offer = offer_id.fetch();
    let vendor = offer.vendor;
    ...
}

需要注意的是,在上述示例中,只能从offer变量中读取offer_id所对应合同中的数据内容,但无法基于offer变量行使任何Offer合同模板中定义的权利。

sign!宏

sign!宏是 Liquid 原生提供的过程宏,用于在权利的执行过程中签订新的合同,无需导入即可直接使用,其使用方式如下所示:

1
2
3
4
5
6
7
8
pub fn add(mut self, voter_addr: address) -> ContractId<Ballot> {
    ...

    sign! { Ballot =>
        voters: self.voters,
        ..self
    }
 }

sign!宏的语法由三部分组成:合同模板名称、向右箭头(=>)及成员赋值。其中,合同模板名称必须是有效的合同模板类型名称,且不能使用Self指代自身;成员赋值部分的语法与 Rust 语言中结构体赋值语法相同,但需要注意的是,成员赋值中包含 StructBase 语法时(即上述示例中的..self,其中self也可以换为其他表达式),务必需要保证..后的表达式的数据类型与待签署的合同模板类型一致。若sign!宏执行成功,则返回一个ContractId类型变量,变量中存储着由sign!宏签署生成的合同的 ID。

注意

sign! 宏是权利执行过程中唯一合法的构造 ContractId 类型变量的方式。