Root Cause
The fuzzer detected that when type(uint256).max(1.157e77) is used as the allowance amount, the subsequent read of that allowance returns 0.
Test: testFuzzTransferFrom
Failure: Initial allowance failed: 0 != 115792089237316195423570985008687907853269984665640564039457584007913129639935
This indicates a flaw in the _allowance mapping assembly logic when handling the all-ones hexadecimal representation (0xFF...FF). While the sstore in _approve likely executes, the read back in _allowance might be calculating an incorrect storage slot (dataSlot), causing sload to return 0.
Impact
Users cannot approve the maximum amount of tokens. If a protocol relies on a "sticky" approval of 2^{256}-1 (infinite approval), this function fails, rendering the token unusable for protocols requiring max allowance.
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
mstore(ptr, owner)
mstore(add(ptr, 0x20), baseSlot)
let initialHash := keccak256(ptr, 0x40)
mstore(ptr, spender)
mstore(add(ptr, 0x20), initialHash)
let allowanceSlot := keccak256(ptr, 0x40)
remaining := sload(allowanceSlot)
}
}
Proof of Concept
function testFuzzTransferFrom(address spender, address receiver, uint256 allowanceAmount, uint256 spendAmount) public {
vm.assume(spender != ZERO_ADDRESS && receiver != ZERO_ADDRESS);
vm.assume(spender != ALICE);
vm.assume(spendAmount <= allowanceAmount);
vm.assume(spendAmount <= token.balanceOf(ALICE));
uint256 initialAliceBalance = token.balanceOf(ALICE);
uint256 initialReceiverBalance = token.balanceOf(receiver);
emit Approval(ALICE, spender, allowanceAmount);
assertEq(token.allowance(ALICE, spender), allowanceAmount, "Initial allowance failed");
vm.prank(spender);
assertTrue(token.transferFrom(ALICE, receiver, spendAmount), "TransferFrom must succeed");
uint256 remainingAllowance = allowanceAmount - spendAmount;
assertEq(token.allowance(ALICE, spender), remainingAllowance, "Allowance was not spent correctly");
assertEq(token.balanceOf(ALICE), initialAliceBalance - spendAmount, "Alice's balance incorrect after spend");
assertEq(token.balanceOf(receiver), initialReceiverBalance + spendAmount, "Receiver's balance incorrect after spend");
}