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
equals2
, a balance of505
coins should be displayed to a user as5.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. | Requirement | Criticality | Implementation | Enforcement |
---|---|---|---|---|
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> exceptinitialize, 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);