One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
Submission Details
Impact: low
Likelihood: low

Minting non zero CRED to unregistered users aborts with EDESTRUCTION_OF_NONZERO_TOKEN

Author Revealed upon completion

Minting non zero CRED to unregistered users aborts with EDESTRUCTION_OF_NONZERO_TOKEN

Description

Minting CRED to a recipient should either deposit tokens to that account if registered, or safely handle the case when the recipient is unregistered without causing unexpected reverts. However, minting non zero amount to an unregistered account triggers an abort due to clean up because the code mints coins and then calls coin::destroy_zero which aborts for non zero amounts.

Risk

Likelihood: Low

The issue impacts all unregistered users who try to unstake.

Impact: Low

The transaction aborts, however, the user can registered and the issue resolves.

Proof of Concept

#[test]
#[expected_failure(abort_code = 65543, location = 0x1::coin)] // EDESTRUCTION_OF_NONZERO_TOKEN
public fun test_owner_mint_cred_to_unregistered_destroys_minted () {
let module_owner = account::create_account_for_test(@battle_addr);
let framework = account::create_account_for_test(@0x1);
let recipient = @0x1234;
cred_token::initialize_for_test(&module_owner, &framework);
assert!(cred_token::is_account_registered(recipient) == false);
cred_token::mint(&module_owner, recipient, 1000);
assert!(cred_token::balance_of(recipient) == 0);
}

The test proves that transaction aborts when the module owner tries to mint coins to an unregistered account.

Recommended Mitigation

It is recommended to update the flow as:

  • mint only if the amount is greater than zero

  • if the user is not registered, register them

public(friend) fun mint(
module_owner: &signer,
- to: address,
+ to: &signer,
amount: u64
) acquires CredCapabilities {
let caps = borrow_global<CredCapabilities>(signer::address_of(module_owner));
- let coins = coin::mint<CRED>(amount, &caps.mint_cap);
- if (coin::is_account_registered<CRED>(to)) {
- coin::deposit(to, coins);
- } else {
- coin::destroy_zero(coins);
- };
+ if(amount > 0) {
+ let to_addr = signer::address_of(to);
+ if (!coin::is_account_registered<CRED>(to_addr)) {
+ coin::register<CRED>(to);
+ }
+ let coins = coin::mint<CRED>(amount, &caps.mint_cap);
+ coin::deposit(to_addr, coins);
+ }
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.