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: 63/84
Findings: 1
Award: $7.18
🌟 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 issue can be exploited to receive additional amounts of the distributed assets and/or to disrupt the core functionality of the protocol.
The LiquidInfrastructureERC20
contract inherits Open Zeppelin's ERC20
implementation. So it is possible to transfer zero values. There is additional restriction in the LiquidInfrastructureERC20._beforeTokenTransfer
huk that the receiver should be approved to hold the token:
require( isApprovedHolder(to), "receiver not approved to hold the token" );
In case the receiver's balanceOf
is zero the receiver's address is pushed to the holders
array:
bool exists = (this.balanceOf(to) != 0); if (!exists) { holders.push(to); }
So if someone sends zero value to an approved address with zero balance the address is pushed to the holders
array. Since the address balance is still zero the operation can be repeated again.
The holders
array is used in the distribute
function to distribute ERC20s tokens among the protocol's tokens holders:
for (i = nextDistributionRecipient; i < limit; i++) { address recipient = holders[i]; 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; } } emit Distribution(recipient, distributableERC20s, receipts); } }
Several includings at the holders
array lets the holder receive the additional amount of assets in case of sufficient assets balance.
When the assets balance of the contract becomes insufficient (because of transferring additional amounts) the distribute
function throws an error and the core functionality of the protocol is blocked by the LockedForDistribution
status. It can only be changed at the distribute
function by calling the _endDistribution
:
nextDistributionRecipient = i; if (nextDistributionRecipient == holders.length) { _endDistribution(); }
Then only the contract owner can solve the accident by removing tokens from distributableERC20s
array or holders from the HolderAllowlist
mapping.
Manual review
Consider pushing addresses at the holders
array only when amount is non zero:
bool exists = (this.balanceOf(to) != 0); - if (!exists) { + if (!exists && amount != 0) { holders.push(to); }
Invalid Validation
#0 - c4-pre-sort
2024-02-20T06:24:17Z
0xRobocop marked the issue as duplicate of #536
#1 - c4-pre-sort
2024-02-20T06:33:41Z
0xRobocop marked the issue as duplicate of #77
#2 - c4-judge
2024-03-03T13:30:16Z
0xA5DF marked the issue as satisfactory