Skip to content

coin - [mainnet]

This module provides the foundation for typesafe Coins.

use 0x1::account;
use 0x1::aggregator;
use 0x1::create_signer;
use 0x1::error;
use 0x1::event;
use 0x1::features;
use 0x1::fungible_asset;
use 0x1::guid;
use 0x1::object;
use 0x1::option;
use 0x1::optional_aggregator;
use 0x1::permissioned_signer;
use 0x1::primary_fungible_store;
use 0x1::signer;
use 0x1::string;
use 0x1::system_addresses;
use 0x1::table;
use 0x1::type_info;
use 0x1::vector;

Constants

Maximum possible aggregatable coin value.

const MAX_U64: u128 = 18446744073709551615;

Not enough coins to complete transaction

const EINSUFFICIENT_BALANCE: u64 = 6;

The value of aggregatable coin used for transaction fees redistribution does not fit in u64.

const EAGGREGATABLE_COIN_VALUE_TOO_LARGE: u64 = 14;

APT pairing is not eanbled yet.

const EAPT_PAIRING_IS_NOT_ENABLED: u64 = 28;

The BurnRef does not exist.

const EBURN_REF_NOT_FOUND: u64 = 25;

The BurnRefReceipt does not match the BurnRef to be returned.

const EBURN_REF_RECEIPT_MISMATCH: u64 = 24;

The coin converison map is not created yet.

const ECOIN_CONVERSION_MAP_NOT_FOUND: u64 = 27;

Address of account which is used to initialize a coin CoinType doesn’t match the deployer of module

const ECOIN_INFO_ADDRESS_MISMATCH: u64 = 1;

CoinType is already initialized as a coin

const ECOIN_INFO_ALREADY_PUBLISHED: u64 = 2;

CoinType hasn’t been initialized as a coin

const ECOIN_INFO_NOT_PUBLISHED: u64 = 3;

Name of the coin is too long

const ECOIN_NAME_TOO_LONG: u64 = 12;

Deprecated. Account already has CoinStore registered for CoinType

const ECOIN_STORE_ALREADY_PUBLISHED: u64 = 4;

Account hasn’t registered CoinStore for CoinType

const ECOIN_STORE_NOT_PUBLISHED: u64 = 5;

Cannot upgrade the total supply of coins to different implementation.

const ECOIN_SUPPLY_UPGRADE_NOT_SUPPORTED: u64 = 11;

Symbol of the coin is too long

const ECOIN_SYMBOL_TOO_LONG: u64 = 13;

The feature of migration from coin to fungible asset is not enabled.

const ECOIN_TO_FUNGIBLE_ASSET_FEATURE_NOT_ENABLED: u64 = 18;

The coin type from the map does not match the calling function type argument.

const ECOIN_TYPE_MISMATCH: u64 = 17;

Cannot destroy non-zero coins

const EDESTRUCTION_OF_NONZERO_TOKEN: u64 = 7;

CoinStore is frozen. Coins cannot be deposited or withdrawn

const EFROZEN: u64 = 10;

The migration process from coin to fungible asset is not enabled yet.

const EMIGRATION_FRAMEWORK_NOT_ENABLED: u64 = 26;

The MintRef does not exist.

const EMINT_REF_NOT_FOUND: u64 = 21;

The MintRefReceipt does not match the MintRef to be returned.

const EMINT_REF_RECEIPT_MISMATCH: u64 = 20;

Error regarding paired coin type of the fungible asset metadata.

const EPAIRED_COIN: u64 = 15;

Error regarding paired fungible asset metadata of a coin type.

const EPAIRED_FUNGIBLE_ASSET: u64 = 16;

PairedFungibleAssetRefs resource does not exist.

const EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND: u64 = 19;

The TransferRef does not exist.

const ETRANSFER_REF_NOT_FOUND: u64 = 23;

The TransferRefReceipt does not match the TransferRef to be returned.

const ETRANSFER_REF_RECEIPT_MISMATCH: u64 = 22;
const MAX_COIN_NAME_LENGTH: u64 = 32;
const MAX_COIN_SYMBOL_LENGTH: u64 = 32;

Structs

Coin

Core data structures Main structure representing a coin/token in an account’s custody.

struct Coin<CoinType> has store
Fields
value: u64
Amount of coin this address has.

AggregatableCoin

DEPRECATED

#[deprecated]
struct AggregatableCoin<CoinType> has store
Fields
value: aggregator::Aggregator
Amount of aggregatable coin this address has.

CoinDeposit

Module event emitted when some amount of a coin is deposited into an account.

#[event]
struct CoinDeposit has drop, store
Fields
coin_type: string::String
account: address
amount: u64

CoinWithdraw

Module event emitted when some amount of a coin is withdrawn from an account.

#[event]
struct CoinWithdraw has drop, store
Fields
coin_type: string::String
account: address
amount: u64

Deposit

#[event]
#[deprecated]
struct Deposit<CoinType> has drop, store
Fields
account: address
amount: u64

Withdraw

#[event]
#[deprecated]
struct Withdraw<CoinType> has drop, store
Fields
account: address
amount: u64

DepositEvent

Event emitted when some amount of a coin is deposited into an account.

struct DepositEvent has drop, store
Fields
amount: u64

WithdrawEvent

Event emitted when some amount of a coin is withdrawn from an account.

struct WithdrawEvent has drop, store
Fields
amount: u64

CoinEventHandleDeletion

Module event emitted when the event handles related to coin store is deleted.

Deprecated: replaced with CoinStoreDeletion

#[event]
#[deprecated]
struct CoinEventHandleDeletion has drop, store
Fields
event_handle_creation_address: address
deleted_deposit_event_handle_creation_number: u64
deleted_withdraw_event_handle_creation_number: u64

CoinStoreDeletion

Module event emitted when the event handles related to coin store is deleted.

#[event]
struct CoinStoreDeletion has drop, store
Fields
coin_type: string::String
event_handle_creation_address: address
deleted_deposit_event_handle_creation_number: u64
deleted_withdraw_event_handle_creation_number: u64

PairCreation

Module event emitted when a new pair of coin and fungible asset is created.

#[event]
struct PairCreation has drop, store
Fields
coin_type: type_info::TypeInfo
fungible_asset_metadata_address: address

MintCapability

Capability required to mint coins.

struct MintCapability<CoinType> has copy, store
Fields
dummy_field: bool

FreezeCapability

Capability required to freeze a coin store.

struct FreezeCapability<CoinType> has copy, store
Fields
dummy_field: bool

BurnCapability

Capability required to burn coins.

struct BurnCapability<CoinType> has copy, store
Fields
dummy_field: bool

MintRefReceipt

The hot potato receipt for flash borrowing MintRef.

struct MintRefReceipt
Fields
metadata: object::Object<fungible_asset::Metadata>

TransferRefReceipt

The hot potato receipt for flash borrowing TransferRef.

struct TransferRefReceipt
Fields
metadata: object::Object<fungible_asset::Metadata>

BurnRefReceipt

The hot potato receipt for flash borrowing BurnRef.

struct BurnRefReceipt
Fields
metadata: object::Object<fungible_asset::Metadata>

Resources

CoinStore

A holder of a specific coin types and associated event handles. These are kept in a single resource to ensure locality of data.

struct CoinStore<CoinType> has key
Fields
coin: coin::Coin<CoinType>
frozen: bool
deposit_events: event::EventHandle<coin::DepositEvent>
withdraw_events: event::EventHandle<coin::WithdrawEvent>

SupplyConfig

Configuration that controls the behavior of total coin supply. If the field is set, coin creators are allowed to upgrade to parallelizable implementations.

#[deprecated]
struct SupplyConfig has key
Fields
allow_upgrades: bool

CoinInfo

Information about a specific coin type. Stored on the creator of the coin’s account.

struct CoinInfo<CoinType> has key
Fields
name: string::String
symbol: string::String
Symbol of the coin, usually a shorter version of the name. For example, Singapore Dollar is SGD.
decimals: u8
Number of decimals used to get its user representation. For example, if decimals equals 2, a balance of 505 coins should be displayed to a user as 5.05 (505 / 10 ** 2).
supply: option::Option<optional_aggregator::OptionalAggregator>
Amount of this coin type in existence.

CoinConversionMap

The mapping between coin and fungible asset.

struct CoinConversionMap has key
Fields
coin_to_fungible_asset_map: table::Table<type_info::TypeInfo, object::Object<fungible_asset::Metadata>>

PairedCoinType

The paired coin type info stored in fungible asset metadata object.

#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
struct PairedCoinType has key
Fields
type: type_info::TypeInfo

PairedFungibleAssetRefs

The refs of the paired fungible asset.

#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
struct PairedFungibleAssetRefs has key
Fields
mint_ref_opt: option::Option<fungible_asset::MintRef>
transfer_ref_opt: option::Option<fungible_asset::TransferRef>
burn_ref_opt: option::Option<fungible_asset::BurnRef>

MigrationFlag

The flag the existence of which indicates the primary fungible store is created by the migration from CoinStore.

#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
#[deprecated]
struct MigrationFlag has key
Fields
dummy_field: bool

Ghost$supply

struct Ghost$supply<CoinType> has copy, drop, store, key
Fields
v: num

Ghost$aggregate_supply

struct Ghost$aggregate_supply<CoinType> has copy, drop, store, key
Fields
v: num

Functions

paired_metadata

Get the paired fungible asset metadata object of a coin type. If not exist, return option::none().

#[view]
public fun paired_metadata<CoinType>(): option::Option<object::Object<fungible_asset::Metadata>>
Implementation
public fun paired_metadata<CoinType>(): Option<Object<Metadata>> acquires CoinConversionMap {
if (exists<CoinConversionMap>(@aptos_framework) && features::coin_to_fungible_asset_migration_feature_enabled(
)) {
let map = &borrow_global<CoinConversionMap>(@aptos_framework).coin_to_fungible_asset_map;
let type = type_info::type_of<CoinType>();
if (table::contains(map, type)) {
return option::some(*table::borrow(map, type))
}
};
option::none()
}

create_coin_conversion_map

public entry fun create_coin_conversion_map(aptos_framework: &signer)
Implementation
public entry fun create_coin_conversion_map(aptos_framework: &signer) {
system_addresses::assert_aptos_framework(aptos_framework);
if (!exists<CoinConversionMap>(@aptos_framework)) {
move_to(aptos_framework, CoinConversionMap {
coin_to_fungible_asset_map: table::new(),
})
};
}

create_pairing

Create APT pairing by passing AptosCoin.

public entry fun create_pairing<CoinType>(aptos_framework: &signer)
Implementation
public entry fun create_pairing<CoinType>(
aptos_framework: &signer
) acquires CoinConversionMap, CoinInfo {
system_addresses::assert_aptos_framework(aptos_framework);
create_and_return_paired_metadata_if_not_exist<CoinType>(true);
}

is_apt

fun is_apt<CoinType>(): bool
Implementation
inline fun is_apt<CoinType>(): bool {
type_info::type_name<CoinType>() == string::utf8(b"0x1::aptos_coin::AptosCoin")
}

create_and_return_paired_metadata_if_not_exist

fun create_and_return_paired_metadata_if_not_exist<CoinType>(allow_apt_creation: bool): object::Object<fungible_asset::Metadata>
Implementation
inline fun create_and_return_paired_metadata_if_not_exist<CoinType>(allow_apt_creation: bool): Object<Metadata> {
assert!(
features::coin_to_fungible_asset_migration_feature_enabled(),
error::invalid_state(EMIGRATION_FRAMEWORK_NOT_ENABLED)
);
assert!(exists<CoinConversionMap>(@aptos_framework), error::not_found(ECOIN_CONVERSION_MAP_NOT_FOUND));
let map = borrow_global_mut<CoinConversionMap>(@aptos_framework);
let type = type_info::type_of<CoinType>();
if (!table::contains(&map.coin_to_fungible_asset_map, type)) {
let is_apt = is_apt<CoinType>();
assert!(!is_apt || allow_apt_creation, error::invalid_state(EAPT_PAIRING_IS_NOT_ENABLED));
let metadata_object_cref =
if (is_apt) {
object::create_sticky_object_at_address(@aptos_framework, @aptos_fungible_asset)
} else {
object::create_named_object(
&create_signer::create_signer(@aptos_fungible_asset),
*string::bytes(&type_info::type_name<CoinType>())
)
};
primary_fungible_store::create_primary_store_enabled_fungible_asset(
&metadata_object_cref,
option::none(),
name<CoinType>(),
symbol<CoinType>(),
decimals<CoinType>(),
string::utf8(b""),
string::utf8(b""),
);
let metadata_object_signer = &object::generate_signer(&metadata_object_cref);
let type = type_info::type_of<CoinType>();
move_to(metadata_object_signer, PairedCoinType { type });
let metadata_obj = object::object_from_constructor_ref(&metadata_object_cref);
table::add(&mut map.coin_to_fungible_asset_map, type, metadata_obj);
event::emit(PairCreation {
coin_type: type,
fungible_asset_metadata_address: object_address(&metadata_obj)
});
// Generates all three refs
let mint_ref = fungible_asset::generate_mint_ref(&metadata_object_cref);
let transfer_ref = fungible_asset::generate_transfer_ref(&metadata_object_cref);
let burn_ref = fungible_asset::generate_burn_ref(&metadata_object_cref);
move_to(metadata_object_signer,
PairedFungibleAssetRefs {
mint_ref_opt: option::some(mint_ref),
transfer_ref_opt: option::some(transfer_ref),
burn_ref_opt: option::some(burn_ref),
}
);
};
*table::borrow(&map.coin_to_fungible_asset_map, type)
}

ensure_paired_metadata

Get the paired fungible asset metadata object of a coin type, create if not exist.

public(friend) fun ensure_paired_metadata<CoinType>(): object::Object<fungible_asset::Metadata>
Implementation
public(friend) fun ensure_paired_metadata<CoinType>(): Object<Metadata> acquires CoinConversionMap, CoinInfo {
create_and_return_paired_metadata_if_not_exist<CoinType>(false)
}

paired_coin

Get the paired coin type of a fungible asset metadata object.

#[view]
public fun paired_coin(metadata: object::Object<fungible_asset::Metadata>): option::Option<type_info::TypeInfo>
Implementation
public fun paired_coin(metadata: Object<Metadata>): Option<TypeInfo> acquires PairedCoinType {
let metadata_addr = object::object_address(&metadata);
if (exists<PairedCoinType>(metadata_addr)) {
option::some(borrow_global<PairedCoinType>(metadata_addr).type)
} else {
option::none()
}
}

coin_to_fungible_asset

Conversion from coin to fungible asset

public fun coin_to_fungible_asset<CoinType>(coin: coin::Coin<CoinType>): fungible_asset::FungibleAsset
Implementation
public fun coin_to_fungible_asset<CoinType>(
coin: Coin<CoinType>
): FungibleAsset acquires CoinConversionMap, CoinInfo {
let metadata = ensure_paired_metadata<CoinType>();
let amount = burn_internal(coin);
fungible_asset::mint_internal(metadata, amount)
}

fungible_asset_to_coin

Conversion from fungible asset to coin. Not public to push the migration to FA.

fun fungible_asset_to_coin<CoinType>(fungible_asset: fungible_asset::FungibleAsset): coin::Coin<CoinType>
Implementation
fun fungible_asset_to_coin<CoinType>(
fungible_asset: FungibleAsset
): Coin<CoinType> acquires CoinInfo, PairedCoinType {
let metadata_addr = object::object_address(&fungible_asset::metadata_from_asset(&fungible_asset));
assert!(
object::object_exists<PairedCoinType>(metadata_addr),
error::not_found(EPAIRED_COIN)
);
let coin_type_info = borrow_global<PairedCoinType>(metadata_addr).type;
assert!(coin_type_info == type_info::type_of<CoinType>(), error::invalid_argument(ECOIN_TYPE_MISMATCH));
let amount = fungible_asset::burn_internal(fungible_asset);
mint_internal<CoinType>(amount)
}

assert_paired_metadata_exists

fun assert_paired_metadata_exists<CoinType>(): object::Object<fungible_asset::Metadata>
Implementation
inline fun assert_paired_metadata_exists<CoinType>(): Object<Metadata> {
let metadata_opt = paired_metadata<CoinType>();
assert!(option::is_some(&metadata_opt), error::not_found(EPAIRED_FUNGIBLE_ASSET));
option::destroy_some(metadata_opt)
}

paired_mint_ref_exists

Check whether MintRef has not been taken.

#[view]
public fun paired_mint_ref_exists<CoinType>(): bool
Implementation
public fun paired_mint_ref_exists<CoinType>(): bool acquires CoinConversionMap, PairedFungibleAssetRefs {
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
option::is_some(&borrow_global<PairedFungibleAssetRefs>(metadata_addr).mint_ref_opt)
}

get_paired_mint_ref

Get the MintRef of paired fungible asset of a coin type from MintCapability.

public fun get_paired_mint_ref<CoinType>(_: &coin::MintCapability<CoinType>): (fungible_asset::MintRef, coin::MintRefReceipt)
Implementation
public fun get_paired_mint_ref<CoinType>(
_: &MintCapability<CoinType>
): (MintRef, MintRefReceipt) acquires CoinConversionMap, PairedFungibleAssetRefs {
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
let mint_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).mint_ref_opt;
assert!(option::is_some(mint_ref_opt), error::not_found(EMINT_REF_NOT_FOUND));
(option::extract(mint_ref_opt), MintRefReceipt { metadata })
}

return_paired_mint_ref

Return the MintRef with the hot potato receipt.

public fun return_paired_mint_ref(mint_ref: fungible_asset::MintRef, receipt: coin::MintRefReceipt)
Implementation
public fun return_paired_mint_ref(mint_ref: MintRef, receipt: MintRefReceipt) acquires PairedFungibleAssetRefs {
let MintRefReceipt { metadata } = receipt;
assert!(
fungible_asset::mint_ref_metadata(&mint_ref) == metadata,
error::invalid_argument(EMINT_REF_RECEIPT_MISMATCH)
);
let metadata_addr = object_address(&metadata);
let mint_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).mint_ref_opt;
option::fill(mint_ref_opt, mint_ref);
}

paired_transfer_ref_exists

Check whether TransferRef still exists.

#[view]
public fun paired_transfer_ref_exists<CoinType>(): bool
Implementation
public fun paired_transfer_ref_exists<CoinType>(): bool acquires CoinConversionMap, PairedFungibleAssetRefs {
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
option::is_some(&borrow_global<PairedFungibleAssetRefs>(metadata_addr).transfer_ref_opt)
}

get_paired_transfer_ref

Get the TransferRef of paired fungible asset of a coin type from FreezeCapability.

public fun get_paired_transfer_ref<CoinType>(_: &coin::FreezeCapability<CoinType>): (fungible_asset::TransferRef, coin::TransferRefReceipt)
Implementation
public fun get_paired_transfer_ref<CoinType>(
_: &FreezeCapability<CoinType>
): (TransferRef, TransferRefReceipt) acquires CoinConversionMap, PairedFungibleAssetRefs {
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
let transfer_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).transfer_ref_opt;
assert!(option::is_some(transfer_ref_opt), error::not_found(ETRANSFER_REF_NOT_FOUND));
(option::extract(transfer_ref_opt), TransferRefReceipt { metadata })
}

return_paired_transfer_ref

Return the TransferRef with the hot potato receipt.

public fun return_paired_transfer_ref(transfer_ref: fungible_asset::TransferRef, receipt: coin::TransferRefReceipt)
Implementation
public fun return_paired_transfer_ref(
transfer_ref: TransferRef,
receipt: TransferRefReceipt
) acquires PairedFungibleAssetRefs {
let TransferRefReceipt { metadata } = receipt;
assert!(
fungible_asset::transfer_ref_metadata(&transfer_ref) == metadata,
error::invalid_argument(ETRANSFER_REF_RECEIPT_MISMATCH)
);
let metadata_addr = object_address(&metadata);
let transfer_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).transfer_ref_opt;
option::fill(transfer_ref_opt, transfer_ref);
}

paired_burn_ref_exists

Check whether BurnRef has not been taken.

#[view]
public fun paired_burn_ref_exists<CoinType>(): bool
Implementation
public fun paired_burn_ref_exists<CoinType>(): bool acquires CoinConversionMap, PairedFungibleAssetRefs {
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
option::is_some(&borrow_global<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt)
}

get_paired_burn_ref

Get the BurnRef of paired fungible asset of a coin type from BurnCapability.

public fun get_paired_burn_ref<CoinType>(_: &coin::BurnCapability<CoinType>): (fungible_asset::BurnRef, coin::BurnRefReceipt)
Implementation
public fun get_paired_burn_ref<CoinType>(
_: &BurnCapability<CoinType>
): (BurnRef, BurnRefReceipt) acquires CoinConversionMap, PairedFungibleAssetRefs {
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
let burn_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
assert!(option::is_some(burn_ref_opt), error::not_found(EBURN_REF_NOT_FOUND));
(option::extract(burn_ref_opt), BurnRefReceipt { metadata })
}

convert_and_take_paired_burn_ref

public fun convert_and_take_paired_burn_ref<CoinType>(burn_cap: coin::BurnCapability<CoinType>): fungible_asset::BurnRef
Implementation
public fun convert_and_take_paired_burn_ref<CoinType>(
burn_cap: BurnCapability<CoinType>
): BurnRef acquires CoinConversionMap, PairedFungibleAssetRefs {
destroy_burn_cap(burn_cap);
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
let burn_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
assert!(option::is_some(burn_ref_opt), error::not_found(EBURN_REF_NOT_FOUND));
option::extract(burn_ref_opt)
}

return_paired_burn_ref

Return the BurnRef with the hot potato receipt.

public fun return_paired_burn_ref(burn_ref: fungible_asset::BurnRef, receipt: coin::BurnRefReceipt)
Implementation
public fun return_paired_burn_ref(
burn_ref: BurnRef,
receipt: BurnRefReceipt
) acquires PairedFungibleAssetRefs {
let BurnRefReceipt { metadata } = receipt;
assert!(
fungible_asset::burn_ref_metadata(&burn_ref) == metadata,
error::invalid_argument(EBURN_REF_RECEIPT_MISMATCH)
);
let metadata_addr = object_address(&metadata);
let burn_ref_opt = &mut borrow_global_mut<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
option::fill(burn_ref_opt, burn_ref);
}

borrow_paired_burn_ref

fun borrow_paired_burn_ref<CoinType>(_: &coin::BurnCapability<CoinType>): &fungible_asset::BurnRef
Implementation
inline fun borrow_paired_burn_ref<CoinType>(
_: &BurnCapability<CoinType>
): &BurnRef acquires CoinConversionMap, PairedFungibleAssetRefs {
let metadata = assert_paired_metadata_exists<CoinType>();
let metadata_addr = object_address(&metadata);
assert!(exists<PairedFungibleAssetRefs>(metadata_addr), error::internal(EPAIRED_FUNGIBLE_ASSET_REFS_NOT_FOUND));
let burn_ref_opt = &borrow_global<PairedFungibleAssetRefs>(metadata_addr).burn_ref_opt;
assert!(option::is_some(burn_ref_opt), error::not_found(EBURN_REF_NOT_FOUND));
option::borrow(burn_ref_opt)
}

allow_supply_upgrades

This should be called by on-chain governance to update the config and allow or disallow upgradability of total supply.

public fun allow_supply_upgrades(_aptos_framework: &signer, _allowed: bool)
Implementation
public fun allow_supply_upgrades(_aptos_framework: &signer, _allowed: bool) {
abort error::invalid_state(ECOIN_SUPPLY_UPGRADE_NOT_SUPPORTED)
}

calculate_amount_to_withdraw

fun calculate_amount_to_withdraw<CoinType>(account_addr: address, amount: u64): (u64, u64)
Implementation
inline fun calculate_amount_to_withdraw<CoinType>(
account_addr: address,
amount: u64
): (u64, u64) {
let coin_balance = coin_balance<CoinType>(account_addr);
if (coin_balance >= amount) {
(amount, 0)
} else {
let metadata = paired_metadata<CoinType>();
if (option::is_some(&metadata) && primary_fungible_store::primary_store_exists(
account_addr,
option::destroy_some(metadata)
))
(coin_balance, amount - coin_balance)
else
abort error::invalid_argument(EINSUFFICIENT_BALANCE)
}
}

maybe_convert_to_fungible_store

fun maybe_convert_to_fungible_store<CoinType>(account: address)
Implementation
fun maybe_convert_to_fungible_store<CoinType>(account: address) acquires CoinStore, CoinConversionMap, CoinInfo {
if (!features::coin_to_fungible_asset_migration_feature_enabled()) {
abort error::unavailable(ECOIN_TO_FUNGIBLE_ASSET_FEATURE_NOT_ENABLED)
};
assert!(is_coin_initialized<CoinType>(), error::invalid_argument(ECOIN_INFO_NOT_PUBLISHED));
let metadata = ensure_paired_metadata<CoinType>();
let store = primary_fungible_store::ensure_primary_store_exists(account, metadata);
if (exists<CoinStore<CoinType>>(account)) {
let CoinStore<CoinType> { coin, frozen, deposit_events, withdraw_events } = move_from<CoinStore<CoinType>>(
account
);
event::emit(
CoinStoreDeletion {
coin_type: type_info::type_name<CoinType>(),
event_handle_creation_address: guid::creator_address(
event::guid(&deposit_events)
),
deleted_deposit_event_handle_creation_number: guid::creation_num(event::guid(&deposit_events)),
deleted_withdraw_event_handle_creation_number: guid::creation_num(event::guid(&withdraw_events))
}
);
if (coin.value == 0) {
destroy_zero(coin);
} else {
fungible_asset::unchecked_deposit_with_no_events(object_address(&store), coin_to_fungible_asset(coin));
};
event::destroy_handle(deposit_events);
event::destroy_handle(withdraw_events);
// Note:
// It is possible the primary fungible store may already exist before this function call.
// In this case, if the account owns a frozen CoinStore and an unfrozen primary fungible store, this
// function would convert and deposit the rest coin into the primary store and freeze it to make the
// `frozen` semantic as consistent as possible.
if (frozen != fungible_asset::is_frozen(store)) {
fungible_asset::set_frozen_flag_internal(store, frozen);
}
};
}

assert_signer_has_permission

fun assert_signer_has_permission<CoinType>(account: &signer)
Implementation
inline fun assert_signer_has_permission<CoinType>(account: &signer) {
if(permissioned_signer::is_permissioned_signer(account)) {
fungible_asset::withdraw_permission_check_by_address(
account,
primary_fungible_store::primary_store_address(
signer::address_of(account),
ensure_paired_metadata<CoinType>()
),
0
);
}
}

migrate_to_fungible_store

Voluntarily migrate to fungible store for CoinType if not yet.

public entry fun migrate_to_fungible_store<CoinType>(account: &signer)
Implementation
public entry fun migrate_to_fungible_store<CoinType>(
account: &signer
) acquires CoinStore, CoinConversionMap, CoinInfo {
let account_addr = signer::address_of(account);
assert_signer_has_permission<CoinType>(account);
maybe_convert_to_fungible_store<CoinType>(account_addr);
}

migrate_coin_store_to_fungible_store

Migrate to fungible store for CoinType if not yet.

public entry fun migrate_coin_store_to_fungible_store<CoinType>(accounts: vector<address>)
Implementation
public entry fun migrate_coin_store_to_fungible_store<CoinType>(
accounts: vector<address>
) acquires CoinStore, CoinConversionMap, CoinInfo {
if (features::new_accounts_default_to_fa_store_enabled() || features::new_accounts_default_to_fa_apt_store_enabled()) {
std::vector::for_each(accounts, |account| {
maybe_convert_to_fungible_store<CoinType>(account);
});
}
}

coin_address

A helper function that returns the address of CoinType.

fun coin_address<CoinType>(): address
Implementation
fun coin_address<CoinType>(): address {
let type_info = type_info::type_of<CoinType>();
type_info::account_address(&type_info)
}

balance

Returns the balance of owner for provided CoinType and its paired FA if exists.

#[view]
public fun balance<CoinType>(owner: address): u64
Implementation
public fun balance<CoinType>(owner: address): u64 acquires CoinConversionMap, CoinStore {
let paired_metadata = paired_metadata<CoinType>();
coin_balance<CoinType>(owner) + if (option::is_some(&paired_metadata)) {
primary_fungible_store::balance(
owner,
option::extract(&mut paired_metadata)
)
} else { 0 }
}

is_balance_at_least

Returns whether the balance of owner for provided CoinType and its paired FA is >= amount.

#[view]
public fun is_balance_at_least<CoinType>(owner: address, amount: u64): bool
Implementation
public fun is_balance_at_least<CoinType>(owner: address, amount: u64): bool acquires CoinConversionMap, CoinStore {
let coin_balance = coin_balance<CoinType>(owner);
if (coin_balance >= amount) {
return true
};
let paired_metadata = paired_metadata<CoinType>();
let left_amount = amount - coin_balance;
if (option::is_some(&paired_metadata)) {
primary_fungible_store::is_balance_at_least(
owner,
option::extract(&mut paired_metadata),
left_amount
)
} else { false }
}

coin_balance

fun coin_balance<CoinType>(owner: address): u64
Implementation
inline fun coin_balance<CoinType>(owner: address): u64 {
if (exists<CoinStore<CoinType>>(owner)) {
borrow_global<CoinStore<CoinType>>(owner).coin.value
} else {
0
}
}

is_coin_initialized

Returns true if the type CoinType is an initialized coin.

#[view]
public fun is_coin_initialized<CoinType>(): bool
Implementation
public fun is_coin_initialized<CoinType>(): bool {
exists<CoinInfo<CoinType>>(coin_address<CoinType>())
}

is_coin_store_frozen

Returns true is account_addr has frozen the CoinStore or if it’s not registered at all

#[view]
public fun is_coin_store_frozen<CoinType>(account_addr: address): bool
Implementation
public fun is_coin_store_frozen<CoinType>(
account_addr: address
): bool acquires CoinStore, CoinConversionMap, CoinInfo {
if (!is_account_registered<CoinType>(account_addr)) {
return true
};
let coin_store = borrow_global<CoinStore<CoinType>>(account_addr);
coin_store.frozen
}

is_account_registered

Returns true if account_addr is registered to receive CoinType.

#[view]
public fun is_account_registered<CoinType>(account_addr: address): bool
Implementation
public fun is_account_registered<CoinType>(account_addr: address): bool acquires CoinConversionMap, CoinInfo {
assert!(is_coin_initialized<CoinType>(), error::invalid_argument(ECOIN_INFO_NOT_PUBLISHED));
if (exists<CoinStore<CoinType>>(account_addr)) {
true
} else {
let paired_metadata = ensure_paired_metadata<CoinType>();
can_receive_paired_fungible_asset(account_addr, paired_metadata)
}
}

name

Returns the name of the coin.

#[view]
public fun name<CoinType>(): string::String
Implementation
public fun name<CoinType>(): string::String acquires CoinInfo {
borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).name
}

symbol

Returns the symbol of the coin, usually a shorter version of the name.

#[view]
public fun symbol<CoinType>(): string::String
Implementation
public fun symbol<CoinType>(): string::String acquires CoinInfo {
borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).symbol
}

decimals

Returns the number of decimals used to get its user representation. For example, if decimals equals 2, a balance of 505 coins should be displayed to a user as 5.05 (505 / 10 ** 2).

#[view]
public fun decimals<CoinType>(): u8
Implementation
public fun decimals<CoinType>(): u8 acquires CoinInfo {
borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).decimals
}

supply

Returns the amount of coin in existence.

#[view]
public fun supply<CoinType>(): option::Option<u128>
Implementation
public fun supply<CoinType>(): Option<u128> acquires CoinInfo, CoinConversionMap {
let coin_supply = coin_supply<CoinType>();
let metadata = paired_metadata<CoinType>();
if (option::is_some(&metadata)) {
let fungible_asset_supply = fungible_asset::supply(option::extract(&mut metadata));
if (option::is_some(&coin_supply)) {
let supply = option::borrow_mut(&mut coin_supply);
*supply = *supply + option::destroy_some(fungible_asset_supply);
};
};
coin_supply
}

coin_supply

Returns the amount of coin in existence.

#[view]
public fun coin_supply<CoinType>(): option::Option<u128>
Implementation
public fun coin_supply<CoinType>(): Option<u128> acquires CoinInfo {
let maybe_supply = &borrow_global<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
// We do track supply, in this case read from optional aggregator.
let supply = option::borrow(maybe_supply);
let value = optional_aggregator::read(supply);
option::some(value)
} else {
option::none()
}
}

burn

Burn coin with capability. The capability _cap should be passed as a reference to BurnCapability<CoinType>.

public fun burn<CoinType>(coin: coin::Coin<CoinType>, _cap: &coin::BurnCapability<CoinType>)
Implementation
public fun burn<CoinType>(coin: Coin<CoinType>, _cap: &BurnCapability<CoinType>) acquires CoinInfo {
burn_internal(coin);
}

burn_from

Burn coin from the specified account with capability. The capability burn_cap should be passed as a reference to BurnCapability<CoinType>. This function shouldn’t fail as it’s called as part of transaction fee burning.

Note: This bypasses CoinStore::frozen — coins within a frozen CoinStore can be burned.

public fun burn_from<CoinType>(account_addr: address, amount: u64, burn_cap: &coin::BurnCapability<CoinType>)
Implementation
public fun burn_from<CoinType>(
account_addr: address,
amount: u64,
burn_cap: &BurnCapability<CoinType>,
) acquires CoinInfo, CoinStore, CoinConversionMap, PairedFungibleAssetRefs {
// Skip burning if amount is zero. This shouldn't error out as it's called as part of transaction fee burning.
if (amount == 0) {
return
};
let (coin_amount_to_burn, fa_amount_to_burn) = calculate_amount_to_withdraw<CoinType>(
account_addr,
amount
);
if (coin_amount_to_burn > 0) {
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
let coin_to_burn = extract(&mut coin_store.coin, coin_amount_to_burn);
burn(coin_to_burn, burn_cap);
};
if (fa_amount_to_burn > 0) {
fungible_asset::burn_from(
borrow_paired_burn_ref(burn_cap),
primary_fungible_store::primary_store(account_addr, option::destroy_some(paired_metadata<CoinType>())),
fa_amount_to_burn
);
};
}

burn_from_for_gas

public(friend) fun burn_from_for_gas<CoinType>(account_addr: address, amount: u64, burn_cap: &coin::BurnCapability<CoinType>)
Implementation
public(friend) fun burn_from_for_gas<CoinType>(
account_addr: address,
amount: u64,
burn_cap: &BurnCapability<CoinType>,
) acquires CoinInfo, CoinStore, CoinConversionMap, PairedFungibleAssetRefs {
// Skip burning if amount is zero. This shouldn't error out as it's called as part of transaction fee burning.
if (amount == 0) {
return
};
let (coin_amount_to_burn, fa_amount_to_burn) = calculate_amount_to_withdraw<CoinType>(
account_addr,
amount
);
if (coin_amount_to_burn > 0) {
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
let coin_to_burn = extract(&mut coin_store.coin, coin_amount_to_burn);
burn(coin_to_burn, burn_cap);
};
if (fa_amount_to_burn > 0) {
fungible_asset::address_burn_from_for_gas(
borrow_paired_burn_ref(burn_cap),
primary_fungible_store::primary_store_address(account_addr, option::destroy_some(paired_metadata<CoinType>())),
fa_amount_to_burn
);
};
}

deposit

Deposit the coin balance into the recipient’s account and emit an event.

public fun deposit<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)
Implementation
public fun deposit<CoinType>(
account_addr: address,
coin: Coin<CoinType>
) acquires CoinStore, CoinConversionMap, CoinInfo {
if (exists<CoinStore<CoinType>>(account_addr)) {
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
assert!(
!coin_store.frozen,
error::permission_denied(EFROZEN),
);
event::emit_event<DepositEvent>(
&mut coin_store.deposit_events,
DepositEvent { amount: coin.value },
);
merge(&mut coin_store.coin, coin);
} else {
let metadata = ensure_paired_metadata<CoinType>();
if (can_receive_paired_fungible_asset( account_addr, metadata)) {
primary_fungible_store::deposit(account_addr, coin_to_fungible_asset(coin));
} else {
abort error::not_found(ECOIN_STORE_NOT_PUBLISHED)
};
}
}

deposit_with_signer

public fun deposit_with_signer<CoinType>(account: &signer, coin: coin::Coin<CoinType>)
Implementation
public fun deposit_with_signer<CoinType>(
account: &signer,
coin: Coin<CoinType>
) acquires CoinStore, CoinConversionMap, CoinInfo {
let metadata = ensure_paired_metadata<CoinType>();
let account_address = signer::address_of(account);
fungible_asset::refill_permission(
account,
coin.value,
primary_fungible_store::primary_store_address_inlined(
account_address,
metadata,
)
);
deposit(account_address, coin);
}

can_receive_paired_fungible_asset

fun can_receive_paired_fungible_asset(account_address: address, metadata: object::Object<fungible_asset::Metadata>): bool
Implementation
inline fun can_receive_paired_fungible_asset(
account_address: address,
metadata: Object<Metadata>
): bool {
features::new_accounts_default_to_fa_store_enabled() || (features::new_accounts_default_to_fa_apt_store_enabled() && object::object_address(&metadata) == @0xa) || {
let primary_store_address = primary_fungible_store::primary_store_address<Metadata>(
account_address,
metadata
);
fungible_asset::store_exists(primary_store_address)
}
}

deposit_for_gas_fee

Deposit the coin balance into the recipient’s account without checking if the account is frozen. This is for internal use only and doesn’t emit an DepositEvent.

public(friend) fun deposit_for_gas_fee<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)
Implementation
public(friend) fun deposit_for_gas_fee<CoinType>(
account_addr: address,
coin: Coin<CoinType>
) acquires CoinStore, CoinConversionMap, CoinInfo {
if (exists<CoinStore<CoinType>>(account_addr)) {
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
merge(&mut coin_store.coin, coin);
} else {
let metadata = ensure_paired_metadata<CoinType>();
if (can_receive_paired_fungible_asset(
account_addr,
metadata
)) {
let fa = coin_to_fungible_asset(coin);
let metadata = fungible_asset::asset_metadata(&fa);
let store = primary_fungible_store::ensure_primary_store_exists(account_addr, metadata);
fungible_asset::unchecked_deposit_with_no_events(object::object_address(&store), fa);
} else {
abort error::not_found(ECOIN_STORE_NOT_PUBLISHED)
}
}
}

destroy_zero

Destroys a zero-value coin. Calls will fail if the value in the passed-in token is non-zero so it is impossible to “burn” any non-zero amount of Coin without having a BurnCapability for the specific CoinType.

public fun destroy_zero<CoinType>(zero_coin: coin::Coin<CoinType>)
Implementation
public fun destroy_zero<CoinType>(zero_coin: Coin<CoinType>) {
spec {
update supply<CoinType> = supply<CoinType> - zero_coin.value;
};
let Coin { value } = zero_coin;
assert!(value == 0, error::invalid_argument(EDESTRUCTION_OF_NONZERO_TOKEN))
}

extract

Extracts amount from the passed-in coin, where the original token is modified in place.

public fun extract<CoinType>(coin: &mut coin::Coin<CoinType>, amount: u64): coin::Coin<CoinType>
Implementation
public fun extract<CoinType>(coin: &mut Coin<CoinType>, amount: u64): Coin<CoinType> {
assert!(coin.value >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
spec {
update supply<CoinType> = supply<CoinType> - amount;
};
coin.value = coin.value - amount;
spec {
update supply<CoinType> = supply<CoinType> + amount;
};
Coin { value: amount }
}

extract_all

Extracts the entire amount from the passed-in coin, where the original token is modified in place.

public fun extract_all<CoinType>(coin: &mut coin::Coin<CoinType>): coin::Coin<CoinType>
Implementation
public fun extract_all<CoinType>(coin: &mut Coin<CoinType>): Coin<CoinType> {
let total_value = coin.value;
spec {
update supply<CoinType> = supply<CoinType> - coin.value;
};
coin.value = 0;
spec {
update supply<CoinType> = supply<CoinType> + total_value;
};
Coin { value: total_value }
}

freeze_coin_store

Freeze a CoinStore to prevent transfers

#[legacy_entry_fun]
public entry fun freeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
Implementation
public entry fun freeze_coin_store<CoinType>(
account_addr: address,
_freeze_cap: &FreezeCapability<CoinType>,
) acquires CoinStore {
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
coin_store.frozen = true;
}

unfreeze_coin_store

Unfreeze a CoinStore to allow transfers

#[legacy_entry_fun]
public entry fun unfreeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
Implementation
public entry fun unfreeze_coin_store<CoinType>(
account_addr: address,
_freeze_cap: &FreezeCapability<CoinType>,
) acquires CoinStore {
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
coin_store.frozen = false;
}

upgrade_supply

Upgrade total supply to use a parallelizable implementation if it is available.

public entry fun upgrade_supply<CoinType>(_account: &signer)
Implementation
public entry fun upgrade_supply<CoinType>(_account: &signer) {
abort error::invalid_state(ECOIN_SUPPLY_UPGRADE_NOT_SUPPORTED)
}

initialize

Creates a new Coin with given CoinType and returns minting/freezing/burning capabilities. The given signer also becomes the account hosting the information about the coin (name, supply, etc.). Supply is initialized as non-parallelizable integer.

public fun initialize<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
Implementation
public fun initialize<CoinType>(
account: &signer,
name: string::String,
symbol: string::String,
decimals: u8,
monitor_supply: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) acquires CoinInfo, CoinConversionMap {
initialize_internal(account, name, symbol, decimals, monitor_supply, false)
}

initialize_with_parallelizable_supply

Same as initialize but supply can be initialized to parallelizable aggregator.

public(friend) fun initialize_with_parallelizable_supply<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
Implementation
public(friend) fun initialize_with_parallelizable_supply<CoinType>(
account: &signer,
name: string::String,
symbol: string::String,
decimals: u8,
monitor_supply: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) acquires CoinInfo, CoinConversionMap {
system_addresses::assert_aptos_framework(account);
initialize_internal(account, name, symbol, decimals, monitor_supply, true)
}

initialize_internal

fun initialize_internal<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool, parallelizable: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
Implementation
fun initialize_internal<CoinType>(
account: &signer,
name: string::String,
symbol: string::String,
decimals: u8,
monitor_supply: bool,
parallelizable: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) acquires CoinInfo, CoinConversionMap {
let account_addr = signer::address_of(account);
assert_signer_has_permission<CoinType>(account);
assert!(
coin_address<CoinType>() == account_addr,
error::invalid_argument(ECOIN_INFO_ADDRESS_MISMATCH),
);
assert!(
!exists<CoinInfo<CoinType>>(account_addr),
error::already_exists(ECOIN_INFO_ALREADY_PUBLISHED),
);
assert!(string::length(&name) <= MAX_COIN_NAME_LENGTH, error::invalid_argument(ECOIN_NAME_TOO_LONG));
assert!(string::length(&symbol) <= MAX_COIN_SYMBOL_LENGTH, error::invalid_argument(ECOIN_SYMBOL_TOO_LONG));
let coin_info = CoinInfo<CoinType> {
name,
symbol,
decimals,
supply: if (monitor_supply) {
option::some(
optional_aggregator::new(parallelizable)
)
} else { option::none() },
};
move_to(account, coin_info);
(BurnCapability<CoinType> {}, FreezeCapability<CoinType> {}, MintCapability<CoinType> {})
}

merge

“Merges” the two given coins. The coin passed in as dst_coin will have a value equal to the sum of the two tokens (dst_coin and source_coin).

public fun merge<CoinType>(dst_coin: &mut coin::Coin<CoinType>, source_coin: coin::Coin<CoinType>)
Implementation
public fun merge<CoinType>(dst_coin: &mut Coin<CoinType>, source_coin: Coin<CoinType>) {
spec {
assume dst_coin.value + source_coin.value <= MAX_U64;
};
spec {
update supply<CoinType> = supply<CoinType> - source_coin.value;
};
let Coin { value } = source_coin;
spec {
update supply<CoinType> = supply<CoinType> + value;
};
dst_coin.value = dst_coin.value + value;
}

mint

Mint new Coin with capability. The capability _cap should be passed as reference to MintCapability<CoinType>. Returns minted Coin.

public fun mint<CoinType>(amount: u64, _cap: &coin::MintCapability<CoinType>): coin::Coin<CoinType>
Implementation
public fun mint<CoinType>(
amount: u64,
_cap: &MintCapability<CoinType>,
): Coin<CoinType> acquires CoinInfo {
mint_internal<CoinType>(amount)
}

register

public fun register<CoinType>(account: &signer)
Implementation
public fun register<CoinType>(account: &signer) acquires CoinInfo, CoinConversionMap {
let account_addr = signer::address_of(account);
assert_signer_has_permission<CoinType>(account);
// Short-circuit and do nothing if account is already registered for CoinType.
if (is_account_registered<CoinType>(account_addr)) {
return
};
account::register_coin<CoinType>(account_addr);
let coin_store = CoinStore<CoinType> {
coin: Coin { value: 0 },
frozen: false,
deposit_events: account::new_event_handle<DepositEvent>(account),
withdraw_events: account::new_event_handle<WithdrawEvent>(account),
};
move_to(account, coin_store);
}

transfer

Transfers amount of coins CoinType from from to to.

public entry fun transfer<CoinType>(from: &signer, to: address, amount: u64)
Implementation
public entry fun transfer<CoinType>(
from: &signer,
to: address,
amount: u64,
) acquires CoinStore, CoinConversionMap, CoinInfo, PairedCoinType {
let coin = withdraw<CoinType>(from, amount);
deposit(to, coin);
}

value

Returns the value passed in coin.

public fun value<CoinType>(coin: &coin::Coin<CoinType>): u64
Implementation
public fun value<CoinType>(coin: &Coin<CoinType>): u64 {
coin.value
}

withdraw

Withdraw specified amount of coin CoinType from the signing account.

public fun withdraw<CoinType>(account: &signer, amount: u64): coin::Coin<CoinType>
Implementation
public fun withdraw<CoinType>(
account: &signer,
amount: u64,
): Coin<CoinType> acquires CoinStore, CoinConversionMap, CoinInfo, PairedCoinType {
let account_addr = signer::address_of(account);
let (coin_amount_to_withdraw, fa_amount_to_withdraw) = calculate_amount_to_withdraw<CoinType>(
account_addr,
amount
);
let withdrawn_coin = if (coin_amount_to_withdraw > 0) {
let metadata = ensure_paired_metadata<CoinType>();
if(permissioned_signer::is_permissioned_signer(account)) {
// Perform the check only if the account is a permissioned signer to save the cost of
// computing the primary store location.
fungible_asset::withdraw_permission_check_by_address(
account,
primary_fungible_store::primary_store_address(account_addr, metadata),
coin_amount_to_withdraw
);
};
let coin_store = borrow_global_mut<CoinStore<CoinType>>(account_addr);
assert!(
!coin_store.frozen,
error::permission_denied(EFROZEN),
);
event::emit_event<WithdrawEvent>(
&mut coin_store.withdraw_events,
WithdrawEvent { amount: coin_amount_to_withdraw },
);
extract(&mut coin_store.coin, coin_amount_to_withdraw)
} else {
zero()
};
if (fa_amount_to_withdraw > 0) {
let fa = primary_fungible_store::withdraw(
account,
option::destroy_some(paired_metadata<CoinType>()),
fa_amount_to_withdraw
);
merge(&mut withdrawn_coin, fungible_asset_to_coin(fa));
};
withdrawn_coin
}

zero

Create a new Coin<CoinType> with a value of 0.

public fun zero<CoinType>(): coin::Coin<CoinType>
Implementation
public fun zero<CoinType>(): Coin<CoinType> {
spec {
update supply<CoinType> = supply<CoinType> + 0;
};
Coin<CoinType> {
value: 0
}
}

destroy_freeze_cap

Destroy a freeze capability. Freeze capability is dangerous and therefore should be destroyed if not used.

public fun destroy_freeze_cap<CoinType>(freeze_cap: coin::FreezeCapability<CoinType>)
Implementation
public fun destroy_freeze_cap<CoinType>(freeze_cap: FreezeCapability<CoinType>) {
let FreezeCapability<CoinType> {} = freeze_cap;
}

destroy_mint_cap

Destroy a mint capability.

public fun destroy_mint_cap<CoinType>(mint_cap: coin::MintCapability<CoinType>)
Implementation
public fun destroy_mint_cap<CoinType>(mint_cap: MintCapability<CoinType>) {
let MintCapability<CoinType> {} = mint_cap;
}

destroy_burn_cap

Destroy a burn capability.

public fun destroy_burn_cap<CoinType>(burn_cap: coin::BurnCapability<CoinType>)
Implementation
public fun destroy_burn_cap<CoinType>(burn_cap: BurnCapability<CoinType>) {
let BurnCapability<CoinType> {} = burn_cap;
}

mint_internal

fun mint_internal<CoinType>(amount: u64): coin::Coin<CoinType>
Implementation
fun mint_internal<CoinType>(amount: u64): Coin<CoinType> acquires CoinInfo {
if (amount == 0) {
return Coin<CoinType> {
value: 0
}
};
let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
let supply = option::borrow_mut(maybe_supply);
spec {
use aptos_framework::optional_aggregator;
use aptos_framework::aggregator;
assume optional_aggregator::is_parallelizable(supply) ==> (aggregator::spec_aggregator_get_val(
option::borrow(supply.aggregator)
)
+ amount <= aggregator::spec_get_limit(option::borrow(supply.aggregator)));
assume !optional_aggregator::is_parallelizable(supply) ==>
(option::borrow(supply.integer).value + amount <= option::borrow(supply.integer).limit);
};
optional_aggregator::add(supply, (amount as u128));
};
spec {
update supply<CoinType> = supply<CoinType> + amount;
};
Coin<CoinType> { value: amount }
}

burn_internal

fun burn_internal<CoinType>(coin: coin::Coin<CoinType>): u64
Implementation
fun burn_internal<CoinType>(coin: Coin<CoinType>): u64 acquires CoinInfo {
spec {
update supply<CoinType> = supply<CoinType> - coin.value;
};
let Coin { value: amount } = coin;
if (amount != 0) {
let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
let supply = option::borrow_mut(maybe_supply);
optional_aggregator::sub(supply, (amount as u128));
};
};
amount
}

Specification

High-level Requirements

No.RequirementCriticalityImplementationEnforcement
1 Only the owner of a coin may mint, burn or freeze coins. Critical Acquiring capabilities for a particular CoinType may only occur if the caller has a signer for the module declaring that type. The initialize function returns these capabilities to the caller. Formally Verified via upgrade_supply and initialize.
2 Each coin may only be created exactly once. Medium The initialization function may only be called once. Formally Verified via initialize.
3 The merging of coins may only be done on coins of the same type. Critical The merge function is limited to merging coins of the same type only. Formally Verified via merge.
4 The supply of a coin is only affected by burn and mint operations. High Only mint and burn operations on a coin alter the total supply of coins. Formally Verified via TotalSupplyNoChange.
5 Users may register an account for a coin multiple times idempotently. Medium The register function should work idempotently. Importantly, it should not abort if the coin is already registered. Formally verified via aborts_if on register.
6 Coin operations should fail if the user has not registered for the coin. Medium Coin operations may succeed only on valid user coin registration. Formally Verified via balance, burn_from, freeze, unfreeze, transfer and withdraw.
7 It should always be possible to (1) determine if a coin exists, and (2) determine if a user registered an account with a particular coin. If a coin exists, it should always be possible to request the following information of the coin: (1) Name, (2) Symbol, and (3) Supply. Low The following functions should never abort: (1) is_coin_initialized, and (2) is_account_registered. The following functions should not abort if the coin exists: (1) name, (2) symbol, and (3) supply. Formally Verified in corresponding functions: is_coin_initialized, is_account_registered, name, symbol and supply.
8 Coin operations should fail if the user's CoinStore is frozen. Medium If the CoinStore of an address is frozen, coin operations are disallowed. Formally Verified via withdraw, transfer and deposit.
9 Utilizing AggregatableCoins does not violate other critical invariants, such as (4). High Utilizing AggregatableCoin does not change the real-supply of any token. Formally Verified via TotalSupplyNoChange.

Module-level Specification

pragma verify = true;
pragma aborts_if_is_partial;
global supply<CoinType>: num;
global aggregate_supply<CoinType>: num;
apply TotalSupplyTracked<CoinType> to *<CoinType> except
initialize, initialize_internal, initialize_with_parallelizable_supply;
fun spec_fun_supply_tracked<CoinType>(val: u64, supply: Option<OptionalAggregator>): bool {
option::spec_is_some(supply) ==> val == optional_aggregator::optional_aggregator_value
(option::spec_borrow(supply))
}
schema TotalSupplyTracked<CoinType> {
ensures old(spec_fun_supply_tracked<CoinType>(supply<CoinType> + aggregate_supply<CoinType>,
global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply)) ==>
spec_fun_supply_tracked<CoinType>(supply<CoinType> + aggregate_supply<CoinType>,
global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply);
}
fun spec_fun_supply_no_change<CoinType>(old_supply: Option<OptionalAggregator>,
supply: Option<OptionalAggregator>): bool {
option::spec_is_some(old_supply) ==> optional_aggregator::optional_aggregator_value
(option::spec_borrow(old_supply)) == optional_aggregator::optional_aggregator_value
(option::spec_borrow(supply))
}
schema TotalSupplyNoChange<CoinType> {
let old_supply = global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply;
let post supply = global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply;
ensures spec_fun_supply_no_change<CoinType>(old_supply, supply);
}

AggregatableCoin

#[deprecated]
struct AggregatableCoin<CoinType> has store
value: aggregator::Aggregator
Amount of aggregatable coin this address has.
invariant aggregator::spec_get_limit(value) == MAX_U64;

coin_to_fungible_asset

public fun coin_to_fungible_asset<CoinType>(coin: coin::Coin<CoinType>): fungible_asset::FungibleAsset
pragma verify = false;
let addr = type_info::type_of<CoinType>().account_address;
modifies global<CoinInfo<CoinType>>(addr);

fungible_asset_to_coin

fun fungible_asset_to_coin<CoinType>(fungible_asset: fungible_asset::FungibleAsset): coin::Coin<CoinType>
pragma verify = false;

allow_supply_upgrades

public fun allow_supply_upgrades(_aptos_framework: &signer, _allowed: bool)

Can only be updated by @aptos_framework.

aborts_if true;

maybe_convert_to_fungible_store

fun maybe_convert_to_fungible_store<CoinType>(account: address)
pragma verify = false;
modifies global<CoinInfo<CoinType>>(account);
modifies global<CoinStore<CoinType>>(account);
schema DepositAbortsIf<CoinType> {
account_addr: address;
let coin_store = global<CoinStore<CoinType>>(account_addr);
aborts_if !exists<CoinStore<CoinType>>(account_addr);
aborts_if coin_store.frozen;
}

coin_address

fun coin_address<CoinType>(): address

Get address by reflection.

pragma opaque;
aborts_if [abstract] false;
ensures [abstract] result == type_info::type_of<CoinType>().account_address;

balance

#[view]
public fun balance<CoinType>(owner: address): u64
pragma verify = false;
aborts_if !exists<CoinStore<CoinType>>(owner);
ensures result == global<CoinStore<CoinType>>(owner).coin.value;

is_coin_initialized

#[view]
public fun is_coin_initialized<CoinType>(): bool
// This enforces high-level requirement 7:
aborts_if false;

is_account_registered

#[view]
public fun is_account_registered<CoinType>(account_addr: address): bool
pragma aborts_if_is_partial;
aborts_if false;
fun get_coin_supply_opt<CoinType>(): Option<OptionalAggregator> {
global<CoinInfo<CoinType>>(type_info::type_of<CoinType>().account_address).supply
}
fun spec_paired_metadata<CoinType>(): Option<Object<Metadata>> {
if (exists<CoinConversionMap>(@aptos_framework)) {
let map = global<CoinConversionMap>(@aptos_framework).coin_to_fungible_asset_map;
if (table::spec_contains(map, type_info::type_of<CoinType>())) {
let metadata = table::spec_get(map, type_info::type_of<CoinType>());
option::spec_some(metadata)
} else {
option::spec_none()
}
} else {
option::spec_none()
}
}
fun spec_is_account_registered<CoinType>(account_addr:address): bool;
pragma aborts_if_is_partial;
aborts_if false;
ensures [abstract] result == spec_is_account_registered<CoinType>(account_addr);
schema CoinSubAbortsIf<CoinType> {
amount: u64;
let addr = type_info::type_of<CoinType>().account_address;
let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
include (option::is_some(
maybe_supply
)) ==> optional_aggregator::SubAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
}
schema CoinAddAbortsIf<CoinType> {
amount: u64;
let addr = type_info::type_of<CoinType>().account_address;
let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
include (option::is_some(
maybe_supply
)) ==> optional_aggregator::AddAbortsIf { optional_aggregator: option::borrow(maybe_supply), value: amount };
}
schema AbortsIfNotExistCoinInfo<CoinType> {
let addr = type_info::type_of<CoinType>().account_address;
aborts_if !exists<CoinInfo<CoinType>>(addr);
}

name

#[view]
public fun name<CoinType>(): string::String
// This enforces high-level requirement 7:
include AbortsIfNotExistCoinInfo<CoinType>;

symbol

#[view]
public fun symbol<CoinType>(): string::String
// This enforces high-level requirement 7:
include AbortsIfNotExistCoinInfo<CoinType>;

decimals

#[view]
public fun decimals<CoinType>(): u8
include AbortsIfNotExistCoinInfo<CoinType>;

supply

#[view]
public fun supply<CoinType>(): option::Option<u128>
pragma verify = false;

coin_supply

#[view]
public fun coin_supply<CoinType>(): option::Option<u128>
let coin_addr = type_info::type_of<CoinType>().account_address;
// This enforces high-level requirement 7:
aborts_if !exists<CoinInfo<CoinType>>(coin_addr);
let maybe_supply = global<CoinInfo<CoinType>>(coin_addr).supply;
let supply = option::spec_borrow(maybe_supply);
let value = optional_aggregator::optional_aggregator_value(supply);
ensures if (option::spec_is_some(maybe_supply)) {
result == option::spec_some(value)
} else {
option::spec_is_none(result)
};

burn

public fun burn<CoinType>(coin: coin::Coin<CoinType>, _cap: &coin::BurnCapability<CoinType>)
pragma verify = false;
let addr = type_info::type_of<CoinType>().account_address;
modifies global<CoinInfo<CoinType>>(addr);
include AbortsIfNotExistCoinInfo<CoinType>;
aborts_if coin.value == 0;
include CoinSubAbortsIf<CoinType> { amount: coin.value };
ensures supply<CoinType> == old(supply<CoinType>) - coin.value;

burn_from

public fun burn_from<CoinType>(account_addr: address, amount: u64, burn_cap: &coin::BurnCapability<CoinType>)
pragma verify = false;
let addr = type_info::type_of<CoinType>().account_address;
let coin_store = global<CoinStore<CoinType>>(account_addr);
let post post_coin_store = global<CoinStore<CoinType>>(account_addr);
modifies global<CoinInfo<CoinType>>(addr);
modifies global<CoinStore<CoinType>>(account_addr);
// This enforces high-level requirement 6:
aborts_if amount != 0 && !exists<CoinInfo<CoinType>>(addr);
aborts_if amount != 0 && !exists<CoinStore<CoinType>>(account_addr);
aborts_if coin_store.coin.value < amount;
let maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
let supply_aggr = option::spec_borrow(maybe_supply);
let value = optional_aggregator::optional_aggregator_value(supply_aggr);
let post post_maybe_supply = global<CoinInfo<CoinType>>(addr).supply;
let post post_supply = option::spec_borrow(post_maybe_supply);
let post post_value = optional_aggregator::optional_aggregator_value(post_supply);
aborts_if option::spec_is_some(maybe_supply) && value < amount;
ensures post_coin_store.coin.value == coin_store.coin.value - amount;
// This enforces high-level requirement 5 of the managed_coin module:
ensures if (option::spec_is_some(maybe_supply)) {
post_value == value - amount
} else {
option::spec_is_none(post_maybe_supply)
};
ensures supply<CoinType> == old(supply<CoinType>) - amount;

deposit

public fun deposit<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)

account_addr is not frozen.

pragma verify = false;
modifies global<CoinInfo<CoinType>>(account_addr);
// This enforces high-level requirement 8:
include DepositAbortsIf<CoinType>;
ensures global<CoinStore<CoinType>>(account_addr).coin.value == old(
global<CoinStore<CoinType>>(account_addr)
).coin.value + coin.value;

deposit_for_gas_fee

public(friend) fun deposit_for_gas_fee<CoinType>(account_addr: address, coin: coin::Coin<CoinType>)
pragma verify = false;
modifies global<CoinStore<CoinType>>(account_addr);
aborts_if !exists<CoinStore<CoinType>>(account_addr);
ensures global<CoinStore<CoinType>>(account_addr).coin.value == old(
global<CoinStore<CoinType>>(account_addr)
).coin.value + coin.value;

destroy_zero

public fun destroy_zero<CoinType>(zero_coin: coin::Coin<CoinType>)

The value of zero_coin must be 0.

aborts_if zero_coin.value > 0;

extract

public fun extract<CoinType>(coin: &mut coin::Coin<CoinType>, amount: u64): coin::Coin<CoinType>
aborts_if coin.value < amount;
ensures result.value == amount;
ensures coin.value == old(coin.value) - amount;

extract_all

public fun extract_all<CoinType>(coin: &mut coin::Coin<CoinType>): coin::Coin<CoinType>
ensures result.value == old(coin).value;
ensures coin.value == 0;

freeze_coin_store

#[legacy_entry_fun]
public entry fun freeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
pragma verify = false;
modifies global<CoinStore<CoinType>>(account_addr);
// This enforces high-level requirement 6:
aborts_if !exists<CoinStore<CoinType>>(account_addr);
let post coin_store = global<CoinStore<CoinType>>(account_addr);
ensures coin_store.frozen;

unfreeze_coin_store

#[legacy_entry_fun]
public entry fun unfreeze_coin_store<CoinType>(account_addr: address, _freeze_cap: &coin::FreezeCapability<CoinType>)
pragma verify = false;
modifies global<CoinStore<CoinType>>(account_addr);
// This enforces high-level requirement 6:
aborts_if !exists<CoinStore<CoinType>>(account_addr);
let post coin_store = global<CoinStore<CoinType>>(account_addr);
ensures !coin_store.frozen;

upgrade_supply

public entry fun upgrade_supply<CoinType>(_account: &signer)

The creator of CoinType must be @aptos_framework. SupplyConfig allow upgrade.

aborts_if true;

initialize

public fun initialize<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
let account_addr = signer::address_of(account);
// This enforces high-level requirement 1:
aborts_if type_info::type_of<CoinType>().account_address != account_addr;
// This enforces high-level requirement 2:
aborts_if exists<CoinInfo<CoinType>>(account_addr);
aborts_if string::length(name) > MAX_COIN_NAME_LENGTH;
aborts_if string::length(symbol) > MAX_COIN_SYMBOL_LENGTH;

initialize_with_parallelizable_supply

public(friend) fun initialize_with_parallelizable_supply<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
let addr = signer::address_of(account);
aborts_if addr != @aptos_framework;
aborts_if monitor_supply && !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
include InitializeInternalSchema<CoinType> {
name: name.bytes,
symbol: symbol.bytes
};
ensures exists<CoinInfo<CoinType>>(addr);

initialize_internal

fun initialize_internal<CoinType>(account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool, parallelizable: bool): (coin::BurnCapability<CoinType>, coin::FreezeCapability<CoinType>, coin::MintCapability<CoinType>)
include InitializeInternalSchema<CoinType> {
name: name.bytes,
symbol: symbol.bytes
};
let account_addr = signer::address_of(account);
let post coin_info = global<CoinInfo<CoinType>>(account_addr);
let post supply = option::spec_borrow(coin_info.supply);
let post value = optional_aggregator::optional_aggregator_value(supply);
let post limit = optional_aggregator::optional_aggregator_limit(supply);
modifies global<CoinInfo<CoinType>>(account_addr);
aborts_if monitor_supply && parallelizable
&& !exists<aggregator_factory::AggregatorFactory>(@aptos_framework);
// This enforces high-level requirement 2 of the managed_coin module:
ensures exists<CoinInfo<CoinType>>(account_addr)
&& coin_info.name == name
&& coin_info.symbol == symbol
&& coin_info.decimals == decimals;
ensures if (monitor_supply) {
value == 0 && limit == MAX_U128
&& (parallelizable == optional_aggregator::is_parallelizable(supply))
} else {
option::spec_is_none(coin_info.supply)
};
ensures result_1 == BurnCapability<CoinType> {};
ensures result_2 == FreezeCapability<CoinType> {};
ensures result_3 == MintCapability<CoinType> {};

merge

public fun merge<CoinType>(dst_coin: &mut coin::Coin<CoinType>, source_coin: coin::Coin<CoinType>)
// This enforces high-level requirement 3:
ensures dst_coin.value == old(dst_coin.value) + source_coin.value;

mint

public fun mint<CoinType>(amount: u64, _cap: &coin::MintCapability<CoinType>): coin::Coin<CoinType>
let addr = type_info::type_of<CoinType>().account_address;
modifies global<CoinInfo<CoinType>>(addr);

register

public fun register<CoinType>(account: &signer)

An account can only be registered once. Updating Account.guid_creation_num will not overflow.

pragma verify = false;

transfer

public entry fun transfer<CoinType>(from: &signer, to: address, amount: u64)

from and to account not frozen. from and to not the same address. from account sufficient balance.

pragma verify = false;
let account_addr_from = signer::address_of(from);
let coin_store_from = global<CoinStore<CoinType>>(account_addr_from);
let post coin_store_post_from = global<CoinStore<CoinType>>(account_addr_from);
let coin_store_to = global<CoinStore<CoinType>>(to);
let post coin_store_post_to = global<CoinStore<CoinType>>(to);
// This enforces high-level requirement 6:
aborts_if !exists<CoinStore<CoinType>>(account_addr_from);
aborts_if !exists<CoinStore<CoinType>>(to);
// This enforces high-level requirement 8:
aborts_if coin_store_from.frozen;
aborts_if coin_store_to.frozen;
aborts_if coin_store_from.coin.value < amount;
ensures account_addr_from != to ==> coin_store_post_from.coin.value ==
coin_store_from.coin.value - amount;
ensures account_addr_from != to ==> coin_store_post_to.coin.value == coin_store_to.coin.value + amount;
ensures account_addr_from == to ==> coin_store_post_from.coin.value == coin_store_from.coin.value;

withdraw

public fun withdraw<CoinType>(account: &signer, amount: u64): coin::Coin<CoinType>

Account is not frozen and sufficient balance.

pragma verify = false;
include WithdrawAbortsIf<CoinType>;
modifies global<CoinStore<CoinType>>(account_addr);
let account_addr = signer::address_of(account);
let coin_store = global<CoinStore<CoinType>>(account_addr);
let balance = coin_store.coin.value;
let post coin_post = global<CoinStore<CoinType>>(account_addr).coin.value;
ensures coin_post == balance - amount;
ensures result == Coin<CoinType> { value: amount };
schema WithdrawAbortsIf<CoinType> {
account: &signer;
amount: u64;
let account_addr = signer::address_of(account);
let coin_store = global<CoinStore<CoinType>>(account_addr);
let balance = coin_store.coin.value;
// This enforces high-level requirement 6:
aborts_if !exists<CoinStore<CoinType>>(account_addr);
// This enforces high-level requirement 8:
aborts_if coin_store.frozen;
aborts_if balance < amount;
}

mint_internal

fun mint_internal<CoinType>(amount: u64): coin::Coin<CoinType>
let addr = type_info::type_of<CoinType>().account_address;
modifies global<CoinInfo<CoinType>>(addr);
aborts_if (amount != 0) && !exists<CoinInfo<CoinType>>(addr);
ensures supply<CoinType> == old(supply<CoinType>) + amount;
ensures result.value == amount;

burn_internal

fun burn_internal<CoinType>(coin: coin::Coin<CoinType>): u64
pragma verify = false;
let addr = type_info::type_of<CoinType>().account_address;
modifies global<CoinInfo<CoinType>>(addr);