MyCut

First Flight #23
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Invalid

`FeeOnTransfer` tokens can lead to inaccurate reward distribution

Description Weird ERC20 tokens charge a cut on every other transaction they process. The fee cut is either sent to the zero address or to the DAO of that particular token contract.

When a player claims reward by calling the claimCut::Pot.sol function, the contract calls the _transferReward internal function which transfers the assigned reward share to player's address.
The amount that is sent out is not what is received by the player. FeeOnTransfer tokens take a cut of the amount while the rest is transfered to the player.

Impact There is a mismatch in the amount sent out as reward to the amount received. The player does not receive his intended cut.

Proof of Concept

WeirdERC20.sol FeeOnTransfer contract is a deflationary token that charges 10 percent cut on every 4th transaction.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// This is a Deflationary ERC20 Token!
contract WeirdERC20 is ERC20 {
uint256 public constant INITIAL_SUPPLY = 1_000_000e18;
// We take a fee once every 5 transactions
uint256 public count = 0;
uint256 public constant FEE = 10;
uint256 public constant USER_AMOUNT = 90;
uint256 public constant PRECISION = 100;
constructor() ERC20("WeirdERC20", "W20") {
_mint(msg.sender,INITIAL_SUPPLY);
}
function mint(address to,uint256 amount)public{
_mint(to,amount);
}
// Every 4th transaction, 10 % of the fee is deducted and burnt.
function _update(address from, address to, uint256 value) internal virtual override {
if (count >= 3) {
uint256 userAmount = value * USER_AMOUNT / PRECISION;
uint256 feeAmount = value * FEE / PRECISION;
count = 0;
super._update(from, to, userAmount);
super._update(from, address(0), feeAmount);
} else {
count++;
super._update(from, to, value);
}
}
}

Place the following test in TestMyCut.t.sol:

function test_FeeOnTransferTokens()public{
vm.startPrank(user);
// 1st transaction of the token contract
WeirdERC20 w20 = new WeirdERC20();
address conMan2 = address(new ContestManager());
address pot = ContestManager(conMan2).createContest(players,rewards,w20,totalRewards);
w20.approve(conMan2,type(uint256).max);
// 2nd transaction of the token contract
ContestManager(conMan2).fundContest(0);
vm.stopPrank();
// 3rd transaction of the token contract
vm.prank(player1);
Pot(pot).claimCut();
console.log("Player2 expected reward:",Pot(pot).checkCut(player2));
// 4th transaction of the token contract
vm.prank(player2);
Pot(pot).claimCut();
assertEq(Pot(pot).getToken().balanceOf(player1),3e18);
console.log("Player2 actual reward:",Pot(pot).getToken().balanceOf(player2));
assertEq(Pot(pot).getToken().balanceOf(player2),0.9e18);
}
Player2 expected reward: 1000000000000000000 = 1 token
Player2 actual reward: 900000000000000000 = 0.9 token
Updates

Lead Judging Commences

equious Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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