Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: low
Likelihood: medium

`ERC20Internals::_balanceOf` and `ERC20Internals::_allowance` forcibly `revert`, breaking the ERC20 standard and causing unexpected reverts in tools, wallets, and protocols that rely on these queries

Author Revealed upon completion

ERC20Internals::_balanceOf and ERC20Internals::_allowance forcibly revert, breaking the ERC20 standard and causing unexpected reverts in tools, wallets, and protocols that rely on these queries

Description

In a standard ERC20, the balanceOf and allowance functions must always return a valid value regardless of the address provided, including address(0), and must never revert.

In this implementation, _balanceOf and _allowance revert when any parameter is address(0), which contradicts ERC20 behavior and causes unexpected reverts in tools and integrations that rely on these standard queries.

function _balanceOf(address owner) internal view returns (uint256) {
assembly {
@> if iszero(owner) {
revert(0, 0)
}
let baseSlot := _balances.slot
(... )
}
function _allowance(address owner, address spender) internal view returns (uint256 remaining) {
assembly {
@> if or(iszero(owner), iszero(spender)) {
revert(0, 0)
}
let ptr := mload(0x40)
(... )
}
}

Risk

Likelihood: Medium

  • This occurs whenever a wallet, explorer, or protocol makes standard queries like balanceOf() or allowance().

  • Tools that iterate addresses or perform general checks on balances and allowances will regularly encounter address(0).

Impact: Low

  • Does not put funds at risk, but causes unexpected reverts in view queries that the ecosystem expects to always succeed.

  • Can prevent wallets, explorers, or integrations from displaying or using the token correctly, severely degrading ERC20 compatibility.

Proof of Concept

The following test shows that balanceOf and allowance revert when any parameter is address(0).
This confirms that the token's view queries do not follow the ERC20 standard, which requires always returning a uint256 and never reverting for valid domain addresses, including 0x0000000000000000000000000000000000000000.

function test_QueryZeroAddress_Reverts() public {
address alice = makeAddr("alice");
token.mint(alice, 1 ether);
// ERC20 standard: balanceOf(0) must return 0, not revert
vm.expectRevert();
token.balanceOf(address(0));
// allowance(alice, 0) should also return 0
vm.expectRevert();
token.allowance(alice, address(0));
// allowance(0, alice) must be a valid query according to ERC20
vm.expectRevert();
token.allowance(address(0), alice);
}
[PASS] test_BalanceOf_Approval_Revert_Address_Zero() (gas: 63678)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.49ms (2.75ms CPU time)

Recommended Mitigation

Remove the checks that force revert when any parameter is address(0), as ERC20 requires that balanceOf and allowance always return a valid value. The functions should handle 0x0 like any other address and return 0 instead of reverting.

function _balanceOf(address owner) internal view returns (uint256) {
assembly {
- if iszero(owner) {
- revert(0, 0)
- }
let baseSlot := _balances.slot
let ptr := mload(0x40)
(...)
}
}
function _allowance(address owner, address spender) internal view returns (uint256 remaining) {
assembly {
- if or(iszero(owner), iszero(spender)) {
- revert(0, 0)
- }
let ptr := mload(0x40)
let baseSlot := _allowances.slot
(... )
}
}

Support

FAQs

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

Give us feedback!