PTB(ProgrammableTransactionBlock)在Sui网络上,可编程事务块(PTB)允许用户指定一系列操作(事务)作为单个事务发送到网络这些操作按顺序执行,并且是原子性的-如果其中任何一个操作失败,整个PTB都会失败,并且所有的更改都会自动回滚PTB可以调用
public
、public entry
和 entry
(private) 函数public
function
public fun fun_name(){}
private
function
fun fun_name(){}
public(friend)
function
public(friend)
函数只能被同一包(package)中的模块(module)调用public(friend)
函数, 需要使用 use 引入public(friend)
函数的 module 中, 需要显式声明 friend [address]::[module_name]
module 0x123::my_other_module {
use 0x123::my_module;
public fun do_it(x: u64): bool {
my_module::friend_only_equal(x)
}
}
module 0x123::my_module {
friend 0x123::my_other_module;
public(friend) fun friend_only_equal(x: u64): bool {
x == 1000
}
}
Entry
function
public entry
function: 与 public
function 实际没有区别entry
function(private): 可以直接从事务中调用而不能在其他 module 中调用(自身所在的module 还是可以调用的, 但是不推荐)我们希望用户必须明确地在事务中调用此功能,而不希望其他模块代表用户 clip_ticket
entry fun clip_ticket(ticket: Ticket) {
let Ticket {
id,
expiration_time: _,
} = ticket;
object::delete(id);
}
key
在 Sui 中, key
能力表示一个结构体是一个 Object 类型
, 并且要求结构体的第一个字段是 id: UID
(链上的唯一地址), Sui 的字节码验证器会保证 UID 不会重复
struct AdminCap has key {
id: UID,
num_frens: u64,
}
copy
copy
能力允许一个结构体被“复制”,从而创建一个具有完全相同额字段值的结构体实例
struct CopyableStruct has copy {
value: u64,
}
fun copy(original: CopyableStruct) {
let copy = original;
original.value = 1;
copy.value = 2;
// We now have two CopyableStructs with two different values.
}
store
store
能力允许一个结构体成为其他结构体的一部分
public struct NestedStruct has store {
value: u64,
doubleNested: DoubleNestedStruct,
}
public struct DoubleNestedStruct has store {
value: u64,
}
public struct Container has key {
id: UID,
nested: NestedStruct,
}
drop
drop
能力允许在函数结束时隐式销毁结构体,而无需进行“析构”操作
struct DroppableStruct has drop {
value: u64,
}
fun copy() {
let droppable = DroppableStruct { value: 1 };
// At the end of this function, droppable would be destroyed.
// We don't need to explicitly destruct:
// let DroppableStruct { value: _ } = droppable;
}
Note: 只有当结构体的所有字段具有相同的能力时, 结构体才具体相关的能力
例如在 Box
对象中包含了一个 Thing
对象:
Thing
对象并将其返回,而不像mint函数那样立即将其传递给发送者方法 2: 首先 mint Thing
对象, mint 之后在当前 Transaction 无法检索到 mint 的 Thing
对象, 需要在后续的 Transaction 中显示的传递
struct Box has key {
id: UID,
thing: Thing,
}
struct Thing has key, store {
id: UID,
}
public fun wrap(thing: Thing, ctx: &mut TxContext) {
let box = Box { id: object::new(ctx), thing };
transfer::transfer(box, tx_context::sender(ctx));
}
Object
都是 owned object
: 是私有对象,只有拥有它们的用户才能读取和修改(所有权)shared object
: 可以被任何用户读取和修改不可变对象 Immutable object
: 不可变对象与共享对象几乎相同。任何用户都可以将它们作为其交易的一部分。然而,共享对象可以作为可变引用包含,因此可以被任何人修改。不可变对象在被“冻结”后永远不会改变
struct ColorObject has key {
id: UID,
red: u8,
green: u8,
blue: u8,
}
public entry fun freeze_owned_object(object: ColorObject) {
transfer::freeze_object(object)
}
public entry fun create_immutable(red: u8, green: u8, blue: u8, ctx: &mut TxContext) {
let color_object = ColorObject {
id: object::new(ctx),
red,
green,
blue,
};
transfer::freeze_object(color_object);
}
create_immutable
创建一个对象并立即将其冻结,使其成为不可变的transfer
的其他 freeze 函数:
freeze_owned_object
: 提供一个现有的 owned object
并使其成为不可变对象
shared object
上调用 transfer::freeze_object
,将会出错!!!不可变对象可以随时通过不可变引用(&) 包含
public fun read_immutable(color: &ColorObject): (u8, u8, u8) {
(color.red, color.green, color.blue)
}
transfer::public_transfer
store
能力的 Object
可以通过 transfer::public_transfer
在定义这个 Object 的module 之外进行 transfer
transfer::transfer
store
能力的对象只能在定义它的模块内部进行传输Non-Object struct
struct
就是 Non-Object struct
这种方法通常在不打算将嵌套的结构类型转换为对象时才有用。这可以将一个长的对象结构分解为更小的组
long object
struct LongObject has key {
id: UID,
field_1: u64,
field_2: u64,
field_3: u64,
field_4: u64,
field_5: u64,
field_6: u64,
field_7: u64,
field_8: u64,
}
big object
struct BigObject has key {
id: UID,
field_group_1: FieldGroup1,
field_group_2: FieldGroup2,
field_group_3: FieldGroup3,
}
struct FieldGroup1 has store {
field_1: u64,
field_2: u64,
field_3: u64,
}
struct FieldGroup2 has store {
field_4: u64,
field_5: u64,
field_6: u64,
}
struct FieldGroup3 has store {
field_7: u64,
field_8: u64,
}
Shared对象
的成本比操作 Owned
或 Immutable
对象要高。不可变的对象
共享对象
(效率最低)owned object
用于其他一切事物当部署一个模块时,任何定义的init
函数将被自动调用。这个init
函数可以接收一个见证对象,这是一个特殊的系统对象,仅在模块第一次部署时创建一次
module 0x123::my_module {
struct MY_MODULE has drop {}
fun init(witness: MY_MODULE) {
// 使用见证对象执行操作。
}
}
为了在init
函数中接收见证对象,需要声明一个与模块名称相同(全大写,保留下划线)的结构体,并且这个结构体必须具有drop
能力。然后,在定义init
函数时,可以将该类型的见证对象作为第一个参数
目前有两种用途
声明发布者对象(Publisher Object):发布者对象是证明部署者已经部署了该对象的证明
fun init(witness: MY_MODULE, ctx: &mut TxContext) {
assert!(types::is_one_time_witness(&witness), ENotOneTimeWitness);
let publisher_object = package::claim(witness, ctx);
// 使用或存储发布者对象...
}
证明正在初始化流程中调用其他模块的函数:当需要作为项目初始化的一部分与多个不同模块进行一系列操作时,这通常很有用
module 0x123::module_b {
// 在 module_b 的 init fun 中传入 A 的 witness, 作用是保证初始化顺序可控
fun init(module_a_witness: MODULE_A, ctx: &mut TxContext) {
assert!(types::is_one_time_witness(&module_a_witness), ENotOneTimeWitness);
}
}
Display<T>
: 指示如何为特定类型的 object 显示字段的 object, 下面是函数原型:
struct Display<phantom T: key> has key, store {
id: UID,
/// Contains fields for display. Currently supported
/// fields are: name, link, image and description.
fields: VecMap<String, String>,
/// Version that can only be updated manually by the Publisher.
version: u16
}
意味着开发者可以为存储在区块链上的对象定义如何在用户界面(比如web UI
)中显示它们的字段
如果创建 display 对象
MyObject
所在的模块)创建Display对象。Publisher对象是在模块部署时通过特定的初始化流程创建的display::new
函数:创建一个新的Display对象。这个过程需要Publisher
对象的引用和 transaction 上下文(ctx
)display::add_multiple
函数,你可以为MyObject
的特定字段添加格式化规则。这个函数接受两个 vector,一个是要显示的字段列表,另一个是对应的格式化字符串display::update_version
函数来确认对Display对象所做的更改,并触发一个事件,这个事件被Sui网络节点捕捉到后,可以让这些节点识别到新的或更新的Display对象一旦Display对象被创建并设置好,每当通过节点API获取对象时,它的显示属性也会根据指定的格式计算出来,并与对象的其他字段一起返回
代码:
module 0x123::my_module {
struct MyObject has key {
id: UID,
num_value: u64,
string_value: String,
}
public fun create_display_object(publisher: &Publisher, ctx: &mut TxContext) {
let display_object = display::new<MyObject>(&publisher, ctx);
display::add_multiple(
&mut display,
vector[
utf8(b"num_value"),
utf8(b"string_value"),
],
vector[
utf8(b"Value: {num_value}"),
utf8(b"Description: {string_value}"),
],
);
display::update_version(&mut display);
}
}
Sui区块链提供了一个特殊的系统对象,名为Clock
,允许用户获取链上记录的当前时间。这个功能在智能合约编程中非常有用,尤其是在需要对事件进行时间戳记录或者基于时间生成伪随机数的场景中。
获取当前时间:
Clock
对象允许通过clock::timestamp_ms
函数获取当前的时间戳。时间戳的常见用途包括但不限于:
记录保持:用时间戳记录事件发生的具体时刻。例如,可以创建一个结构体TimeEvent
,包含时间戳字段timestamp_ms
,并在特定操作时发出事件,将时间戳记录下来。
struct TimeEvent has copy, drop {
timestamp_ms: u64,
}
public entry fun log_event(clock: &Clock) {
event::emit(TimeEvent { timestamp_ms: clock::timestamp_ms(clock) });
}
生成伪随机数:利用时间戳作为种子来生成伪随机数。这种方法虽然方便,但技术上容易受到验证者操纵,因为验证者可以在很小的误差范围内设置时间戳。
entry fun flip_coin(clock: &Clock): u64 {
let timestamp_ms = clock::timestamp_ms(clock);
// 0是正面,1是反面
timestamp_ms % 2
}
安全事项:
使用Clock
对象的条件:
Clock
对象,需要在合约函数中将其作为参数传入object::new(ctx)
来创建一个新的对象ID。这个ID是唯一的,确保了对象在全局的唯一性tx_context::sender(ctx)
获取当前事务的发送者地址。这可以用来确定是谁发起了当前的事务TxContext
的其他功能
digest
函数返回当前事务的哈希值,可以用于日志记录或跟踪epoch
和epoch_timestamp_ms
函数分别返回当前的纪元号和对应的时间戳,有助于实现基于时间的逻辑fresh_object_address
使用与object::new
相同的底层函数来生成新对象的地址,这对于创建新对象时指定特定的地址可能很有用transfer::public_transfer
定义的同一模块之外transfer::transfer
进行传输public / private / public entry/ private entry
key, store, copy, drop
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!