UID 與 ID (UID and ID)
Sui 校驗器要求在所有具有 key 能力的類型上使用 UID 類型。在這裡,我們將深入探討 UID 及其用法。
定義
UID 類型定義在 sui::object 模組中,它是 ID 的封裝器,而 ID 則進一步封裝了 address 類型。Sui 上的 UID 保證是唯一的,並且在物件被刪除後不能重複使用。
module sui::object;
/// UID 是物件的唯一識別碼
public struct UID has store {
id: ID
}
/// ID 是地址的封裝器
public struct ID has store, drop {
bytes: address
}
新 UID 的產生 (Fresh UID Generation)
- UID 是從 tx_hash 和為每個新 UID 遞增的 index 中「衍生 (derived)」出來的。
- derive_id 函式在 sui::tx_context 模組中實作,這就是為什麼產生 UID 需要 交易上下文 (TxContext)。
- Sui 校驗器不允許使用不是在同一個函式中建立的 UID。這防止了 UID 被預先產生或在物件拆包後重複使用。
使用 object::new 函式建立新的 UID。它接收一個對 TxContext 的可變參考,並傳回一個新的 UID。
public fun uid(ctx: &mut TxContext) {
let id = object::new(ctx); // 從 TxContext 建立一個新的 UID。
id.delete(); // 刪除此 UID。
}
UID 充當物件的代表,並允許定義物件的行為和功能。其中一個關鍵功能 —— 動態欄位 (Dynamic Fields) —— 之所以可行,是因為 UID 類型是顯式的。此外,它允許接收發送到其他物件的物件。此功能稱為 轉移至物件 (Transfer to Object, TTO),我們稍後將在本章中解釋。
UID 衍生 (UID Derivation)
Sui 允許使用「衍生金鑰 (derivation keys)」從其他 UID 衍生出 UID。此功能在 sui::derived_object 模組中實作,允許產生可預測且確定性的 UIDs,以便於鏈下發現。每一對「父項 (parent) + 金鑰 (key)」的 UID 只能產生一次!
use sui::derived_object;
/// 某個核心應用程式物件。
public struct Base has key { id: UID }
/// 衍生物件。
public struct Derived has key { id: UID }
/// 使用 `address` 作為 `key` 建立並共享一個新的 `Derived` 物件。
public fun derive(base: &mut Base, key: address) {
let id = derived_object::claim(&mut base.id, key);
transfer::share_object(Derived { id })
}
衍生地址降低了鏈下索引器 (indexers) 的負載,因為只需存儲父物件的 ID,並使用衍生函式即可獲取衍生 ID。ID 衍生函式是大多數 SDK 的一部分,也存在於 Move 中:
module sui::derived_object;
/// 檢查 UID 是否是使用 `key` 在 `parent` 處衍生出來的。
public fun exists<K: copy + drop + store>(parent: &UID, key: K): bool;
/// 不管是否已建立,衍生 UID 的內部 `address`。
public fun derive_address<K: copy + drop + store>(parent: ID, key: K): address;
同樣的衍生功能也被用於為 動態欄位 (dynamic fields) 產生 UID。
UID 的生命週期
UID 類型使用 object::new 函式建立,並使用 object::delete 函式刪除。object::delete 「按值 (by value)」消耗 UID,因此只有在物件 被拆包 (unpacked) 之後才能刪除物件的 UID。
public struct Character has key { id: UID }
public fun character(ctx: &mut TxContext) {
// 實例化 `Character` 物件。
let char = Character { id: object::new(ctx) };
// 拆包物件以獲取其 UID。
let Character { id } = char;
// 刪除此 UID。
id.delete();
}
保留 UID
UID 在物件結構被拆包後不需要立即刪除。有時它可能帶有 動態欄位 或透過 轉移至物件 轉移給它的物件。在這種情況下,UID 可能會保留並存儲在另一個物件中。
刪除證明 (Proof of Deletion)
返回物件 UID 的能力可以用於一種稱為「刪除證明 (proof of deletion)」的模式。這是一種很少使用的技術,但在某些情況下可能很有用,例如,創作者或應用程式可以透過將已刪除的 ID 更換為某些獎勵來激勵物件的刪除。
在框架開發中,此方法可以用於忽略/繞過對「獲取 (taking)」物件的某些限制。如果有一個像 Kiosk 那樣對轉移強制執行某些邏輯的容器,則可能存在透過提供刪除證明來跳過檢查的特殊場景。
這是目前仍在探索和研究的開放主題之一,可以以多種方式使用。
ID
在討論 UID 時,我們也應該提到 ID 類型。它是對 address 類型的封裝,用於代表一個地址指標。通常,ID 用於指向一個物件,然而,這沒有任何限制,也不保證 ID 指向一個現有的物件。
ID 可以作為 交易區塊 (Transaction Block) 中的交易參數接收。或者,可以使用 to_id() 函式從 address 數值建立 ID。
public fun conversion_methods(ctx: &mut TxContext) {
let uid: UID = object::new(ctx);
let id: ID = uid.to_inner();
let addr_from_uid: address = uid.to_address();
let addr_from_id: address = id.to_address();
uid.delete();
}
此範例展示了不同的轉換方法:UID.to_inner 建立了底層 ID 的副本,而 UID.to_address 返回內部地址。另一個非常有用的方法 ID.to_address 會從 ID 類型中複製內部數值。
新物件地址 (Fresh Object Address)
TxContext 提供了 fresh_object_address 函式,可用於建立唯一的地址和 ID —— 這在某些為使用者動作分配唯一識別碼(例如:市場中的 order_id)的應用程式中非常有用。
相關連結
- sui::object 模組文件
- sui::derived_object 模組文件
- Sui 文件中的 衍生物件 (Derived Objects)