_approve emits Approval event even when allowance unchanged + Medium
Description
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)
success := 1
mstore(0x00, value)
log3(
0x00,
0x20,
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925,
owner,
spender
)
}
}
Risk
Likelihood:
This occurs during repeated approvals by routers, staking contracts, or permit-enabled flows where allowance is often re-written to the same value.
Impact:
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);
_approve(msg.sender, spender, 500);
}
}
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)
+ }
}
}