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: 50/84
Findings: 2
Award: $32.91
🌟 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
Since zero addresses are inserted into holders
array repeatedly, the length of holders
array becomes extremely large.
Thus, transfer
function call of LiquidInfrastructureERC20
token will revert due to the lack of gas (DoS).
And also, call of other several functions involving distributeToAllHolders
function will revert due to the lack gas too (DoS).
LiquidInfrastructureERC20.sol#_beforeTokenTransfer
function is the following.
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(); } 142: bool exists = (this.balanceOf(to) != 0); if (!exists) { holders.push(to); } }
When _beforeTokenTransfer
function is called by burn
function, to = address(0)
holds.
Since the balance of zero address always be zero, this.balanceOf(to) = 0
and exist = false
holds in L142
, and so zero address is pushed into holders
array.
Thus, whenever burn
function is called, zero addresses are pushed into holders
array repeatedly.
On the other hand, the zero addresses which are pushed above may be popped in the following LiquidInfrastructureERC20.sol#_afterTokenTransfer
function which is called by mint
function.
function _afterTokenTransfer( address from, address to, uint256 amount ) internal virtual override { bool stillHolding = (this.balanceOf(from) != 0); if (!stillHolding) { for (uint i = 0; i < holders.length; i++) { if (holders[i] == from) { // Remove the element at i by copying the last one into its place and removing the last element 174: holders[i] = holders[holders.length - 1]; holders.pop(); } } } }
But if holders[holders.length - 1] = from
in L174
, the holder moved into holders[i]
(=from
) will not be removed.
That is, at the end of the loop, from
s could be remained in holders
array.
If zero addresses are located at the end of the array, at most half of the zero addresses are not removed from holders
array after the mint
function call.
Due to the two above issues, when burn
function is called many times and mint
function is called extremely small times, the length of holders
array will become too large to process with zero addresses.
Then, transfer
function call of LiquidInfrastructureERC20
token will revert due to the lack of gas (DoS).
And also, call of other several functions involving distributeToAllHolders
function will revert due to the lack gas too (DoS).
Manual Review
Modify LiquidInfrastructureERC20.sol#_beforeTokenTransfer
function as follows.
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) { ++ if (!exists && to != address(0)) { holders.push(to); } }
And modify LiquidInfrastructureERC20.sol#_afterTokenTransfer
function as follows.
function _afterTokenTransfer( address from, address to, uint256 amount ) internal virtual override { bool stillHolding = (this.balanceOf(from) != 0); if (!stillHolding) { for (uint i = 0; i < holders.length; i++) { if (holders[i] == from) { // Remove the element at i by copying the last one into its place and removing the last element holders[i] = holders[holders.length - 1]; holders.pop(); ++ i--; } } } }
DoS
#0 - c4-pre-sort
2024-02-20T06:24:53Z
0xRobocop marked the issue as duplicate of #727
#1 - c4-pre-sort
2024-02-20T06:34:09Z
0xRobocop marked the issue as duplicate of #77
#2 - c4-judge
2024-03-04T13:06:33Z
0xA5DF marked the issue as satisfactory
#3 - c4-judge
2024-03-08T15:08:04Z
0xA5DF changed the severity to 3 (High Risk)
🌟 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
If distributableERC20s
is changed during distribution, this can lead to wrong distribution.
LiquidInfrastructureERC20.sol#setDistributableERC20s
function which sets distributed tokens is as follows.
function setDistributableERC20s( address[] memory _distributableERC20s ) public onlyOwner { distributableERC20s = _distributableERC20s; }
As we can see above, there isn't any check. If this function is called during distribution, after this time unintended tokens are distributed to users. This process can be caused by front-running of malicious user.
Manual Review
LiquidInfrastructureERC20.sol#setDistributableERC20s
function has to be modified as follows.
function setDistributableERC20s( address[] memory _distributableERC20s ) public onlyOwner { + require(!LockedForDistribution, "Still distributting"); distributableERC20s = _distributableERC20s; }
Invalid Validation
#0 - c4-pre-sort
2024-02-20T05:55:49Z
0xRobocop marked the issue as duplicate of #260
#1 - c4-judge
2024-03-04T15:30:19Z
0xA5DF marked the issue as satisfactory
#2 - c4-judge
2024-03-08T15:13:05Z
0xA5DF changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-08T15:26:19Z
0xA5DF changed the severity to 2 (Med Risk)