RebateFi Hook

First Flight #53
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: low
Valid

Incorrect Event Parameter Order in TokensWithdrawn

Root + Impact

The TokensWithdrawn event is emitted with swapped parameter order, causing off-chain systems to misinterpret token addresses as recipients and vice versa.

Description

  • Events serve as the primary data source for off-chain indexers, block explorers, and analytics.

  • The event signature declares (token, to, amount) but emission uses (to, token, amount).

// Event declaration (line 44)
event TokensWithdrawn(address indexed token, address indexed to, uint256 amount);
// Event emission (line 75) - parameters swapped
function withdrawTokens(address token, address to, uint256 amount) external onlyOwner {
IERC20(token).transfer(to, amount);
@> emit TokensWithdrawn(to, token, amount); // @> Should be (token, to, amount)
}

Risk

Likelihood:

  • Occurs on every withdrawal operation

  • Any system parsing events will get incorrect data

Impact:

  • Block explorers show wrong token/recipient addresses

  • Accounting systems track incorrect data

  • Auditing withdrawal history becomes unreliable

Proof of Concept

Here is the PoC to understand the event issue:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {ReFiSwapRebateHook} from "../src/RebateFiHook.sol";
import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
contract EventOrderPoCTest is Test, Deployers {
ReFiSwapRebateHook public hook;
MockERC20 public reFiToken;
address recipient = address(0xBEEF);
// Event as declared in contract
event TokensWithdrawn(address indexed token, address indexed to, uint256 amount);
function setUp() public {
deployFreshManagerAndRouters();
reFiToken = new MockERC20("ReFi", "REFI", 18);
hook = new ReFiSwapRebateHook(manager, address(reFiToken));
// Send tokens to hook contract
reFiToken.mint(address(hook), 100 ether);
}
function test_PoC_EventParametersSwapped() public {
uint256 withdrawAmount = 10 ether;
address tokenAddress = address(reFiToken);
address recipientAddress = recipient;
// The event SHOULD emit: TokensWithdrawn(token, to, amount)
// But it ACTUALLY emits: TokensWithdrawn(to, token, amount) - SWAPPED!
// This expectEmit will FAIL if we expect correct order
// because contract emits in wrong order
console.log("=== Event Parameter Analysis ===");
console.log("Token Address:", tokenAddress);
console.log("Recipient Address:", recipientAddress);
console.log("");
console.log("EXPECTED Event: TokensWithdrawn(token, recipient, amount)");
console.log(" - First indexed param (token):", tokenAddress);
console.log(" - Second indexed param (to):", recipientAddress);
console.log("");
console.log("ACTUAL Event: TokensWithdrawn(recipient, token, amount)");
console.log(" - First indexed param (token):", recipientAddress, "<-- WRONG!");
console.log(" - Second indexed param (to):", tokenAddress, "<-- WRONG!");
// Expect the WRONG order (what contract actually emits)
vm.expectEmit(true, true, false, true);
emit TokensWithdrawn(recipientAddress, tokenAddress, withdrawAmount); // Swapped!
hook.withdrawTokens(tokenAddress, recipientAddress, withdrawAmount);
}
function test_PoC_IndexerMisinterpretation() public {
// Simulating how an indexer would parse the event
address tokenAddress = address(reFiToken);
address recipientAddress = recipient;
uint256 amount = 10 ether;
// When indexer receives the event log:
// topics[1] = recipient (but indexer thinks it's token)
// topics[2] = token (but indexer thinks it's recipient)
console.log("=== Indexer Interpretation ===");
console.log("");
console.log("Indexer sees in event logs:");
console.log(" topics[1] (thinks 'token'):", recipientAddress);
console.log(" topics[2] (thinks 'to'):", tokenAddress);
console.log("");
console.log("Indexer database records:");
console.log(" Token withdrawn: 0xBEEF (actually an EOA!)");
console.log(" Sent to: ReFi token contract (actually a token!)");
console.log("");
console.log("Result: Completely corrupted withdrawal history");
vm.expectEmit(true, true, false, true);
emit TokensWithdrawn(recipientAddress, tokenAddress, amount);
hook.withdrawTokens(tokenAddress, recipientAddress, amount);
}
}

Recommended Mitigation

Fix the emission order to match the declaration:

function withdrawTokens(address token, address to, uint256 amount) external onlyOwner {
IERC20(token).transfer(to, amount);
- emit TokensWithdrawn(to, token, amount);
+ emit TokensWithdrawn(token, to, amount);
}
Updates

Lead Judging Commences

chaossr Lead Judge 12 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Swapped token and to parameters in TokensWithdrawn event.

Support

FAQs

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

Give us feedback!