7 个不稳定版本 (3 个破坏性更新)

0.4.2 2023年6月21日
0.3.2 2022年11月1日
0.3.1 2022年10月19日
0.2.1 2022年7月7日
0.1.0 2022年6月28日

#2388 in 魔法豆

Download history 60/week @ 2024-03-14 12/week @ 2024-03-21 11/week @ 2024-03-28 8/week @ 2024-04-04 14/week @ 2024-04-11 10/week @ 2024-05-16 11/week @ 2024-05-23 13/week @ 2024-05-30

每月下载量 103

Apache-2.0

92KB
1.5K SLoC

并行链主网合约 SDK

并行链主网合约 SDK (pchain-sdk) 提供了 Rust 结构体、函数、类型和宏,有助于在实现并行链主网合约二进制接口 (CBI) 子协议的 WebAssembly (WASM) 引擎中开发可执行的智能合约。

合约是并行链主网网络的运行时可编程机制。它们允许用户(账户所有者)在全局、去中心化和拜占庭容错复制状态机中实现任意逻辑,以支持他们最关键的业务应用。

理论上,任何实现 CBI 子协议的 WebAssembly (WASM) 模块都可以部署到并行链主网区块链上。实际上,然而,所有开发者(除了可能喜欢实验或想测试系统极限的人)都希望使用这个 pchain-sdk 中的类型和宏来用 Rust 编写合约,并使用 pchain-compile 中的命令将 Rust 源代码编译成可以在部署交易中包含的 WASM 字节码。

合约编程模型

pchain-sdk 允许开发者以直观和易读的风格编写智能合约,我们称之为“合约编程模型”。SDK 的宏透明地为您生成更低级别的“模板”代码,因此您可以专注于编写应用程序的业务逻辑。

合同编程模型受到面向对象编程(OOP)的启发。在模型中,合同可以被视为一个控制对持久存储访问的Rust结构体。账户通过提交包含调用命令的事务来与合同交互,以调用合同的方法,或简称方法。以下两节详细阐述了使用合同编程模型编程的两个基本概念:合同方法和合同存储。

合同方法

模型使用宏#[call]定义了合同方法,每个方法对应于CBI子协议中可调用的调用命令。

为了生成适当的CBI导出集绑定,最终允许从外部世界调用方法,必须在标记有#[contract_methods]宏的impl Contract语句中编写方法定义,如下面的示例所示。

#[contract_methods]
impl PrinceTheDog {
    #[call]
    fn eat_food(&mut self, food: DogFood) {
        ...
    }
}

方法可以修改合同存储。然而,请注意(如事务子协议中指定),在调用事务中对合同存储的修改只有在事务成功时才会应用(例如,事务必须退出时具有足够的gas,必须在执行期间没有崩溃等)。

函数可以调用的条件是

  1. 在函数声明上方添加宏#[call]
  2. 它的(零个或多个)其他参数实现BorshDeserialize
  3. 它的返回值实现BorshSerialize,或者它没有返回值。

接受参数和返回值

本文件中提供的示例代码片段中的一些代码片段描述了合同方法,该方法接受函数参数(除了合同结构的借用)并/或返回一个值。为了使合同能够从“外部世界”(调用者)接收参数并返回值,合同和调用者需要就序列化格式达成一致。

pchain-sdk期望调用者使用borsh序列化标准来序列化方法参数,并为将序列化值包含在事务收据中生成代码。更准确地说,事务命令调用指定要调用的合同方法,并通过在其arguments字段中包含borsh序列化的数据结构Option<Vec<Vec<u8>>>来提供调用参数,并且合同包含一个borsh序列化的ContractMethodOutput结构。前者类型定义在pchain-types中,后者定义在pchain-sdk中。未来,我们计划将它们都移入SDK。

合同存储

合同可以使用存储在调用之间持久化数据。将数据写入存储的最简单方法是为合同结构添加字段。

#[contract]
struct PrinceTheDog {
    age: u8, 
    breed: String,
    hungry: bool,
    toy: DogToy,
}

#[contract] 宏可以透明地生成代码,在合约方法执行之前从存储中加载所有 Contract 结构体的字段。所有实现了 Storage 特性的类型都可以用作 Contract 字段。默认情况下,这包括所有 Rust 基本类型,以及其他常用类型,如 Option<T>Result<T>Vec<T> 等。此外,开发者定义的结构体可以通过在其定义上应用 #[contract_field] 宏来实现 Storage,前提是所有字段都实现了 Storage。

#[contract_field]
struct DogToy {
    ...
}

存储和集合

由于存储的gas成本很高,通常在方法执行前加载所有 Contract 字段的字段,并在执行后写入存储,这通常会导致合约的经济性不高。对于存储不多或其方法总是读取和写入大多数字段的合约,这可能可以接受,甚至理想,但对于一些无法避免在链上存储大量状态的应用,每次调用都积极加载和保存字段可能过于昂贵。

为了解决这个问题,SDK 包含了一个 pchain_sdk::collections 模块。该模块中定义的所有类型都会“懒加载”存储:只有在从集合中读取或写入确切项目时才会产生读取或写入的gas成本。它们还提供了一个可以更方便地处理大量数据的API。

#[contract]
struct PrinceTheDog {
    nicknames: Vector<String>
}

Cacher (Cacher<T>)

包装任何实现了 Storage 的非集合类型,使其变为懒加载(所有 collections 类型在 Cacher 之外已经懒加载)。Cacher 实现 Deref,因此 Cacher<T> 可以在几乎任何可以使用 T 的地方使用,而不需要任何特殊语法。

向量 (Vector<T>)

Storage 中懒加载一系列项目。Vector 实现 IndexIndexMut,并有一个 iter 方法,因此您可以用 Vector 做几乎所有可以用 std::vec::Vec 做的事情。

映射 (FastMap<K, V>IterableMap<K, V>)

集合包括两种类型,它们在键和值之间存储静态类型的映射。这两种类型之间的区别在于,IterableMap,正如其名称所暗示的,是可迭代的。也就是说,它具有标准库中HashMap的keysitervalues方法集。这种功能是以在存储中存储比FastMap略多的数据为代价的。这两种类型在其他方面功能相同,包括能够嵌套类似映射(例如,FastMap<T, FastMap<K, V>>,但不是 FastMap<T, IterableMap<K, V>>)。

如果您应用程序绝对需要迭代存储项,则应使用IterableMap,否则请使用FastMap。

访问区块链信息

可以编写合约方法,不仅依赖于调用参数和合约的存储,还依赖于关于区块链的信息,例如,上一个块的哈希值或使用调用命令发起事务的外部账户的身份。

定义在pchain_sdk::transactionpchain_sdk::blockchain中的函数分别用于获取触发合约调用的交易信息以及有关更广泛的区块链的一般信息。内部,这些函数是CBI导入集中定义的函数的薄包装。

调用其他合约

SDK包含一对函数,用于执行合约到合约的内部调用

  • callcall_untyped

它执行显而易见的功能:使用给定的参数调用指定合约中的方法。

转账余额

pchain_sdk::transfer从合约账户将余额转移到另一个账户,并返回转账后的接收者余额。

依赖关系

~8.5MB
~115K SLoC