Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: high
Valid

Internal View Functions `_balanceOf` and `totalSupply_` Terminate Execution Context

Description

  • Internal view functions like _balanceOf and totalSupply_ are intended to return values to the calling Solidity code for further logic processing.

  • These functions use the Yul return(offset, size) opcode, which terminates the entire EVM execution frame immediately; this means any contract inheriting this token and calling these internal functions will have its execution halted abruptly, returning the value to the external caller instead of continuing execution.

/// File: src/helpers/ERC20Internals.sol:L36
@> return(ptr, 0x20) // Terminates execution context!

Risk

Likelihood:

  • Developers inherit this contract and attempt to use standard internal hooks like _balanceOf(user) within conditional logic (e.g., if (_balanceOf(msg.sender) > 0) ...).

Impact:

  • Composition is completely broken; downstream logic in child contracts will never execute (silent failure/abort).

  • Contracts relying on these checks will behave unpredictably or fail to enforce logic.

Proof of Concept

function test_BalanceOfBreaksFlow() public {
// 1. Setup
// WrapperToken has a function `checkBalanceAndReturnBool` that calls `_balanceOf`
// and then should return `true`.
token.mint(address(this), 100);
// 2. Exploit steps & Assertions
// We use a low-level call to inspect the raw return data.
// If the bug exists, the return data will be the balance (100) instead of the bool (1).
(bool success, bytes memory data) = address(token).staticcall(
abi.encodeWithSelector(WrapperToken.checkBalanceAndReturnBool.selector, address(this))
);
assertTrue(success, "Call failed");
// Decode as uint256 to see if we got the balance back
uint256 result = abi.decode(data, (uint256));
console.log("Returned Data as Uint:", result);
// If logic wasn't broken, it would return bool(true) -> 1.
// Because it returns 100, we know it returned early.
assertEq(result, 100, "Function returned balance instead of boolean result!");
}

Recommended Mitigation

- return(ptr, 0x20)
+ balance := sload(dataSlot)
Updates

Lead Judging Commences

gaurangbrdv Lead Judge 19 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

gaurangbrdv Lead Judge 14 days ago
Submission Judgement Published
Validated
Assigned finding tags:

opcode disaster

the vulnerabilities related to incorrect opcode used

Support

FAQs

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

Give us feedback!