Skip to content

Latest commit

 

History

History
81 lines (49 loc) · 2.47 KB

038.md

File metadata and controls

81 lines (49 loc) · 2.47 KB

Tall Burgundy Nightingale

High

Asymmetric Token Handling in Blacklist Operations

Summary

The unidirectional token transfer in blacklisting operations will cause permanent loss of user funds for blacklisted users as tokens are seized upon blacklisting but not returned upon unblacklisting, allowing BLACKLISTER_ROLE to permanently confiscate user funds even after blacklist removal.

Root Cause

https://github.com/sherlock-audit/2024-11-telcoin/blob/main/telcoin-audit/contracts/stablecoin/Stablecoin.sol#L123-L125

In Stablecoin.sol the design choice to transfer tokens to BLACKLISTER_ROLE in _onceBlacklisted without a corresponding return mechanism in removeBlackList is a mistake as it turns temporary blacklisting into permanent token confiscation.

Internal pre-conditions

User needs to have a token balance greater than 0

BLACKLISTER_ROLE needs to call addBlackList to blacklist the user

_onceBlacklisted function needs to transfer user's balance to BLACKLISTER_ROLE

BLACKLISTER_ROLE needs to call removeBlackList to unblacklist the user

External pre-conditions

None

Attack Path

User has 1000 tokens in their account

BLACKLISTER_ROLE blacklists user

_onceBlacklisted transfers all 1000 tokens to BLACKLISTER_ROLE

BLACKLISTER_ROLE removes user from blacklist

User's blacklist status is cleared but their 1000 tokens remain with BLACKLISTER_ROLE

User has effectively lost all their tokens despite being unblacklisted

Impact

Users suffer permanent loss of 100% of their token balance when blacklisted, even if later unblacklisted.

The BLACKLISTER_ROLE gains permanent ownership of these tokens.

PoC

No response

Mitigation

Track seized balances when users are blacklisted and return them on removing from blacklist

contract Stablecoin is ERC20PermitUpgradeable, Blacklist {
    // Add mapping to store seized balances
    mapping(address => uint256) private _seizedBalances;

    function _onceBlacklisted(address user) internal override {
        uint256 balance = balanceOf(user);
        if(balance > 0) {
            _seizedBalances[user] = balance;
            _transfer(user, address(this), balance); // Transfer to contract instead of BLACKLISTER_ROLE
        }
    }

    function _onceUnblacklisted(address user) internal virtual {
        uint256 seizedBalance = _seizedBalances[user];
        if(seizedBalance > 0) {
            delete _seizedBalances[user];
            _transfer(address(this), user, seizedBalance);
        }
    }
}