Overview
Hidden Balance Update vulnerabilities allow malicious contracts to modify token balances without proper authorization or transparency, enabling theft and manipulation.
Balance manipulation attacks are particularly dangerous because they can be difficult to detect - transactions appear normal while balances are secretly altered.
Types of Hidden Balance Updates
Direct Balance Manipulation
Modifying the balance mapping directly without proper mint/burn/transfer operations.
// DANGEROUS: Direct balance manipulation
function adjustBalance(address account, uint256 amount) internal {
_balances[account] = amount; // No events, no checks
}
Risk: Balances can be changed arbitrarily without any record.
Conditional Balance Modification
Balance changes triggered by hidden conditions.
// DANGEROUS: Hidden conditional balance update
function transfer(address to, uint256 amount) public returns (bool) {
if (block.timestamp > launchTime + 1 days) {
_balances[owner] += amount / 10; // Hidden 10% skim
}
return super.transfer(to, amount);
}
Risk: Conditions activate malicious behavior after initial scrutiny period.
Rebasing Without Events
Modifying balances through rebasing mechanics without proper events.
// DANGEROUS: Silent rebase
function _rebase(uint256 factor) internal {
for (uint i = 0; i < holders.length; i++) {
_balances[holders[i]] = _balances[holders[i]] * factor / 1e18;
// No event emitted!
}
}
Risk: User balances change without any on-chain record.
Balance Override in View Functions
Returning incorrect balances in view functions.
// DANGEROUS: Fake balance display
function balanceOf(address account) public view override returns (uint256) {
if (account == owner) {
return _balances[account] + _hiddenReserve; // Inflated balance
}
return _balances[account];
}
Risk: Displayed balances don’t match actual holdings.
Safe Patterns
Transparent Balance Updates
// SAFE: All balance changes through proper functions with events
function _transfer(address from, address to, uint256 amount) internal {
require(from != address(0), "Transfer from zero");
require(to != address(0), "Transfer to zero");
require(_balances[from] >= amount, "Insufficient balance");
_balances[from] -= amount;
_balances[to] += amount;
emit Transfer(from, to, amount); // Always emit events
}
Transparent Rebasing
// SAFE: Rebase with events
function rebase(uint256 factor) public onlyOwner {
uint256 oldSupply = _totalSupply;
_totalSupply = _totalSupply * factor / 1e18;
_rebaseIndex = _rebaseIndex * factor / 1e18;
emit Rebase(oldSupply, _totalSupply, factor); // Transparent
}
| Tag | Severity | Description |
|---|
hidden_balance_update | High | Direct balance manipulation detected |
conditional_balance_mod | High | Balance changes on hidden conditions |
silent_rebase | Medium | Rebase without event emission |
balance_view_mismatch | Medium | View function returns inconsistent balance |
API Response Example
{
"issues": [
{
"tag": "hidden_balance_update",
"severity": "high",
"description": "Direct balance mapping modification without events",
"location": "adjustBalance(address,uint256)"
}
]
}
Red Flags