capability - [mainnet]
A module which defines the basic concept of capabilities for managing access control.
EXPERIMENTAL
Overview
A capability is a unforgeable token which testifies that a signer has authorized a certain operation.
The token is valid during the transaction where it is obtained. Since the type capability::Cap
has
no ability to be stored in global memory, capabilities cannot leak out of a transaction. For every function
called within a transaction which has a capability as a parameter, it is guaranteed that the capability
has been obtained via a proper signer-based authorization step previously in the transaction’s execution.
Usage
Initializing and acquiring capabilities is usually encapsulated in a module with a type tag which can only be constructed by this module.
module Pkg::Feature {use std::capability::Cap;
/// A type tag used in Cap<Feature>. Only this module can create an instance,/// and there is no public function other than Self::acquire which returns a value of this type./// This way, this module has full control how Cap<Feature> is given out.struct Feature has drop {}
/// Initializes this module.public fun initialize(s: &signer) {// Create capability. This happens once at module initialization time.// One needs to provide a witness for being the owner of Feature// in the 2nd parameter.<<additional conditions allowing to initialize this capability>>capability::create<Feature>(s, &Feature{});}
/// Acquires the capability to work with this feature.public fun acquire(s: &signer): Cap<Feature> {<<additional conditions allowing to acquire this capability>>capability::acquire<Feature>(s, &Feature{});}
/// Does something related to the feature. The caller must pass a Cap<Feature>.public fun do_something(_cap: Cap<Feature>) { ... }}
Delegation
Capabilities come with the optional feature of delegation. Via Self::delegate
, an owner of a capability
can designate another signer to be also capable of acquiring the capability. Like the original creator,
the delegate needs to present his signer to obtain the capability in his transactions. Delegation can
be revoked via Self::revoke
, removing this access right from the delegate.
While the basic authorization mechanism for delegates is the same as with core capabilities, the target of delegation might be subject of restrictions which need to be specified and verified. This can be done via global invariants in the specification language. For example, in order to prevent delegation all together for a capability, one can use the following invariant:
invariant forall a: address where capability::spec_has_cap<Feature>(a):len(capability::spec_delegates<Feature>(a)) == 0;
Similarly, the following invariant would enforce that delegates, if existent, must satisfy a certain predicate:
invariant forall a: address where capability::spec_has_cap<Feature>(a):forall d in capability::spec_delegates<Feature>(a):is_valid_delegate_for_feature(d);
use 0x1::error;use 0x1::signer;use 0x1::vector;
Constants
Capability resource already exists on the specified account
const ECAPABILITY_ALREADY_EXISTS: u64 = 1;
Capability resource not found
const ECAPABILITY_NOT_FOUND: u64 = 2;
Account does not have delegated permissions
const EDELEGATE: u64 = 3;
Structs
Cap
The token representing an acquired capability. Cannot be stored in memory, but copied and dropped freely.
struct Cap<Feature> has copy, drop
Fields
-
root: address
LinearCap
A linear version of a capability token. This can be used if an acquired capability should be enforced to be used only once for an authorization.
struct LinearCap<Feature> has drop
Fields
-
root: address
Resources
CapState
An internal data structure for representing a configured capability.
struct CapState<Feature> has key
Fields
-
delegates: vector<address>
CapDelegateState
An internal data structure for representing a configured delegated capability.
struct CapDelegateState<Feature> has key
Fields
-
root: address
Functions
create
Creates a new capability class, owned by the passed signer. A caller must pass a witness that
they own the Feature
type parameter.
public fun create<Feature>(owner: &signer, _feature_witness: &Feature)
Implementation
public fun create<Feature>(owner: &signer, _feature_witness: &Feature) { let addr = signer::address_of(owner); assert!(!exists<CapState<Feature>>(addr), error::already_exists(ECAPABILITY_ALREADY_EXISTS)); move_to<CapState<Feature>>(owner, CapState { delegates: vector::empty() });}
acquire
Acquires a capability token. Only the owner of the capability class, or an authorized delegate,
can succeed with this operation. A caller must pass a witness that they own the Feature
type
parameter.
public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): capability::Cap<Feature>
Implementation
public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): Cap<Feature>acquires CapState, CapDelegateState { Cap<Feature> { root: validate_acquire<Feature>(requester) }}
acquire_linear
Acquires a linear capability token. It is up to the module which owns Feature
to decide
whether to expose a linear or non-linear capability.
public fun acquire_linear<Feature>(requester: &signer, _feature_witness: &Feature): capability::LinearCap<Feature>
Implementation
public fun acquire_linear<Feature>(requester: &signer, _feature_witness: &Feature): LinearCap<Feature>acquires CapState, CapDelegateState { LinearCap<Feature> { root: validate_acquire<Feature>(requester) }}
validate_acquire
Helper to validate an acquire. Returns the root address of the capability.
fun validate_acquire<Feature>(requester: &signer): address
Implementation
fun validate_acquire<Feature>(requester: &signer): addressacquires CapState, CapDelegateState { let addr = signer::address_of(requester); if (exists<CapDelegateState<Feature>>(addr)) { let root_addr = borrow_global<CapDelegateState<Feature>>(addr).root; // double check that requester is actually registered as a delegate assert!(exists<CapState<Feature>>(root_addr), error::invalid_state(EDELEGATE)); assert!(borrow_global<CapState<Feature>>(root_addr).delegates.contains(&addr), error::invalid_state(EDELEGATE)); root_addr } else { assert!(exists<CapState<Feature>>(addr), error::not_found(ECAPABILITY_NOT_FOUND)); addr }}
root_addr
Returns the root address associated with the given capability token. Only the owner of the feature can do this.
public fun root_addr<Feature>(self: capability::Cap<Feature>, _feature_witness: &Feature): address
Implementation
public fun root_addr<Feature>(self: Cap<Feature>, _feature_witness: &Feature): address { self.root}
linear_root_addr
Returns the root address associated with the given linear capability token.
public fun linear_root_addr<Feature>(self: capability::LinearCap<Feature>, _feature_witness: &Feature): address
Implementation
public fun linear_root_addr<Feature>(self: LinearCap<Feature>, _feature_witness: &Feature): address { self.root}
delegate
Registers a delegation relation. If the relation already exists, this function does nothing.
public fun delegate<Feature>(self: capability::Cap<Feature>, _feature_witness: &Feature, to: &signer)
Implementation
public fun delegate<Feature>(self: Cap<Feature>, _feature_witness: &Feature, to: &signer)acquires CapState { let addr = signer::address_of(to); if (exists<CapDelegateState<Feature>>(addr)) return; move_to(to, CapDelegateState<Feature> { root: self.root }); add_element(&mut borrow_global_mut<CapState<Feature>>(self.root).delegates, addr);}
revoke
Revokes a delegation relation. If no relation exists, this function does nothing.
public fun revoke<Feature>(self: capability::Cap<Feature>, _feature_witness: &Feature, from: address)
Implementation
public fun revoke<Feature>(self: Cap<Feature>, _feature_witness: &Feature, from: address)acquires CapState, CapDelegateState{ if (!exists<CapDelegateState<Feature>>(from)) return; let CapDelegateState { root: _root } = move_from<CapDelegateState<Feature>>(from); remove_element(&mut borrow_global_mut<CapState<Feature>>(self.root).delegates, &from);}
remove_element
Helper to remove an element from a vector.
fun remove_element<E: drop>(v: &mut vector<E>, x: &E)
Implementation
fun remove_element<E: drop>(v: &mut vector<E>, x: &E) { let (found, index) = v.index_of(x); if (found) { v.remove(index); }}
add_element
Helper to add an element to a vector.
fun add_element<E: drop>(v: &mut vector<E>, x: E)
Implementation
fun add_element<E: drop>(v: &mut vector<E>, x: E) { if (!v.contains(&x)) { v.push_back(x) }}
Specification
Helper specification function to check whether a capability exists at address.
fun spec_has_cap<Feature>(addr: address): bool { exists<CapState<Feature>>(addr)}
Helper specification function to obtain the delegates of a capability.
fun spec_delegates<Feature>(addr: address): vector<address> { global<CapState<Feature>>(addr).delegates}
Helper specification function to check whether a delegated capability exists at address.
fun spec_has_delegate_cap<Feature>(addr: address): bool { exists<CapDelegateState<Feature>>(addr)}
create
public fun create<Feature>(owner: &signer, _feature_witness: &Feature)
let addr = signer::address_of(owner);aborts_if spec_has_cap<Feature>(addr);ensures spec_has_cap<Feature>(addr);
acquire
public fun acquire<Feature>(requester: &signer, _feature_witness: &Feature): capability::Cap<Feature>
let addr = signer::address_of(requester);let root_addr = global<CapDelegateState<Feature>>(addr).root;include AcquireSchema<Feature>;ensures spec_has_delegate_cap<Feature>(addr) ==> result.root == root_addr;ensures !spec_has_delegate_cap<Feature>(addr) ==> result.root == addr;
acquire_linear
public fun acquire_linear<Feature>(requester: &signer, _feature_witness: &Feature): capability::LinearCap<Feature>
let addr = signer::address_of(requester);let root_addr = global<CapDelegateState<Feature>>(addr).root;include AcquireSchema<Feature>;ensures spec_has_delegate_cap<Feature>(addr) ==> result.root == root_addr;ensures !spec_has_delegate_cap<Feature>(addr) ==> result.root == addr;
schema AcquireSchema<Feature> { addr: address; root_addr: address; aborts_if spec_has_delegate_cap<Feature>(addr) && !spec_has_cap<Feature>(root_addr); aborts_if spec_has_delegate_cap<Feature>(addr) && !vector::spec_contains(spec_delegates<Feature>(root_addr), addr); aborts_if !spec_has_delegate_cap<Feature>(addr) && !spec_has_cap<Feature>(addr);}
delegate
public fun delegate<Feature>(self: capability::Cap<Feature>, _feature_witness: &Feature, to: &signer)
let addr = signer::address_of(to);ensures spec_has_delegate_cap<Feature>(addr);ensures !old(spec_has_delegate_cap<Feature>(addr)) ==> global<CapDelegateState<Feature>>(addr).root == self.root;ensures !old(spec_has_delegate_cap<Feature>(addr)) ==> vector::spec_contains(spec_delegates<Feature>(self.root), addr);
revoke
public fun revoke<Feature>(self: capability::Cap<Feature>, _feature_witness: &Feature, from: address)
ensures !spec_has_delegate_cap<Feature>(from);
remove_element
fun remove_element<E: drop>(v: &mut vector<E>, x: &E)
add_element
fun add_element<E: drop>(v: &mut vector<E>, x: E)
ensures vector::spec_contains(v, x);