Skip to content

resource_account - [mainnet]

A resource account is used to manage resources independent of an account managed by a user. This contains several utilities to make using resource accounts more effective.

Resource Accounts to manage liquidity pools

A dev wishing to use resource accounts for a liquidity pool, would likely do the following:

  1. Create a new account using resource_account::create_resource_account. This creates the account, stores the signer_cap within a resource_account::Container, and rotates the key to the current account’s authentication key or a provided authentication key.
  2. Define the liquidity pool module’s address to be the same as the resource account.
  3. Construct a package-publishing transaction for the resource account using the authentication key used in step 1.
  4. In the liquidity pool module’s init_module function, call retrieve_resource_account_cap which will retrieve the signer_cap and rotate the resource account’s authentication key to 0x0, effectively locking it off.
  5. When adding a new coin, the liquidity pool will load the capability and hence the signer to register and store new LiquidityCoin resources.

Code snippets to help:

fun init_module(resource_account: &signer) {
let dev_address = @DEV_ADDR;
let signer_cap = retrieve_resource_account_cap(resource_account, dev_address);
let lp = LiquidityPoolInfo { signer_cap: signer_cap, ... };
move_to(resource_account, lp);
}

Later on during a coin registration:

public fun add_coin<X, Y>(lp: &LP, x: Coin<x>, y: Coin<y>) {
if(!exists<LiquidityCoin<X, Y>(LP::Address(lp), LiquidityCoin<X, Y>)) {
let mint, burn = Coin::initialize<LiquidityCoin<X, Y>>(...);
move_to(&create_signer_with_capability(&lp.cap), LiquidityCoin<X, Y>{ mint, burn });
}
...
}

Resource accounts to manage an account for module publishing (i.e., contract account)

A dev wishes to have an account dedicated to managing a contract. The contract itself does not require signer post initialization. The dev could do the following:

  1. Create a new account using resource_account::create_resource_account_and_publish_package. This creates the account and publishes the package for that account.
  2. At a later point in time, the account creator can move the signer capability to the module.
struct MyModuleResource has key {
...
resource_signer_cap: Option<SignerCapability>,
}
public fun provide_signer_capability(resource_signer_cap: SignerCapability) {
let account_addr = account::get_signer_capability_address(resource_signer_cap);
let resource_addr = type_info::account_address(&type_info::type_of<MyModuleResource>());
assert!(account_addr == resource_addr, EADDRESS_MISMATCH);
let module = borrow_global_mut<MyModuleResource>(account_addr);
module.resource_signer_cap = option::some(resource_signer_cap);
}
- [Resource Accounts to manage liquidity pools](#@Resource_Accounts_to_manage_liquidity_pools_0)
- [Resource accounts to manage an account for module publishing (i.e., contract account)](#@Resource_accounts_to_manage_an_account_for_module_publishing_(i.e.,_contract_account)_1)
use 0x1::account;
use 0x1::aptos_coin;
use 0x1::code;
use 0x1::coin;
use 0x1::error;
use 0x1::signer;
use 0x1::simple_map;
use 0x1::vector;

Constants

const ZERO_AUTH_KEY: vector<u8> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

Container resource not found in account

const ECONTAINER_NOT_PUBLISHED: u64 = 1;

The resource account was not created by the specified source account

const EUNAUTHORIZED_NOT_OWNER: u64 = 2;

Resources

Container

struct Container has key
Fields
store: simple_map::SimpleMap<address, account::SignerCapability>

Functions

create_resource_account

Creates a new resource account and rotates the authentication key to either the optional auth key if it is non-empty (though auth keys are 32-bytes) or the source accounts current auth key.

public entry fun create_resource_account(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>)
Implementation
public entry fun create_resource_account(
origin: &signer,
seed: vector<u8>,
optional_auth_key: vector<u8>,
) acquires Container {
let (resource, resource_signer_cap) = account::create_resource_account(origin, seed);
rotate_account_authentication_key_and_store_capability(
origin,
resource,
resource_signer_cap,
optional_auth_key,
);
}

create_resource_account_and_fund

Creates a new resource account, transfer the amount of coins from the origin to the resource account, and rotates the authentication key to either the optional auth key if it is non-empty (though auth keys are 32-bytes) or the source accounts current auth key. Note, this function adds additional resource ownership to the resource account and should only be used for resource accounts that need access to Coin<AptosCoin>.

public entry fun create_resource_account_and_fund(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>, fund_amount: u64)
Implementation
public entry fun create_resource_account_and_fund(
origin: &signer,
seed: vector<u8>,
optional_auth_key: vector<u8>,
fund_amount: u64,
) acquires Container {
let (resource, resource_signer_cap) = account::create_resource_account(origin, seed);
coin::register<AptosCoin>(&resource);
coin::transfer<AptosCoin>(origin, signer::address_of(&resource), fund_amount);
rotate_account_authentication_key_and_store_capability(
origin,
resource,
resource_signer_cap,
optional_auth_key,
);
}

create_resource_account_and_publish_package

Creates a new resource account, publishes the package under this account transaction under this account and leaves the signer cap readily available for pickup.

public entry fun create_resource_account_and_publish_package(origin: &signer, seed: vector<u8>, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
Implementation
public entry fun create_resource_account_and_publish_package(
origin: &signer,
seed: vector<u8>,
metadata_serialized: vector<u8>,
code: vector<vector<u8>>,
) acquires Container {
let (resource, resource_signer_cap) = account::create_resource_account(origin, seed);
aptos_framework::code::publish_package_txn(&resource, metadata_serialized, code);
rotate_account_authentication_key_and_store_capability(
origin,
resource,
resource_signer_cap,
ZERO_AUTH_KEY,
);
}

rotate_account_authentication_key_and_store_capability

fun rotate_account_authentication_key_and_store_capability(origin: &signer, resource: signer, resource_signer_cap: account::SignerCapability, optional_auth_key: vector<u8>)
Implementation
fun rotate_account_authentication_key_and_store_capability(
origin: &signer,
resource: signer,
resource_signer_cap: account::SignerCapability,
optional_auth_key: vector<u8>,
) acquires Container {
let origin_addr = signer::address_of(origin);
if (!exists<Container>(origin_addr)) {
move_to(origin, Container { store: simple_map::create() })
};
let container = borrow_global_mut<Container>(origin_addr);
let resource_addr = signer::address_of(&resource);
simple_map::add(&mut container.store, resource_addr, resource_signer_cap);
let auth_key = if (vector::is_empty(&optional_auth_key)) {
account::get_authentication_key(origin_addr)
} else {
optional_auth_key
};
account::rotate_authentication_key_internal(&resource, auth_key);
}

retrieve_resource_account_cap

When called by the resource account, it will retrieve the capability associated with that account and rotate the account’s auth key to 0x0 making the account inaccessible without the SignerCapability.

public fun retrieve_resource_account_cap(resource: &signer, source_addr: address): account::SignerCapability
Implementation
public fun retrieve_resource_account_cap(
resource: &signer,
source_addr: address,
): account::SignerCapability acquires Container {
assert!(exists<Container>(source_addr), error::not_found(ECONTAINER_NOT_PUBLISHED));
let resource_addr = signer::address_of(resource);
let (resource_signer_cap, empty_container) = {
let container = borrow_global_mut<Container>(source_addr);
assert!(
simple_map::contains_key(&container.store, &resource_addr),
error::invalid_argument(EUNAUTHORIZED_NOT_OWNER)
);
let (_resource_addr, signer_cap) = simple_map::remove(&mut container.store, &resource_addr);
(signer_cap, simple_map::length(&container.store) == 0)
};
if (empty_container) {
let container = move_from<Container>(source_addr);
let Container { store } = container;
simple_map::destroy_empty(store);
};
account::rotate_authentication_key_internal(resource, ZERO_AUTH_KEY);
resource_signer_cap
}

Specification

High-level Requirements

No.RequirementCriticalityImplementationEnforcement
1 The length of the authentication key must be 32 bytes. Medium The rotate_authentication_key_internal function ensures that the authentication key passed to it is of 32 bytes. Formally verified via RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf.
2 The Container structure must exist in the origin account in order to rotate the authentication key of a resource account and to store its signer capability. High The rotate_account_authentication_key_and_store_capability function makes sure the Container structure exists under the origin account. Formally verified via rotate_account_authentication_key_and_store_capability.
3 The resource account is registered for the Aptos coin. High The create_resource_account_and_fund ensures the newly created resource account is registered to receive the AptosCoin. Formally verified via create_resource_account_and_fund.
4 It is not possible to store two capabilities for the same resource address. Medium The rotate_account_authentication_key_and_store_capability will abort if the resource signer capability for the given resource address already exists in container.store. Formally verified via rotate_account_authentication_key_and_store_capability.
5 If provided, the optional authentication key is used for key rotation. Low The rotate_account_authentication_key_and_store_capability function will use optional_auth_key if it is provided as a parameter. Formally verified via rotate_account_authentication_key_and_store_capability.
6 The container stores the resource accounts' signer capabilities. Low retrieve_resource_account_cap will abort if there is no Container structure assigned to source_addr. Formally verified via retreive_resource_account_cap.
7 Resource account may retrieve the signer capability if it was previously added to its container. High retrieve_resource_account_cap will abort if the container of source_addr doesn't store the signer capability for the given resource. Formally verified via retrieve_resource_account_cap.
8 Retrieving the last signer capability from the container must result in the container being removed. Low retrieve_resource_account_cap will remove the container if the retrieved signer_capability was the last one stored under it. Formally verified via retrieve_resource_account_cap.

Module-level Specification

pragma verify = false;
pragma aborts_if_is_partial;

create_resource_account

public entry fun create_resource_account(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>)
let source_addr = signer::address_of(origin);
let resource_addr = account::spec_create_resource_address(source_addr, seed);
let resource = create_signer::spec_create_signer(resource_addr);
include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;

create_resource_account_and_fund

public entry fun create_resource_account_and_fund(origin: &signer, seed: vector<u8>, optional_auth_key: vector<u8>, fund_amount: u64)
pragma verify = false;
let source_addr = signer::address_of(origin);
let resource_addr = account::spec_create_resource_address(source_addr, seed);
let coin_store_resource = global<coin::CoinStore<AptosCoin>>(resource_addr);
include aptos_account::WithdrawAbortsIf<AptosCoin>{from: origin, amount: fund_amount};
include aptos_account::GuidAbortsIf<AptosCoin>{to: resource_addr};
include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;
// This enforces high-level requirement 3:
ensures exists<aptos_framework::coin::CoinStore<AptosCoin>>(resource_addr);

create_resource_account_and_publish_package

public entry fun create_resource_account_and_publish_package(origin: &signer, seed: vector<u8>, metadata_serialized: vector<u8>, code: vector<vector<u8>>)
pragma verify = false;
let source_addr = signer::address_of(origin);
let resource_addr = account::spec_create_resource_address(source_addr, seed);
let optional_auth_key = ZERO_AUTH_KEY;
include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit;

rotate_account_authentication_key_and_store_capability

fun rotate_account_authentication_key_and_store_capability(origin: &signer, resource: signer, resource_signer_cap: account::SignerCapability, optional_auth_key: vector<u8>)
pragma aborts_if_is_partial;
let resource_addr = signer::address_of(resource);
// This enforces high-level requirement 1:
include RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf;
// This enforces high-level requirement 2:
ensures exists<Container>(signer::address_of(origin));
// This enforces high-level requirement 5:
ensures vector::length(optional_auth_key) != 0 ==>
global<aptos_framework::account::Account>(resource_addr).authentication_key == optional_auth_key;
schema RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIf {
origin: signer;
resource_addr: address;
optional_auth_key: vector<u8>;
let source_addr = signer::address_of(origin);
let container = global<Container>(source_addr);
let get = len(optional_auth_key) == 0;
aborts_if get && !exists<Account>(source_addr);
// This enforces high-level requirement 4:
aborts_if exists<Container>(source_addr) && simple_map::spec_contains_key(container.store, resource_addr);
aborts_if get && !(exists<Account>(resource_addr) && len(global<Account>(source_addr).authentication_key) == 32);
aborts_if !get && !(exists<Account>(resource_addr) && len(optional_auth_key) == 32);
ensures simple_map::spec_contains_key(global<Container>(source_addr).store, resource_addr);
ensures exists<Container>(source_addr);
}
schema RotateAccountAuthenticationKeyAndStoreCapabilityAbortsIfWithoutAccountLimit {
source_addr: address;
optional_auth_key: vector<u8>;
resource_addr: address;
let container = global<Container>(source_addr);
let get = len(optional_auth_key) == 0;
let account = global<account::Account>(source_addr);
aborts_if len(ZERO_AUTH_KEY) != 32;
include account::spec_exists_at(resource_addr) ==> account::CreateResourceAccountAbortsIf;
include !account::spec_exists_at(resource_addr) ==> account::CreateAccountAbortsIf {addr: resource_addr};
aborts_if get && !exists<account::Account>(source_addr);
aborts_if exists<Container>(source_addr) && simple_map::spec_contains_key(container.store, resource_addr);
aborts_if get && len(global<account::Account>(source_addr).authentication_key) != 32;
aborts_if !get && len(optional_auth_key) != 32;
ensures simple_map::spec_contains_key(global<Container>(source_addr).store, resource_addr);
ensures exists<Container>(source_addr);
}

retrieve_resource_account_cap

public fun retrieve_resource_account_cap(resource: &signer, source_addr: address): account::SignerCapability
pragma aborts_if_is_partial;
// This enforces high-level requirement 6:
aborts_if !exists<Container>(source_addr);
let resource_addr = signer::address_of(resource);
let container = global<Container>(source_addr);
// This enforces high-level requirement 7:
aborts_if !simple_map::spec_contains_key(container.store, resource_addr);
aborts_if !exists<account::Account>(resource_addr);
// This enforces high-level requirement 8:
ensures simple_map::spec_contains_key(old(global<Container>(source_addr)).store, resource_addr) &&
simple_map::spec_len(old(global<Container>(source_addr)).store) == 1 ==> !exists<Container>(source_addr);
ensures exists<Container>(source_addr) ==> !simple_map::spec_contains_key(global<Container>(source_addr).store, resource_addr);