(other_signer);
...
// The granted capability can be revoked. There is no need to have the other signer for this.
vault::revoke_read_cap(&delegate_cap, signer::address_of(other_signer));
```
### Abilities
Currently, we require that the Content
type of a vault has the drop
ability in order to instantiate
a capability type like ReadCap<Content>
. Without this, capabilities themselves would need to have an
explicit release function, which makes little sense as they are pure values. We expect the Move
language to have 'phantom type parameters' or similar features added, which will allows us to have
ReadCap<Content>
droppable and copyable without Content
needing the same.
- [Overview](#@Overview_0)
- [Capabilities](#@Capabilities_1)
- [Delegation](#@Delegation_2)
- [Abilities](#@Abilities_3)
- [Struct `ReadCap`](#0x1_vault_ReadCap)
- [Struct `ModifyCap`](#0x1_vault_ModifyCap)
- [Struct `DelegateCap`](#0x1_vault_DelegateCap)
- [Struct `TransferCap`](#0x1_vault_TransferCap)
- [Struct `CapType`](#0x1_vault_CapType)
- [Struct `VaultDelegateEvent`](#0x1_vault_VaultDelegateEvent)
- [Struct `VaultTransferEvent`](#0x1_vault_VaultTransferEvent)
- [Resource `Vault`](#0x1_vault_Vault)
- [Resource `VaultDelegates`](#0x1_vault_VaultDelegates)
- [Resource `VaultEvents`](#0x1_vault_VaultEvents)
- [Resource `VaultDelegate`](#0x1_vault_VaultDelegate)
- [Struct `ReadAccessor`](#0x1_vault_ReadAccessor)
- [Struct `ModifyAccessor`](#0x1_vault_ModifyAccessor)
- [Constants](#@Constants_4)
- [Function `read_cap_type`](#0x1_vault_read_cap_type)
- [Function `modify_cap_type`](#0x1_vault_modify_cap_type)
- [Function `delegate_cap_type`](#0x1_vault_delegate_cap_type)
- [Function `transfer_cap_type`](#0x1_vault_transfer_cap_type)
- [Function `new`](#0x1_vault_new)
- [Function `is_delegation_enabled`](#0x1_vault_is_delegation_enabled)
- [Function `enable_delegation`](#0x1_vault_enable_delegation)
- [Function `enable_events`](#0x1_vault_enable_events)
- [Function `remove_vault`](#0x1_vault_remove_vault)
- [Function `acquire_read_cap`](#0x1_vault_acquire_read_cap)
- [Function `acquire_modify_cap`](#0x1_vault_acquire_modify_cap)
- [Function `acquire_delegate_cap`](#0x1_vault_acquire_delegate_cap)
- [Function `acquire_transfer_cap`](#0x1_vault_acquire_transfer_cap)
- [Function `validate_cap`](#0x1_vault_validate_cap)
- [Function `read_accessor`](#0x1_vault_read_accessor)
- [Function `borrow`](#0x1_vault_borrow)
- [Function `release_read_accessor`](#0x1_vault_release_read_accessor)
- [Function `modify_accessor`](#0x1_vault_modify_accessor)
- [Function `borrow_mut`](#0x1_vault_borrow_mut)
- [Function `release_modify_accessor`](#0x1_vault_release_modify_accessor)
- [Function `delegate`](#0x1_vault_delegate)
- [Function `revoke`](#0x1_vault_revoke)
- [Function `revoke_all`](#0x1_vault_revoke_all)
- [Function `remove_element`](#0x1_vault_remove_element)
- [Function `add_element`](#0x1_vault_add_element)
- [Function `emit_delegate_event`](#0x1_vault_emit_delegate_event)
- [Function `transfer`](#0x1_vault_transfer)
use 0x1::error;
use 0x1::event;
use 0x1::option;
use 0x1::signer;
use 0x1::vector;
## Struct `ReadCap`
A capability to read the content of the vault. Notice that the capability cannot be
stored but can be freely copied and dropped.
TODO: remove drop
on Content
here and elsewhere once we have phantom type parameters.
struct ReadCap<Content: drop, store> has copy, drop
Fields
-
vault_address: address
-
-
authority: address
-
## Struct `ModifyCap`
A capability to modify the content of the vault.
struct ModifyCap<Content: drop, store> has copy, drop
Fields
-
vault_address: address
-
-
authority: address
-
## Struct `DelegateCap`
A capability to delegate access to the vault.
struct DelegateCap<Content: drop, store> has copy, drop
Fields
-
vault_address: address
-
-
authority: address
-
## Struct `TransferCap`
A capability to transfer ownership of the vault.
struct TransferCap<Content: drop, store> has copy, drop
Fields
-
vault_address: address
-
-
authority: address
-
## Struct `CapType`
A type describing a capability. This is used for functions like Self::delegate
where we need to
specify capability types.
struct CapType has copy, drop, store
Fields
-
code: u8
-
## Struct `VaultDelegateEvent`
An event which we generate on vault access delegation or revocation if event generation is enabled.
struct VaultDelegateEvent has drop, store
Fields
-
metadata: vector<u8>
-
-
vault_address: address
-
-
authority: address
-
-
delegate: address
-
-
cap: vault::CapType
-
-
is_revoked: bool
-
## Struct `VaultTransferEvent`
An event which we generate on vault transfer if event generation is enabled.
struct VaultTransferEvent has drop, store
Fields
-
metadata: vector<u8>
-
-
vault_address: address
-
-
authority: address
-
-
new_vault_address: address
-
## Resource `Vault`
Private. The vault representation.
struct Vault<Content: store> has key
Fields
-
content: option::Option<Content>
-
The content. If the option is empty, the content is currently moved into an
accessor in order to work with it.
## Resource `VaultDelegates`
Private. If the vault supports delegation, information about the delegates.
struct VaultDelegates<Content: store> has key
Fields
-
delegates: vector<address>
-
The currently authorized delegates.
## Resource `VaultEvents`
Private. If event generation is enabled, contains the event generators.
struct VaultEvents<Content: store> has key
Fields
-
metadata: vector<u8>
-
Metadata which identifies this vault. This information is used
in events generated by this module.
-
delegate_events: event::EventHandle<vault::VaultDelegateEvent>
-
Event handle for vault delegation.
-
transfer_events: event::EventHandle<vault::VaultTransferEvent>
-
Event handle for vault transfer.
## Resource `VaultDelegate`
Private. A value stored at a delegates address pointing to the owner of the vault. Also
describes the capabilities granted to this delegate.
struct VaultDelegate<Content: store> has key
Fields
-
vault_address: address
-
-
granted_caps: vector<vault::CapType>
-
## Struct `ReadAccessor`
A read accessor for the content of the vault.
struct ReadAccessor<Content: drop, store>
Fields
-
content: Content
-
-
vault_address: address
-
## Struct `ModifyAccessor`
A modify accessor for the content of the vault.
struct ModifyAccessor<Content: drop, store>
Fields
-
content: Content
-
-
vault_address: address
-
## Constants
const EDELEGATE: u64 = 1;
const EACCESSOR_INCONSISTENCY: u64 = 3;
const EACCESSOR_IN_USE: u64 = 2;
const EDELEGATE_TO_SELF: u64 = 4;
const EDELEGATION_NOT_ENABLED: u64 = 5;
const EEVENT: u64 = 6;
const EVAULT: u64 = 0;
## Function `read_cap_type`
Creates a read capability type.
public fun read_cap_type(): vault::CapType
Implementation
public fun read_cap_type(): CapType { CapType{ code : 0 } }
## Function `modify_cap_type`
Creates a modify capability type.
public fun modify_cap_type(): vault::CapType
Implementation
public fun modify_cap_type(): CapType { CapType{ code : 1 } }
## Function `delegate_cap_type`
Creates a delegate capability type.
public fun delegate_cap_type(): vault::CapType
Implementation
public fun delegate_cap_type(): CapType { CapType{ code : 2 } }
## Function `transfer_cap_type`
Creates a transfer capability type.
public fun transfer_cap_type(): vault::CapType
Implementation
public fun transfer_cap_type(): CapType { CapType{ code : 3 } }
## Function `new`
Creates new vault for the given signer. The vault is populated with the initial_content
.
public fun new<Content: store>(owner: &signer, initial_content: Content)
Implementation
public fun new<Content: store>(owner: &signer, initial_content: Content) {
let addr = signer::address_of(owner);
assert!(!exists<Vault<Content>>(addr), error::already_exists(EVAULT));
move_to<Vault<Content>>(
owner,
Vault{
content: option::some(initial_content)
}
)
}
## Function `is_delegation_enabled`
Returns true
if the delegation functionality has been enabled.
Returns false
otherwise.
public fun is_delegation_enabled<Content: store>(owner: &signer): bool
Implementation
public fun is_delegation_enabled<Content: store>(owner: &signer): bool {
let addr = signer::address_of(owner);
assert!(exists<Vault<Content>>(addr), error::not_found(EVAULT));
exists<VaultDelegates<Content>>(addr)
}
## Function `enable_delegation`
Enables delegation functionality for this vault. By default, vaults to not support delegation.
public fun enable_delegation<Content: store>(owner: &signer)
Implementation
public fun enable_delegation<Content: store>(owner: &signer) {
assert!(!is_delegation_enabled<Content>(owner), error::already_exists(EDELEGATE));
move_to<VaultDelegates<Content>>(owner, VaultDelegates{delegates: vector::empty()})
}
## Function `enable_events`
Enables event generation for this vault. This passed metadata is used to identify
the vault in events.
public fun enable_events<Content: store>(owner: &signer, metadata: vector<u8>)
Implementation
public fun enable_events<Content: store>(owner: &signer, metadata: vector<u8>) {
let addr = signer::address_of(owner);
assert!(exists<Vault<Content>>(addr), error::not_found(EVAULT));
assert!(!exists<VaultEvents<Content>>(addr), error::already_exists(EEVENT));
move_to<VaultEvents<Content>>(
owner,
VaultEvents{
metadata,
delegate_events: event::new_event_handle<VaultDelegateEvent>(owner),
transfer_events: event::new_event_handle<VaultTransferEvent>(owner),
}
);
}
## Function `remove_vault`
Removes a vault and all its associated data, returning the current content. In order for
this to succeed, there must be no active accessor for the vault.
public fun remove_vault<Content: drop, store>(owner: &signer): Content
Implementation
public fun remove_vault<Content: store + drop>(owner: &signer): Content
acquires Vault, VaultDelegates, VaultDelegate, VaultEvents {
let addr = signer::address_of(owner);
assert!(exists<Vault<Content>>(addr), error::not_found(EVAULT));
let Vault{content} = move_from<Vault<Content>>(addr);
assert!(option::is_some(&content), error::invalid_state(EACCESSOR_IN_USE));
if (exists<VaultDelegates<Content>>(addr)) {
let delegate_cap = DelegateCap<Content>{vault_address: addr, authority: addr};
revoke_all(&delegate_cap);
};
if (exists<VaultEvents<Content>>(addr)) {
let VaultEvents{metadata: _metadata, delegate_events, transfer_events} =
move_from<VaultEvents<Content>>(addr);
event::destroy_handle(delegate_events);
event::destroy_handle(transfer_events);
};
option::extract(&mut content)
}
## Function `acquire_read_cap`
Acquires the capability to read the vault. The passed signer must either be the owner
of the vault or a delegate with appropriate access.
public fun acquire_read_cap<Content: drop, store>(requester: &signer): vault::ReadCap<Content>
Implementation
public fun acquire_read_cap<Content: store + drop>(requester: &signer): ReadCap<Content>
acquires VaultDelegate {
let (vault_address, authority) = validate_cap<Content>(requester, read_cap_type());
ReadCap{ vault_address, authority }
}
## Function `acquire_modify_cap`
Acquires the capability to modify the vault. The passed signer must either be the owner
of the vault or a delegate with appropriate access.
public fun acquire_modify_cap<Content: drop, store>(requester: &signer): vault::ModifyCap<Content>
Implementation
public fun acquire_modify_cap<Content: store + drop>(requester: &signer): ModifyCap<Content>
acquires VaultDelegate {
let (vault_address, authority) = validate_cap<Content>(requester, modify_cap_type());
ModifyCap{ vault_address, authority }
}
## Function `acquire_delegate_cap`
Acquires the capability to delegate access to the vault. The passed signer must either be the owner
of the vault or a delegate with appropriate access.
public fun acquire_delegate_cap<Content: drop, store>(requester: &signer): vault::DelegateCap<Content>
Implementation
public fun acquire_delegate_cap<Content: store + drop>(requester: &signer): DelegateCap<Content>
acquires VaultDelegate {
let (vault_address, authority) = validate_cap<Content>(requester, delegate_cap_type());
DelegateCap{ vault_address, authority }
}
## Function `acquire_transfer_cap`
Acquires the capability to transfer the vault. The passed signer must either be the owner
of the vault or a delegate with appropriate access.
public fun acquire_transfer_cap<Content: drop, store>(requester: &signer): vault::TransferCap<Content>
Implementation
public fun acquire_transfer_cap<Content: store + drop>(requester: &signer): TransferCap<Content>
acquires VaultDelegate {
let (vault_address, authority) = validate_cap<Content>(requester, transfer_cap_type());
TransferCap{ vault_address, authority }
}
## Function `validate_cap`
Private. Validates whether a capability can be acquired by the given signer. Returns the
pair of the vault address and the used authority.
fun validate_cap<Content: drop, store>(requester: &signer, cap: vault::CapType): (address, address)
Implementation
fun validate_cap<Content: store + drop>(requester: &signer, cap: CapType): (address, address)
acquires VaultDelegate {
let addr = signer::address_of(requester);
if (exists<VaultDelegate<Content>>(addr)) {
// The signer is a delegate. Check it's granted capabilities.
let delegate = borrow_global<VaultDelegate<Content>>(addr);
assert!(vector::contains(&delegate.granted_caps, &cap), error::permission_denied(EDELEGATE));
(delegate.vault_address, addr)
} else {
// If it is not a delegate, it must be the owner to succeed.
assert!(exists<Vault<Content>>(addr), error::not_found(EVAULT));
(addr, addr)
}
}
## Function `read_accessor`
Creates a read accessor for the content in the vault based on a read capability.
Only one accessor (whether read or modify) for the same vault can exist at a time, and this
function will abort if one is in use. An accessor must be explicitly released using
Self::release_read_accessor
.
public fun read_accessor<Content: drop, store>(cap: &vault::ReadCap<Content>): vault::ReadAccessor<Content>
Implementation
public fun read_accessor<Content: store + drop>(cap: &ReadCap<Content>): ReadAccessor<Content>
acquires Vault {
let content = &mut borrow_global_mut<Vault<Content>>(cap.vault_address).content;
assert!(option::is_some(content), error::invalid_state(EACCESSOR_IN_USE));
ReadAccessor{ vault_address: cap.vault_address, content: option::extract(content) }
}
## Function `borrow`
Returns a reference to the content represented by a read accessor.
public fun borrow<Content: drop, store>(accessor: &vault::ReadAccessor<Content>): &Content
Implementation
public fun borrow<Content: store + drop>(accessor: &ReadAccessor<Content>): &Content {
&accessor.content
}
## Function `release_read_accessor`
Releases read accessor.
public fun release_read_accessor<Content: drop, store>(accessor: vault::ReadAccessor<Content>)
Implementation
public fun release_read_accessor<Content: store + drop>(accessor: ReadAccessor<Content>)
acquires Vault {
let ReadAccessor{ content: new_content, vault_address } = accessor;
let content = &mut borrow_global_mut<Vault<Content>>(vault_address).content;
// We (should be/are) able to prove that the below cannot happen, but we leave the assertion
// here anyway for double safety.
assert!(option::is_none(content), error::internal(EACCESSOR_INCONSISTENCY));
option::fill(content, new_content);
}
## Function `modify_accessor`
Creates a modify accessor for the content in the vault based on a modify capability. This
is similar like Self::read_accessor
but the returned accessor will allow to mutate
the content.
public fun modify_accessor<Content: drop, store>(cap: &vault::ModifyCap<Content>): vault::ModifyAccessor<Content>
Implementation
public fun modify_accessor<Content: store + drop>(cap: &ModifyCap<Content>): ModifyAccessor<Content>
acquires Vault {
let content = &mut borrow_global_mut<Vault<Content>>(cap.vault_address).content;
assert!(option::is_some(content), error::invalid_state(EACCESSOR_IN_USE));
ModifyAccessor{ vault_address: cap.vault_address, content: option::extract(content) }
}
## Function `borrow_mut`
Returns a mutable reference to the content represented by a modify accessor.
public fun borrow_mut<Content: drop, store>(accessor: &mut vault::ModifyAccessor<Content>): &mut Content
Implementation
public fun borrow_mut<Content: store + drop>(accessor: &mut ModifyAccessor<Content>): &mut Content {
&mut accessor.content
}
## Function `release_modify_accessor`
Releases a modify accessor. This will ensure that any modifications are written back
to the vault.
public fun release_modify_accessor<Content: drop, store>(accessor: vault::ModifyAccessor<Content>)
Implementation
public fun release_modify_accessor<Content: store + drop>(accessor: ModifyAccessor<Content>)
acquires Vault {
let ModifyAccessor{ content: new_content, vault_address } = accessor;
let content = &mut borrow_global_mut<Vault<Content>>(vault_address).content;
// We (should be/are) able to prove that the below cannot happen, but we leave the assertion
// here anyway for double safety.
assert!(option::is_none(content), error::internal(EACCESSOR_INCONSISTENCY));
option::fill(content, new_content);
}
## Function `delegate`
Delegates the right to acquire a capability of the given type. Delegation must have been enabled
during vault creation for this to succeed.
public fun delegate<Content: drop, store>(cap: &vault::DelegateCap<Content>, to_signer: &signer, cap_type: vault::CapType)
Implementation
public fun delegate<Content: store + drop>(cap: &DelegateCap<Content>, to_signer: &signer, cap_type: CapType)
acquires VaultDelegates, VaultDelegate, VaultEvents {
assert!(
exists<VaultDelegates<Content>>(cap.vault_address),
error::invalid_state(EDELEGATION_NOT_ENABLED)
);
let addr = signer::address_of(to_signer);
assert!(addr != cap.vault_address, error::invalid_argument(EDELEGATE_TO_SELF));
if (!exists<VaultDelegate<Content>>(addr)) {
// Create VaultDelegate if it is not yet existing.
move_to<VaultDelegate<Content>>(
to_signer,
VaultDelegate{vault_address: cap.vault_address, granted_caps: vector::empty()}
);
// Add the the delegate to VaultDelegates.
let vault_delegates = borrow_global_mut<VaultDelegates<Content>>(cap.vault_address);
add_element(&mut vault_delegates.delegates, addr);
};
// Grant the capability.
let delegate = borrow_global_mut<VaultDelegate<Content>>(addr);
add_element(&mut delegate.granted_caps, *&cap_type);
// Generate event
emit_delegate_event(cap, cap_type, addr, false);
}
## Function `revoke`
Revokes the delegated right to acquire a capability of given type.
public fun revoke<Content: drop, store>(cap: &vault::DelegateCap<Content>, addr: address, cap_type: vault::CapType)
Implementation
public fun revoke<Content: store + drop>(cap: &DelegateCap<Content>, addr: address, cap_type: CapType)
acquires VaultDelegates, VaultDelegate, VaultEvents {
assert!(
exists<VaultDelegates<Content>>(cap.vault_address),
error::invalid_state(EDELEGATION_NOT_ENABLED)
);
assert!(exists<VaultDelegate<Content>>(addr), error::not_found(EDELEGATE));
let delegate = borrow_global_mut<VaultDelegate<Content>>(addr);
remove_element(&mut delegate.granted_caps, &cap_type);
// If the granted caps of this delegate drop to zero, remove it.
if (vector::is_empty(&delegate.granted_caps)) {
let VaultDelegate{ vault_address: _owner, granted_caps: _granted_caps} =
move_from<VaultDelegate<Content>>(addr);
let vault_delegates = borrow_global_mut<VaultDelegates<Content>>(cap.vault_address);
remove_element(&mut vault_delegates.delegates, &addr);
};
// Generate event.
emit_delegate_event(cap, cap_type, addr, true);
}
## Function `revoke_all`
Revokes all delegate rights for this vault.
public fun revoke_all<Content: drop, store>(cap: &vault::DelegateCap<Content>)
Implementation
public fun revoke_all<Content: store + drop>(cap: &DelegateCap<Content>)
acquires VaultDelegates, VaultDelegate, VaultEvents {
assert!(
exists<VaultDelegates<Content>>(cap.vault_address),
error::invalid_state(EDELEGATION_NOT_ENABLED)
);
let delegates = &mut borrow_global_mut<VaultDelegates<Content>>(cap.vault_address).delegates;
while (!vector::is_empty(delegates)) {
let addr = vector::pop_back(delegates);
let VaultDelegate{ vault_address: _vault_address, granted_caps} =
move_from<VaultDelegate<Content>>(cap.vault_address);
while (!vector::is_empty(&granted_caps)) {
let cap_type = vector::pop_back(&mut granted_caps);
emit_delegate_event(cap, cap_type, addr, true);
}
}
}
## Function `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) = vector::index_of(v, x);
if (found) {
vector::remove(v, index);
}
}
## Function `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 (!vector::contains(v, &x)) {
vector::push_back(v, x)
}
}
## Function `emit_delegate_event`
Emits a delegation or revocation event if event generation is enabled.
fun emit_delegate_event<Content: drop, store>(cap: &vault::DelegateCap<Content>, cap_type: vault::CapType, delegate: address, is_revoked: bool)
Implementation
fun emit_delegate_event<Content: store + drop>(
cap: &DelegateCap<Content>,
cap_type: CapType,
delegate: address,
is_revoked: bool
) acquires VaultEvents {
if (exists<VaultEvents<Content>>(cap.vault_address)) {
let event = VaultDelegateEvent{
metadata: *&borrow_global<VaultEvents<Content>>(cap.vault_address).metadata,
vault_address: cap.vault_address,
authority: cap.authority,
delegate,
cap: cap_type,
is_revoked
};
event::emit_event(&mut borrow_global_mut<VaultEvents<Content>>(cap.vault_address).delegate_events, event);
}
}
## Function `transfer`
Transfers ownership of the vault to a new signer. All delegations are revoked before transfer,
and the new owner must re-create delegates as needed.
public fun transfer<Content: drop, store>(cap: &vault::TransferCap<Content>, to_owner: &signer)
Implementation
public fun transfer<Content: store + drop>(cap: &TransferCap<Content>, to_owner: &signer)
acquires Vault, VaultEvents, VaultDelegate, VaultDelegates {
let new_addr = signer::address_of(to_owner);
assert!(!exists<Vault<Content>>(new_addr), error::already_exists(EVAULT));
assert!(
option::is_some(&borrow_global<Vault<Content>>(cap.vault_address).content),
error::invalid_state(EACCESSOR_IN_USE)
);
// Revoke all delegates.
if (exists<VaultDelegates<Content>>(cap.vault_address)) {
let delegate_cap = DelegateCap<Content>{vault_address: cap.vault_address, authority: cap.authority };
revoke_all(&delegate_cap);
};
// Emit event if event generation is enabled. We emit the event on the old vault not the new one.
if (exists<VaultEvents<Content>>(cap.vault_address)) {
let event = VaultTransferEvent {
metadata: *&borrow_global<VaultEvents<Content>>(cap.vault_address).metadata,
vault_address: cap.vault_address,
authority: cap.authority,
new_vault_address: new_addr
};
event::emit_event(&mut borrow_global_mut<VaultEvents<Content>>(cap.vault_address).transfer_events, event);
};
// Move the vault.
move_to<Vault<Content>>(to_owner, move_from<Vault<Content>>(cap.vault_address));
}