From af43aab8b558d7ec8ca46edb334191717de4fd63 Mon Sep 17 00:00:00 2001 From: Aaron Gao Date: Tue, 11 Apr 2023 16:52:57 -0700 Subject: [PATCH] add back managed_coin example --- .../fungible_asset/sources/coin_example.move | 1 + .../fungible_asset/sources/managed_coin.move | 154 ++++++++++++++++++ .../sources/managed_fungible_asset.move | 5 +- 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 aptos-move/move-examples/fungible_asset/sources/managed_coin.move diff --git a/aptos-move/move-examples/fungible_asset/sources/coin_example.move b/aptos-move/move-examples/fungible_asset/sources/coin_example.move index 136a342c73876..25ea5560c063f 100644 --- a/aptos-move/move-examples/fungible_asset/sources/coin_example.move +++ b/aptos-move/move-examples/fungible_asset/sources/coin_example.move @@ -1,3 +1,4 @@ +/// A coin example using managed_fungible_asset. module fungible_asset_extension::coin_example { use aptos_framework::object; use aptos_framework::fungible_asset::{Metadata, FungibleAsset}; 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 76bde75594d4a..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,6 +1,5 @@ -/// 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_extension::managed_fungible_asset { use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, FungibleAsset, Metadata};