diff --git a/aptos-move/framework/aptos-framework/doc/fungible_asset.md b/aptos-move/framework/aptos-framework/doc/fungible_asset.md index 7120f8447b96e..88614f55d7d6a 100644 --- a/aptos-move/framework/aptos-framework/doc/fungible_asset.md +++ b/aptos-move/framework/aptos-framework/doc/fungible_asset.md @@ -7,10 +7,11 @@ This defines the fungible asset module that can issue fungible asset of any Metadata resource. +- [Struct `Supply`](#0x1_fungible_asset_Supply) - [Resource `Metadata`](#0x1_fungible_asset_Metadata) -- [Resource `FungibleAsset`](#0x1_fungible_asset_FungibleAsset) +- [Resource `FungibleStore`](#0x1_fungible_asset_FungibleStore) - [Resource `FungibleAssetEvents`](#0x1_fungible_asset_FungibleAssetEvents) -- [Struct `ExtractedAsset`](#0x1_fungible_asset_ExtractedAsset) +- [Struct `FungibleAsset`](#0x1_fungible_asset_FungibleAsset) - [Struct `MintRef`](#0x1_fungible_asset_MintRef) - [Struct `TransferRef`](#0x1_fungible_asset_TransferRef) - [Struct `BurnRef`](#0x1_fungible_asset_BurnRef) @@ -27,24 +28,26 @@ metadata object can be any object that equipped with use 0x1::event; use 0x1::object; use 0x1::option; +use 0x1::optional_aggregator; use 0x1::signer; use 0x1::string; + + +## Struct `Supply` + + + +
struct Supply has store
+
+ + + +
+Fields + + +
+
+current: optional_aggregator::OptionalAggregator +
+
+ +
+
+maximum: option::Option<u128> +
+
+ +
+
+ + +
+ ## Resource `Metadata` @@ -86,18 +123,12 @@ Define the metadata required of an metadata to be fungible.
-supply: u64 +supply: option::Option<fungible_asset::Supply>
The current supply of the fungible asset.
-maximum: option::Option<u64> -
-
- The maximum supply limit where option::none() means no limit. -
-
name: string::String
@@ -123,14 +154,14 @@ Define the metadata required of an metadata to be fungible. - + -## Resource `FungibleAsset` +## Resource `FungibleStore` -The wallet object that holds fungible assets of a specific type associated with an account. +The store object that holds fungible assets of a specific type associated with an account. -
struct FungibleAsset has key
+
struct FungibleStore has key
 
@@ -153,7 +184,7 @@ The wallet object that holds fungible assets of a specific type associated with The balance of the fungible metadata.
-allow_ungated_transfer: bool +allow_ungated_balance_transfer: bool
Fungible Assets transferring is a common operation, this allows for freezing/unfreezing accounts. @@ -202,15 +233,15 @@ The wallet object that holds fungible assets of a specific type associated with - + -## Struct `ExtractedAsset` +## Struct `FungibleAsset` FungibleAsset can be passed into function for type safety and to guarantee a specific amount. -FungibleAsset cannot be stored directly and will have to be deposited back into a wallet. +FungibleAsset is ephermeral that it cannot be stored directly and will have to be deposited back into a store. -
struct ExtractedAsset
+
struct FungibleAsset
 
@@ -241,7 +272,7 @@ FungibleAsset cannot be stored directly and will have to be deposited back into ## Struct `MintRef` -MintRef can be used to mint the fungible asset into an account's wallet. +MintRef can be used to mint the fungible asset into an account's store.
struct MintRef has drop, store
@@ -326,7 +357,7 @@ BurnRef can be used to burn fungible assets from a given holder account.
 
 ## Struct `DepositEvent`
 
-Emitted when fungible assets are deposited into a wallet.
+Emitted when fungible assets are deposited into a store.
 
 
 
struct DepositEvent has drop, store
@@ -354,7 +385,7 @@ Emitted when fungible assets are deposited into a wallet.
 
 ## Struct `WithdrawEvent`
 
-Emitted when fungible assets are withdrawn from a wallet.
+Emitted when fungible assets are withdrawn from a store.
 
 
 
struct WithdrawEvent has drop, store
@@ -382,7 +413,7 @@ Emitted when fungible assets are withdrawn from a wallet.
 
 ## Struct `SetUngatedTransferEvent`
 
-Emitted when a wallet's ungated (owner) transfer permission is updated.
+Emitted when a store's ungated (owner) transfer permission is updated.
 
 
 
struct SetUngatedTransferEvent has drop, store
@@ -411,6 +442,16 @@ Emitted when a wallet's ungated (owner) transfer permission is updated.
 ## Constants
 
 
+
+
+Maximum possible coin supply.
+
+
+
const MAX_U128: u128 = 340282366920938463463374607431768211455;
+
+ + + Insufficient balance to withdraw or transfer. @@ -441,22 +482,42 @@ Cannot destroy non-empty fungible assets. - + + +Cannot destroy fungible stores with non-zero balance. + + +
const EBALANCE_IS_NOT_ZERO: u64 = 14;
+
+ + + + + +Burn ref and fungible asset do not match. + + +
const EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 13;
+
+ + + + -Burn ref and wallet do not match. +Burn ref and store do not match. -
const EBURN_REF_AND_WALLET_MISMATCH: u64 = 10;
+
const EBURN_REF_AND_STORE_MISMATCH: u64 = 10;
 
- + -Fungible asset and wallet do not match. +Fungible asset and store do not match. -
const EFUNGIBLE_ASSET_AND_WALLET_MISMATCH: u64 = 11;
+
const EFUNGIBLE_ASSET_AND_STORE_MISMATCH: u64 = 11;
 
@@ -471,22 +532,32 @@ The fungible asset's supply has exceeded maximum. - + -The mint ref and the the wallet do not match. +The mint ref and the the store do not match. -
const EMINT_REF_AND_WALLET_MISMATCH: u64 = 7;
+
const EMINT_REF_AND_STORE_MISMATCH: u64 = 7;
 
- + -Account is not the wallet's owner. +Name of the fungible asset metadata is too long -
const ENOT_WALLET_OWNER: u64 = 8;
+
const ENAME_TOO_LONG: u64 = 15;
+
+ + + + + +Account is not the store's owner. + + +
const ENOT_STORE_OWNER: u64 = 8;
 
@@ -501,6 +572,16 @@ More tokens than remaining supply are being burnt. + + +Symbol of the fungible asset metadata is too long + + +
const ESYMBOL_TOO_LONG: u64 = 16;
+
+ + + The transfer ref and the fungible asset do not match. @@ -511,12 +592,12 @@ The transfer ref and the fungible asset do not match. - + -Transfer ref and wallet do not match. +Transfer ref and store do not match. -
const ETRANSFER_REF_AND_WALLET_MISMATCH: u64 = 9;
+
const ETRANSFER_REF_AND_STORE_MISMATCH: u64 = 9;
 
@@ -531,6 +612,24 @@ Account cannot transfer or receive fungible assets. + + + + +
const MAX_NAME_LENGTH: u64 = 32;
+
+ + + + + + + +
const MAX_SYMBOL_LENGTH: u64 = 10;
+
+ + + ## Function `add_fungibility` @@ -539,7 +638,7 @@ Make an existing object fungible by adding the Metadata resource. This returns the capabilities to mint, burn, and transfer. -
public fun add_fungibility(constructor_ref: &object::ConstructorRef, maximum_supply: u64, name: string::String, symbol: string::String, decimals: u8): object::Object<fungible_asset::Metadata>
+
public fun add_fungibility(constructor_ref: &object::ConstructorRef, monitoring_supply_with_maximum: option::Option<option::Option<u128>>, name: string::String, symbol: string::String, decimals: u8): object::Object<fungible_asset::Metadata>
 
@@ -550,21 +649,29 @@ This returns the capabilities to mint, burn, and transfer.
public fun add_fungibility(
     constructor_ref: &ConstructorRef,
-    maximum_supply: u64,
+    monitoring_supply_with_maximum: Option<Option<u128>>,
     name: String,
     symbol: String,
     decimals: u8,
 ): Object<Metadata> {
     let metadata_object_signer = &object::generate_signer(constructor_ref);
-    let converted_maximum = if (maximum_supply == 0) {
-        option::none()
-    } else {
-        option::some(maximum_supply)
-    };
+    let supply = option::map(monitoring_supply_with_maximum, |maximum| {
+        Supply {
+            current: optional_aggregator::new(MAX_U128, false),
+            maximum
+        }
+    });
+    assert!(
+        string::length(&name) <= MAX_NAME_LENGTH,
+        error::invalid_argument(ENAME_TOO_LONG)
+    );
+    assert!(
+        string::length(&symbol) <= MAX_SYMBOL_LENGTH,
+        error::invalid_argument(ESYMBOL_TOO_LONG)
+    );
     move_to(metadata_object_signer,
         Metadata {
-            supply: 0,
-            maximum: converted_maximum,
+            supply,
             name,
             symbol,
             decimals,
@@ -667,7 +774,7 @@ This can only be called at object creation time as constructor_ref is only avail
 Get the current supply from metadata.
 
 
-
public fun supply<T: key>(metadata: object::Object<T>): u64
+
public fun supply<T: key>(metadata: object::Object<T>): option::Option<u128>
 
@@ -676,8 +783,14 @@ Get the current supply from metadata. Implementation -
public fun supply<T: key>(metadata: Object<T>): u64 acquires Metadata {
-    borrow_fungible_metadata(&metadata).supply
+
public fun supply<T: key>(metadata: Object<T>): Option<u128> acquires Metadata {
+    let supply_opt = &borrow_fungible_metadata(&metadata).supply;
+    if (option::is_none(supply_opt)) {
+        option::none()
+    } else {
+        let supply = option::borrow(supply_opt);
+        option::some(optional_aggregator::read(&supply.current))
+    }
 }
 
@@ -692,7 +805,7 @@ Get the current supply from metadata. Get the maximum supply from metadata. -
public fun maximum<T: key>(metadata: object::Object<T>): option::Option<u64>
+
public fun maximum<T: key>(metadata: object::Object<T>): option::Option<u128>
 
@@ -701,8 +814,14 @@ Get the maximum supply from metadata. Implementation -
public fun maximum<T: key>(metadata: Object<T>): Option<u64> acquires Metadata {
-    borrow_fungible_metadata(&metadata).maximum
+
public fun maximum<T: key>(metadata: Object<T>): Option<u128> acquires Metadata {
+    let supply_opt = &borrow_fungible_metadata(&metadata).supply;
+    if (option::is_none(supply_opt)) {
+        option::none()
+    } else {
+        let supply = option::borrow(supply_opt);
+        supply.maximum
+    }
 }
 
@@ -785,14 +904,14 @@ Get the decimals from metadata. - + -## Function `wallet_exists` +## Function `store_exists` -Return whether the provided address has a wallet initialized. +Return whether the provided address has a store initialized. -
public fun wallet_exists(wallet: address): bool
+
public fun store_exists(store: address): bool
 
@@ -801,8 +920,8 @@ Return whether the provided address has a wallet initialized. Implementation -
public fun wallet_exists(wallet: address): bool {
-    exists<FungibleAsset>(wallet)
+
public fun store_exists(store: address): bool {
+    exists<FungibleStore>(store)
 }
 
@@ -817,7 +936,7 @@ Return whether the provided address has a wallet initialized. Return the underlying metadata object -
public fun metadata_from_asset(fa: &fungible_asset::ExtractedAsset): object::Object<fungible_asset::Metadata>
+
public fun metadata_from_asset(fa: &fungible_asset::FungibleAsset): object::Object<fungible_asset::Metadata>
 
@@ -826,7 +945,7 @@ Return the underlying metadata object Implementation -
public fun metadata_from_asset(fa: &ExtractedAsset): Object<Metadata> {
+
public fun metadata_from_asset(fa: &FungibleAsset): Object<Metadata> {
     fa.metadata
 }
 
@@ -835,14 +954,14 @@ Return the underlying metadata object - + -## Function `wallet_metadata` +## Function `store_metadata` Return the underlying metadata object. -
public fun wallet_metadata<T: key>(wallet: object::Object<T>): object::Object<fungible_asset::Metadata>
+
public fun store_metadata<T: key>(store: object::Object<T>): object::Object<fungible_asset::Metadata>
 
@@ -851,8 +970,8 @@ Return the underlying metadata object. Implementation -
public fun wallet_metadata<T: key>(wallet: Object<T>): Object<Metadata> acquires FungibleAsset {
-    borrow_wallet_resource(&wallet).metadata
+
public fun store_metadata<T: key>(store: Object<T>): Object<Metadata> acquires FungibleStore {
+    borrow_store_resource(&store).metadata
 }
 
@@ -867,7 +986,7 @@ Return the underlying metadata object. Return amount of a given fungible asset. -
public fun amount(fa: &fungible_asset::ExtractedAsset): u64
+
public fun amount(fa: &fungible_asset::FungibleAsset): u64
 
@@ -876,7 +995,7 @@ Return amount of a given fungible asset. Implementation -
public fun amount(fa: &ExtractedAsset): u64 {
+
public fun amount(fa: &FungibleAsset): u64 {
     fa.amount
 }
 
@@ -889,10 +1008,10 @@ Return amount of a given fungible asset. ## Function `balance` -Get the balance of a given wallet. +Get the balance of a given store. -
public fun balance<T: key>(wallet: object::Object<T>): u64
+
public fun balance<T: key>(store: object::Object<T>): u64
 
@@ -901,9 +1020,9 @@ Get the balance of a given wallet. Implementation -
public fun balance<T: key>(wallet: Object<T>): u64 acquires FungibleAsset {
-    if (wallet_exists(object::object_address(&wallet))) {
-        borrow_wallet_resource(&wallet).balance
+
public fun balance<T: key>(store: Object<T>): u64 acquires FungibleStore {
+    if (store_exists(object::object_address(&store))) {
+        borrow_store_resource(&store).balance
     } else {
         0
     }
@@ -914,15 +1033,15 @@ Get the balance of a given wallet.
 
 
 
-
+
 
-## Function `ungated_transfer_allowed`
+## Function `ungated_balance_transfer_allowed`
 
-Return whether a wallet can freely send or receive fungible assets.
-If the wallet has not been created, we default to returning true as deposits can be sent to it.
+Return whether a store can freely send or receive fungible assets.
+If the store has not been created, we default to returning true as deposits can be sent to it.
 
 
-
public fun ungated_transfer_allowed<T: key>(wallet: object::Object<T>): bool
+
public fun ungated_balance_transfer_allowed<T: key>(store: object::Object<T>): bool
 
@@ -931,9 +1050,9 @@ If the wallet has not been created, we default to returning true as deposits can Implementation -
public fun ungated_transfer_allowed<T: key>(wallet: Object<T>): bool acquires FungibleAsset {
-    !wallet_exists(object::object_address(&wallet)) ||
-        borrow_wallet_resource(&wallet).allow_ungated_transfer
+
public fun ungated_balance_transfer_allowed<T: key>(store: Object<T>): bool acquires FungibleStore {
+    !store_exists(object::object_address(&store)) ||
+        borrow_store_resource(&store).allow_ungated_balance_transfer
 }
 
@@ -947,7 +1066,7 @@ If the wallet has not been created, we default to returning true as deposits can -
public fun asset_metadata(fa: &fungible_asset::ExtractedAsset): object::Object<fungible_asset::Metadata>
+
public fun asset_metadata(fa: &fungible_asset::FungibleAsset): object::Object<fungible_asset::Metadata>
 
@@ -956,7 +1075,7 @@ If the wallet has not been created, we default to returning true as deposits can Implementation -
public fun asset_metadata(fa: &ExtractedAsset): Object<Metadata> {
+
public fun asset_metadata(fa: &FungibleAsset): Object<Metadata> {
     fa.metadata
 }
 
@@ -1044,7 +1163,7 @@ Get the underlying metadata object from FungibleAsset, FungibleAssetEvents { +) acquires FungibleStore, FungibleAssetEvents { let fa = withdraw(sender, from, amount); deposit(to, fa); } @@ -1072,15 +1191,15 @@ Note: it does not move the underlying object. - + -## Function `create_wallet` +## Function `create_store` -Allow an object to hold a wallet for fungible assets. -Applications can use this to create multiple wallets for isolating fungible assets for different purposes. +Allow an object to hold a store for fungible assets. +Applications can use this to create multiple stores for isolating fungible assets for different purposes. -
public fun create_wallet<T: key>(constructor_ref: &object::ConstructorRef, metadata: object::Object<T>): object::Object<fungible_asset::FungibleAsset>
+
public fun create_store<T: key>(constructor_ref: &object::ConstructorRef, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
 
@@ -1089,26 +1208,62 @@ Applications can use this to create multiple wallets for isolating fungible asse Implementation -
public fun create_wallet<T: key>(
+
public fun create_store<T: key>(
     constructor_ref: &ConstructorRef,
     metadata: Object<T>,
-): Object<FungibleAsset> {
-    let wallet_obj = &object::generate_signer(constructor_ref);
+): Object<FungibleStore> {
+    let store_obj = &object::generate_signer(constructor_ref);
     let metadata = object::convert<T, Metadata>(metadata);
-    move_to(wallet_obj, FungibleAsset {
+    move_to(store_obj, FungibleStore {
         metadata,
         balance: 0,
-        allow_ungated_transfer: true,
+        allow_ungated_balance_transfer: true,
     });
-    move_to(wallet_obj,
+    move_to(store_obj,
         FungibleAssetEvents {
-            deposit_events: object::new_event_handle<DepositEvent>(wallet_obj),
-            withdraw_events: object::new_event_handle<WithdrawEvent>(wallet_obj),
-            set_ungated_transfer_events: object::new_event_handle<SetUngatedTransferEvent>(wallet_obj),
+            deposit_events: object::new_event_handle<DepositEvent>(store_obj),
+            withdraw_events: object::new_event_handle<WithdrawEvent>(store_obj),
+            set_ungated_transfer_events: object::new_event_handle<SetUngatedTransferEvent>(store_obj),
         }
     );
 
-    object::object_from_constructor_ref<FungibleAsset>(constructor_ref)
+    object::object_from_constructor_ref<FungibleStore>(constructor_ref)
+}
+
+ + + + + + + +## Function `remove_store` + + + +
public fun remove_store(delete_ref: &object::DeleteRef)
+
+ + + +
+Implementation + + +
public fun remove_store(delete_ref: &DeleteRef) acquires FungibleStore, FungibleAssetEvents {
+    let store = &object::object_from_delete_ref<FungibleStore>(delete_ref);
+    let addr = object::object_address(store);
+    let FungibleStore { metadata: _, balance, allow_ungated_balance_transfer: _ }
+        = move_from<FungibleStore>(addr);
+    assert!(balance == 0, error::permission_denied(EBALANCE_IS_NOT_ZERO));
+    let FungibleAssetEvents {
+        deposit_events,
+        withdraw_events,
+        set_ungated_transfer_events,
+    } = move_from<FungibleAssetEvents>(addr);
+    event::destroy_handle(deposit_events);
+    event::destroy_handle(withdraw_events);
+    event::destroy_handle(set_ungated_transfer_events);
 }
 
@@ -1120,10 +1275,10 @@ Applications can use this to create multiple wallets for isolating fungible asse ## Function `withdraw` -Withdraw amount of fungible asset from wallet by the owner. +Withdraw amount of fungible asset from store by the owner. -
public fun withdraw<T: key>(owner: &signer, wallet: object::Object<T>, amount: u64): fungible_asset::ExtractedAsset
+
public fun withdraw<T: key>(owner: &signer, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
 
@@ -1134,12 +1289,12 @@ Withdraw amount of fungible asset from wallet by the o
public fun withdraw<T: key>(
     owner: &signer,
-    wallet: Object<T>,
+    store: Object<T>,
     amount: u64,
-): ExtractedAsset acquires FungibleAsset, FungibleAssetEvents {
-    assert!(object::owns(wallet, signer::address_of(owner)), error::permission_denied(ENOT_WALLET_OWNER));
-    assert!(ungated_transfer_allowed(wallet), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED));
-    withdraw_internal(object::object_address(&wallet), amount)
+): FungibleAsset acquires FungibleStore, FungibleAssetEvents {
+    assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_STORE_OWNER));
+    assert!(ungated_balance_transfer_allowed(store), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED));
+    withdraw_internal(object::object_address(&store), amount)
 }
 
@@ -1151,10 +1306,10 @@ Withdraw amount of fungible asset from wallet by the o ## Function `deposit` -Deposit amount of fungible asset to wallet. +Deposit amount of fungible asset to store. -
public fun deposit<T: key>(wallet: object::Object<T>, fa: fungible_asset::ExtractedAsset)
+
public fun deposit<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset)
 
@@ -1163,9 +1318,9 @@ Deposit amount of fungible asset to wallet. Implementation -
public fun deposit<T: key>(wallet: Object<T>, fa: ExtractedAsset) acquires FungibleAsset, FungibleAssetEvents {
-    assert!(ungated_transfer_allowed(wallet), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED));
-    deposit_internal(wallet, fa);
+
public fun deposit<T: key>(store: Object<T>, fa: FungibleAsset) acquires FungibleStore, FungibleAssetEvents {
+    assert!(ungated_balance_transfer_allowed(store), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED));
+    deposit_internal(store, fa);
 }
 
@@ -1180,7 +1335,7 @@ Deposit amount of fungible asset to wallet. Mint the specified amount of fungible asset. -
public fun mint(ref: &fungible_asset::MintRef, amount: u64): fungible_asset::ExtractedAsset
+
public fun mint(ref: &fungible_asset::MintRef, amount: u64): fungible_asset::FungibleAsset
 
@@ -1189,12 +1344,12 @@ Mint the specified amount of fungible asset. Implementation -
public fun mint(ref: &MintRef, amount: u64): ExtractedAsset acquires Metadata {
+
public fun mint(ref: &MintRef, amount: u64): FungibleAsset acquires Metadata {
     assert!(amount > 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO));
     let metadata = ref.metadata;
     increase_supply(&metadata, amount);
 
-    ExtractedAsset {
+    FungibleAsset {
         metadata,
         amount
     }
@@ -1209,10 +1364,10 @@ Mint the specified amount of fungible asset.
 
 ## Function `mint_to`
 
-Mint the specified amount of fungible asset to a destination wallet.
+Mint the specified amount of fungible asset to a destination store.
 
 
-
public fun mint_to<T: key>(ref: &fungible_asset::MintRef, wallet: object::Object<T>, amount: u64)
+
public fun mint_to<T: key>(ref: &fungible_asset::MintRef, store: object::Object<T>, amount: u64)
 
@@ -1221,12 +1376,9 @@ Mint the specified amount of fungible asset to a destination wallet Implementation -
public fun mint_to<T: key>(
-    ref: &MintRef,
-    wallet: Object<T>,
-    amount: u64,
-) acquires Metadata, FungibleAsset, FungibleAssetEvents {
-    deposit(wallet, mint(ref, amount));
+
public fun mint_to<T: key>(ref: &MintRef, store: Object<T>, amount: u64)
+acquires Metadata, FungibleStore, FungibleAssetEvents {
+    deposit(store, mint(ref, amount));
 }
 
@@ -1238,10 +1390,10 @@ Mint the specified amount of fungible asset to a destination wallet ## Function `set_ungated_transfer` -Enable/disable a wallet's ability to do direct transfers of fungible asset. +Enable/disable a store's ability to do direct transfers of fungible asset. -
public fun set_ungated_transfer<T: key>(ref: &fungible_asset::TransferRef, wallet: object::Object<T>, allow: bool)
+
public fun set_ungated_transfer<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, allow: bool)
 
@@ -1252,17 +1404,17 @@ Enable/disable a wallet's ability to do direct transfers of fungible asset.
public fun set_ungated_transfer<T: key>(
     ref: &TransferRef,
-    wallet: Object<T>,
+    store: Object<T>,
     allow: bool,
-) acquires FungibleAsset, FungibleAssetEvents {
+) acquires FungibleStore, FungibleAssetEvents {
     assert!(
-        ref.metadata == wallet_metadata(wallet),
-        error::invalid_argument(ETRANSFER_REF_AND_WALLET_MISMATCH),
+        ref.metadata == store_metadata(store),
+        error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH),
     );
-    let wallet_addr = object::object_address(&wallet);
-    borrow_global_mut<FungibleAsset>(wallet_addr).allow_ungated_transfer = allow;
+    let store_addr = object::object_address(&store);
+    borrow_global_mut<FungibleStore>(store_addr).allow_ungated_balance_transfer = allow;
 
-    let events = borrow_global_mut<FungibleAssetEvents>(wallet_addr);
+    let events = borrow_global_mut<FungibleAssetEvents>(store_addr);
     event::emit_event(&mut events.set_ungated_transfer_events, SetUngatedTransferEvent { transfer_allowed: allow });
 }
 
@@ -1275,10 +1427,9 @@ Enable/disable a wallet's ability to do direct transfers of fungible asset. ## Function `burn` -Burn the amount of fungible metadata from the given wallet. -
public fun burn<T: key>(ref: &fungible_asset::BurnRef, wallet: object::Object<T>, amount: u64)
+
public fun burn(ref: &fungible_asset::BurnRef, fa: fungible_asset::FungibleAsset)
 
@@ -1287,34 +1438,60 @@ Burn the amount of fungible metadata from the given wallet. Implementation -
public fun burn<T: key>(
-    ref: &BurnRef,
-    wallet: Object<T>,
-    amount: u64
-) acquires Metadata, FungibleAsset, FungibleAssetEvents {
-    let metadata = ref.metadata;
-    assert!(metadata == wallet_metadata(wallet), error::invalid_argument(EBURN_REF_AND_WALLET_MISMATCH));
-    let wallet_addr = object::object_address(&wallet);
-    let ExtractedAsset {
+
public fun burn(ref: &BurnRef, fa: FungibleAsset) acquires Metadata {
+    let FungibleAsset {
         metadata,
         amount,
-    } = withdraw_internal(wallet_addr, amount);
+    } = fa;
+    assert!(ref.metadata == metadata, error::invalid_argument(EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH));
     decrease_supply(&metadata, amount);
 }
 
+
+ + + +## Function `burn_from` + +Burn the amount of fungible metadata from the given store. + + +
public fun burn_from<T: key>(ref: &fungible_asset::BurnRef, store: object::Object<T>, amount: u64)
+
+ + + +
+Implementation + + +
public fun burn_from<T: key>(
+    ref: &BurnRef,
+    store: Object<T>,
+    amount: u64
+) acquires Metadata, FungibleStore, FungibleAssetEvents {
+    let metadata = ref.metadata;
+    assert!(metadata == store_metadata(store), error::invalid_argument(EBURN_REF_AND_STORE_MISMATCH));
+    let store_addr = object::object_address(&store);
+    burn(ref, withdraw_internal(store_addr, amount));
+}
+
+ + +
## Function `withdraw_with_ref` -Withdraw amount of fungible metadata from wallet ignoring allow_ungated_transfer. +Withdraw amount of fungible metadata from store ignoring allow_ungated_transfer. -
public fun withdraw_with_ref<T: key>(ref: &fungible_asset::TransferRef, wallet: object::Object<T>, amount: u64): fungible_asset::ExtractedAsset
+
public fun withdraw_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
 
@@ -1325,14 +1502,14 @@ Withdraw amount of fungible metadata from wallet ignor
public fun withdraw_with_ref<T: key>(
     ref: &TransferRef,
-    wallet: Object<T>,
+    store: Object<T>,
     amount: u64
-): ExtractedAsset acquires FungibleAsset, FungibleAssetEvents {
+): FungibleAsset acquires FungibleStore, FungibleAssetEvents {
     assert!(
-        ref.metadata == wallet_metadata(wallet),
-        error::invalid_argument(ETRANSFER_REF_AND_WALLET_MISMATCH),
+        ref.metadata == store_metadata(store),
+        error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH),
     );
-    withdraw_internal(object::object_address(&wallet), amount)
+    withdraw_internal(object::object_address(&store), amount)
 }
 
@@ -1344,10 +1521,10 @@ Withdraw amount of fungible metadata from wallet ignor ## Function `deposit_with_ref` -Deposit fungible asset into wallet ignoring allow_ungated_transfer. +Deposit fungible asset into store ignoring allow_ungated_transfer. -
public fun deposit_with_ref<T: key>(ref: &fungible_asset::TransferRef, wallet: object::Object<T>, fa: fungible_asset::ExtractedAsset)
+
public fun deposit_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, fa: fungible_asset::FungibleAsset)
 
@@ -1358,14 +1535,14 @@ Deposit fungible asset into wallet ignoring allow_ungated_tra
public fun deposit_with_ref<T: key>(
     ref: &TransferRef,
-    wallet: Object<T>,
-    fa: ExtractedAsset
-) acquires FungibleAsset, FungibleAssetEvents {
+    store: Object<T>,
+    fa: FungibleAsset
+) acquires FungibleStore, FungibleAssetEvents {
     assert!(
         ref.metadata == fa.metadata,
         error::invalid_argument(ETRANSFER_REF_AND_FUNGIBLE_ASSET_MISMATCH)
     );
-    deposit_internal(wallet, fa);
+    deposit_internal(store, fa);
 }
 
@@ -1394,7 +1571,7 @@ Transfer ammount of fungible metadata with FungibleAsset, FungibleAssetEvents { +) acquires FungibleStore, FungibleAssetEvents { let fa = withdraw_with_ref(transfer_ref, from, amount); deposit_with_ref(transfer_ref, to, fa); } @@ -1411,7 +1588,7 @@ Transfer ammount of fungible metadata with extract(fungible_asset: &mut fungible_asset::ExtractedAsset, amount: u64): fungible_asset::ExtractedAsset +
public fun extract(fungible_asset: &mut fungible_asset::FungibleAsset, amount: u64): fungible_asset::FungibleAsset
 
@@ -1420,10 +1597,10 @@ Extract a given amount from the given fungible asset and return a new one. Implementation -
public fun extract(fungible_asset: &mut ExtractedAsset, amount: u64): ExtractedAsset {
+
public fun extract(fungible_asset: &mut FungibleAsset, amount: u64): FungibleAsset {
     assert!(fungible_asset.amount >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
     fungible_asset.amount = fungible_asset.amount - amount;
-    ExtractedAsset {
+    FungibleAsset {
         metadata: fungible_asset.metadata,
         amount,
     }
@@ -1442,7 +1619,7 @@ Extract a given amount from the given fungible asset and return a new one.
 to the sum of the two (dst_fungible_asset and src_fungible_asset).
 
 
-
public fun merge(dst_fungible_asset: &mut fungible_asset::ExtractedAsset, src_fungible_asset: fungible_asset::ExtractedAsset)
+
public fun merge(dst_fungible_asset: &mut fungible_asset::FungibleAsset, src_fungible_asset: fungible_asset::FungibleAsset)
 
@@ -1451,8 +1628,8 @@ to the sum of the two (dst_fungible_asset and src_fungible_as Implementation -
public fun merge(dst_fungible_asset: &mut ExtractedAsset, src_fungible_asset: ExtractedAsset) {
-    let ExtractedAsset { metadata: _, amount } = src_fungible_asset;
+
public fun merge(dst_fungible_asset: &mut FungibleAsset, src_fungible_asset: FungibleAsset) {
+    let FungibleAsset { metadata: _, amount } = src_fungible_asset;
     dst_fungible_asset.amount = dst_fungible_asset.amount + amount;
 }
 
@@ -1468,7 +1645,7 @@ to the sum of the two (dst_fungible_asset and src_fungible_as Destroy an empty fungible asset. -
public fun destroy_zero(fungible_asset: fungible_asset::ExtractedAsset)
+
public fun destroy_zero(fungible_asset: fungible_asset::FungibleAsset)
 
@@ -1477,8 +1654,8 @@ Destroy an empty fungible asset. Implementation -
public fun destroy_zero(fungible_asset: ExtractedAsset) {
-    let ExtractedAsset { amount, metadata: _ } = fungible_asset;
+
public fun destroy_zero(fungible_asset: FungibleAsset) {
+    let FungibleAsset { amount, metadata: _ } = fungible_asset;
     assert!(amount == 0, error::invalid_argument(EAMOUNT_IS_NOT_ZERO));
 }
 
@@ -1493,7 +1670,7 @@ Destroy an empty fungible asset. -
fun deposit_internal<T: key>(wallet: object::Object<T>, fa: fungible_asset::ExtractedAsset)
+
fun deposit_internal<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset)
 
@@ -1502,15 +1679,15 @@ Destroy an empty fungible asset. Implementation -
fun deposit_internal<T: key>(wallet: Object<T>, fa: ExtractedAsset) acquires FungibleAsset, FungibleAssetEvents {
-    let ExtractedAsset { metadata, amount } = fa;
-    let wallet_metadata = wallet_metadata(wallet);
-    assert!(metadata == wallet_metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_WALLET_MISMATCH));
-    let wallet_addr = object::object_address(&wallet);
-    let wallet = borrow_global_mut<FungibleAsset>(wallet_addr);
-    wallet.balance = wallet.balance + amount;
+
fun deposit_internal<T: key>(store: Object<T>, fa: FungibleAsset) acquires FungibleStore, FungibleAssetEvents {
+    let FungibleAsset { metadata, amount } = fa;
+    let store_metadata = store_metadata(store);
+    assert!(metadata == store_metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_STORE_MISMATCH));
+    let store_addr = object::object_address(&store);
+    let store = borrow_global_mut<FungibleStore>(store_addr);
+    store.balance = store.balance + amount;
 
-    let events = borrow_global_mut<FungibleAssetEvents>(wallet_addr);
+    let events = borrow_global_mut<FungibleAssetEvents>(store_addr);
     event::emit_event(&mut events.deposit_events, DepositEvent { amount });
 }
 
@@ -1523,10 +1700,10 @@ Destroy an empty fungible asset. ## Function `withdraw_internal` -Extract amount of fungible asset from wallet. +Extract amount of fungible asset from store. -
fun withdraw_internal(wallet_addr: address, amount: u64): fungible_asset::ExtractedAsset
+
fun withdraw_internal(store_addr: address, amount: u64): fungible_asset::FungibleAsset
 
@@ -1536,19 +1713,19 @@ Extract amount of fungible asset from wallet.
fun withdraw_internal(
-    wallet_addr: address,
+    store_addr: address,
     amount: u64,
-): ExtractedAsset acquires FungibleAsset, FungibleAssetEvents {
+): FungibleAsset acquires FungibleStore, FungibleAssetEvents {
     assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO));
-    let wallet = borrow_global_mut<FungibleAsset>(wallet_addr);
-    assert!(wallet.balance >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
-    wallet.balance = wallet.balance - amount;
+    let store = borrow_global_mut<FungibleStore>(store_addr);
+    assert!(store.balance >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
+    store.balance = store.balance - amount;
 
-    let events = borrow_global_mut<FungibleAssetEvents>(wallet_addr);
-    let metadata = wallet.metadata;
+    let events = borrow_global_mut<FungibleAssetEvents>(store_addr);
+    let metadata = store.metadata;
     event::emit_event(&mut events.withdraw_events, WithdrawEvent { amount });
 
-    ExtractedAsset { metadata, amount }
+    FungibleAsset { metadata, amount }
 }
 
@@ -1575,11 +1752,17 @@ Increase the supply of a fungible metadata by minting.
fun increase_supply<T: key>(metadata: &Object<T>, amount: u64) acquires Metadata {
     assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO));
     let fungible_metadata = borrow_fungible_metadata_mut(metadata);
-    if (option::is_some(&fungible_metadata.maximum)) {
-        let max = *option::borrow(&fungible_metadata.maximum);
-        assert!(max - fungible_metadata.supply >= amount, error::invalid_argument(EMAX_SUPPLY_EXCEEDED))
+    if (option::is_some(&fungible_metadata.supply)) {
+        let supply = option::borrow_mut(&mut fungible_metadata.supply);
+        if (option::is_some(&supply.maximum)) {
+            let max = *option::borrow_mut(&mut supply.maximum);
+            assert!(
+                max - optional_aggregator::read(&supply.current) >= (amount as u128),
+                error::invalid_argument(EMAX_SUPPLY_EXCEEDED)
+            )
+        };
+        optional_aggregator::add(&mut supply.current, (amount as u128))
     };
-    fungible_metadata.supply = fungible_metadata.supply + amount;
 }
 
@@ -1606,8 +1789,16 @@ Decrease the supply of a fungible metadata by burning.
fun decrease_supply<T: key>(metadata: &Object<T>, amount: u64) acquires Metadata {
     assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO));
     let fungible_metadata = borrow_fungible_metadata_mut(metadata);
-    assert!(fungible_metadata.supply >= amount, error::invalid_argument(ESUPPLY_UNDERFLOW));
-    fungible_metadata.supply = fungible_metadata.supply - amount;
+    if (option::is_some(&fungible_metadata.supply)) {
+        let supply = option::borrow_mut(&mut fungible_metadata.supply);
+        if (option::is_some(&supply.maximum)) {
+            assert!(
+                optional_aggregator::read(&supply.current) >= (amount as u128),
+                error::invalid_argument(ESUPPLY_UNDERFLOW)
+            )
+        };
+        optional_aggregator::sub(&mut supply.current, (amount as u128))
+    };
 }
 
diff --git a/aptos-move/framework/aptos-framework/doc/object.md b/aptos-move/framework/aptos-framework/doc/object.md index 080b61146c50f..df3620c966689 100644 --- a/aptos-move/framework/aptos-framework/doc/object.md +++ b/aptos-move/framework/aptos-framework/doc/object.md @@ -68,6 +68,7 @@ make it so that a reference to a global object can be returned from a function. - [Function `transfer_raw`](#0x1_object_transfer_raw) - [Function `transfer_to_object`](#0x1_object_transfer_to_object) - [Function `verify_ungated_and_descendant`](#0x1_object_verify_ungated_and_descendant) +- [Function `ungated_transfer_allowed`](#0x1_object_ungated_transfer_allowed) - [Function `owner`](#0x1_object_owner) - [Function `is_owner`](#0x1_object_is_owner) - [Function `owns`](#0x1_object_owns) @@ -581,7 +582,7 @@ Produces an ObjectId from the given address. This is not verified.
public fun address_to_object<T: key>(object: address): Object<T> {
     assert!(exists<ObjectCore>(object), error::not_found(EOBJECT_DOES_NOT_EXIST));
     assert!(exists_at<T>(object), error::not_found(ERESOURCE_DOES_NOT_EXIST));
-    Object<T>{ inner: object }
+    Object<T> { inner: object }
 }
 
@@ -753,7 +754,7 @@ Create a new object whose address is derived based on the creator account addres Derivde objects, similar to named objects, cannot be deleted. -
public fun create_user_derived_object(creator: &signer, derive_ref: &object::DeriveRef): object::ConstructorRef
+
public(friend) fun create_user_derived_object(creator_address: address, derive_ref: &object::DeriveRef): object::ConstructorRef
 
@@ -762,8 +763,7 @@ Derivde objects, similar to named objects, cannot be deleted. Implementation -
public fun create_user_derived_object(creator: &signer, derive_ref: &DeriveRef): ConstructorRef {
-    let creator_address = signer::address_of(creator);
+
public(friend) fun create_user_derived_object(creator_address: address, derive_ref: &DeriveRef): ConstructorRef {
     let obj_addr = create_user_derived_object_address(creator_address, derive_ref.self);
     create_object_internal(creator_address, obj_addr, false)
 }
@@ -1595,13 +1595,42 @@ objects may have cyclic dependencies.
 
 
 
+
+
+
+
+## Function `ungated_transfer_allowed`
+
+Accessors
+Return true if ungated transfer is allowed.
+
+
+
public fun ungated_transfer_allowed<T: key>(object: object::Object<T>): bool
+
+ + + +
+Implementation + + +
public fun ungated_transfer_allowed<T: key>(object: Object<T>): bool acquires ObjectCore {
+    assert!(
+        exists<ObjectCore>(object.inner),
+        error::not_found(EOBJECT_DOES_NOT_EXIST),
+    );
+    borrow_global<ObjectCore>(object.inner).allow_ungated_transfer
+}
+
+ + +
## Function `owner` -Accessors Return the current owner. diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md index d3a35aee771e2..a275a0775cd83 100644 --- a/aptos-move/framework/aptos-framework/doc/overview.md +++ b/aptos-move/framework/aptos-framework/doc/overview.md @@ -36,7 +36,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::multisig_account`](multisig_account.md#0x1_multisig_account) - [`0x1::object`](object.md#0x1_object) - [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator) -- [`0x1::primary_wallet`](primary_wallet.md#0x1_primary_wallet) +- [`0x1::primary_store`](primary_store.md#0x1_primary_store) - [`0x1::reconfiguration`](reconfiguration.md#0x1_reconfiguration) - [`0x1::resource_account`](resource_account.md#0x1_resource_account) - [`0x1::stake`](stake.md#0x1_stake) diff --git a/aptos-move/framework/aptos-framework/doc/primary_store.md b/aptos-move/framework/aptos-framework/doc/primary_store.md new file mode 100644 index 0000000000000..c20bb2f96f314 --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/primary_store.md @@ -0,0 +1,391 @@ + + + +# Module `0x1::primary_store` + +This defines the module for interacting with primary stores of accounts/objects, which have deterministic addresses + + +- [Resource `DeriveRefPod`](#0x1_primary_store_DeriveRefPod) +- [Function `create_primary_store_enabled_fungible_asset`](#0x1_primary_store_create_primary_store_enabled_fungible_asset) +- [Function `ensure_primary_store_exists`](#0x1_primary_store_ensure_primary_store_exists) +- [Function `create_primary_store`](#0x1_primary_store_create_primary_store) +- [Function `primary_store_address`](#0x1_primary_store_primary_store_address) +- [Function `primary_store`](#0x1_primary_store_primary_store) +- [Function `primary_store_exists`](#0x1_primary_store_primary_store_exists) +- [Function `balance`](#0x1_primary_store_balance) +- [Function `ungated_balance_transfer_allowed`](#0x1_primary_store_ungated_balance_transfer_allowed) +- [Function `withdraw`](#0x1_primary_store_withdraw) +- [Function `deposit`](#0x1_primary_store_deposit) +- [Function `transfer`](#0x1_primary_store_transfer) +- [Specification](#@Specification_0) + + +
use 0x1::fungible_asset;
+use 0x1::object;
+use 0x1::option;
+use 0x1::signer;
+use 0x1::string;
+
+ + + + + +## Resource `DeriveRefPod` + +Resource stored on the fungible asset metadata object to allow creating primary stores for it. + + +
struct DeriveRefPod has key
+
+ + + +
+Fields + + +
+
+metadata_derive_ref: object::DeriveRef +
+
+ +
+
+ + +
+ + + +## Function `create_primary_store_enabled_fungible_asset` + +Creators of fungible assets can call this to enable support for creating primary (deterministic) stores for +their users. + + +
public fun create_primary_store_enabled_fungible_asset(constructor_ref: &object::ConstructorRef, monitoring_supply_with_maximum: option::Option<option::Option<u128>>, name: string::String, symbol: string::String, decimals: u8)
+
+ + + +
+Implementation + + +
public fun create_primary_store_enabled_fungible_asset(
+    constructor_ref: &ConstructorRef,
+    monitoring_supply_with_maximum: Option<Option<u128>>,
+    name: String,
+    symbol: String,
+    decimals: u8,
+) {
+    fungible_asset::add_fungibility(constructor_ref, monitoring_supply_with_maximum, name, symbol, decimals);
+    let metadata_obj = &object::generate_signer(constructor_ref);
+    move_to(metadata_obj, DeriveRefPod {
+        metadata_derive_ref: object::generate_derive_ref(constructor_ref),
+    });
+}
+
+ + + +
+ + + +## Function `ensure_primary_store_exists` + + + +
public fun ensure_primary_store_exists<T: key>(owner: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+ + + +
+Implementation + + +
public fun ensure_primary_store_exists<T: key>(
+    owner: address,
+    metadata: Object<T>,
+): Object<FungibleStore> acquires DeriveRefPod {
+    if (!primary_store_exists(owner, metadata)) {
+        create_primary_store(owner, metadata)
+    } else {
+        primary_store(owner, metadata)
+    }
+}
+
+ + + +
+ + + +## Function `create_primary_store` + +Create a primary store object to hold fungible asset for the given address. + + +
public fun create_primary_store<T: key>(owner_addr: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+ + + +
+Implementation + + +
public fun create_primary_store<T: key>(
+    owner_addr: address,
+    metadata: Object<T>,
+): Object<FungibleStore> acquires DeriveRefPod {
+    let metadata_addr = object::object_address(&metadata);
+    let derive_ref = &borrow_global<DeriveRefPod>(metadata_addr).metadata_derive_ref;
+    let constructor_ref = &object::create_user_derived_object(owner_addr, derive_ref);
+
+    // Disable ungated transfer as deterministic stores shouldn't be transferrable.
+    let transfer_ref = &object::generate_transfer_ref(constructor_ref);
+    object::disable_ungated_transfer(transfer_ref);
+
+    fungible_asset::create_store(constructor_ref, metadata)
+}
+
+ + + +
+ + + +## Function `primary_store_address` + + + +
public fun primary_store_address<T: key>(owner: address, metadata: object::Object<T>): address
+
+ + + +
+Implementation + + +
public fun primary_store_address<T: key>(owner: address, metadata: Object<T>): address {
+    let metadata_addr = object::object_address(&metadata);
+    object::create_user_derived_object_address(owner, metadata_addr)
+}
+
+ + + +
+ + + +## Function `primary_store` + + + +
public fun primary_store<T: key>(owner: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleStore>
+
+ + + +
+Implementation + + +
public fun primary_store<T: key>(owner: address, metadata: Object<T>): Object<FungibleStore> {
+    let store = primary_store_address(owner, metadata);
+    object::address_to_object<FungibleStore>(store)
+}
+
+ + + +
+ + + +## Function `primary_store_exists` + + + +
public fun primary_store_exists<T: key>(account: address, metadata: object::Object<T>): bool
+
+ + + +
+Implementation + + +
public fun primary_store_exists<T: key>(account: address, metadata: Object<T>): bool {
+    fungible_asset::store_exists(primary_store_address(account, metadata))
+}
+
+ + + +
+ + + +## Function `balance` + +Get the balance of account's primary store. + + +
public fun balance<T: key>(account: address, metadata: object::Object<T>): u64
+
+ + + +
+Implementation + + +
public fun balance<T: key>(account: address, metadata: Object<T>): u64 {
+    if (primary_store_exists(account, metadata)) {
+        fungible_asset::balance(primary_store(account, metadata))
+    } else {
+        0
+    }
+}
+
+ + + +
+ + + +## Function `ungated_balance_transfer_allowed` + +Return whether the given account's primary store can do direct transfers. + + +
public fun ungated_balance_transfer_allowed<T: key>(account: address, metadata: object::Object<T>): bool
+
+ + + +
+Implementation + + +
public fun ungated_balance_transfer_allowed<T: key>(account: address, metadata: Object<T>): bool {
+    if (primary_store_exists(account, metadata)) {
+        fungible_asset::ungated_balance_transfer_allowed(primary_store(account, metadata))
+    } else {
+        true
+    }
+}
+
+ + + +
+ + + +## Function `withdraw` + +Withdraw amount of fungible asset from store by the owner. + + +
public fun withdraw<T: key>(owner: &signer, metadata: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
+
+ + + +
+Implementation + + +
public fun withdraw<T: key>(owner: &signer, metadata: Object<T>, amount: u64): FungibleAsset {
+    let store = primary_store(signer::address_of(owner), metadata);
+    fungible_asset::withdraw(owner, store, amount)
+}
+
+ + + +
+ + + +## Function `deposit` + +Deposit amount of fungible asset to the given account's primary store. + + +
public fun deposit(owner: address, fa: fungible_asset::FungibleAsset)
+
+ + + +
+Implementation + + +
public fun deposit(owner: address, fa: FungibleAsset) acquires DeriveRefPod {
+    let metadata = fungible_asset::asset_metadata(&fa);
+    let store = ensure_primary_store_exists(owner, metadata);
+    fungible_asset::deposit(store, fa);
+}
+
+ + + +
+ + + +## Function `transfer` + +Transfer amount of fungible asset from sender's primary store to receiver's primary store. + + +
public entry fun transfer<T: key>(sender: &signer, metadata: object::Object<T>, recipient: address, amount: u64)
+
+ + + +
+Implementation + + +
public entry fun transfer<T: key>(
+    sender: &signer,
+    metadata: Object<T>,
+    recipient: address,
+    amount: u64,
+) acquires DeriveRefPod {
+    let sender_store = ensure_primary_store_exists(signer::address_of(sender), metadata);
+    let recipient_store = ensure_primary_store_exists(recipient, metadata);
+    fungible_asset::transfer(sender, sender_store, recipient_store, amount);
+}
+
+ + + +
+ + + +## Specification + + + +
pragma verify = false;
+
+ + +[move-book]: https://aptos.dev/guides/move-guides/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/primary_wallet.md b/aptos-move/framework/aptos-framework/doc/primary_wallet.md deleted file mode 100644 index 30f7114275d10..0000000000000 --- a/aptos-move/framework/aptos-framework/doc/primary_wallet.md +++ /dev/null @@ -1,387 +0,0 @@ - - - -# Module `0x1::primary_wallet` - -This defines the module for interacting with primary wallets of accounts/objects, which have deterministic addresses - - -- [Resource `PrimaryWalletSupport`](#0x1_primary_wallet_PrimaryWalletSupport) -- [Function `create_primary_wallet_enabled_fungible_asset`](#0x1_primary_wallet_create_primary_wallet_enabled_fungible_asset) -- [Function `ensure_primary_wallet_exists`](#0x1_primary_wallet_ensure_primary_wallet_exists) -- [Function `create_primary_wallet`](#0x1_primary_wallet_create_primary_wallet) -- [Function `primary_wallet_address`](#0x1_primary_wallet_primary_wallet_address) -- [Function `primary_wallet`](#0x1_primary_wallet_primary_wallet) -- [Function `primary_wallet_exists`](#0x1_primary_wallet_primary_wallet_exists) -- [Function `balance`](#0x1_primary_wallet_balance) -- [Function `ungated_transfer_allowed`](#0x1_primary_wallet_ungated_transfer_allowed) -- [Function `withdraw`](#0x1_primary_wallet_withdraw) -- [Function `deposit`](#0x1_primary_wallet_deposit) -- [Function `transfer`](#0x1_primary_wallet_transfer) -- [Specification](#@Specification_0) - - -
use 0x1::create_signer;
-use 0x1::fungible_asset;
-use 0x1::object;
-use 0x1::signer;
-use 0x1::string;
-
- - - - - -## Resource `PrimaryWalletSupport` - -Resource stored on the fungible asset metadata object to allow creating primary wallets for it. - - -
struct PrimaryWalletSupport has key
-
- - - -
-Fields - - -
-
-metadata_derive_ref: object::DeriveRef -
-
- -
-
- - -
- - - -## Function `create_primary_wallet_enabled_fungible_asset` - -Creators of fungible assets can call this to enable support for creating primary (deterministic) wallets for -their users. - - -
public fun create_primary_wallet_enabled_fungible_asset(constructor_ref: &object::ConstructorRef, maximum_supply: u64, name: string::String, symbol: string::String, decimals: u8)
-
- - - -
-Implementation - - -
public fun create_primary_wallet_enabled_fungible_asset(
-    constructor_ref: &ConstructorRef,
-    maximum_supply: u64,
-    name: String,
-    symbol: String,
-    decimals: u8,
-) {
-    fungible_asset::add_fungibility(constructor_ref, maximum_supply, name, symbol, decimals);
-    let metadata_obj = &object::generate_signer(constructor_ref);
-    move_to(metadata_obj, PrimaryWalletSupport {
-        metadata_derive_ref: object::generate_derive_ref(constructor_ref),
-    });
-}
-
- - - -
- - - -## Function `ensure_primary_wallet_exists` - - - -
public fun ensure_primary_wallet_exists<T: key>(owner: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleAsset>
-
- - - -
-Implementation - - -
public fun ensure_primary_wallet_exists<T: key>(
-    owner: address,
-    metadata: Object<T>,
-): Object<FungibleAsset> acquires PrimaryWalletSupport {
-    if (!primary_wallet_exists(owner, metadata)) {
-        create_primary_wallet(owner, metadata);
-    };
-    primary_wallet(owner, metadata)
-}
-
- - - -
- - - -## Function `create_primary_wallet` - -Create a primary wallet object to hold fungible asset for the given address. - - -
public fun create_primary_wallet<T: key>(owner_addr: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleAsset>
-
- - - -
-Implementation - - -
public fun create_primary_wallet<T: key>(
-    owner_addr: address,
-    metadata: Object<T>,
-): Object<FungibleAsset> acquires PrimaryWalletSupport {
-    let owner = &create_signer::create_signer(owner_addr);
-    let metadata_addr = object::object_address(&metadata);
-    let derive_ref = &borrow_global<PrimaryWalletSupport>(metadata_addr).metadata_derive_ref;
-    let constructor_ref = &object::create_user_derived_object(owner, derive_ref);
-
-    // Disable ungated transfer as deterministic wallets shouldn't be transferrable.
-    let transfer_ref = &object::generate_transfer_ref(constructor_ref);
-    object::disable_ungated_transfer(transfer_ref);
-
-    fungible_asset::create_wallet(constructor_ref, metadata)
-}
-
- - - -
- - - -## Function `primary_wallet_address` - - - -
public fun primary_wallet_address<T: key>(owner: address, metadata: object::Object<T>): address
-
- - - -
-Implementation - - -
public fun primary_wallet_address<T: key>(owner: address, metadata: Object<T>): address {
-    let metadata_addr = object::object_address(&metadata);
-    object::create_user_derived_object_address(owner, metadata_addr)
-}
-
- - - -
- - - -## Function `primary_wallet` - - - -
public fun primary_wallet<T: key>(owner: address, metadata: object::Object<T>): object::Object<fungible_asset::FungibleAsset>
-
- - - -
-Implementation - - -
public fun primary_wallet<T: key>(owner: address, metadata: Object<T>): Object<FungibleAsset> {
-    let wallet = primary_wallet_address(owner, metadata);
-    object::address_to_object<FungibleAsset>(wallet)
-}
-
- - - -
- - - -## Function `primary_wallet_exists` - - - -
public fun primary_wallet_exists<T: key>(account: address, metadata: object::Object<T>): bool
-
- - - -
-Implementation - - -
public fun primary_wallet_exists<T: key>(account: address, metadata: Object<T>): bool {
-    fungible_asset::wallet_exists(primary_wallet_address(account, metadata))
-}
-
- - - -
- - - -## Function `balance` - -Get the balance of account's primary wallet. - - -
public fun balance<T: key>(account: address, metadata: object::Object<T>): u64
-
- - - -
-Implementation - - -
public fun balance<T: key>(account: address, metadata: Object<T>): u64 {
-    if (primary_wallet_exists(account, metadata)) {
-        fungible_asset::balance(primary_wallet(account, metadata))
-    } else {
-        0
-    }
-}
-
- - - -
- - - -## Function `ungated_transfer_allowed` - -Return whether the given account's primary wallet can do direct transfers. - - -
public fun ungated_transfer_allowed<T: key>(account: address, metadata: object::Object<T>): bool
-
- - - -
-Implementation - - -
public fun ungated_transfer_allowed<T: key>(account: address, metadata: Object<T>): bool {
-    fungible_asset::ungated_transfer_allowed(primary_wallet(account, metadata))
-}
-
- - - -
- - - -## Function `withdraw` - -Withdraw amount of fungible asset from wallet by the owner. - - -
public fun withdraw<T: key>(owner: &signer, metadata: object::Object<T>, amount: u64): fungible_asset::ExtractedAsset
-
- - - -
-Implementation - - -
public fun withdraw<T: key>(owner: &signer, metadata: Object<T>, amount: u64): ExtractedAsset {
-    let wallet = primary_wallet(signer::address_of(owner), metadata);
-    fungible_asset::withdraw(owner, wallet, amount)
-}
-
- - - -
- - - -## Function `deposit` - -Deposit amount of fungible asset to the given account's primary wallet. - - -
public fun deposit(owner: address, fa: fungible_asset::ExtractedAsset)
-
- - - -
-Implementation - - -
public fun deposit(owner: address, fa: ExtractedAsset) acquires PrimaryWalletSupport {
-    let metadata = fungible_asset::asset_metadata(&fa);
-    let wallet = ensure_primary_wallet_exists(owner, metadata);
-    fungible_asset::deposit(wallet, fa);
-}
-
- - - -
- - - -## Function `transfer` - -Transfer amount of fungible asset from sender's primary wallet to receiver's primary wallet. - - -
public entry fun transfer<T: key>(sender: &signer, metadata: object::Object<T>, recipient: address, amount: u64)
-
- - - -
-Implementation - - -
public entry fun transfer<T: key>(
-    sender: &signer,
-    metadata: Object<T>,
-    recipient: address,
-    amount: u64,
-) acquires PrimaryWalletSupport {
-    let sender_wallet = ensure_primary_wallet_exists(signer::address_of(sender), metadata);
-    let recipient_wallet = ensure_primary_wallet_exists(recipient, metadata);
-    fungible_asset::transfer(sender, sender_wallet, recipient_wallet, amount);
-}
-
- - - -
- - - -## Specification - - - -
pragma verify=false;
-
- - -[move-book]: https://aptos.dev/guides/move-guides/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move b/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move index 4957770a5522b..f3a545600edbc 100644 --- a/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move +++ b/aptos-move/framework/aptos-framework/sources/aggregator/optional_aggregator.move @@ -8,6 +8,7 @@ module aptos_framework::optional_aggregator { use aptos_framework::aggregator::{Self, Aggregator}; friend aptos_framework::coin; + friend aptos_framework::fungible_asset; /// The value of aggregator underflows (goes below zero). Raised by native code. const EAGGREGATOR_OVERFLOW: u64 = 1; diff --git a/aptos-move/framework/aptos-framework/sources/create_signer.move b/aptos-move/framework/aptos-framework/sources/create_signer.move index 3ae2ea6c99e75..154c68b32ac54 100644 --- a/aptos-move/framework/aptos-framework/sources/create_signer.move +++ b/aptos-move/framework/aptos-framework/sources/create_signer.move @@ -14,7 +14,6 @@ module aptos_framework::create_signer { friend aptos_framework::genesis; friend aptos_framework::multisig_account; friend aptos_framework::object; - friend aptos_framework::primary_wallet; public(friend) native fun create_signer(addr: address): signer; } diff --git a/aptos-move/framework/aptos-framework/sources/fungible_asset.move b/aptos-move/framework/aptos-framework/sources/fungible_asset.move index 23b4615e16e20..deaf99cd8ba5f 100644 --- a/aptos-move/framework/aptos-framework/sources/fungible_asset.move +++ b/aptos-move/framework/aptos-framework/sources/fungible_asset.move @@ -2,7 +2,9 @@ /// metadata object can be any object that equipped with `Metadata` resource. module aptos_framework::fungible_asset { use aptos_framework::event; - use aptos_framework::object::{Self, Object, ConstructorRef}; + use aptos_framework::object::{Self, Object, ConstructorRef, DeleteRef}; + use aptos_framework::optional_aggregator::{Self, OptionalAggregator}; + use std::string; use std::error; use std::option::{Self, Option}; @@ -21,26 +23,47 @@ module aptos_framework::fungible_asset { const EMAX_SUPPLY_EXCEEDED: u64 = 5; /// More tokens than remaining supply are being burnt. const ESUPPLY_UNDERFLOW: u64 = 6; - /// The mint ref and the the wallet do not match. - const EMINT_REF_AND_WALLET_MISMATCH: u64 = 7; - /// Account is not the wallet's owner. - const ENOT_WALLET_OWNER: u64 = 8; - /// Transfer ref and wallet do not match. - const ETRANSFER_REF_AND_WALLET_MISMATCH: u64 = 9; - /// Burn ref and wallet do not match. - const EBURN_REF_AND_WALLET_MISMATCH: u64 = 10; - /// Fungible asset and wallet do not match. - const EFUNGIBLE_ASSET_AND_WALLET_MISMATCH: u64 = 11; + /// The mint ref and the the store do not match. + const EMINT_REF_AND_STORE_MISMATCH: u64 = 7; + /// Account is not the store's owner. + const ENOT_STORE_OWNER: u64 = 8; + /// Transfer ref and store do not match. + const ETRANSFER_REF_AND_STORE_MISMATCH: u64 = 9; + /// Burn ref and store do not match. + const EBURN_REF_AND_STORE_MISMATCH: u64 = 10; + /// Fungible asset and store do not match. + const EFUNGIBLE_ASSET_AND_STORE_MISMATCH: u64 = 11; /// Cannot destroy non-empty fungible assets. const EAMOUNT_IS_NOT_ZERO: u64 = 12; + /// Burn ref and fungible asset do not match. + const EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 13; + /// Cannot destroy fungible stores with non-zero balance. + const EBALANCE_IS_NOT_ZERO: u64 = 14; + /// Name of the fungible asset metadata is too long + const ENAME_TOO_LONG: u64 = 15; + /// Symbol of the fungible asset metadata is too long + const ESYMBOL_TOO_LONG: u64 = 16; + + // + // Constants + // + + const MAX_NAME_LENGTH: u64 = 32; + const MAX_SYMBOL_LENGTH: u64 = 10; + + /// Maximum possible coin supply. + const MAX_U128: u128 = 340282366920938463463374607431768211455; + + struct Supply has store { + current: OptionalAggregator, + maximum: Option, + } #[resource_group_member(group = aptos_framework::object::ObjectGroup)] /// Define the metadata required of an metadata to be fungible. struct Metadata has key { /// The current supply of the fungible asset. - supply: u64, - /// The maximum supply limit where `option::none()` means no limit. - maximum: Option, + supply: Option, /// Name of the fungible metadata, i.e., "USDT". name: String, /// Symbol of the fungible metadata, usually a shorter version of the name. @@ -53,14 +76,14 @@ module aptos_framework::fungible_asset { } #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - /// The wallet object that holds fungible assets of a specific type associated with an account. - struct FungibleAsset has key { + /// The store object that holds fungible assets of a specific type associated with an account. + struct FungibleStore has key { /// The address of the base metadata object. metadata: Object, /// The balance of the fungible metadata. balance: u64, /// Fungible Assets transferring is a common operation, this allows for freezing/unfreezing accounts. - allow_ungated_transfer: bool, + allow_ungated_balance_transfer: bool, } #[resource_group_member(group = aptos_framework::object::ObjectGroup)] @@ -71,13 +94,13 @@ module aptos_framework::fungible_asset { } /// FungibleAsset can be passed into function for type safety and to guarantee a specific amount. - /// FungibleAsset cannot be stored directly and will have to be deposited back into a wallet. - struct ExtractedAsset { + /// FungibleAsset is ephermeral that it cannot be stored directly and will have to be deposited back into a store. + struct FungibleAsset { metadata: Object, amount: u64, } - /// MintRef can be used to mint the fungible asset into an account's wallet. + /// MintRef can be used to mint the fungible asset into an account's store. struct MintRef has drop, store { metadata: Object } @@ -93,17 +116,17 @@ module aptos_framework::fungible_asset { metadata: Object } - /// Emitted when fungible assets are deposited into a wallet. + /// Emitted when fungible assets are deposited into a store. struct DepositEvent has drop, store { amount: u64, } - /// Emitted when fungible assets are withdrawn from a wallet. + /// Emitted when fungible assets are withdrawn from a store. struct WithdrawEvent has drop, store { amount: u64, } - /// Emitted when a wallet's ungated (owner) transfer permission is updated. + /// Emitted when a store's ungated (owner) transfer permission is updated. struct SetUngatedTransferEvent has drop, store { transfer_allowed: bool, } @@ -112,21 +135,29 @@ module aptos_framework::fungible_asset { /// This returns the capabilities to mint, burn, and transfer. public fun add_fungibility( constructor_ref: &ConstructorRef, - maximum_supply: u64, + monitoring_supply_with_maximum: Option>, name: String, symbol: String, decimals: u8, ): Object { let metadata_object_signer = &object::generate_signer(constructor_ref); - let converted_maximum = if (maximum_supply == 0) { - option::none() - } else { - option::some(maximum_supply) - }; + let supply = option::map(monitoring_supply_with_maximum, |maximum| { + Supply { + current: optional_aggregator::new(MAX_U128, false), + maximum + } + }); + assert!( + string::length(&name) <= MAX_NAME_LENGTH, + error::invalid_argument(ENAME_TOO_LONG) + ); + assert!( + string::length(&symbol) <= MAX_SYMBOL_LENGTH, + error::invalid_argument(ESYMBOL_TOO_LONG) + ); move_to(metadata_object_signer, Metadata { - supply: 0, - maximum: converted_maximum, + supply, name, symbol, decimals, @@ -159,14 +190,26 @@ module aptos_framework::fungible_asset { #[view] /// Get the current supply from `metadata`. - public fun supply(metadata: Object): u64 acquires Metadata { - borrow_fungible_metadata(&metadata).supply + public fun supply(metadata: Object): Option acquires Metadata { + let supply_opt = &borrow_fungible_metadata(&metadata).supply; + if (option::is_none(supply_opt)) { + option::none() + } else { + let supply = option::borrow(supply_opt); + option::some(optional_aggregator::read(&supply.current)) + } } #[view] /// Get the maximum supply from `metadata`. - public fun maximum(metadata: Object): Option acquires Metadata { - borrow_fungible_metadata(&metadata).maximum + public fun maximum(metadata: Object): Option acquires Metadata { + let supply_opt = &borrow_fungible_metadata(&metadata).supply; + if (option::is_none(supply_opt)) { + option::none() + } else { + let supply = option::borrow(supply_opt); + supply.maximum + } } #[view] @@ -188,46 +231,46 @@ module aptos_framework::fungible_asset { } #[view] - /// Return whether the provided address has a wallet initialized. - public fun wallet_exists(wallet: address): bool { - exists(wallet) + /// Return whether the provided address has a store initialized. + public fun store_exists(store: address): bool { + exists(store) } /// Return the underlying metadata object - public fun metadata_from_asset(fa: &ExtractedAsset): Object { + public fun metadata_from_asset(fa: &FungibleAsset): Object { fa.metadata } #[view] /// Return the underlying metadata object. - public fun wallet_metadata(wallet: Object): Object acquires FungibleAsset { - borrow_wallet_resource(&wallet).metadata + public fun store_metadata(store: Object): Object acquires FungibleStore { + borrow_store_resource(&store).metadata } /// Return `amount` of a given fungible asset. - public fun amount(fa: &ExtractedAsset): u64 { + public fun amount(fa: &FungibleAsset): u64 { fa.amount } #[view] - /// Get the balance of a given wallet. - public fun balance(wallet: Object): u64 acquires FungibleAsset { - if (wallet_exists(object::object_address(&wallet))) { - borrow_wallet_resource(&wallet).balance + /// Get the balance of a given store. + public fun balance(store: Object): u64 acquires FungibleStore { + if (store_exists(object::object_address(&store))) { + borrow_store_resource(&store).balance } else { 0 } } #[view] - /// Return whether a wallet can freely send or receive fungible assets. - /// If the wallet has not been created, we default to returning true as deposits can be sent to it. - public fun ungated_transfer_allowed(wallet: Object): bool acquires FungibleAsset { - !wallet_exists(object::object_address(&wallet)) || - borrow_wallet_resource(&wallet).allow_ungated_transfer + /// Return whether a store can freely send or receive fungible assets. + /// If the store has not been created, we default to returning true as deposits can be sent to it. + public fun ungated_balance_transfer_allowed(store: Object): bool acquires FungibleStore { + !store_exists(object::object_address(&store)) || + borrow_store_resource(&store).allow_ungated_balance_transfer } - public fun asset_metadata(fa: &ExtractedAsset): Object { + public fun asset_metadata(fa: &FungibleAsset): Object { fa.metadata } @@ -246,137 +289,155 @@ module aptos_framework::fungible_asset { ref.metadata } - /// Transfer `amount` of fungible asset from `from_wallet`, which should be owned by `sender`, to `receiver`. + /// Transfer `amount` of fungible asset from `from_store`, which should be owned by `sender`, to `receiver`. /// Note: it does not move the underlying object. public entry fun transfer( sender: &signer, from: Object, to: Object, amount: u64, - ) acquires FungibleAsset, FungibleAssetEvents { + ) acquires FungibleStore, FungibleAssetEvents { let fa = withdraw(sender, from, amount); deposit(to, fa); } - /// Allow an object to hold a wallet for fungible assets. - /// Applications can use this to create multiple wallets for isolating fungible assets for different purposes. - public fun create_wallet( + /// Allow an object to hold a store for fungible assets. + /// Applications can use this to create multiple stores for isolating fungible assets for different purposes. + public fun create_store( constructor_ref: &ConstructorRef, metadata: Object, - ): Object { - let wallet_obj = &object::generate_signer(constructor_ref); + ): Object { + let store_obj = &object::generate_signer(constructor_ref); let metadata = object::convert(metadata); - move_to(wallet_obj, FungibleAsset { + move_to(store_obj, FungibleStore { metadata, balance: 0, - allow_ungated_transfer: true, + allow_ungated_balance_transfer: true, }); - move_to(wallet_obj, + move_to(store_obj, FungibleAssetEvents { - deposit_events: object::new_event_handle(wallet_obj), - withdraw_events: object::new_event_handle(wallet_obj), - set_ungated_transfer_events: object::new_event_handle(wallet_obj), + deposit_events: object::new_event_handle(store_obj), + withdraw_events: object::new_event_handle(store_obj), + set_ungated_transfer_events: object::new_event_handle(store_obj), } ); - object::object_from_constructor_ref(constructor_ref) + object::object_from_constructor_ref(constructor_ref) } - /// Withdraw `amount` of fungible asset from `wallet` by the owner. + public fun remove_store(delete_ref: &DeleteRef) acquires FungibleStore, FungibleAssetEvents { + let store = &object::object_from_delete_ref(delete_ref); + let addr = object::object_address(store); + let FungibleStore { metadata: _, balance, allow_ungated_balance_transfer: _ } + = move_from(addr); + assert!(balance == 0, error::permission_denied(EBALANCE_IS_NOT_ZERO)); + let FungibleAssetEvents { + deposit_events, + withdraw_events, + set_ungated_transfer_events, + } = move_from(addr); + event::destroy_handle(deposit_events); + event::destroy_handle(withdraw_events); + event::destroy_handle(set_ungated_transfer_events); + } + + /// Withdraw `amount` of fungible asset from `store` by the owner. public fun withdraw( owner: &signer, - wallet: Object, + store: Object, amount: u64, - ): ExtractedAsset acquires FungibleAsset, FungibleAssetEvents { - assert!(object::owns(wallet, signer::address_of(owner)), error::permission_denied(ENOT_WALLET_OWNER)); - assert!(ungated_transfer_allowed(wallet), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED)); - withdraw_internal(object::object_address(&wallet), amount) + ): FungibleAsset acquires FungibleStore, FungibleAssetEvents { + assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_STORE_OWNER)); + assert!(ungated_balance_transfer_allowed(store), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED)); + withdraw_internal(object::object_address(&store), amount) } - /// Deposit `amount` of fungible asset to `wallet`. - public fun deposit(wallet: Object, fa: ExtractedAsset) acquires FungibleAsset, FungibleAssetEvents { - assert!(ungated_transfer_allowed(wallet), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED)); - deposit_internal(wallet, fa); + /// Deposit `amount` of fungible asset to `store`. + public fun deposit(store: Object, fa: FungibleAsset) acquires FungibleStore, FungibleAssetEvents { + assert!(ungated_balance_transfer_allowed(store), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED)); + deposit_internal(store, fa); } /// Mint the specified `amount` of fungible asset. - public fun mint(ref: &MintRef, amount: u64): ExtractedAsset acquires Metadata { + public fun mint(ref: &MintRef, amount: u64): FungibleAsset acquires Metadata { assert!(amount > 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO)); let metadata = ref.metadata; increase_supply(&metadata, amount); - ExtractedAsset { + FungibleAsset { metadata, amount } } - /// Mint the specified `amount` of fungible asset to a destination wallet. - public fun mint_to( - ref: &MintRef, - wallet: Object, - amount: u64, - ) acquires Metadata, FungibleAsset, FungibleAssetEvents { - deposit(wallet, mint(ref, amount)); + /// Mint the specified `amount` of fungible asset to a destination store. + public fun mint_to(ref: &MintRef, store: Object, amount: u64) + acquires Metadata, FungibleStore, FungibleAssetEvents { + deposit(store, mint(ref, amount)); } - /// Enable/disable a wallet's ability to do direct transfers of fungible asset. + /// Enable/disable a store's ability to do direct transfers of fungible asset. public fun set_ungated_transfer( ref: &TransferRef, - wallet: Object, + store: Object, allow: bool, - ) acquires FungibleAsset, FungibleAssetEvents { + ) acquires FungibleStore, FungibleAssetEvents { assert!( - ref.metadata == wallet_metadata(wallet), - error::invalid_argument(ETRANSFER_REF_AND_WALLET_MISMATCH), + ref.metadata == store_metadata(store), + error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH), ); - let wallet_addr = object::object_address(&wallet); - borrow_global_mut(wallet_addr).allow_ungated_transfer = allow; + let store_addr = object::object_address(&store); + borrow_global_mut(store_addr).allow_ungated_balance_transfer = allow; - let events = borrow_global_mut(wallet_addr); + let events = borrow_global_mut(store_addr); event::emit_event(&mut events.set_ungated_transfer_events, SetUngatedTransferEvent { transfer_allowed: allow }); } - /// Burn the `amount` of fungible metadata from the given wallet. - public fun burn( - ref: &BurnRef, - wallet: Object, - amount: u64 - ) acquires Metadata, FungibleAsset, FungibleAssetEvents { - let metadata = ref.metadata; - assert!(metadata == wallet_metadata(wallet), error::invalid_argument(EBURN_REF_AND_WALLET_MISMATCH)); - let wallet_addr = object::object_address(&wallet); - let ExtractedAsset { + public fun burn(ref: &BurnRef, fa: FungibleAsset) acquires Metadata { + let FungibleAsset { metadata, amount, - } = withdraw_internal(wallet_addr, amount); + } = fa; + assert!(ref.metadata == metadata, error::invalid_argument(EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH)); decrease_supply(&metadata, amount); } - /// Withdraw `amount` of fungible metadata from `wallet` ignoring `allow_ungated_transfer`. + /// Burn the `amount` of fungible metadata from the given store. + public fun burn_from( + ref: &BurnRef, + store: Object, + amount: u64 + ) acquires Metadata, FungibleStore, FungibleAssetEvents { + let metadata = ref.metadata; + assert!(metadata == store_metadata(store), error::invalid_argument(EBURN_REF_AND_STORE_MISMATCH)); + let store_addr = object::object_address(&store); + burn(ref, withdraw_internal(store_addr, amount)); + } + + /// Withdraw `amount` of fungible metadata from `store` ignoring `allow_ungated_transfer`. public fun withdraw_with_ref( ref: &TransferRef, - wallet: Object, + store: Object, amount: u64 - ): ExtractedAsset acquires FungibleAsset, FungibleAssetEvents { + ): FungibleAsset acquires FungibleStore, FungibleAssetEvents { assert!( - ref.metadata == wallet_metadata(wallet), - error::invalid_argument(ETRANSFER_REF_AND_WALLET_MISMATCH), + ref.metadata == store_metadata(store), + error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH), ); - withdraw_internal(object::object_address(&wallet), amount) + withdraw_internal(object::object_address(&store), amount) } - /// Deposit fungible asset into `wallet` ignoring `allow_ungated_transfer`. + /// Deposit fungible asset into `store` ignoring `allow_ungated_transfer`. public fun deposit_with_ref( ref: &TransferRef, - wallet: Object, - fa: ExtractedAsset - ) acquires FungibleAsset, FungibleAssetEvents { + store: Object, + fa: FungibleAsset + ) acquires FungibleStore, FungibleAssetEvents { assert!( ref.metadata == fa.metadata, error::invalid_argument(ETRANSFER_REF_AND_FUNGIBLE_ASSET_MISMATCH) ); - deposit_internal(wallet, fa); + deposit_internal(store, fa); } /// Transfer `ammount` of fungible metadata with `TransferRef` even ungated transfer is disabled. @@ -385,16 +446,16 @@ module aptos_framework::fungible_asset { from: Object, to: Object, amount: u64, - ) acquires FungibleAsset, FungibleAssetEvents { + ) acquires FungibleStore, FungibleAssetEvents { let fa = withdraw_with_ref(transfer_ref, from, amount); deposit_with_ref(transfer_ref, to, fa); } /// Extract a given amount from the given fungible asset and return a new one. - public fun extract(fungible_asset: &mut ExtractedAsset, amount: u64): ExtractedAsset { + public fun extract(fungible_asset: &mut FungibleAsset, amount: u64): FungibleAsset { assert!(fungible_asset.amount >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE)); fungible_asset.amount = fungible_asset.amount - amount; - ExtractedAsset { + FungibleAsset { metadata: fungible_asset.metadata, amount, } @@ -402,63 +463,77 @@ module aptos_framework::fungible_asset { /// "Merges" the two given fungible assets. The coin passed in as `dst_fungible_asset` will have a value equal /// to the sum of the two (`dst_fungible_asset` and `src_fungible_asset`). - public fun merge(dst_fungible_asset: &mut ExtractedAsset, src_fungible_asset: ExtractedAsset) { - let ExtractedAsset { metadata: _, amount } = src_fungible_asset; + public fun merge(dst_fungible_asset: &mut FungibleAsset, src_fungible_asset: FungibleAsset) { + let FungibleAsset { metadata: _, amount } = src_fungible_asset; dst_fungible_asset.amount = dst_fungible_asset.amount + amount; } /// Destroy an empty fungible asset. - public fun destroy_zero(fungible_asset: ExtractedAsset) { - let ExtractedAsset { amount, metadata: _ } = fungible_asset; + public fun destroy_zero(fungible_asset: FungibleAsset) { + let FungibleAsset { amount, metadata: _ } = fungible_asset; assert!(amount == 0, error::invalid_argument(EAMOUNT_IS_NOT_ZERO)); } - fun deposit_internal(wallet: Object, fa: ExtractedAsset) acquires FungibleAsset, FungibleAssetEvents { - let ExtractedAsset { metadata, amount } = fa; - let wallet_metadata = wallet_metadata(wallet); - assert!(metadata == wallet_metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_WALLET_MISMATCH)); - let wallet_addr = object::object_address(&wallet); - let wallet = borrow_global_mut(wallet_addr); - wallet.balance = wallet.balance + amount; + fun deposit_internal(store: Object, fa: FungibleAsset) acquires FungibleStore, FungibleAssetEvents { + let FungibleAsset { metadata, amount } = fa; + let store_metadata = store_metadata(store); + assert!(metadata == store_metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_STORE_MISMATCH)); + let store_addr = object::object_address(&store); + let store = borrow_global_mut(store_addr); + store.balance = store.balance + amount; - let events = borrow_global_mut(wallet_addr); + let events = borrow_global_mut(store_addr); event::emit_event(&mut events.deposit_events, DepositEvent { amount }); } - /// Extract `amount` of fungible asset from `wallet`. + /// Extract `amount` of fungible asset from `store`. fun withdraw_internal( - wallet_addr: address, + store_addr: address, amount: u64, - ): ExtractedAsset acquires FungibleAsset, FungibleAssetEvents { + ): FungibleAsset acquires FungibleStore, FungibleAssetEvents { assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO)); - let wallet = borrow_global_mut(wallet_addr); - assert!(wallet.balance >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE)); - wallet.balance = wallet.balance - amount; + let store = borrow_global_mut(store_addr); + assert!(store.balance >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE)); + store.balance = store.balance - amount; - let events = borrow_global_mut(wallet_addr); - let metadata = wallet.metadata; + let events = borrow_global_mut(store_addr); + let metadata = store.metadata; event::emit_event(&mut events.withdraw_events, WithdrawEvent { amount }); - ExtractedAsset { metadata, amount } + FungibleAsset { metadata, amount } } /// Increase the supply of a fungible metadata by minting. fun increase_supply(metadata: &Object, amount: u64) acquires Metadata { assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO)); let fungible_metadata = borrow_fungible_metadata_mut(metadata); - if (option::is_some(&fungible_metadata.maximum)) { - let max = *option::borrow(&fungible_metadata.maximum); - assert!(max - fungible_metadata.supply >= amount, error::invalid_argument(EMAX_SUPPLY_EXCEEDED)) + if (option::is_some(&fungible_metadata.supply)) { + let supply = option::borrow_mut(&mut fungible_metadata.supply); + if (option::is_some(&supply.maximum)) { + let max = *option::borrow_mut(&mut supply.maximum); + assert!( + max - optional_aggregator::read(&supply.current) >= (amount as u128), + error::invalid_argument(EMAX_SUPPLY_EXCEEDED) + ) + }; + optional_aggregator::add(&mut supply.current, (amount as u128)) }; - fungible_metadata.supply = fungible_metadata.supply + amount; } /// Decrease the supply of a fungible metadata by burning. fun decrease_supply(metadata: &Object, amount: u64) acquires Metadata { assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO)); let fungible_metadata = borrow_fungible_metadata_mut(metadata); - assert!(fungible_metadata.supply >= amount, error::invalid_argument(ESUPPLY_UNDERFLOW)); - fungible_metadata.supply = fungible_metadata.supply - amount; + if (option::is_some(&fungible_metadata.supply)) { + let supply = option::borrow_mut(&mut fungible_metadata.supply); + if (option::is_some(&supply.maximum)) { + assert!( + optional_aggregator::read(&supply.current) >= (amount as u128), + error::invalid_argument(ESUPPLY_UNDERFLOW) + ) + }; + optional_aggregator::sub(&mut supply.current, (amount as u128)) + }; } inline fun borrow_fungible_metadata( @@ -475,12 +550,10 @@ module aptos_framework::fungible_asset { borrow_global_mut(addr) } - inline fun borrow_wallet_resource(wallet: &Object): &FungibleAsset acquires FungibleAsset { - borrow_global(object::object_address(wallet)) + inline fun borrow_store_resource(store: &Object): &FungibleStore acquires FungibleStore { + borrow_global(object::object_address(store)) } - #[test_only] - use std::string; #[test_only] use aptos_framework::account; @@ -503,7 +576,7 @@ module aptos_framework::fungible_asset { public fun init_test_metadata(constructor_ref: &ConstructorRef): (MintRef, TransferRef, BurnRef) { add_fungibility( constructor_ref, - 100 /* max supply */, + option::some(option::some(100)) /* max supply */, string::utf8(b"USDA"), string::utf8(b"$$$"), 0 @@ -524,28 +597,28 @@ module aptos_framework::fungible_asset { } #[test_only] - public fun create_test_wallet(owner: &signer, metadata: Object): Object { + public fun create_test_store(owner: &signer, metadata: Object): Object { let owner_addr = signer::address_of(owner); if (!account::exists_at(owner_addr)) { account::create_account_for_test(owner_addr); }; - create_wallet(&object::create_object_from_account(owner), metadata) + create_store(&object::create_object_from_account(owner), metadata) } #[test(creator = @0xcafe)] fun test_metadata_basic_flow(creator: &signer) acquires Metadata { let (creator_ref, asset) = create_test_token(creator); init_test_metadata(&creator_ref); - assert!(supply(asset) == 0, 1); + assert!(supply(asset) == option::some(0), 1); assert!(maximum(asset) == option::some(100), 2); assert!(name(asset) == string::utf8(b"USDA"), 3); assert!(symbol(asset) == string::utf8(b"$$$"), 4); assert!(decimals(asset) == 0, 5); increase_supply(&asset, 50); - assert!(supply(asset) == 50, 6); + assert!(supply(asset) == option::some(50), 6); decrease_supply(&asset, 30); - assert!(supply(asset) == 20, 7); + assert!(supply(asset) == option::some(20), 7); } #[test(creator = @0xcafe)] @@ -564,69 +637,92 @@ module aptos_framework::fungible_asset { decrease_supply(&asset, 1); } + #[test(creator = @0xcafe)] + fun test_create_and_remove_store(creator: &signer) acquires FungibleStore, FungibleAssetEvents { + let (_, _, _, asset) = create_fungible_asset(creator); + let creator_ref = object::create_object_from_account(creator); + create_store(&creator_ref, asset); + let delete_ref = object::generate_delete_ref(&creator_ref); + remove_store(&delete_ref); + } + #[test(creator = @0xcafe, aaron = @0xface)] fun test_e2e_basic_flow( creator: &signer, aaron: &signer, - ) acquires Metadata, FungibleAsset, FungibleAssetEvents { + ) acquires Metadata, FungibleStore, FungibleAssetEvents { let (mint_ref, transfer_ref, burn_ref, test_token) = create_fungible_asset(creator); let metadata = mint_ref.metadata; - let creator_wallet = create_test_wallet(creator, metadata); - let aaron_wallet = create_test_wallet(aaron, metadata); + let creator_store = create_test_store(creator, metadata); + let aaron_store = create_test_store(aaron, metadata); - assert!(supply(test_token) == 0, 1); + assert!(supply(test_token) == option::some(0), 1); // Mint let fa = mint(&mint_ref, 100); - assert!(supply(test_token) == 100, 2); + assert!(supply(test_token) == option::some(100), 2); // Deposit - deposit(creator_wallet, fa); + deposit(creator_store, fa); // Withdraw - let fa = withdraw(creator, creator_wallet, 80); - assert!(supply(test_token) == 100, 3); - deposit(aaron_wallet, fa); + let fa = withdraw(creator, creator_store, 80); + assert!(supply(test_token) == option::some(100), 3); + deposit(aaron_store, fa); // Burn - burn(&burn_ref, aaron_wallet, 30); - assert!(supply(test_token) == 70, 4); + burn_from(&burn_ref, aaron_store, 30); + assert!(supply(test_token) == option::some(70), 4); // Transfer - transfer(creator, creator_wallet, aaron_wallet, 10); - assert!(balance(creator_wallet) == 10, 5); - assert!(balance(aaron_wallet) == 60, 6); + transfer(creator, creator_store, aaron_store, 10); + assert!(balance(creator_store) == 10, 5); + assert!(balance(aaron_store) == 60, 6); - set_ungated_transfer(&transfer_ref, aaron_wallet, false); - assert!(!ungated_transfer_allowed(aaron_wallet), 7); + set_ungated_transfer(&transfer_ref, aaron_store, false); + assert!(!ungated_balance_transfer_allowed(aaron_store), 7); } #[test(creator = @0xcafe)] #[expected_failure(abort_code = 0x10003, location = Self)] fun test_ungated_transfer( creator: &signer - ) acquires Metadata, FungibleAsset, FungibleAssetEvents { + ) acquires Metadata, FungibleStore, FungibleAssetEvents { let (mint_ref, transfer_ref, _burn_ref, _) = create_fungible_asset(creator); - let creator_wallet = create_test_wallet(creator, mint_ref.metadata); + let creator_store = create_test_store(creator, mint_ref.metadata); let fa = mint(&mint_ref, 100); - set_ungated_transfer(&transfer_ref, creator_wallet, false); - deposit(creator_wallet, fa); + set_ungated_transfer(&transfer_ref, creator_store, false); + deposit(creator_store, fa); } #[test(creator = @0xcafe, aaron = @0xface)] fun test_transfer_with_ref( creator: &signer, aaron: &signer, - ) acquires Metadata, FungibleAsset, FungibleAssetEvents { + ) acquires Metadata, FungibleStore, FungibleAssetEvents { let (mint_ref, transfer_ref, _burn_ref, _) = create_fungible_asset(creator); let metadata = mint_ref.metadata; - let creator_wallet = create_test_wallet(creator, metadata); - let aaron_wallet = create_test_wallet(aaron, metadata); + let creator_store = create_test_store(creator, metadata); + let aaron_store = create_test_store(aaron, metadata); let fa = mint(&mint_ref, 100); - set_ungated_transfer(&transfer_ref, creator_wallet, false); - set_ungated_transfer(&transfer_ref, aaron_wallet, false); - deposit_with_ref(&transfer_ref, creator_wallet, fa); - transfer_with_ref(&transfer_ref, creator_wallet, aaron_wallet, 80); - assert!(balance(creator_wallet) == 20, 1); - assert!(balance(aaron_wallet) == 80, 2); - assert!(!ungated_transfer_allowed(creator_wallet), 3); - assert!(!ungated_transfer_allowed(aaron_wallet), 4); + set_ungated_transfer(&transfer_ref, creator_store, false); + set_ungated_transfer(&transfer_ref, aaron_store, false); + deposit_with_ref(&transfer_ref, creator_store, fa); + transfer_with_ref(&transfer_ref, creator_store, aaron_store, 80); + assert!(balance(creator_store) == 20, 1); + assert!(balance(aaron_store) == 80, 2); + assert!(!ungated_balance_transfer_allowed(creator_store), 3); + assert!(!ungated_balance_transfer_allowed(aaron_store), 4); + } + + #[test(creator = @0xcafe)] + fun test_merge_and_exact(creator: &signer) acquires Metadata { + let (mint_ref, _transfer_ref, burn_ref, _) = create_fungible_asset(creator); + let fa = mint(&mint_ref, 100); + let cash = extract(&mut fa, 80); + assert!(fa.amount == 20, 1); + assert!(cash.amount == 80, 2); + let more_cash = extract(&mut fa, 20); + destroy_zero(fa); + merge(&mut cash, more_cash); + assert!(cash.amount == 100, 3); + burn(&burn_ref, cash); } } diff --git a/aptos-move/framework/aptos-framework/sources/object.move b/aptos-move/framework/aptos-framework/sources/object.move index e9b3635796239..a6f2be6f34334 100644 --- a/aptos-move/framework/aptos-framework/sources/object.move +++ b/aptos-move/framework/aptos-framework/sources/object.move @@ -27,6 +27,8 @@ module aptos_framework::object { use aptos_framework::from_bcs; use aptos_framework::guid; + friend aptos_framework::primary_store; + /// An object already exists at this address const EOBJECT_EXISTS: u64 = 1; /// An object does not exist at this address @@ -93,7 +95,7 @@ module aptos_framework::object { #[resource_group(scope = global)] /// A shared resource group for storing object resources together in storage. - struct ObjectGroup { } + struct ObjectGroup {} /// A pointer to an object -- these can only provide guarantees based upon the underlying data /// type, that is the validity of T existing at an address is something that cannot be verified @@ -149,7 +151,7 @@ module aptos_framework::object { public fun address_to_object(object: address): Object { assert!(exists(object), error::not_found(EOBJECT_DOES_NOT_EXIST)); assert!(exists_at(object), error::not_found(ERESOURCE_DOES_NOT_EXIST)); - Object{ inner: object } + Object { inner: object } } /// Derives an object address from source material: sha3_256([creator address | seed | 0xFE]). @@ -190,8 +192,7 @@ module aptos_framework::object { /// Create a new object whose address is derived based on the creator account address and another object. /// Derivde objects, similar to named objects, cannot be deleted. - public fun create_user_derived_object(creator: &signer, derive_ref: &DeriveRef): ConstructorRef { - let creator_address = signer::address_of(creator); + public(friend) fun create_user_derived_object(creator_address: address, derive_ref: &DeriveRef): ConstructorRef { let obj_addr = create_user_derived_object_address(creator_address, derive_ref.self); create_object_internal(creator_address, obj_addr, false) } @@ -471,6 +472,14 @@ module aptos_framework::object { } /// Accessors + /// Return true if ungated transfer is allowed. + public fun ungated_transfer_allowed(object: Object): bool acquires ObjectCore { + assert!( + exists(object.inner), + error::not_found(EOBJECT_DOES_NOT_EXIST), + ); + borrow_global(object.inner).allow_ungated_transfer + } /// Return the current owner. public fun owner(object: Object): address acquires ObjectCore { @@ -537,7 +546,7 @@ module aptos_framework::object { #[test_only] #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - struct Weapon has key { } + struct Weapon has key {} #[test_only] public fun create_hero(creator: &signer): (ConstructorRef, Object) acquires ObjectCore { @@ -560,7 +569,7 @@ module aptos_framework::object { public fun create_weapon(creator: &signer): (ConstructorRef, Object) { let weapon_constructor_ref = create_named_object(creator, b"weapon"); let weapon_signer = generate_signer(&weapon_constructor_ref); - move_to(&weapon_signer, Weapon { }); + move_to(&weapon_signer, Weapon {}); let weapon = object_from_constructor_ref(&weapon_constructor_ref); (weapon_constructor_ref, weapon) } diff --git a/aptos-move/framework/aptos-framework/sources/primary_store.move b/aptos-move/framework/aptos-framework/sources/primary_store.move new file mode 100644 index 0000000000000..470e1852dc7e2 --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/primary_store.move @@ -0,0 +1,182 @@ +/// This defines the module for interacting with primary stores of accounts/objects, which have deterministic addresses +module aptos_framework::primary_store { + use aptos_framework::fungible_asset::{Self, FungibleAsset, FungibleStore}; + use aptos_framework::object::{Self, Object, ConstructorRef, DeriveRef}; + + use std::option::Option; + use std::signer; + use std::string::String; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + /// Resource stored on the fungible asset metadata object to allow creating primary stores for it. + struct DeriveRefPod has key { + metadata_derive_ref: DeriveRef, + } + + /// Creators of fungible assets can call this to enable support for creating primary (deterministic) stores for + /// their users. + public fun create_primary_store_enabled_fungible_asset( + constructor_ref: &ConstructorRef, + monitoring_supply_with_maximum: Option>, + name: String, + symbol: String, + decimals: u8, + ) { + fungible_asset::add_fungibility(constructor_ref, monitoring_supply_with_maximum, name, symbol, decimals); + let metadata_obj = &object::generate_signer(constructor_ref); + move_to(metadata_obj, DeriveRefPod { + metadata_derive_ref: object::generate_derive_ref(constructor_ref), + }); + } + + public fun ensure_primary_store_exists( + owner: address, + metadata: Object, + ): Object acquires DeriveRefPod { + if (!primary_store_exists(owner, metadata)) { + create_primary_store(owner, metadata) + } else { + primary_store(owner, metadata) + } + } + + /// Create a primary store object to hold fungible asset for the given address. + public fun create_primary_store( + owner_addr: address, + metadata: Object, + ): Object acquires DeriveRefPod { + let metadata_addr = object::object_address(&metadata); + let derive_ref = &borrow_global(metadata_addr).metadata_derive_ref; + let constructor_ref = &object::create_user_derived_object(owner_addr, derive_ref); + + // Disable ungated transfer as deterministic stores shouldn't be transferrable. + let transfer_ref = &object::generate_transfer_ref(constructor_ref); + object::disable_ungated_transfer(transfer_ref); + + fungible_asset::create_store(constructor_ref, metadata) + } + + #[view] + public fun primary_store_address(owner: address, metadata: Object): address { + let metadata_addr = object::object_address(&metadata); + object::create_user_derived_object_address(owner, metadata_addr) + } + + #[view] + public fun primary_store(owner: address, metadata: Object): Object { + let store = primary_store_address(owner, metadata); + object::address_to_object(store) + } + + #[view] + public fun primary_store_exists(account: address, metadata: Object): bool { + fungible_asset::store_exists(primary_store_address(account, metadata)) + } + + #[view] + /// Get the balance of `account`'s primary store. + public fun balance(account: address, metadata: Object): u64 { + if (primary_store_exists(account, metadata)) { + fungible_asset::balance(primary_store(account, metadata)) + } else { + 0 + } + } + + #[view] + /// Return whether the given account's primary store can do direct transfers. + public fun ungated_balance_transfer_allowed(account: address, metadata: Object): bool { + if (primary_store_exists(account, metadata)) { + fungible_asset::ungated_balance_transfer_allowed(primary_store(account, metadata)) + } else { + true + } + } + + /// Withdraw `amount` of fungible asset from `store` by the owner. + public fun withdraw(owner: &signer, metadata: Object, amount: u64): FungibleAsset { + let store = primary_store(signer::address_of(owner), metadata); + fungible_asset::withdraw(owner, store, amount) + } + + /// Deposit `amount` of fungible asset to the given account's primary store. + public fun deposit(owner: address, fa: FungibleAsset) acquires DeriveRefPod { + let metadata = fungible_asset::asset_metadata(&fa); + let store = ensure_primary_store_exists(owner, metadata); + fungible_asset::deposit(store, fa); + } + + /// Transfer `amount` of fungible asset from sender's primary store to receiver's primary store. + public entry fun transfer( + sender: &signer, + metadata: Object, + recipient: address, + amount: u64, + ) acquires DeriveRefPod { + let sender_store = ensure_primary_store_exists(signer::address_of(sender), metadata); + let recipient_store = ensure_primary_store_exists(recipient, metadata); + fungible_asset::transfer(sender, sender_store, recipient_store, amount); + } + + #[test_only] + use aptos_framework::fungible_asset::{create_test_token, mint, generate_mint_ref, generate_burn_ref, MintRef, TransferRef, BurnRef, generate_transfer_ref}; + #[test_only] + use std::string; + #[test_only] + use std::option; + + #[test_only] + public fun init_test_metadata_with_primary_store_enabled( + constructor_ref: &ConstructorRef + ): (MintRef, TransferRef, BurnRef) { + create_primary_store_enabled_fungible_asset( + constructor_ref, + option::some(option::some(100)) /* max supply */, + string::utf8(b"USDA"), + string::utf8(b"$$$"), + 0 + ); + let mint_ref = generate_mint_ref(constructor_ref); + let burn_ref = generate_burn_ref(constructor_ref); + let transfer_ref = generate_transfer_ref(constructor_ref); + (mint_ref, transfer_ref, burn_ref) + } + + #[test(creator = @0xcafe, aaron = @0xface)] + fun test_default_behavior(creator: &signer, aaron: &signer) acquires DeriveRefPod { + let (creator_ref, metadata) = create_test_token(creator); + init_test_metadata_with_primary_store_enabled(&creator_ref); + let creator_address = signer::address_of(creator); + let aaron_address = signer::address_of(aaron); + assert!(!primary_store_exists(creator_address, metadata), 1); + assert!(!primary_store_exists(aaron_address, metadata), 2); + assert!(balance(creator_address, metadata) == 0, 3); + assert!(balance(aaron_address, metadata) == 0, 4); + assert!(ungated_balance_transfer_allowed(creator_address, metadata), 5); + assert!(ungated_balance_transfer_allowed(aaron_address, metadata), 6); + ensure_primary_store_exists(creator_address, metadata); + ensure_primary_store_exists(aaron_address, metadata); + assert!(primary_store_exists(creator_address, metadata), 7); + assert!(primary_store_exists(aaron_address, metadata), 8); + } + + #[test(creator = @0xcafe, aaron = @0xface)] + fun test_basic_flow( + creator: &signer, + aaron: &signer, + ) acquires DeriveRefPod { + let (creator_ref, metadata) = create_test_token(creator); + let (mint_ref, _transfer_ref, _burn_ref) = init_test_metadata_with_primary_store_enabled(&creator_ref); + let creator_address = signer::address_of(creator); + let aaron_address = signer::address_of(aaron); + assert!(balance(creator_address, metadata) == 0, 1); + assert!(balance(aaron_address, metadata) == 0, 2); + let fa = mint(&mint_ref, 100); + deposit(creator_address, fa); + transfer(creator, metadata, aaron_address, 80); + let fa = withdraw(aaron, metadata, 10); + deposit(creator_address, fa); + assert!(balance(creator_address, metadata) == 30, 3); + assert!(balance(aaron_address, metadata) == 70, 4); + } +} diff --git a/aptos-move/framework/aptos-framework/sources/primary_wallet.spec.move b/aptos-move/framework/aptos-framework/sources/primary_store.spec.move similarity index 58% rename from aptos-move/framework/aptos-framework/sources/primary_wallet.spec.move rename to aptos-move/framework/aptos-framework/sources/primary_store.spec.move index 6943786a06c1a..5c132b3d56960 100644 --- a/aptos-move/framework/aptos-framework/sources/primary_wallet.spec.move +++ b/aptos-move/framework/aptos-framework/sources/primary_store.spec.move @@ -1,6 +1,6 @@ -spec aptos_framework::primary_wallet { +spec aptos_framework::primary_store { spec module { // TODO: verification disabled until this module is specified. - pragma verify=false; + pragma verify = false; } } diff --git a/aptos-move/framework/aptos-framework/sources/primary_wallet.move b/aptos-move/framework/aptos-framework/sources/primary_wallet.move deleted file mode 100644 index 2385977fce3ab..0000000000000 --- a/aptos-move/framework/aptos-framework/sources/primary_wallet.move +++ /dev/null @@ -1,116 +0,0 @@ -/// This defines the module for interacting with primary wallets of accounts/objects, which have deterministic addresses -module aptos_framework::primary_wallet { - use aptos_framework::create_signer; - use aptos_framework::fungible_asset::{Self, ExtractedAsset, FungibleAsset}; - use aptos_framework::object::{Self, Object, ConstructorRef, DeriveRef}; - - use std::signer; - use std::string::String; - - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] - /// Resource stored on the fungible asset metadata object to allow creating primary wallets for it. - struct PrimaryWalletSupport has key { - metadata_derive_ref: DeriveRef, - } - - /// Creators of fungible assets can call this to enable support for creating primary (deterministic) wallets for - /// their users. - public fun create_primary_wallet_enabled_fungible_asset( - constructor_ref: &ConstructorRef, - maximum_supply: u64, - name: String, - symbol: String, - decimals: u8, - ) { - fungible_asset::add_fungibility(constructor_ref, maximum_supply, name, symbol, decimals); - let metadata_obj = &object::generate_signer(constructor_ref); - move_to(metadata_obj, PrimaryWalletSupport { - metadata_derive_ref: object::generate_derive_ref(constructor_ref), - }); - } - - public fun ensure_primary_wallet_exists( - owner: address, - metadata: Object, - ): Object acquires PrimaryWalletSupport { - if (!primary_wallet_exists(owner, metadata)) { - create_primary_wallet(owner, metadata); - }; - primary_wallet(owner, metadata) - } - - /// Create a primary wallet object to hold fungible asset for the given address. - public fun create_primary_wallet( - owner_addr: address, - metadata: Object, - ): Object acquires PrimaryWalletSupport { - let owner = &create_signer::create_signer(owner_addr); - let metadata_addr = object::object_address(&metadata); - let derive_ref = &borrow_global(metadata_addr).metadata_derive_ref; - let constructor_ref = &object::create_user_derived_object(owner, derive_ref); - - // Disable ungated transfer as deterministic wallets shouldn't be transferrable. - let transfer_ref = &object::generate_transfer_ref(constructor_ref); - object::disable_ungated_transfer(transfer_ref); - - fungible_asset::create_wallet(constructor_ref, metadata) - } - - #[view] - public fun primary_wallet_address(owner: address, metadata: Object): address { - let metadata_addr = object::object_address(&metadata); - object::create_user_derived_object_address(owner, metadata_addr) - } - - #[view] - public fun primary_wallet(owner: address, metadata: Object): Object { - let wallet = primary_wallet_address(owner, metadata); - object::address_to_object(wallet) - } - - #[view] - public fun primary_wallet_exists(account: address, metadata: Object): bool { - fungible_asset::wallet_exists(primary_wallet_address(account, metadata)) - } - - #[view] - /// Get the balance of `account`'s primary wallet. - public fun balance(account: address, metadata: Object): u64 { - if (primary_wallet_exists(account, metadata)) { - fungible_asset::balance(primary_wallet(account, metadata)) - } else { - 0 - } - } - - #[view] - /// Return whether the given account's primary wallet can do direct transfers. - public fun ungated_transfer_allowed(account: address, metadata: Object): bool { - fungible_asset::ungated_transfer_allowed(primary_wallet(account, metadata)) - } - - /// Withdraw `amount` of fungible asset from `wallet` by the owner. - public fun withdraw(owner: &signer, metadata: Object, amount: u64): ExtractedAsset { - let wallet = primary_wallet(signer::address_of(owner), metadata); - fungible_asset::withdraw(owner, wallet, amount) - } - - /// Deposit `amount` of fungible asset to the given account's primary wallet. - public fun deposit(owner: address, fa: ExtractedAsset) acquires PrimaryWalletSupport { - let metadata = fungible_asset::asset_metadata(&fa); - let wallet = ensure_primary_wallet_exists(owner, metadata); - fungible_asset::deposit(wallet, fa); - } - - /// Transfer `amount` of fungible asset from sender's primary wallet to receiver's primary wallet. - public entry fun transfer( - sender: &signer, - metadata: Object, - recipient: address, - amount: u64, - ) acquires PrimaryWalletSupport { - let sender_wallet = ensure_primary_wallet_exists(signer::address_of(sender), metadata); - let recipient_wallet = ensure_primary_wallet_exists(recipient, metadata); - fungible_asset::transfer(sender, sender_wallet, recipient_wallet, amount); - } -} diff --git a/aptos-move/move-examples/fungible_asset/Move.toml b/aptos-move/move-examples/fungible_asset/Move.toml index 50f15538150b9..210b4b31e3026 100644 --- a/aptos-move/move-examples/fungible_asset/Move.toml +++ b/aptos-move/move-examples/fungible_asset/Move.toml @@ -4,7 +4,7 @@ version = "0.0.0" [addresses] aptos_framework = "0x1" -fungible_asset = "0xcafe" +fungible_asset_extension = "0xcafe" [dependencies] AptosFramework = { local = "../../framework/aptos-framework" } diff --git a/aptos-move/move-examples/fungible_asset/sources/coin_example.move b/aptos-move/move-examples/fungible_asset/sources/coin_example.move new file mode 100644 index 0000000000000..25ea5560c063f --- /dev/null +++ b/aptos-move/move-examples/fungible_asset/sources/coin_example.move @@ -0,0 +1,97 @@ +/// A coin example using managed_fungible_asset. +module fungible_asset_extension::coin_example { + use aptos_framework::object; + use aptos_framework::fungible_asset::{Metadata, FungibleAsset}; + use aptos_framework::object::Object; + use fungible_asset_extension::managed_fungible_asset; + use std::string::utf8; + + const ASSET_SYMBOL: vector = b"APT"; + + /// Initialize metadata object and store the refs. + fun init_module(admin: &signer) { + let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL); + managed_fungible_asset::initialize( + constructor_ref, + false, + 0, /* maximum_supply. 0 means no maximum */ + utf8(b"Aptos Token"), /* name */ + utf8(ASSET_SYMBOL), /* symbol */ + 8, /* decimals */ + ); + } + + #[view] + /// Return the address of the metadata that's created when this module is deployed. + public fun get_metadata(): Object { + let metadata_address = object::create_object_address(&@fungible_asset_extension, ASSET_SYMBOL); + object::address_to_object(metadata_address) + } + + /// Mint as the owner of metadata object. + public entry fun mint(admin: &signer, amount: u64, to: address) { + managed_fungible_asset::mint(admin, get_metadata(), amount, to); + } + + /// Transfer as the owner of metadata object ignoring `allow_ungated_transfer` field. + public entry fun transfer(admin: &signer, from: address, to: address, amount: u64) { + managed_fungible_asset::transfer(admin, get_metadata(), from, to, amount); + } + + /// Burn fungible assets as the owner of metadata object. + public entry fun burn(admin: &signer, from: address, amount: u64) { + managed_fungible_asset::burn(admin, get_metadata(), from, amount); + } + + /// Freeze an account so it cannot transfer or receive fungible assets. + public entry fun freeze_account(admin: &signer, account: address) { + managed_fungible_asset::freeze_account(admin, get_metadata(), account); + } + + /// Unfreeze an account so it can transfer or receive fungible assets. + public entry fun unfreeze_account(admin: &signer, account: address) { + managed_fungible_asset::unfreeze_account(admin, get_metadata(), account); + } + + /// Withdraw as the owner of metadata object ignoring `allow_ungated_transfer` field. + public fun withdraw(admin: &signer, amount: u64, from: address): FungibleAsset { + managed_fungible_asset::withdraw(admin, get_metadata(), amount, from) + } + + /// Deposit as the owner of metadata object ignoring `allow_ungated_transfer` field. + public fun deposit(admin: &signer, to: address, fa: FungibleAsset) { + managed_fungible_asset::deposit(admin, get_metadata(), to, fa); + } + + #[test_only] + use aptos_framework::primary_store; + #[test_only] + use std::signer; + + #[test(creator = @0xcafe)] + fun test_basic_flow(creator: &signer) { + init_module(creator); + let creator_address = signer::address_of(creator); + let aaron_address = @0xface; + + mint(creator, 100, creator_address); + let metadata = get_metadata(); + assert!(primary_store::balance(creator_address, metadata) == 100, 4); + freeze_account(creator, creator_address); + assert!(!primary_store::ungated_balance_transfer_allowed(creator_address, metadata), 5); + transfer(creator, creator_address, aaron_address, 10); + assert!(primary_store::balance(aaron_address, metadata) == 10, 6); + + unfreeze_account(creator, creator_address); + assert!(primary_store::ungated_balance_transfer_allowed(creator_address, metadata), 7); + burn(creator, creator_address, 90); + } + + #[test(creator = @0xcafe, aaron = @0xface)] + #[expected_failure(abort_code = 0x50001, location = fungible_asset_extension::managed_fungible_asset)] + fun test_permission_denied(creator: &signer, aaron: &signer) { + init_module(creator); + let creator_address = signer::address_of(creator); + mint(aaron, 100, creator_address); + } +} diff --git a/aptos-move/move-examples/fungible_asset/sources/managed_coin.move b/aptos-move/move-examples/fungible_asset/sources/managed_coin.move new file mode 100644 index 0000000000000..2745219cc350e --- /dev/null +++ b/aptos-move/move-examples/fungible_asset/sources/managed_coin.move @@ -0,0 +1,154 @@ +/// A 2-in-1 module that combines managed_fungible_asset and coin_example into one module that when deployed, the +/// deployer will be creating a new managed fungible asset with the hardcoded supply config, name, symbol, and decimals. +/// The address of the asset can be obtained via get_metadata(). +module fungible_asset_extension::managed_coin { + use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleAsset}; + use aptos_framework::object::{Self, Object}; + use aptos_framework::primary_store; + use std::error; + use std::signer; + use std::string::utf8; + use std::option; + + /// Only fungible asset metadata owner can make changes. + const ENOT_OWNER: u64 = 1; + + const ASSET_SYMBOL: vector = b"APT"; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + /// Hold refs to control the minting, transfer and burning of fungible assets. + struct ManagedFungibleAsset has key { + mint_ref: MintRef, + transfer_ref: TransferRef, + burn_ref: BurnRef, + } + + /// Initialize metadata object and store the refs. + fun init_module(admin: &signer) { + let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL); + primary_store::create_primary_store_enabled_fungible_asset( + constructor_ref, + option::some(option::none()), + utf8(b"Aptos Token"), /* name */ + utf8(ASSET_SYMBOL), /* symbol */ + 8, /* decimals */ + ); + + // Create mint/burn/transfer refs to allow creator to manage the fungible asset. + let mint_ref = fungible_asset::generate_mint_ref(constructor_ref); + let burn_ref = fungible_asset::generate_burn_ref(constructor_ref); + let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref); + let metadata_object_signer = object::generate_signer(constructor_ref); + move_to( + &metadata_object_signer, + ManagedFungibleAsset { mint_ref, transfer_ref, burn_ref } + ) + } + + #[view] + /// Return the address of the managed fungible asset that's created when this module is deployed. + public fun get_metadata(): Object { + let asset_address = object::create_object_address(&@fungible_asset_extension, ASSET_SYMBOL); + object::address_to_object(asset_address) + } + + /// Mint as the owner of metadata object. + public entry fun mint(admin: &signer, amount: u64, to: address) acquires ManagedFungibleAsset { + let asset = get_metadata(); + let managed_fungible_asset = authorized_borrow_refs(admin, asset); + let to_wallet = primary_store::ensure_primary_store_exists(to, asset); + let fa = fungible_asset::mint(&managed_fungible_asset.mint_ref, amount); + fungible_asset::deposit_with_ref(&managed_fungible_asset.transfer_ref, to_wallet, fa); + } + + /// Transfer as the owner of metadata object ignoring `allow_ungated_transfer` field. + public entry fun transfer(admin: &signer, from: address, to: address, amount: u64) acquires ManagedFungibleAsset { + let asset = get_metadata(); + let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; + let from_wallet = primary_store::ensure_primary_store_exists(from, asset); + let to_wallet = primary_store::ensure_primary_store_exists(to, asset); + fungible_asset::transfer_with_ref(transfer_ref, from_wallet, to_wallet, amount); + } + + /// Burn fungible assets as the owner of metadata object. + public entry fun burn(admin: &signer, from: address, amount: u64) acquires ManagedFungibleAsset { + let asset = get_metadata(); + let burn_ref = &authorized_borrow_refs(admin, asset).burn_ref; + let from_wallet = primary_store::ensure_primary_store_exists(from, asset); + fungible_asset::burn_from(burn_ref, from_wallet, amount); + } + + /// Freeze an account so it cannot transfer or receive fungible assets. + public entry fun freeze_account(admin: &signer, account: address) acquires ManagedFungibleAsset { + let asset = get_metadata(); + let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; + let wallet = primary_store::ensure_primary_store_exists(account, asset); + fungible_asset::set_ungated_transfer(transfer_ref, wallet, false); + } + + /// Unfreeze an account so it can transfer or receive fungible assets. + public entry fun unfreeze_account(admin: &signer, account: address) acquires ManagedFungibleAsset { + let asset = get_metadata(); + let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; + let wallet = primary_store::ensure_primary_store_exists(account, asset); + fungible_asset::set_ungated_transfer(transfer_ref, wallet, true); + } + + /// Withdraw as the owner of metadata object ignoring `allow_ungated_transfer` field. + public fun withdraw(admin: &signer, amount: u64, from: address): FungibleAsset acquires ManagedFungibleAsset { + let asset = get_metadata(); + let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; + let from_wallet = primary_store::ensure_primary_store_exists(from, asset); + fungible_asset::withdraw_with_ref(transfer_ref, from_wallet, amount) + } + + /// Deposit as the owner of metadata object ignoring `allow_ungated_transfer` field. + public fun deposit(admin: &signer, to: address, fa: FungibleAsset) acquires ManagedFungibleAsset { + let asset = get_metadata(); + let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; + let to_wallet = primary_store::ensure_primary_store_exists(to, asset); + fungible_asset::deposit_with_ref(transfer_ref, to_wallet, fa); + } + + /// Borrow the immutable reference of the refs of `metadata`. + /// This validates that the signer is the metadata object's owner. + inline fun authorized_borrow_refs( + owner: &signer, + asset: Object, + ): &ManagedFungibleAsset acquires ManagedFungibleAsset { + assert!(object::is_owner(asset, signer::address_of(owner)), error::permission_denied(ENOT_OWNER)); + borrow_global(object::object_address(&asset)) + } + + #[test(creator = @0xcafe)] + fun test_basic_flow( + creator: &signer, + ) acquires ManagedFungibleAsset { + init_module(creator); + let creator_address = signer::address_of(creator); + let aaron_address = @0xface; + + mint(creator, 100, creator_address); + let asset = get_metadata(); + assert!(primary_store::balance(creator_address, asset) == 100, 4); + freeze_account(creator, creator_address); + assert!(!primary_store::ungated_balance_transfer_allowed(creator_address, asset), 5); + transfer(creator, creator_address, aaron_address, 10); + assert!(primary_store::balance(aaron_address, asset) == 10, 6); + + unfreeze_account(creator, creator_address); + assert!(primary_store::ungated_balance_transfer_allowed(creator_address, asset), 7); + burn(creator, creator_address, 90); + } + + #[test(creator = @0xcafe, aaron = @0xface)] + #[expected_failure(abort_code = 0x50001, location = Self)] + fun test_permission_denied( + creator: &signer, + aaron: &signer + ) acquires ManagedFungibleAsset { + init_module(creator); + let creator_address = signer::address_of(creator); + mint(aaron, 100, creator_address); + } +} diff --git a/aptos-move/move-examples/fungible_asset/sources/managed_fungible_asset.move b/aptos-move/move-examples/fungible_asset/sources/managed_fungible_asset.move index 734bde3493ae5..1ccf7a04f1df8 100644 --- a/aptos-move/move-examples/fungible_asset/sources/managed_fungible_asset.move +++ b/aptos-move/move-examples/fungible_asset/sources/managed_fungible_asset.move @@ -1,20 +1,18 @@ -/// By deploying this module, the deployer will be creating a new managed fungible asset with the hardcoded -/// maximum supply, name, symbol, and decimals. The address of the asset can be obtained via get_asset(). -/// The deployer will also become the initial admin and can mint/burn/freeze/unfreeze accounts. +/// By deploying this module, the deployer provide an extension layer upon fungible asset that helps manage +/// the refs for the deployer, who is set to be the initial admin that can mint/burn/freeze/unfreeze accounts. /// The admin can transfer the asset via object::transfer() at any point to set a new admin. -module fungible_asset::managed_fungible_asset { - use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, ExtractedAsset, Metadata}; - use aptos_framework::object::{Self, Object}; - use aptos_framework::primary_wallet; +module fungible_asset_extension::managed_fungible_asset { + use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, FungibleAsset, Metadata}; + use aptos_framework::object::{Self, Object, ConstructorRef}; + use aptos_framework::primary_store; use std::error; use std::signer; - use std::string::utf8; + use std::string::String; + use std::option; /// Only fungible asset metadata owner can make changes. const ENOT_OWNER: u64 = 1; - const ASSET_SYMBOL: vector = b"APT"; - #[resource_group_member(group = aptos_framework::object::ObjectGroup)] /// Hold refs to control the minting, transfer and burning of fungible assets. struct ManagedFungibleAsset has key { @@ -24,14 +22,28 @@ module fungible_asset::managed_fungible_asset { } /// Initialize metadata object and store the refs. - fun init_module(admin: &signer) { - let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL); - primary_wallet::create_primary_wallet_enabled_fungible_asset( + public fun initialize( + constructor_ref: &ConstructorRef, + monitoring_supply: bool, + maximum_supply: u128, + name: String, + symbol: String, + decimals: u8 + ) { + primary_store::create_primary_store_enabled_fungible_asset( constructor_ref, - 0, /* maximum_supply. 0 means no maximum */ - utf8(b"Aptos Token"), /* name */ - utf8(ASSET_SYMBOL), /* symbol */ - 8, /* decimals */ + if (monitoring_supply) { + option::some(if (maximum_supply != 0) { + option::some(maximum_supply) + } else { + option::none() + }) + } else { + option::none() + }, + name, + symbol, + decimals, ); // Create mint/burn/transfer refs to allow creator to manage the fungible asset. @@ -45,68 +57,80 @@ module fungible_asset::managed_fungible_asset { ) } - #[view] - /// Return the address of the managed fungible asset that's created when this module is deployed. - public fun get_asset(): Object { - let asset_address = object::create_object_address(&@fungible_asset, ASSET_SYMBOL); - object::address_to_object(asset_address) - } - /// Mint as the owner of metadata object. - public entry fun mint(admin: &signer, amount: u64, to: address) acquires ManagedFungibleAsset { - let asset = get_asset(); - let managed_fungible_asset = authorized_borrow_refs(admin, asset); - let to_wallet = primary_wallet::ensure_primary_wallet_exists(to, asset); + public entry fun mint( + admin: &signer, + metadata: Object, + amount: u64, + to: address + ) acquires ManagedFungibleAsset { + let managed_fungible_asset = authorized_borrow_refs(admin, metadata); + let to_wallet = primary_store::ensure_primary_store_exists(to, metadata); let fa = fungible_asset::mint(&managed_fungible_asset.mint_ref, amount); fungible_asset::deposit_with_ref(&managed_fungible_asset.transfer_ref, to_wallet, fa); } /// Transfer as the owner of metadata object ignoring `allow_ungated_transfer` field. - public entry fun transfer(admin: &signer, from: address, to: address, amount: u64) acquires ManagedFungibleAsset { - let asset = get_asset(); - let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; - let from_wallet = primary_wallet::ensure_primary_wallet_exists(from, asset); - let to_wallet = primary_wallet::ensure_primary_wallet_exists(to, asset); + public entry fun transfer( + admin: &signer, + metadata: Object, + from: address, + to: address, + amount: u64 + ) acquires ManagedFungibleAsset { + let transfer_ref = &authorized_borrow_refs(admin, metadata).transfer_ref; + let from_wallet = primary_store::ensure_primary_store_exists(from, metadata); + let to_wallet = primary_store::ensure_primary_store_exists(to, metadata); fungible_asset::transfer_with_ref(transfer_ref, from_wallet, to_wallet, amount); } /// Burn fungible assets as the owner of metadata object. - public entry fun burn(admin: &signer, from: address, amount: u64) acquires ManagedFungibleAsset { - let asset = get_asset(); - let burn_ref = &authorized_borrow_refs(admin, asset).burn_ref; - let from_wallet = primary_wallet::ensure_primary_wallet_exists(from, asset); - fungible_asset::burn(burn_ref, from_wallet, amount); + public entry fun burn( + admin: &signer, + metadata: Object, + from: address, + amount: u64 + ) acquires ManagedFungibleAsset { + let burn_ref = &authorized_borrow_refs(admin, metadata).burn_ref; + let from_wallet = primary_store::ensure_primary_store_exists(from, metadata); + fungible_asset::burn_from(burn_ref, from_wallet, amount); } /// Freeze an account so it cannot transfer or receive fungible assets. - public entry fun freeze_account(admin: &signer, account: address) acquires ManagedFungibleAsset { - let asset = get_asset(); - let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; - let wallet = primary_wallet::ensure_primary_wallet_exists(account, asset); + public entry fun freeze_account( + admin: &signer, + metadata: Object, + account: address + ) acquires ManagedFungibleAsset { + let transfer_ref = &authorized_borrow_refs(admin, metadata).transfer_ref; + let wallet = primary_store::ensure_primary_store_exists(account, metadata); fungible_asset::set_ungated_transfer(transfer_ref, wallet, false); } /// Unfreeze an account so it can transfer or receive fungible assets. - public entry fun unfreeze_account(admin: &signer, account: address) acquires ManagedFungibleAsset { - let asset = get_asset(); - let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; - let wallet = primary_wallet::ensure_primary_wallet_exists(account, asset); + public entry fun unfreeze_account(admin: &signer, + metadata: Object, + account: address) acquires ManagedFungibleAsset { + let transfer_ref = &authorized_borrow_refs(admin, metadata).transfer_ref; + let wallet = primary_store::ensure_primary_store_exists(account, metadata); fungible_asset::set_ungated_transfer(transfer_ref, wallet, true); } /// Withdraw as the owner of metadata object ignoring `allow_ungated_transfer` field. - public fun withdraw(admin: &signer, amount: u64, from: address): ExtractedAsset acquires ManagedFungibleAsset { - let asset = get_asset(); - let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; - let from_wallet = primary_wallet::ensure_primary_wallet_exists(from, asset); + public fun withdraw(admin: &signer, + metadata: Object, + amount: u64, from: address): FungibleAsset acquires ManagedFungibleAsset { + let transfer_ref = &authorized_borrow_refs(admin, metadata).transfer_ref; + let from_wallet = primary_store::ensure_primary_store_exists(from, metadata); fungible_asset::withdraw_with_ref(transfer_ref, from_wallet, amount) } /// Deposit as the owner of metadata object ignoring `allow_ungated_transfer` field. - public fun deposit(admin: &signer, to: address, fa: ExtractedAsset) acquires ManagedFungibleAsset { - let asset = get_asset(); - let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref; - let to_wallet = primary_wallet::ensure_primary_wallet_exists(to, asset); + public fun deposit(admin: &signer, + metadata: Object, + to: address, fa: FungibleAsset) acquires ManagedFungibleAsset { + let transfer_ref = &authorized_borrow_refs(admin, metadata).transfer_ref; + let to_wallet = primary_store::ensure_primary_store_exists(to, metadata); fungible_asset::deposit_with_ref(transfer_ref, to_wallet, fa); } @@ -120,25 +144,43 @@ module fungible_asset::managed_fungible_asset { borrow_global(object::object_address(&asset)) } + #[test_only] + use aptos_framework::object::object_from_constructor_ref; + #[test_only] + use std::string::utf8; + + #[test_only] + fun create_test_mfa(creator: &signer): Object { + let constructor_ref = &object::create_named_object(creator, b"APT"); + initialize( + constructor_ref, + true, + 0, + utf8(b"Aptos Token"), /* name */ + utf8(b"APT"), /* symbol */ + 8, /* decimals */ + ); + object_from_constructor_ref(constructor_ref) + } + #[test(creator = @0xcafe)] fun test_basic_flow( creator: &signer, ) acquires ManagedFungibleAsset { - init_module(creator); + let metadata = create_test_mfa(creator); let creator_address = signer::address_of(creator); let aaron_address = @0xface; - mint(creator, 100, creator_address); - let asset = get_asset(); - assert!(primary_wallet::balance(creator_address, asset) == 100, 4); - freeze_account(creator, creator_address); - assert!(!primary_wallet::ungated_transfer_allowed(creator_address, asset), 5); - transfer(creator, creator_address, aaron_address, 10); - assert!(primary_wallet::balance(aaron_address, asset) == 10, 6); - - unfreeze_account(creator, creator_address); - assert!(primary_wallet::ungated_transfer_allowed(creator_address, asset), 7); - burn(creator, creator_address, 90); + mint(creator, metadata, 100, creator_address); + assert!(primary_store::balance(creator_address, metadata) == 100, 4); + freeze_account(creator, metadata, creator_address); + assert!(!primary_store::ungated_balance_transfer_allowed(creator_address, metadata), 5); + transfer(creator, metadata, creator_address, aaron_address, 10); + assert!(primary_store::balance(aaron_address, metadata) == 10, 6); + + unfreeze_account(creator, metadata, creator_address); + assert!(primary_store::ungated_balance_transfer_allowed(creator_address, metadata), 7); + burn(creator, metadata, creator_address, 90); } #[test(creator = @0xcafe, aaron = @0xface)] @@ -147,8 +189,8 @@ module fungible_asset::managed_fungible_asset { creator: &signer, aaron: &signer ) acquires ManagedFungibleAsset { - init_module(creator); + let metadata = create_test_mfa(creator); let creator_address = signer::address_of(creator); - mint(aaron, 100, creator_address); + mint(aaron, metadata, 100, creator_address); } }