Skip to content

Commit

Permalink
[framework] Add ability to destroy caps from a managed coin
Browse files Browse the repository at this point in the history
  • Loading branch information
gregnazario committed Oct 2, 2024
1 parent 1d0cf10 commit 5fb05ee
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 1 deletion.
106 changes: 106 additions & 0 deletions aptos-move/framework/aptos-framework/doc/managed_coin.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ By utilizing this current module, a developer can create his own coin and care l
- [Function `initialize`](#0x1_managed_coin_initialize)
- [Function `mint`](#0x1_managed_coin_mint)
- [Function `register`](#0x1_managed_coin_register)
- [Function `destroy_caps`](#0x1_managed_coin_destroy_caps)
- [Function `remove_caps`](#0x1_managed_coin_remove_caps)
- [Specification](#@Specification_1)
- [High-level Requirements](#high-level-req)
- [Module-level Specification](#module-level-spec)
- [Function `burn`](#@Specification_1_burn)
- [Function `initialize`](#@Specification_1_initialize)
- [Function `mint`](#@Specification_1_mint)
- [Function `register`](#@Specification_1_register)
- [Function `destroy_caps`](#@Specification_1_destroy_caps)
- [Function `remove_caps`](#@Specification_1_remove_caps)


<pre><code><b>use</b> <a href="coin.md#0x1_coin">0x1::coin</a>;
Expand Down Expand Up @@ -231,6 +235,72 @@ Required if user wants to start accepting deposits of <code>CoinType</code> in h



</details>

<a id="0x1_managed_coin_destroy_caps"></a>

## Function `destroy_caps`

Destroys capabilities from the account, so that the user no longer has access to mint or burn.


<pre><code><b>public</b> entry <b>fun</b> <a href="managed_coin.md#0x1_managed_coin_destroy_caps">destroy_caps</a>&lt;CoinType&gt;(<a href="account.md#0x1_account">account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>)
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> entry <b>fun</b> <a href="managed_coin.md#0x1_managed_coin_destroy_caps">destroy_caps</a>&lt;CoinType&gt;(<a href="account.md#0x1_account">account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>) <b>acquires</b> <a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a> {
<b>let</b> (burn_cap, freeze_cap, mint_cap) = <a href="managed_coin.md#0x1_managed_coin_remove_caps">remove_caps</a>&lt;CoinType&gt;(<a href="account.md#0x1_account">account</a>);
destroy_burn_cap(burn_cap);
destroy_freeze_cap(freeze_cap);
destroy_mint_cap(mint_cap);
}
</code></pre>



</details>

<a id="0x1_managed_coin_remove_caps"></a>

## Function `remove_caps`

Removes capabilities from the account to be stored or destroyed elsewhere


<pre><code><b>public</b> <b>fun</b> <a href="managed_coin.md#0x1_managed_coin_remove_caps">remove_caps</a>&lt;CoinType&gt;(<a href="account.md#0x1_account">account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>): (<a href="coin.md#0x1_coin_BurnCapability">coin::BurnCapability</a>&lt;CoinType&gt;, <a href="coin.md#0x1_coin_FreezeCapability">coin::FreezeCapability</a>&lt;CoinType&gt;, <a href="coin.md#0x1_coin_MintCapability">coin::MintCapability</a>&lt;CoinType&gt;)
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="managed_coin.md#0x1_managed_coin_remove_caps">remove_caps</a>&lt;CoinType&gt;(
<a href="account.md#0x1_account">account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>
): (BurnCapability&lt;CoinType&gt;, FreezeCapability&lt;CoinType&gt;, MintCapability&lt;CoinType&gt;) <b>acquires</b> <a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a> {
<b>let</b> account_addr = <a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer_address_of">signer::address_of</a>(<a href="account.md#0x1_account">account</a>);
<b>assert</b>!(
<b>exists</b>&lt;<a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a>&lt;CoinType&gt;&gt;(account_addr),
<a href="../../aptos-stdlib/../move-stdlib/doc/error.md#0x1_error_not_found">error::not_found</a>(<a href="managed_coin.md#0x1_managed_coin_ENO_CAPABILITIES">ENO_CAPABILITIES</a>),
);

<b>let</b> <a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a>&lt;CoinType&gt; {
burn_cap,
freeze_cap,
mint_cap,
} = <b>move_from</b>&lt;<a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a>&lt;CoinType&gt;&gt;(account_addr);
(burn_cap, freeze_cap, mint_cap)
}
</code></pre>



</details>

<a id="@Specification_1"></a>
Expand Down Expand Up @@ -423,4 +493,40 @@ Updating <code>Account.guid_creation_num</code> will not overflow.
</code></pre>



<a id="@Specification_1_destroy_caps"></a>

### Function `destroy_caps`


<pre><code><b>public</b> entry <b>fun</b> <a href="managed_coin.md#0x1_managed_coin_destroy_caps">destroy_caps</a>&lt;CoinType&gt;(<a href="account.md#0x1_account">account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>)
</code></pre>




<pre><code><b>let</b> account_addr = <a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer_address_of">signer::address_of</a>(<a href="account.md#0x1_account">account</a>);
<b>aborts_if</b> !<b>exists</b>&lt;<a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a>&lt;CoinType&gt;&gt;(account_addr);
<b>ensures</b> !<b>exists</b>&lt;<a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a>&lt;CoinType&gt;&gt;(account_addr);
</code></pre>



<a id="@Specification_1_remove_caps"></a>

### Function `remove_caps`


<pre><code><b>public</b> <b>fun</b> <a href="managed_coin.md#0x1_managed_coin_remove_caps">remove_caps</a>&lt;CoinType&gt;(<a href="account.md#0x1_account">account</a>: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>): (<a href="coin.md#0x1_coin_BurnCapability">coin::BurnCapability</a>&lt;CoinType&gt;, <a href="coin.md#0x1_coin_FreezeCapability">coin::FreezeCapability</a>&lt;CoinType&gt;, <a href="coin.md#0x1_coin_MintCapability">coin::MintCapability</a>&lt;CoinType&gt;)
</code></pre>




<pre><code><b>let</b> account_addr = <a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer_address_of">signer::address_of</a>(<a href="account.md#0x1_account">account</a>);
<b>aborts_if</b> !<b>exists</b>&lt;<a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a>&lt;CoinType&gt;&gt;(account_addr);
<b>ensures</b> !<b>exists</b>&lt;<a href="managed_coin.md#0x1_managed_coin_Capabilities">Capabilities</a>&lt;CoinType&gt;&gt;(account_addr);
</code></pre>


[move-book]: https://aptos.dev/move/book/SUMMARY
64 changes: 63 additions & 1 deletion aptos-move/framework/aptos-framework/sources/managed_coin.move
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ module aptos_framework::managed_coin {
use std::error;
use std::signer;

use aptos_framework::coin::{Self, BurnCapability, FreezeCapability, MintCapability};
use aptos_framework::coin::{Self, BurnCapability, FreezeCapability, MintCapability, destroy_burn_cap,
destroy_freeze_cap, destroy_mint_cap
};

//
// Errors
Expand Down Expand Up @@ -97,6 +99,32 @@ module aptos_framework::managed_coin {
coin::register<CoinType>(account);
}

/// Destroys capabilities from the account, so that the user no longer has access to mint or burn.
public entry fun destroy_caps<CoinType>(account: &signer) acquires Capabilities {
let (burn_cap, freeze_cap, mint_cap) = remove_caps<CoinType>(account);
destroy_burn_cap(burn_cap);
destroy_freeze_cap(freeze_cap);
destroy_mint_cap(mint_cap);
}

/// Removes capabilities from the account to be stored or destroyed elsewhere
public fun remove_caps<CoinType>(
account: &signer
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) acquires Capabilities {
let account_addr = signer::address_of(account);
assert!(
exists<Capabilities<CoinType>>(account_addr),
error::not_found(ENO_CAPABILITIES),
);

let Capabilities<CoinType> {
burn_cap,
freeze_cap,
mint_cap,
} = move_from<Capabilities<CoinType>>(account_addr);
(burn_cap, freeze_cap, mint_cap)
}

//
// Tests
//
Expand Down Expand Up @@ -156,6 +184,40 @@ module aptos_framework::managed_coin {

let new_supply = coin::supply<FakeMoney>();
assert!(option::extract(&mut new_supply) == 20, 2);

// Destroy mint capabilities
destroy_caps<FakeMoney>(&mod_account);
assert!(!exists<Capabilities<FakeMoney>>(signer::address_of(&mod_account)), 3);
}

#[test(source = @0xa11ce, destination = @0xb0b, mod_account = @0x1)]
public entry fun test_end_to_end_caps_removal(
source: signer,
destination: signer,
mod_account: signer
) acquires Capabilities {
let source_addr = signer::address_of(&source);
let destination_addr = signer::address_of(&destination);
aptos_framework::account::create_account_for_test(source_addr);
aptos_framework::account::create_account_for_test(destination_addr);
aptos_framework::account::create_account_for_test(signer::address_of(&mod_account));
aggregator_factory::initialize_aggregator_factory_for_test(&mod_account);

initialize<FakeMoney>(
&mod_account,
b"Fake Money",
b"FMD",
10,
true
);
assert!(coin::is_coin_initialized<FakeMoney>(), 0);

// Remove capabilities
let (burn_cap, freeze_cap, mint_cap) = remove_caps<FakeMoney>(&mod_account);
assert!(!exists<Capabilities<FakeMoney>>(signer::address_of(&mod_account)), 3);
coin::destroy_mint_cap(mint_cap);
coin::destroy_freeze_cap(freeze_cap);
coin::destroy_burn_cap(burn_cap);
}

#[test(source = @0xa11ce, destination = @0xb0b, mod_account = @0x1)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,16 @@ spec aptos_framework::managed_coin {
aborts_if !exists<coin::CoinStore<CoinType>>(account_addr) && !type_info::spec_is_struct<CoinType>();
ensures exists<coin::CoinStore<CoinType>>(account_addr);
}

spec remove_caps<CoinType>(account: &signer): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
let account_addr = signer::address_of(account);
aborts_if !exists<Capabilities<CoinType>>(account_addr);
ensures !exists<Capabilities<CoinType>>(account_addr);
}

spec destroy_caps <CoinType>(account: &signer) {
let account_addr = signer::address_of(account);
aborts_if !exists<Capabilities<CoinType>>(account_addr);
ensures !exists<Capabilities<CoinType>>(account_addr);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,11 @@ pub enum EntryFunctionCall {
amount: u64,
},

/// Destroys capabilities from the account, so that the user no longer has access to mint or burn.
ManagedCoinDestroyCaps {
coin_type: TypeTag,
},

/// Initialize new coin `CoinType` in Aptos Blockchain.
/// Mint and Burn Capabilities will be stored under `account` in `Capabilities` resource.
ManagedCoinInitialize {
Expand Down Expand Up @@ -1341,6 +1346,7 @@ impl EntryFunctionCall {
n_vec,
} => jwks_update_federated_jwk_set(iss, kid_vec, alg_vec, e_vec, n_vec),
ManagedCoinBurn { coin_type, amount } => managed_coin_burn(coin_type, amount),
ManagedCoinDestroyCaps { coin_type } => managed_coin_destroy_caps(coin_type),
ManagedCoinInitialize {
coin_type,
name,
Expand Down Expand Up @@ -2983,6 +2989,22 @@ pub fn managed_coin_burn(coin_type: TypeTag, amount: u64) -> TransactionPayload
))
}

/// Destroys capabilities from the account, so that the user no longer has access to mint or burn.
pub fn managed_coin_destroy_caps(coin_type: TypeTag) -> TransactionPayload {
TransactionPayload::EntryFunction(EntryFunction::new(
ModuleId::new(
AccountAddress::new([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1,
]),
ident_str!("managed_coin").to_owned(),
),
ident_str!("destroy_caps").to_owned(),
vec![coin_type],
vec![],
))
}

/// Initialize new coin `CoinType` in Aptos Blockchain.
/// Mint and Burn Capabilities will be stored under `account` in `Capabilities` resource.
pub fn managed_coin_initialize(
Expand Down Expand Up @@ -5498,6 +5520,16 @@ mod decoder {
}
}

pub fn managed_coin_destroy_caps(payload: &TransactionPayload) -> Option<EntryFunctionCall> {
if let TransactionPayload::EntryFunction(script) = payload {
Some(EntryFunctionCall::ManagedCoinDestroyCaps {
coin_type: script.ty_args().get(0)?.clone(),
})
} else {
None
}
}

pub fn managed_coin_initialize(payload: &TransactionPayload) -> Option<EntryFunctionCall> {
if let TransactionPayload::EntryFunction(script) = payload {
Some(EntryFunctionCall::ManagedCoinInitialize {
Expand Down Expand Up @@ -6801,6 +6833,10 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy<EntryFunctionDecoderMa
"managed_coin_burn".to_string(),
Box::new(decoder::managed_coin_burn),
);
map.insert(
"managed_coin_destroy_caps".to_string(),
Box::new(decoder::managed_coin_destroy_caps),
);
map.insert(
"managed_coin_initialize".to_string(),
Box::new(decoder::managed_coin_initialize),
Expand Down

0 comments on commit 5fb05ee

Please sign in to comment.