Platform: Code4rena
Start Date: 13/02/2024
Pot Size: $24,500 USDC
Total HM: 5
Participants: 84
Period: 6 days
Judge: 0xA5DF
Id: 331
League: ETH
Rank: 43/84
Findings: 3
Award: $53.05
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: BowTiedOriole
Also found by: 0x0bserver, 0xAadi, 0xJoyBoy03, 0xlamide, 0xlemon, 0xpiken, Babylen, Breeje, Brenzee, CodeWasp, DanielArmstrong, DarkTower, Fassi_Security, Fitro, Honour, JohnSmith, Krace, MrPotatoMagic, Myrault, ReadyPlayer2, SovaSlava, SpicyMeatball, TheSavageTeddy, Tigerfrake, atoko, cryptphi, csanuragjain, d3e4, gesha17, kinda_very_good, krikolkk, matejdb, max10afternoon, miaowu, n0kto, nuthan2x, parlayan_yildizlar_takimi, peanuts, petro_1912, pontifex, psb01, pynschon, rouhsamad, shaflow2, slippopz, spark, turvy_fuzz, web3pwn, zhaojohnson
7.1828 USDC - $7.18
The following function is used to transfer tokens to an address to which proceeds to add the user to Holders array if they do not have any balance
bool exists = (this.balanceOf(to) != 0); if (!exists) { holders.push(to); }
while it checks to ensure the user does not have any balances before pushing them to the array, the check is not sufficient
function _beforeTokenTransfer( address from, address to, uint256 amount ) internal virtual override { require(!LockedForDistribution, "distribution in progress"); if (!(to == address(0))) { require( isApprovedHolder(to), "receiver not approved to hold the token" ); } if (from == address(0) || to == address(0)) { _beforeMintOrBurn(); } bool exists = (this.balanceOf(to) != 0); if (!exists) { holders.push(to); } }
lets assume that the function _beforeTokenTransfer()
is successful and receiver address to is added to the holders array. Since there is no check to ensure that user in not already in the array, a malicious user could take advantage of this discrepancy. lets take a look at an example POC
1.Malicious user gets token and is added to the holders array. 2.He burns all the tokens and his balance is back to 0 3.The user then receives tokens multiple times from different senders, each time being added to the Holders array anew. 4.The distribute function is called it loops through the holders array and gives him rewards multiple times, since there is no check to prevent addresses from getting multiple rewards in one block. when this process is repeated multiple times the malicious user could benefit from getting so many rewards 5.Through this process, the malicious user unfairly receives multiple rewards, exploiting the lack of validation for existing holders.
manual review
Before adding an address to the Holders array, verify if the address is already present to prevent duplication.
Implement checks to ensure that rewards are not distributed to the same address multiple times in the same block or distribution cycle.
Token-Transfer
#0 - c4-pre-sort
2024-02-22T05:20:01Z
0xRobocop marked the issue as duplicate of #77
#1 - c4-judge
2024-03-04T13:22:24Z
0xA5DF marked the issue as satisfactory
🌟 Selected for report: 0xloscar01
Also found by: 0xAadi, 0xpiken, BowTiedOriole, Breeje, Fassi_Security, JohnSmith, Limbooo, SpicyMeatball, Tendency, Topmark, ZanyBonzy, aslanbek, atoko, jesjupyter, matejdb, max10afternoon, n0kto, peanuts, pkqs90, rouhsamad, thank_you, zhaojohnson
20.1396 USDC - $20.14
in the protocol a user can be disapproved by the owner using the disapproveHolder
which removes them from the HolderAllowlist
mapping. While this approach is okay the user will end up not receiving their rewards.
in the function
function disapproveHolder(address holder) public onlyOwner { require(isApprovedHolder(holder), "holder not approved"); HolderAllowlist[holder] = false; }
while distributing rewards the following function is also checked
function approveHolder(address holder) public onlyOwner { require(!isApprovedHolder(holder), "holder already approved"); HolderAllowlist[holder] = true; }
the approveHolder function makes sure that the user is in the HolderAllowlist
which is further checked by the distribute
function before distributing the rewards
if (isApprovedHolder(recipient)) { uint256[] memory receipts = new uint256[]( distributableERC20s.length ); for (uint j = 0; j < distributableERC20s.length; j++) { IERC20 toDistribute = IERC20(distributableERC20s[j]); uint256 entitlement = erc20EntitlementPerUnit[j] * this.balanceOf(recipient); if (toDistribute.transfer(recipient, entitlement)) { receipts[j] = entitlement; } }
The check makes sure the user is in the HolderAllowlist
before they can get rewards. this means that if a user was in the list and had accrued some rewards the rewards will be stuck and never get to him.
manual review
Implement a mechanism to distribute accrued rewards to disapproved users before they are removed from the HolderAllowlist
mapping. This ensures that users receive the rewards they have earned even if they are later disapproved by the owner.
Other
#0 - c4-pre-sort
2024-02-22T05:25:43Z
0xRobocop marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-22T05:25:45Z
0xRobocop marked the issue as primary issue
#2 - 0xRobocop
2024-02-22T05:25:49Z
Consider QA
#3 - c4-judge
2024-03-05T12:18:53Z
0xA5DF marked the issue as duplicate of #703
#4 - c4-judge
2024-03-05T12:19:00Z
0xA5DF marked the issue as partial-25
#5 - 0xA5DF
2024-03-05T12:19:55Z
Partial dupe of #703, would credit the dupes according to how much they've identified the impact
🌟 Selected for report: nuthan2x
Also found by: 0x0bserver, AM, CaeraDenoir, DanielArmstrong, JrNet, Kirkeelee, KmanOfficial, Krace, Limbooo, Meera, SovaSlava, SpicyMeatball, TheSavageTeddy, agadzhalov, aslanbek, atoko, csanuragjain, d3e4, imare, jesjupyter, juancito, kartik_giri_47538, kutugu, max10afternoon, offside0011, pkqs90, turvy_fuzz, xchen1130, zhaojohnson, ziyou-
25.7286 USDC - $25.73
The function setDistributableERC20s
enables the owner to add or remove ERC20 tokens from the list of distributable tokens. However, this could potentially overwrite the existing tokens in the system, leading to users lose rewards earned from the removed ERC20 tokens.
function setDistributableERC20s( address[] memory _distributableERC20s ) public onlyOwner { distributableERC20s = _distributableERC20s; }
The code above will alter the existing distributableERC20s in case one is removed from the list and it has pending rewards the rewards could be lost.
manual review
Add a check to ensure that Before modifying the list of distributable ERC20 tokens, ensure that any pending distributions for the affected tokens are completed. This can prevent users from losing their rewards associated with the removed tokens.
Context
#0 - c4-pre-sort
2024-02-20T04:25:17Z
0xRobocop marked the issue as duplicate of #111
#1 - c4-pre-sort
2024-02-20T04:40:10Z
0xRobocop marked the issue as duplicate of #260
#2 - c4-judge
2024-03-04T15:20:59Z
0xA5DF marked the issue as satisfactory
#3 - c4-judge
2024-03-08T15:26:19Z
0xA5DF changed the severity to 2 (Med Risk)