Pattern: Capability 能力 (Pattern: Capability)
在程式設計中,**capability(能力)**是一種賦予擁有者執行特定動作權利的權杖(token)。這是一種用來控制對資源與操作存取權的模式。能力最簡單的範例是門的鑰匙:有鑰匙就能開門,沒有鑰匙就不能開門。更實際的範例是 Admin Capability,它允許擁有者執行一般使用者無法執行的管理操作。
Capability 是一個 Object (Capability is an Object)
在 Sui Object Model 中,capability 是以 object 的形式表示。object 的擁有者可以將此 object 傳入函式,以證明自己有權執行特定動作。由於嚴格的型別系統,接受 capability 作為引數的函式,只能用正確的 capability 呼叫。
有個慣例是以 Cap 後綴為 capability 命名,例如 AdminCap 或 KioskOwnerCap。
module book::capability;
use std::string::String;
/// The capability granting the application admin the right to create new
/// accounts in the system.
public struct AdminCap has key, store { id: UID }
/// The user account in the system.
public struct Account has key, store {
id: UID,
name: String
}
/// Creates a new account in the system. Requires the `AdminCap` capability
/// to be passed as the first argument.
public fun new(_: &AdminCap, name: String, ctx: &mut TxContext): Account {
Account {
id: object::new(ctx),
name,
}
}
/// The `Account` itself acts as a capability too: only its owner can pass
/// a mutable reference to it, and hence only the owner can update the name.
public fun update(account: &mut Account, name: String) {
account.name = name;
}
使用 init 建立 Admin Capability (Using init for Admin Capability)
一個非常常見的做法,是在 package 發布時建立單一的 AdminCap object。如此一來,應用程式便能有一個設定階段,讓 admin 帳號準備好應用程式的狀態。
module book::admin_cap;
/// The capability granting the admin privileges in the system.
/// Created only once in the `init` function.
public struct AdminCap has key { id: UID }
/// Create the AdminCap object on package publish and transfer it to the
/// package owner.
fun init(ctx: &mut TxContext) {
transfer::transfer(
AdminCap { id: object::new(ctx) },
ctx.sender()
)
}
請注意,這個 AdminCap 只有 key 能力,不像第一個範例中同時擁有 store 能力。capability 的能力(abilities)定義了它如何在帳號之間移動:具備 key 和 store 時,capability 可以透過 public transfer 函式自由轉移,也能儲存在其他 object 中;只有 key 時,就只能透過其模組定義的函式轉移,因此模組可以限制——甚至完全禁止——這個 capability 被傳遞出去。如同Storage Functions章節所述,這正是內部轉移與 public 轉移之間的差異。
Sui Framework 中的 Capability (Capabilities in the Sui Framework)
capability 模式不只是一種慣例——Sui Framework本身就是圍繞它建立的。認識標準的 capability 有助於在實際程式碼中辨識這個模式;以下是你最可能遇到的幾種:
- sui::coin::TreasuryCap<T> - 與新貨幣一同建立,賦予鑄造與銷毀型別 T 代幣的權利。擁有 TreasuryCap 就是擁有該貨幣的供應量;我們會在 Balance and Coin 章節深入探討;
- sui::package::UpgradeCap - 在 package 發布時建立,授權對該 package 的未來升級。UpgradeCap 的擁有者也可以限制未來的升級,或透過使該 capability 不可變(immutable)來完全停用升級;
- sui::kiosk::KioskOwnerCap - 賦予在 Kiosk(Sui 的交易基本元件)中 place、take 與 list 物品的權利。雖然 Kiosk object 本身是共享的(shared),任何人都能存取,但對它的「擁有者」操作需要此 capability;
- sui::transfer_policy::TransferPolicyCap<T> - 賦予管理 TransferPolicy<T> 的權利:新增與移除交易規則,以及提領已收取的費用。
其中兩個 capability 帶有型別參數——這是值得留意的技巧。透過為 capability 加上泛型(generic),它所賦予的權限便會侷限於單一型別:TreasuryCap<GOLD> 只能控制 GOLD 的供應量,對 SILVER 貨幣沒有任何權利。
該 framework 也提供了一種更廣泛的權限形式——Publisher object,它證明了對某個 package 中所有型別的權限。這個部分會在 Publisher Authority 章節中另外說明。
地址檢查 vs Capability (Address Check vs Capability)
將 object 用作 capability,在區塊鏈程式設計中是相對較新的概念。在其他智能合約語言中,授權通常是透過檢查發送者的地址來完成。這種模式在 Sui 上依然可行,然而,整體建議是使用 capability,以獲得更好的安全性、可發現性與程式碼組織方式。
讓我們來看看,若建立使用者的 new 函式改用地址檢查,會是什麼樣子:
/// Error code for unauthorized access.
const ENotAuthorized: u64 = 0;
/// The application admin address.
const APPLICATION_ADMIN: address = @0xa11ce;
/// Creates a new user in the system. Requires the sender to be the application
/// admin.
public fun new(ctx: &mut TxContext): User {
assert!(ctx.sender() == APPLICATION_ADMIN, ENotAuthorized);
User { id: object::new(ctx) }
}
接著,讓我們看看同一個函式改用 capability 後的樣子:
/// Grants the owner the right to create new users in the system.
public struct AdminCap has key { id: UID }
/// Creates a new user in the system. Requires the `AdminCap` capability to be
/// passed as the first argument.
public fun new(_: &AdminCap, ctx: &mut TxContext): User {
User { id: object::new(ctx) }
}
相較於地址檢查,使用 capability 有幾項優勢:
- 由於 capability 是 object,管理員權限的轉移更加容易。若使用地址檢查,一旦管理員地址變更,所有檢查該地址的函式都需要更新——因而需要進行 package 升級。
- 使用 capability 時,函式簽章更具描述性。可以清楚看出 new 函式需要傳入 AdminCap 作為引數,且沒有它便無法呼叫此函式。
- Object Capability 不需要在函式主體中做額外檢查,因而降低了開發者出錯的機率。
- 擁有的 Capability 也有助於發現性。AdminCap 的擁有者可以在自己的帳號中看到該 object(透過錢包或瀏覽器),並知道自己擁有管理員權限。這一點在使用地址檢查時較不透明。
然而,地址檢查方式也有自己的優勢。其中一種情況是 **multisig(多重簽名)**地址——由多方共同控制的地址,只有在足夠多方簽署時,交易才算有效。若應用程式的管理員權限屬於一個 multisig 地址,檢查發送者可能會比建構一筆呈現該地址所擁有 capability object 的交易來得簡單。
另一種情況是應用程式擁有一個中央 object——例如 config 或 registry——它已經被傳入每個函式中。這樣的 object 可以將管理員地址儲存為一般欄位,檢查它不需要額外的輸入。地址是純資料,因此可以在執行期變更,不需要 package 升級。同樣的概念也讓 **revocation(撤銷)**成為可能:一個已擁有的 capability,一旦被轉移出去,就無法從其擁有者手中取回;但中央 registry 中的一筆條目——先前發出的 capability 的地址或 ID——則可以由管理員隨時移除,立即撤銷存取權。