From c767194a3b777e821b9a5476aaaa6a4dea56e7b2 Mon Sep 17 00:00:00 2001 From: Aaron Gao Date: Thu, 6 Apr 2023 10:34:32 -0700 Subject: [PATCH] [fungible_assets] fix bugs, add tests and change object api --- .../aptos-framework/doc/fungible_asset.md | 157 +++++++++++------- .../framework/aptos-framework/doc/object.md | 7 +- .../aptos-framework/doc/primary_store.md | 45 +++-- .../sources/create_signer.move | 1 - .../sources/fungible_asset.move | 42 +++-- .../aptos-framework/sources/object.move | 13 +- .../sources/primary_store.move | 77 ++++++++- .../sources/managed_fungible_asset.move | 18 +- 8 files changed, 242 insertions(+), 118 deletions(-) diff --git a/aptos-move/framework/aptos-framework/doc/fungible_asset.md b/aptos-move/framework/aptos-framework/doc/fungible_asset.md index ce75440a803f6..df3f3eb186e50 100644 --- a/aptos-move/framework/aptos-framework/doc/fungible_asset.md +++ b/aptos-move/framework/aptos-framework/doc/fungible_asset.md @@ -10,7 +10,7 @@ metadata object can be any object that equipped with - + -## Struct `FuungibleAsset` +## 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 store. +FungibleAsset is ephermeral that it cannot be stored directly and will have to be deposited back into a store. -
struct FuungibleAsset
+
struct FungibleAsset
 
@@ -441,22 +442,32 @@ Cannot destroy non-empty fungible assets. - + + +Burn ref and fungible asset do not match. + + +
const EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 13;
+
+ + + + 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 store do not match. -
const EFUNGIBLE_ASSET_AND_WALLET_MISMATCH: u64 = 11;
+
const EFUNGIBLE_ASSET_AND_STORE_MISMATCH: u64 = 11;
 
@@ -471,22 +482,22 @@ The fungible asset's supply has exceeded maximum. - + 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 store's owner. -
const ENOT_WALLET_OWNER: u64 = 8;
+
const ENOT_STORE_OWNER: u64 = 8;
 
@@ -511,12 +522,12 @@ The transfer ref and the fungible asset 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;
 
@@ -817,7 +828,7 @@ Return whether the provided address has a store initialized. Return the underlying metadata object -
public fun metadata_from_asset(fa: &fungible_asset::FuungibleAsset): object::Object<fungible_asset::Metadata>
+
public fun metadata_from_asset(fa: &fungible_asset::FungibleAsset): object::Object<fungible_asset::Metadata>
 
@@ -826,7 +837,7 @@ Return the underlying metadata object Implementation -
public fun metadata_from_asset(fa: &FuungibleAsset): Object<Metadata> {
+
public fun metadata_from_asset(fa: &FungibleAsset): Object<Metadata> {
     fa.metadata
 }
 
@@ -867,7 +878,7 @@ Return the underlying metadata object. Return amount of a given fungible asset. -
public fun amount(fa: &fungible_asset::FuungibleAsset): u64
+
public fun amount(fa: &fungible_asset::FungibleAsset): u64
 
@@ -876,7 +887,7 @@ Return amount of a given fungible asset. Implementation -
public fun amount(fa: &FuungibleAsset): u64 {
+
public fun amount(fa: &FungibleAsset): u64 {
     fa.amount
 }
 
@@ -947,7 +958,7 @@ If the store has not been created, we default to returning true as deposits can -
public fun asset_metadata(fa: &fungible_asset::FuungibleAsset): object::Object<fungible_asset::Metadata>
+
public fun asset_metadata(fa: &fungible_asset::FungibleAsset): object::Object<fungible_asset::Metadata>
 
@@ -956,7 +967,7 @@ If the store has not been created, we default to returning true as deposits can Implementation -
public fun asset_metadata(fa: &FuungibleAsset): Object<Metadata> {
+
public fun asset_metadata(fa: &FungibleAsset): Object<Metadata> {
     fa.metadata
 }
 
@@ -1123,7 +1134,7 @@ Applications can use this to create multiple stores for isolating fungible asset Withdraw amount of fungible asset from store by the owner. -
public fun withdraw<T: key>(owner: &signer, store: object::Object<T>, amount: u64): fungible_asset::FuungibleAsset
+
public fun withdraw<T: key>(owner: &signer, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
 
@@ -1136,8 +1147,8 @@ Withdraw amount of fungible asset from store by the ow owner: &signer, store: Object<T>, amount: u64, -): FuungibleAsset acquires FungibleAssetStore, FungibleAssetEvents { - assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_WALLET_OWNER)); +): FungibleAsset acquires FungibleAssetStore, FungibleAssetEvents { + assert!(object::owns(store, signer::address_of(owner)), error::permission_denied(ENOT_STORE_OWNER)); assert!(ungated_transfer_allowed(store), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED)); withdraw_internal(object::object_address(&store), amount) } @@ -1154,7 +1165,7 @@ Withdraw amount of fungible asset from store by the ow Deposit amount of fungible asset to store. -
public fun deposit<T: key>(store: object::Object<T>, fa: fungible_asset::FuungibleAsset)
+
public fun deposit<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset)
 
@@ -1163,7 +1174,7 @@ Deposit amount of fungible asset to store. Implementation -
public fun deposit<T: key>(store: Object<T>, fa: FuungibleAsset) acquires FungibleAssetStore, FungibleAssetEvents {
+
public fun deposit<T: key>(store: Object<T>, fa: FungibleAsset) acquires FungibleAssetStore, FungibleAssetEvents {
     assert!(ungated_transfer_allowed(store), error::invalid_argument(EUNGATED_TRANSFER_IS_NOT_ALLOWED));
     deposit_internal(store, fa);
 }
@@ -1180,7 +1191,7 @@ Deposit amount of fungible asset to store.
 Mint the specified amount of fungible asset.
 
 
-
public fun mint(ref: &fungible_asset::MintRef, amount: u64): fungible_asset::FuungibleAsset
+
public fun mint(ref: &fungible_asset::MintRef, amount: u64): fungible_asset::FungibleAsset
 
@@ -1189,12 +1200,12 @@ Mint the specified amount of fungible asset. Implementation -
public fun mint(ref: &MintRef, amount: u64): FuungibleAsset 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);
 
-    FuungibleAsset {
+    FungibleAsset {
         metadata,
         amount
     }
@@ -1257,7 +1268,7 @@ Enable/disable a store's ability to do direct transfers of fungible asset.
 ) acquires FungibleAssetStore, FungibleAssetEvents {
     assert!(
         ref.metadata == store_metadata(store),
-        error::invalid_argument(ETRANSFER_REF_AND_WALLET_MISMATCH),
+        error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH),
     );
     let store_addr = object::object_address(&store);
     borrow_global_mut<FungibleAssetStore>(store_addr).allow_ungated_transfer = allow;
@@ -1275,10 +1286,39 @@ Enable/disable a store's ability to do direct transfers of fungible asset.
 
 ## Function `burn`
 
+
+
+
public fun burn(ref: &fungible_asset::BurnRef, fa: fungible_asset::FungibleAsset)
+
+ + + +
+Implementation + + +
public fun burn(ref: &BurnRef, fa: FungibleAsset) acquires Metadata {
+    let FungibleAsset {
+        metadata,
+        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<T: key>(ref: &fungible_asset::BurnRef, store: object::Object<T>, amount: u64)
+
public fun burn_from<T: key>(ref: &fungible_asset::BurnRef, store: object::Object<T>, amount: u64)
 
@@ -1287,19 +1327,15 @@ Burn the amount of fungible metadata from the given store. Implementation -
public fun burn<T: key>(
+
public fun burn_from<T: key>(
     ref: &BurnRef,
     store: Object<T>,
     amount: u64
 ) acquires Metadata, FungibleAssetStore, FungibleAssetEvents {
     let metadata = ref.metadata;
-    assert!(metadata == store_metadata(store), error::invalid_argument(EBURN_REF_AND_WALLET_MISMATCH));
+    assert!(metadata == store_metadata(store), error::invalid_argument(EBURN_REF_AND_STORE_MISMATCH));
     let store_addr = object::object_address(&store);
-    let FuungibleAsset {
-        metadata,
-        amount,
-    } = withdraw_internal(store_addr, amount);
-    decrease_supply(&metadata, amount);
+    burn(ref, withdraw_internal(store_addr, amount));
 }
 
@@ -1314,7 +1350,7 @@ Burn the amount of fungible metadata from the given store. Withdraw amount of fungible metadata from store ignoring allow_ungated_transfer. -
public fun withdraw_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, amount: u64): fungible_asset::FuungibleAsset
+
public fun withdraw_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
 
@@ -1327,10 +1363,10 @@ Withdraw amount of fungible metadata from store ignori ref: &TransferRef, store: Object<T>, amount: u64 -): FuungibleAsset acquires FungibleAssetStore, FungibleAssetEvents { +): FungibleAsset acquires FungibleAssetStore, FungibleAssetEvents { assert!( ref.metadata == store_metadata(store), - error::invalid_argument(ETRANSFER_REF_AND_WALLET_MISMATCH), + error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH), ); withdraw_internal(object::object_address(&store), amount) } @@ -1347,7 +1383,7 @@ Withdraw amount of fungible metadata from store ignori Deposit fungible asset into store ignoring allow_ungated_transfer. -
public fun deposit_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, fa: fungible_asset::FuungibleAsset)
+
public fun deposit_with_ref<T: key>(ref: &fungible_asset::TransferRef, store: object::Object<T>, fa: fungible_asset::FungibleAsset)
 
@@ -1359,7 +1395,7 @@ Deposit fungible asset into store ignoring allow_ungated_tran
public fun deposit_with_ref<T: key>(
     ref: &TransferRef,
     store: Object<T>,
-    fa: FuungibleAsset
+    fa: FungibleAsset
 ) acquires FungibleAssetStore, FungibleAssetEvents {
     assert!(
         ref.metadata == fa.metadata,
@@ -1411,7 +1447,7 @@ Transfer ammount of  fungible metadata with extract(fungible_asset: &mut fungible_asset::FuungibleAsset, amount: u64): fungible_asset::FuungibleAsset
+
public fun extract(fungible_asset: &mut fungible_asset::FungibleAsset, amount: u64): fungible_asset::FungibleAsset
 
@@ -1420,10 +1456,10 @@ Extract a given amount from the given fungible asset and return a new one. Implementation -
public fun extract(fungible_asset: &mut FuungibleAsset, amount: u64): FuungibleAsset {
+
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;
-    FuungibleAsset {
+    FungibleAsset {
         metadata: fungible_asset.metadata,
         amount,
     }
@@ -1442,7 +1478,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::FuungibleAsset, src_fungible_asset: fungible_asset::FuungibleAsset)
+
public fun merge(dst_fungible_asset: &mut fungible_asset::FungibleAsset, src_fungible_asset: fungible_asset::FungibleAsset)
 
@@ -1451,8 +1487,8 @@ to the sum of the two (dst_fungible_asset and src_fungible_as Implementation -
public fun merge(dst_fungible_asset: &mut FuungibleAsset, src_fungible_asset: FuungibleAsset) {
-    let FuungibleAsset { 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 +1504,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::FuungibleAsset)
+
public fun destroy_zero(fungible_asset: fungible_asset::FungibleAsset)
 
@@ -1477,8 +1513,8 @@ Destroy an empty fungible asset. Implementation -
public fun destroy_zero(fungible_asset: FuungibleAsset) {
-    let FuungibleAsset { 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 +1529,7 @@ Destroy an empty fungible asset. -
fun deposit_internal<T: key>(store: object::Object<T>, fa: fungible_asset::FuungibleAsset)
+
fun deposit_internal<T: key>(store: object::Object<T>, fa: fungible_asset::FungibleAsset)
 
@@ -1502,13 +1538,10 @@ Destroy an empty fungible asset. Implementation -
fun deposit_internal<T: key>(
-    store: Object<T>,
-    fa: FuungibleAsset
-) acquires FungibleAssetStore, FungibleAssetEvents {
-    let FuungibleAsset { metadata, amount } = fa;
+
fun deposit_internal<T: key>(store: Object<T>, fa: FungibleAsset) acquires FungibleAssetStore, FungibleAssetEvents {
+    let FungibleAsset { metadata, amount } = fa;
     let store_metadata = store_metadata(store);
-    assert!(metadata == store_metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_WALLET_MISMATCH));
+    assert!(metadata == store_metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_STORE_MISMATCH));
     let store_addr = object::object_address(&store);
     let store = borrow_global_mut<FungibleAssetStore>(store_addr);
     store.balance = store.balance + amount;
@@ -1529,7 +1562,7 @@ Destroy an empty fungible asset.
 Extract amount of fungible asset from store.
 
 
-
fun withdraw_internal(store_addr: address, amount: u64): fungible_asset::FuungibleAsset
+
fun withdraw_internal(store_addr: address, amount: u64): fungible_asset::FungibleAsset
 
@@ -1541,7 +1574,7 @@ Extract amount of fungible asset from store.
fun withdraw_internal(
     store_addr: address,
     amount: u64,
-): FuungibleAsset acquires FungibleAssetStore, FungibleAssetEvents {
+): FungibleAsset acquires FungibleAssetStore, FungibleAssetEvents {
     assert!(amount != 0, error::invalid_argument(EAMOUNT_CANNOT_BE_ZERO));
     let store = borrow_global_mut<FungibleAssetStore>(store_addr);
     assert!(store.balance >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
@@ -1551,7 +1584,7 @@ Extract amount of fungible asset from store.
     let metadata = store.metadata;
     event::emit_event(&mut events.withdraw_events, WithdrawEvent { amount });
 
-    FuungibleAsset { metadata, amount }
+    FungibleAsset { metadata, amount }
 }
 
diff --git a/aptos-move/framework/aptos-framework/doc/object.md b/aptos-move/framework/aptos-framework/doc/object.md index 080b61146c50f..f50645e71e4c4 100644 --- a/aptos-move/framework/aptos-framework/doc/object.md +++ b/aptos-move/framework/aptos-framework/doc/object.md @@ -581,7 +581,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 +753,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 +762,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)
 }
diff --git a/aptos-move/framework/aptos-framework/doc/primary_store.md b/aptos-move/framework/aptos-framework/doc/primary_store.md
index 61d23ff23354a..1065d314cc193 100644
--- a/aptos-move/framework/aptos-framework/doc/primary_store.md
+++ b/aptos-move/framework/aptos-framework/doc/primary_store.md
@@ -7,7 +7,7 @@ This defines the module for interacting with primary stores of accounts/objects,
 
 
 -  [Resource `DeriveRefPod`](#0x1_primary_store_DeriveRefPod)
--  [Function `add_derived_ref_pod`](#0x1_primary_store_add_derived_ref_pod)
+-  [Function `create_primary_wallet_enabled_fungible_asset`](#0x1_primary_store_create_primary_wallet_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)
@@ -21,10 +21,10 @@ This defines the module for interacting with primary stores of accounts/objects,
 -  [Specification](#@Specification_0)
 
 
-
use 0x1::create_signer;
-use 0x1::fungible_asset;
+
use 0x1::fungible_asset;
 use 0x1::object;
 use 0x1::signer;
+use 0x1::string;
 
@@ -57,15 +57,15 @@ Resource stored on the fungible asset metadata object to allow creating primary - + -## Function `add_derived_ref_pod` +## Function `create_primary_wallet_enabled_fungible_asset` Creators of fungible assets can call this to enable support for creating primary (deterministic) stores for their users. -
public fun add_derived_ref_pod(constructor_ref: &object::ConstructorRef)
+
public fun create_primary_wallet_enabled_fungible_asset(constructor_ref: &object::ConstructorRef, maximum_supply: u64, name: string::String, symbol: string::String, decimals: u8)
 
@@ -74,7 +74,14 @@ their users. Implementation -
public fun add_derived_ref_pod(constructor_ref: &ConstructorRef) {
+
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, DeriveRefPod {
         metadata_derive_ref: object::generate_derive_ref(constructor_ref),
@@ -106,9 +113,10 @@ their users.
     metadata: Object<T>,
 ): Object<FungibleAssetStore> acquires DeriveRefPod {
     if (!primary_store_exists(owner, metadata)) {
-        create_primary_store(owner, metadata);
-    };
-    primary_store(owner, metadata)
+        create_primary_store(owner, metadata)
+    } else {
+        primary_store(owner, metadata)
+    }
 }
 
@@ -136,10 +144,9 @@ Create a primary store object to hold fungible asset for the given address. owner_addr: address, metadata: Object<T>, ): Object<FungibleAssetStore> acquires DeriveRefPod { - let owner = &create_signer::create_signer(owner_addr); 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, 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); @@ -273,7 +280,11 @@ Return whether the given account's primary store can do direct transfers.
public fun ungated_transfer_allowed<T: key>(account: address, metadata: Object<T>): bool {
-    fungible_asset::ungated_transfer_allowed(primary_store(account, metadata))
+    if (primary_store_exists(account, metadata)) {
+        fungible_asset::ungated_transfer_allowed(primary_store(account, metadata))
+    } else {
+        true
+    }
 }
 
@@ -288,7 +299,7 @@ Return whether the given account's primary store can do direct transfers. 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::FuungibleAsset
+
public fun withdraw<T: key>(owner: &signer, metadata: object::Object<T>, amount: u64): fungible_asset::FungibleAsset
 
@@ -297,7 +308,7 @@ Withdraw amount of fungible asset from store by the ow Implementation -
public fun withdraw<T: key>(owner: &signer, metadata: Object<T>, amount: u64): FuungibleAsset {
+
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)
 }
@@ -314,7 +325,7 @@ Withdraw amount of fungible asset from store by the ow
 Deposit amount of fungible asset to the given account's primary store.
 
 
-
public fun deposit(owner: address, fa: fungible_asset::FuungibleAsset)
+
public fun deposit(owner: address, fa: fungible_asset::FungibleAsset)
 
@@ -323,7 +334,7 @@ Deposit amount of fungible asset to the given account's primary sto Implementation -
public fun deposit(owner: address, fa: FuungibleAsset) acquires DeriveRefPod {
+
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);
diff --git a/aptos-move/framework/aptos-framework/sources/create_signer.move b/aptos-move/framework/aptos-framework/sources/create_signer.move
index 39dcdb0f01c1a..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_store;
 
     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 dc58c8590b43b..ed6c6795c259a 100644
--- a/aptos-move/framework/aptos-framework/sources/fungible_asset.move
+++ b/aptos-move/framework/aptos-framework/sources/fungible_asset.move
@@ -33,6 +33,8 @@ module aptos_framework::fungible_asset {
     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;
 
     #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
     /// Define the metadata required of an metadata to be fungible.
@@ -71,7 +73,7 @@ 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 store.
+    /// 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,
@@ -337,8 +339,17 @@ module aptos_framework::fungible_asset {
         event::emit_event(&mut events.set_ungated_transfer_events, SetUngatedTransferEvent { transfer_allowed: allow });
     }
 
+    public fun burn(ref: &BurnRef, fa: FungibleAsset) acquires Metadata {
+        let FungibleAsset {
+            metadata,
+            amount,
+        } = fa;
+        assert!(ref.metadata == metadata, error::invalid_argument(EBURN_REF_AND_FUNGIBLE_ASSET_MISMATCH));
+        decrease_supply(&metadata, amount);
+    }
+
     /// Burn the `amount` of fungible metadata from the given store.
-    public fun burn(
+    public fun burn_from(
         ref: &BurnRef,
         store: Object,
         amount: u64
@@ -346,11 +357,7 @@ module aptos_framework::fungible_asset {
         let metadata = ref.metadata;
         assert!(metadata == store_metadata(store), error::invalid_argument(EBURN_REF_AND_STORE_MISMATCH));
         let store_addr = object::object_address(&store);
-        let FungibleAsset {
-            metadata,
-            amount,
-        } = withdraw_internal(store_addr, amount);
-        decrease_supply(&metadata, amount);
+        burn(ref, withdraw_internal(store_addr, amount));
     }
 
     /// Withdraw `amount` of fungible metadata from `store` ignoring `allow_ungated_transfer`.
@@ -413,10 +420,7 @@ module aptos_framework::fungible_asset {
         assert!(amount == 0, error::invalid_argument(EAMOUNT_IS_NOT_ZERO));
     }
 
-    fun deposit_internal(
-        store: Object,
-        fa: FungibleAsset
-    ) acquires FungibleAssetStore, FungibleAssetEvents {
+    fun deposit_internal(store: Object, fa: FungibleAsset) acquires FungibleAssetStore, FungibleAssetEvents {
         let FungibleAsset { metadata, amount } = fa;
         let store_metadata = store_metadata(store);
         assert!(metadata == store_metadata, error::invalid_argument(EFUNGIBLE_ASSET_AND_STORE_MISMATCH));
@@ -588,7 +592,7 @@ module aptos_framework::fungible_asset {
         assert!(supply(test_token) == 100, 3);
         deposit(aaron_store, fa);
         // Burn
-        burn(&burn_ref, aaron_store, 30);
+        burn_from(&burn_ref, aaron_store, 30);
         assert!(supply(test_token) == 70, 4);
         // Transfer
         transfer(creator, creator_store, aaron_store, 10);
@@ -632,4 +636,18 @@ module aptos_framework::fungible_asset {
         assert!(!ungated_transfer_allowed(creator_store), 3);
         assert!(!ungated_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..61cf480013bae 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)
     }
@@ -537,7 +538,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 +561,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
index a5e500be94409..207d09784c26a 100644
--- a/aptos-move/framework/aptos-framework/sources/primary_store.move
+++ b/aptos-move/framework/aptos-framework/sources/primary_store.move
@@ -1,6 +1,5 @@
 /// This defines the module for interacting with primary stores of accounts/objects, which have deterministic addresses
 module aptos_framework::primary_store {
-    use aptos_framework::create_signer;
     use aptos_framework::fungible_asset::{Self, FungibleAsset, FungibleAssetStore};
     use aptos_framework::object::{Self, Object, ConstructorRef, DeriveRef};
 
@@ -34,9 +33,10 @@ module aptos_framework::primary_store {
         metadata: Object,
     ): Object acquires DeriveRefPod {
         if (!primary_store_exists(owner, metadata)) {
-            create_primary_store(owner, metadata);
-        };
-        primary_store(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.
@@ -44,10 +44,9 @@ module aptos_framework::primary_store {
         owner_addr: address,
         metadata: Object,
     ): Object acquires DeriveRefPod {
-        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);
+        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);
@@ -86,7 +85,11 @@ module aptos_framework::primary_store {
     #[view]
     /// Return whether the given account's primary store can do direct transfers.
     public fun ungated_transfer_allowed(account: address, metadata: Object): bool {
-        fungible_asset::ungated_transfer_allowed(primary_store(account, metadata))
+        if (primary_store_exists(account, metadata)) {
+            fungible_asset::ungated_transfer_allowed(primary_store(account, metadata))
+        } else {
+            true
+        }
     }
 
     /// Withdraw `amount` of fungible asset from `store` by the owner.
@@ -113,4 +116,64 @@ module aptos_framework::primary_store {
         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]
+    public fun init_test_metadata_with_primary_store_enabled(
+        constructor_ref: &ConstructorRef
+    ): (MintRef, TransferRef, BurnRef) {
+        create_primary_wallet_enabled_fungible_asset(
+            constructor_ref,
+            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_transfer_allowed(creator_address, metadata), 5);
+        assert!(ungated_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/move-examples/fungible_asset/sources/managed_fungible_asset.move b/aptos-move/move-examples/fungible_asset/sources/managed_fungible_asset.move
index 2ff3615bb0eb8..0903b97ba14dc 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
@@ -56,7 +56,7 @@ module fungible_asset::managed_fungible_asset {
     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_store::ensure_primary_wallet_exists(to, 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);
     }
@@ -65,8 +65,8 @@ module fungible_asset::managed_fungible_asset {
     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_store::ensure_primary_wallet_exists(from, asset);
-        let to_wallet = primary_store::ensure_primary_wallet_exists(to, asset);
+        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);
     }
 
@@ -74,15 +74,15 @@ module fungible_asset::managed_fungible_asset {
     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_store::ensure_primary_wallet_exists(from, asset);
-        fungible_asset::burn(burn_ref, from_wallet, amount);
+        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_asset();
         let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref;
-        let wallet = primary_store::ensure_primary_wallet_exists(account, asset);
+        let wallet = primary_store::ensure_primary_store_exists(account, asset);
         fungible_asset::set_ungated_transfer(transfer_ref, wallet, false);
     }
 
@@ -90,7 +90,7 @@ module fungible_asset::managed_fungible_asset {
     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_store::ensure_primary_wallet_exists(account, asset);
+        let wallet = primary_store::ensure_primary_store_exists(account, asset);
         fungible_asset::set_ungated_transfer(transfer_ref, wallet, true);
     }
 
@@ -98,7 +98,7 @@ module fungible_asset::managed_fungible_asset {
     public fun withdraw(admin: &signer, amount: u64, from: address): FungibleAsset acquires ManagedFungibleAsset {
         let asset = get_asset();
         let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref;
-        let from_wallet = primary_store::ensure_primary_wallet_exists(from, asset);
+        let from_wallet = primary_store::ensure_primary_store_exists(from, asset);
         fungible_asset::withdraw_with_ref(transfer_ref, from_wallet, amount)
     }
 
@@ -106,7 +106,7 @@ module fungible_asset::managed_fungible_asset {
     public fun deposit(admin: &signer, to: address, fa: FungibleAsset) acquires ManagedFungibleAsset {
         let asset = get_asset();
         let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref;
-        let to_wallet = primary_store::ensure_primary_wallet_exists(to, asset);
+        let to_wallet = primary_store::ensure_primary_store_exists(to, asset);
         fungible_asset::deposit_with_ref(transfer_ref, to_wallet, fa);
     }