プログラミングにおいて、*能力(Capability)*は所有者に特定のアクションを実行する権利を与えるトークンです。これはリソースや操作へのアクセスを制御するために使用されるパターンです。能力(Capability)の簡単な例としてドアの鍵があります。鍵を持っていればドアを開けることができ、持っていなければ開けることができません。より実用的な例としては、通常のユーザーが実行できない管理操作を所有者が実行できるようにする管理者能力があります。

能力(Capability)はオブジェクト

Sui オブジェクトモデルでは、能力(Capability)はオブジェクトとして表現されます。オブジェクトの所有者は、特定のアクションを実行する権利があることを証明するために、このオブジェクトを関数に渡すことができます。厳密な型付けにより、能力(Capability)を引数として受け取る関数は、正しい能力(Capability)でのみ呼び出すことができます。

能力(Capability)には Cap という接尾辞を付ける慣習があります。例えば、AdminCap や KioskOwnerCap などです。

module book::capability {
    use std::string::String;
    use sui::event;

    /// アプリケーション管理者にシステム内で新しいアカウントを作成する権利を
    /// 付与する能力(Capability)。
    public struct AdminCap has key, store { id: UID }

    /// システム内のユーザーアカウント。
    public struct Account has key, store {
        id: UID,
        name: String
    }

    /// データを持たないシンプルな `Ping` イベント。
    public struct Ping has copy, drop { by: ID }

    /// システム内に新しいアカウントを作成します。最初の引数として `AdminCap`
    /// 能力(Capability)を渡す必要があります。
    public fun new(_: &AdminCap, name: String, ctx: &mut TxContext): Account {
        Account {
            id: object::new(ctx),
            name,
        }
    }

    /// アカウントや他のオブジェクトも、アプリケーション内で能力(Capability)として
    /// 使用できます。例えば、イベントを発行する場合などです。
    public fun send_ping(acc: &Account) {
        event::emit(Ping {
            by: acc.id.to_inner()
        })
    }

    /// アカウント名を更新します。`Account` の所有者のみが呼び出せます。
    public fun update(account: &mut Account, name: String) {
        account.name = name;
    }
}

管理者能力(Capability)にinit を使用する

非常に一般的な方法として、パッケージの公開時に単一の AdminCap オブジェクトを作成することがあります。これにより、アプリケーションは管理者アカウントがアプリケーションの状態を準備するセットアップフェーズを持つことができます。

module book::admin_cap {
    /// システム内で管理者権限を付与する能力(Capability)。
    /// `init` 関数内で一度だけ作成されます。
    public struct AdminCap has key { id: UID }

    /// パッケージ公開時に AdminCap オブジェクトを作成し、
    /// パッケージ所有者に転送します。
    fun init(ctx: &mut TxContext) {
        transfer::transfer(
            AdminCap { id: object::new(ctx) },
            ctx.sender()
        )
    }
}

アドレスチェック vs 能力(Capability)

オブジェクトを能力(Capability)として利用することは、ブロックチェーンプログラミングにおいては比較的新しい概念です。他のスマートコントラクト言語では、認証は多くの場合、送信者のアドレスをチェックすることで行われます。このパターンは Sui でも依然として有効ですが、全体的な推奨事項としては、セキュリティ、発見可能性、コード構成の向上のために能力(Capability)を使用することです。

アドレスチェックを使用した場合の、ユーザーを作成する new 関数がどのように見えるか見てみましょう:

/// 未認証アクセスのエラーコード。
const ENotAuthorized: u64 = 0;

/// アプリケーション管理者のアドレス。
const APPLICATION_ADMIN: address = @0xa11ce;

/// システム内に新しいユーザーを作成します。送信者がアプリケーション
/// 管理者である必要があります。
public fun new(ctx: &mut TxContext): User {
    assert!(ctx.sender() == APPLICATION_ADMIN, ENotAuthorized);
    User { id: object::new(ctx) }
}

そして、同じ関数を能力(Capability)を使用して実装した場合を見てみましょう:

/// システム内で新しいユーザーを作成する権利を所有者に付与します。
public struct AdminCap {}

/// システム内に新しいユーザーを作成します。最初の引数として `AdminCap`
/// 能力(Capability)を渡す必要があります。
public fun new(_: &AdminCap, ctx: &mut TxContext): User {
    User { id: object::new(ctx) }
}

能力(Capability)の使用には、アドレスチェックに比べていくつかの利点があります:

しかし、アドレスアプローチにも利点があります。例えば、アドレスがマルチシグであり、トランザクションの構築がより複雑になる場合、アドレスをチェックする方が簡単かもしれません。また、アプリケーションの中心的なオブジェクトがすべての関数で使用される場合、そこに管理者アドレスを保存することで移行が簡素化されます。中心的なオブジェクトアプローチは、管理者がユーザーから能力(Capability)を取り消すことができる取り消し可能な能力(Capability)にも有効です。