Platform: Code4rena
Start Date: 07/07/2022
Pot Size: $75,000 USDC
Total HM: 32
Participants: 141
Period: 7 days
Judge: HardlyDifficult
Total Solo HM: 4
Id: 144
League: ETH
Rank: 39/141
Findings: 6
Award: $328.95
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: dipp
Also found by: 0x52, Lambda, PwnedNoMore, Ruhum, Treasure-Seeker, ak1, auditor0517, hansfriese, jonatascm, kenzo, panprog, smiling_heretic, xiaoming90
81.2985 USDC - $81.30
https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Migration.sol#L433
The migrateFractions()
function can be called multiple times by the same user allowing them to withdraw more fractions than they should be able to.
Here's a test as the PoC:
// Migration.t.sol function testMigrationMultipleTimes() public { testSettle(); (, , , , address newVault, , , , ) = migrationModule.migrationInfo( vault, 1 ); (address newToken, uint256 id) = registry.vaultToToken(newVault); assertEq(getFractionBalance(alice.addr), 4000); alice.migrationModule.migrateFractions(vault, 1); assertEq(IERC1155(newToken).balanceOf(alice.addr, id), 6000); alice.migrationModule.migrateFractions(vault, 1); assertEq(IERC1155(newToken).balanceOf(alice.addr, id), 12000); alice.migrationModule.migrateFractions(vault, 1); assertEq(IERC1155(newToken).balanceOf(alice.addr, id), 18000); }
none
add mapping(uint => address) claimed
where the key is the proposalID
and the value is the user's address. Then, check whether the user has already claimed in the migrateFractions()
function.
#0 - mehtaculous
2022-07-20T17:01:06Z
Duplicate of #460
#1 - HardlyDifficult
2022-08-11T17:18:22Z
132.2028 USDC - $132.20
https://github.com/code-423n4/2022-07-fractional/blob/main/src/FERC1155.sol#L223
Generally, when accessing the contract's royalties, the caller will use the following schema:
uint amount = x; (address receiver, uint royalty) = c.royaltyInfo(id, amount); token.transfer(receiver, royalty); token.transfer(owner, amount - royalty); // send remaining balance to the owner of the token.
If the royalty is larger than 100%, the last line will trigger an underflow.
https://github.com/code-423n4/2022-07-fractional/blob/main/src/FERC1155.sol#L223
Allows the controller to set whatever value they want.
none
Use a reasonable limit, e.g. 10%
#0 - 0x0aa0
2022-07-18T18:41:30Z
Duplicate of #166
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, Amithuddar, Avci, BowTiedWardens, Kthere, Limbooo, MEP, Ruhum, StyxRave, TomJ, Treasure-Seeker, TrungOre, Tutturu, Waze, bardamu, c3phas, cccz, codexploder, cryptphi, hake, horsefacts, hyh, oyc_109, pashov, peritoflores, scaraven, simon135, slywaters, sseefried, tofunmi, xiaoming90
1.3977 USDC - $1.40
https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Migration.sol#L325 https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Migration.sol#L172
You should use call()
instead of transfer()
. If the recipient is a contract it might use more than 2300 gas which will make the transaction fail if the ETH was sent with transfer()
.
https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
none
Use call()
instead.
#0 - mehtaculous
2022-07-19T21:51:17Z
Duplicate of #325
#1 - HardlyDifficult
2022-07-28T15:47:53Z
Duping to #504
🌟 Selected for report: 0xA5DF
Also found by: 0x52, 0xDjango, 0xsanson, Lambda, PwnedNoMore, Ruhum, Treasure-Seeker, async, berndartmueller, cccz, hubble, kenzo, scaraven, shenwilly, sseefried, xiaoming90
14.6423 USDC - $14.64
https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Buyout.sol#L57
As described in the module's docs, a vault can only have a single active Buyout proposal at any time. An attacker is able to DOS the module by repeatedly creating a proposal with a "weird" configuration. That would make the module unusable for honest participants.
The easiest "weird" configuration looks like this: The attacker doesn't have any fractional tokens. They create a proposal with 1 wei balance. Users are now able to sell their fractional for 1 wei. That's it. Since no one will probably do that, you effectively created an useless proposal:
// Buyout.t.sol function testDOS() public { initializeBuyout(alice, bob, TOTAL_SUPPLY, 0, true); assertEq(getFractionBalance(bob.addr), 0); assertEq(getFractionBalance(buyout), 0); assertEq(getETHBalance(bob.addr), INITIAL_BALANCE); assertEq(getETHBalance(buyout), 0 ether); bob.buyoutModule.start{value: 1}(vault); assertEq(getFractionBalance(bob.addr), 0); assertEq(getFractionBalance(buyout), 0); assertEq(getETHBalance(buyout), 1); }
This is possible because the ERC1155 contract you use doesn't block transfers of 0
tokens.
Even if it didn't you could still DOS the module with the cost of a single fractional token. If the attacker has enough ETH they can also create a proposal with an absurdly high fractionPrice
. Blocking anybody from buying or selling tokens.
none
The issue is the limit of one buyout at a time. I'm not entirely sure, but would allowing multiple concurrent proposals break anything? As long as one of them reaches the limit of 51% you have the same outcome.
#0 - 0x0aa0
2022-07-18T16:23:07Z
Duplicate of #87
#1 - HardlyDifficult
2022-08-02T21:54:06Z
🌟 Selected for report: xiaoming90
Also found by: 0x1f8b, 0x29A, 0x52, 0xA5DF, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xsanson, 0xsolstars, 242, 8olidity, Amithuddar, Aymen0909, Bnke0x0, BowTiedWardens, David_, Deivitto, ElKu, Funen, Hawkeye, IllIllI, JC, Kaiziron, Keen_Sheen, Kthere, Kulk0, Kumpa, Lambda, MEP, ReyAdmirado, Rohan16, Ruhum, Sm4rty, TomJ, Tomio, Treasure-Seeker, TrungOre, Tutturu, Viksaa39, Waze, _Adam, __141345__, ak1, apostle0x01, asutorufos, async, ayeslick, aysha, bbrho, benbaessler, berndartmueller, c3phas, cccz, chatch, cloudjunky, codexploder, cryptphi, delfin454000, dipp, durianSausage, dy, exd0tpy, fatherOfBlocks, hake, hansfriese, horsefacts, hubble, joestakey, jonatascm, kebabsec, kenzo, kyteg, mektigboy, neumo, oyc_109, pashov, pedr02b2, peritoflores, rajatbeladiya, rbserver, robee, rokinot, s3cunda, sach1r0, sahar, sashik_eth, scaraven, shenwilly, simon135, sorrynotsorry, sseefried, svskaushik, unforgiven, z3s, zzzitron
61.9379 USDC - $61.94
According to the contract's comment, there are two phases for an auction, the proposal & rejection period. The former lasts 2 days and the other one lasts 4 days. Reading that, one would expect them to be two distinct phases. So in the first two days you're only able to sell your fractional tokens. Then, the next 4 days you're only allowed to buy fractional tokens. After that, the auction is finished (full duration is 6 days). Instead, you can sell only the first two days while you can buy from the beginning to the end (4 days).
According to the contract's comment, the pool needs more than 51% of the total supply for it to succeed. But, the actual implementation expects only more than 50%. Either update the documentation or the implementation.
They should be accompanied by an event to allow third parties to easily track them.
🌟 Selected for report: joestakey
Also found by: 0x1f8b, 0x29A, 0xA5DF, 0xKitsune, 0xNazgul, 0xNineDec, 0xalpharush, 0xkatana, 0xsanson, 0xsolstars, 8olidity, Avci, Bnke0x0, BowTiedWardens, Chom, Deivitto, ElKu, Fitraldys, Funen, IllIllI, JC, Kaiziron, Lambda, Limbooo, MEP, NoamYakov, PwnedNoMore, RedOneN, ReyAdmirado, Rohan16, Ruhum, Saintcode_, Sm4rty, TomJ, Tomio, TrungOre, Tutturu, Waze, _Adam, __141345__, ajtra, apostle0x01, asutorufos, benbaessler, brgltd, c3phas, codexploder, cryptphi, delfin454000, dharma09, djxploit, durianSausage, fatherOfBlocks, giovannidisiena, gogo, horsefacts, hrishibhat, hyh, ignacio, jocxyen, jonatascm, karanctf, kebabsec, kyteg, m_Rassska, mektigboy, oyc_109, pedr02b2, rbserver, robee, rokinot, sach1r0, sashik_eth, simon135, slywaters
37.4743 USDC - $37.47
The gas for calldata parameters is already paid. It'll be cheaper.
e.g.
Since i
can't overflow because of the boundary set in the for loop, you can use unchecked to increment it. That will save a little gas
e.g.