Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

_approve emits Approval event even when allowance unchanged

_approve emits Approval event even when allowance unchanged + Medium

Description

  • The _approve() function emits an event even if the allowance did not change. Industry-standard implementations emit events only when a change occurs.

function _approve(address owner, address spender, uint256 value) internal virtual returns (bool success) {
assembly ("memory-safe") {
if iszero(owner) {
mstore(0x00, shl(224, 0xe602df05))
mstore(add(0x00, 4), owner)
revert(0x00, 0x24)
}
if iszero(spender) {
mstore(0x00, shl(224, 0x94280d62))
mstore(add(0x00, 4), spender)
revert(0x00, 0x24)
}
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)
sstore(allowanceSlot, value) // >>@ Always writes and logs even if unchanged
success := 1
mstore(0x00, value)
log3(
0x00,
0x20,
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925,
owner,
spender
)
}
}

Risk

Likelihood:

  • This occurs whenever a wallet, UI, or dApp re-sends the same approval value (common in EOA wallet flows).

This occurs during repeated approvals by routers, staking contracts, or permit-enabled flows where allowance is often re-written to the same value.

Impact:

  • Emits duplicate Approval logs that do not reflect a real state change.

Wastes gas for users and pollutes event indexing on chain analytics tools.

Proof of Concept

contract TestApprove is ERC20 {
function testDoubleApprove(address spender) external {
_approve(msg.sender, spender, 500); // emits
_approve(msg.sender, spender, 500); // ❌ emits again even though unchanged
}
}

Recommended Mitigation

Emit event only when value changes

function _approve(address owner, address spender, uint256 value) internal virtual returns (bool success) {
assembly ("memory-safe") {
if iszero(owner) {
mstore(0x00, shl(224, 0xe602df05))
mstore(add(0x00, 4), owner)
revert(0x00, 0x24)
}
if iszero(spender) {
mstore(0x00, shl(224, 0x94280d62))
mstore(add(0x00, 4), spender)
revert(0x00, 0x24)
}
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)
+ let oldValue := sload(allowanceSlot)
- sstore(allowanceSlot, value)
+ sstore(allowanceSlot, value)
success := 1
- mstore(0x00, value)
- log3(0x00, 0x20, 0x8c5be1e5..., owner, spender)
+ if iszero(eq(oldValue, value)) {
+ let logPtr := mload(0x40)
+ mstore(logPtr, value)
+ log3(logPtr, 0x20,
+ 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925,
+ owner, spender)
+ }
}
}
Updates

Lead Judging Commences

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

Support

FAQs

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

Give us feedback!