对象ID和地址

我们已经学到了很多关于对象的酷技巧。我们想要深入探讨的对象的一个基本方面是对象ID。

你是否想过对象的ID和地址是如何创建的?从技术上讲,对象的ID和地址是相同的——它们是唯一的标识符,可以让开发人员识别和获取对象中的数据。 这在链上(在Move中)和链下(通过Web UI查询对象的所有数据和字段)都是如此:

public fun get_object_id_from_address(object_addr: address): ID {
    object::id_from_address(address)
}

public fun get_object_address(object: &MyObject): address {
    object::id_to_address(&object.id)
}

请注意,对象ID的返回类型是ID,这与每个对象结构体都有的UID字段不同。 不过,你可以使用object::uid_to_inner将UID引用转换为ID。这允许你比较对象是否与存储的地址相同。

当对象被创建时,其ID字段通过object::new(ctx)生成,其中ctx是Sui虚拟机(VM)传递给交易的TxContext类型的可变引用。以下是代码示例:

public fun new(ctx: &mut TxContext): UID {
    UID {
        id: ID { bytes: tx_context::fresh_object_address(ctx) },
    }
}

如你所见,在幕后,首先通过 tx_context::fresh_object_address(ctx) 生成一个唯一的地址, 然后转换为字节以创建对象的ID。fresh_object_address 是 Move 中的一种特殊类型的函数——原生函数。 原生函数非常特殊,因为它们的实现是用 Rust 编写的,作为 Move VM 的一部分。

这使得函数运行得更快,并且可以访问 VM 的内部结构。在这种情况下,fresh_object_address 可以看到用户的交易负载并使用它来生成对象的特殊地址。 它还使用一个计数器来跟踪在同一交易中创建的对象数量。然后,fresh_object_address 对交易负载和计数器进行哈希处理,并确保结果在同一交易中创建的多个对象中是唯一的。

地址生成过程的一个有趣用例是潜在地将其用作随机性来源——从新创建的对象的ID生成的字节可以用作随机值。这通常是因为交易负载和计数器的哈希值看起来往往是随机的(不遵循任何模式):

use sui::bcs;

public fun get_random_value(ctx: &mut TxContext): u64 {
    let object_id = object::new(ctx);
    let bytes = object::uid_to_bytes(&object_id);
    let random_number = bcs::peel_u64(&mut bcs::new(bytes));
    object::delete(object_id);
    random_number
}

get_random_value 可以多次调用以创建伪随机数(看似随机)。这取决于使用场景,可以通常用作随机值的来源,但它可能不安全,因为用户可能会操纵它。他们可以更改交易中的到期时间戳和其他字段,以生成所需的对象ID和结果随机数(例如,为了赢得链上彩票)。包括时间戳(来自系统对象Clock)可能更安全,但仍然存在风险:

  1. 用户仍然可以在一定程度上操纵随机值(不像以前那么多,因为他们无法控制Clock对象)。
  2. 验证者也可以部分操纵随机值,因为他们可以将Clock对象的时间戳设置为特定值,在一个小范围的误差内。

我们将在后续课程中介绍更安全的随机值生成方法。