One Shot: Reloaded

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

Staking rewards are destroyed if the user's account is not pre-registered for CRED.

Author Revealed upon completion

Root + Impact

Description

  • The normal behavior of the cred_token module is to mint and deposit CRED tokens to a user's account. The specific issue is that the mint function checks if the recipient's account is already registered for the CRED token. If the account is not registered, the function calls coin::destroy_zero, which permanently destroys the minted coins. The user receives no error or notification that their rewards were lost.

// Root cause in the codebase with @> marks to highlight the relevant section
public(friend) fun mint(
module_owner: &signer,
to: address,
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); @>
};
}

Risk

Likelihood:

  • This vulnerability will occur whenever a user attempts to unstake a Rapper and they have not yet registered their account for the CRED token.

  • This is a common pattern in Aptos, and users may not be aware they need to call the register function first.

Impact:

  • While not a critical security vulnerability, this creates a poor user experience.

  • Users will believe they have received their staking rewards when in fact they have been destroyed, which could lead to confusion and loss of trust.

Proof of Concept

public(friend) fun mint(
module_owner: &signer,
to: address,
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); @>
};
}

Recommended Mitigation

- if (coin::is_account_registered<CRED>(to)) {
- coin::deposit(to, coins);
- } else {
- coin::destroy_zero(coins);
- };
+ assert!(coin::is_account_registered<CRED>(to), E_ACCOUNT_NOT_REGISTERED);
+ coin::deposit(to, coins);

Support

FAQs

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