MyCut

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

# When branding a reward in `Pot::claimCut`, the user may not receive the reward.

When branding a reward in Pot::claimCut, the user may not receive the reward.

Description

When the user calls the function to receive their share of the reward, there is a possibility that they will not receive anything. Because the _transferReward function is called, which does not check whether the transfer was successful.

function claimCut() public {
address player = msg.sender;
uint256 reward = playersToRewards[player];
if (reward <= 0) {
revert Pot__RewardNotFound();
}
playersToRewards[player] = 0;
remainingRewards -= reward;
claimants.push(player);
@> _transferReward(player, reward);
}
function _transferReward(address player, uint256 reward) internal {
@> i_token.transfer(player, reward);
}

Risk

If for some reason the transaction fails and there is no verification that this is a failure. The mapping will be reduced by the entire amount for the user, but he will not receive money. Because the token did not return a revert, but only a false one.

Impact:

  • This will happen every time and the money will be permanently locked in the protocol.

Proof of Concept

We create a token that only returns false on unsuccessful transactions.

We create a new Pot and do not fund it.

After that, Alice calls the function to get her reward, but her balance will be zero. And the mapping itself has also changed, although she did not receive anything.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {Pot} from "../src/Pot.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// ========================================
// MALICIOUS TOKEN - повертає false замість revert
// ========================================
contract MaliciousToken {
mapping(address => uint256) public balanceOf;
constructor() {
balanceOf[msg.sender] = 1000000 * 1e18;
}
function transfer(address to, uint256 amount) external returns (bool) {
if (balanceOf[msg.sender] < amount) {
return false; // 🚨 Silent failure!
}
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool) {
if (balanceOf[from] < amount) {
return false; // 🚨 Silent failure!
}
balanceOf[from] -= amount;
balanceOf[to] += amount;
return true;
}
}
contract MyCut is Test {
MaliciousToken public token;
Pot public pot;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
function setUp() public {
token = new MaliciousToken();
address[] memory players = new address[](2);
players[0] = alice;
players[1] = bob;
uint256[] memory rewards = new uint256[](2);
rewards[0] = 100 * 1e18;
rewards[1] = 100 * 1e18;
pot = new Pot(players, rewards, IERC20(address(token)), 200 * 1e18);
console.log("Setup complete");
console.log(
"Token balance of this contract: ",
token.balanceOf(address(this))
);
console.log("Pot contract address: ", address(pot));
}
function testSilentTransferFailed() public {
vm.prank(alice);
pot.claimCut();
assertEq(pot.checkCut(alice), 0);
assertEq(pot.getRemainingRewards(), 100 * 1e18);
assertEq(token.balanceOf(alice), 0);
}
}

Recommended Mitigation

The easiest way is to use secure transfers from OpenZeppelin.

  1. Import the contract.

  2. Replace the transfer function, which will return a rollback if the transaction fails.

+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
function _transferReward(address player, uint256 reward) internal {
- i_token.transfer(player, reward);
+ i.token.safeTransfer(player, reward);
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 16 days 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!