Platform: Code4rena
Start Date: 06/09/2022
Pot Size: $90,000 USDC
Total HM: 33
Participants: 168
Period: 9 days
Judge: GalloDaSballo
Total Solo HM: 10
Id: 157
League: ETH
Rank: 78/168
Findings: 2
Award: $109.85
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: zzzitron
Also found by: 0xSmartContract, ChristianKuri, ElKu, Lambda, MiloTruck, davidbrai, elad, hansfriese, immeas, ladboy233, scaraven, volky
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L88 https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L152-L157
In the rare case that, the totalOwnership
of the founders is 100, the whole DAO will fail. Because with such a setting, the Token contract wont be able to mint any tokens. Any call on the mint()
method will result in an infinite while
loop, eventually reverting because of gas limit.
I added the following function to Token.t.sol
for POC.
function test_pct100Founder() public { createUsers(10, 1 ether); address[] memory wallets = new address[](10); uint256[] memory percents = new uint256[](10); uint256[] memory vestExpirys = new uint256[](10); uint256 pct = 10; uint256 end = 4 weeks; unchecked { for (uint256 i; i < 10; ++i) { wallets[i] = otherUsers[i]; percents[i] = pct; vestExpirys[i] = end; } } // 10 founders. each with a pct of 10. which means totalOwnership is 10*10=100. deployWithCustomFounders(wallets, percents, vestExpirys); // some routine checking assertEq(token.totalFounders(), 10); assertEq(token.totalFounderOwnership(), 100); vm.prank(token.auction()); token.mint(); //this will result in a infinite while loop }
The following forge test command was ran:
forge test --match-contract Token --match-test test_pct100Founder -vvvv --block-gas-limit 28000000
And the output looked like this: Test Result
The issue is caused by the condition of the while loop in mint() method.
do { // Get the next token to mint tokenId = settings.totalSupply++; // Lookup whether the token is for a founder, and mint accordingly if so } while (_isForFounder(tokenId));
Since 100 out of 100 tokens belongs to the founders, _isForFounder
always returns true
, resulting in an Infinite loop.
Once set, the founder parameters cannot be edited, which makes the whole system useless and would require a fresh deployment.
VSCode, Foundry, Manual analysis
This can be avoided by simply making the below change to line 88 of Token.sol
.
if ((totalOwnership += uint8(founderPct)) >= 100) revert INVALID_FOUNDER_OWNERSHIP();
This will cause the _addFounders
method to revert if the totalOwnership
is 100 or more.
#0 - horsefacts
2022-09-15T20:43:56Z
Agree with the finding, defer on the severity.
#1 - GalloDaSballo
2022-09-20T19:47:18Z
🌟 Selected for report: Lambda
Also found by: 0x1337, 0x1f8b, 0x4non, 0x85102, 0xA5DF, 0xNazgul, 0xSmartContract, 0xbepresent, 0xc0ffEE, 8olidity, Aymen0909, B2, Bnke0x0, CRYP70, Captainkay, CertoraInc, Ch_301, Chom, ChristianKuri, CodingNameKiki, Deivitto, Diana, DimitarDimitrov, ElKu, EthLedger, Franfran, Funen, GimelSec, JansenC, Jeiwan, Jujic, Lead_Belly, MEP, MasterCookie, MiloTruck, Noah3o6, PPrieditis, PaludoX0, Picodes, PwnPatrol, R2, Randyyy, RaymondFam, Respx, ReyAdmirado, Rolezn, Samatak, Tointer, Tomo, V_B, Waze, _Adam, __141345__, a12jmx, ak1, asutorufos, azephiar, ballx, bharg4v, bin2chen, bobirichman, brgltd, bulej93, c3phas, cccz, ch0bu, cloudjunky, cryptonue, cryptostellar5, cryptphi, csanuragjain, d3e4, datapunk, davidbrai, delfin454000, dharma09, dic0de, dipp, djxploit, eierina, erictee, fatherOfBlocks, gogo, hansfriese, hyh, imare, indijanc, izhuer, jonatascm, ladboy233, leosathya, lucacez, lukris02, m9800, martin, minhtrng, ne0n, neumo, oyc_109, p_crypt0, pashov, pauliax, pcarranzav, pedr02b2, peritoflores, pfapostol, rbserver, ret2basic, robee, rvierdiiev, sach1r0, sahar, scaraven, sikorico, simon135, slowmoses, sorrynotsorry, tnevler, tonisives, volky, yixxas, zkhorse, zzzitron
60.7743 USDC - $60.77
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L97 https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/Token.sol#L182
In the _addFounders
internal function, the wallet addresses of the founders are not checked for zero address. If the wallet address is zero, the contract will not emit any warnings, but simply will skip minting the token for that particular founder.
I added the following function to Token.t.sol
for POC.
function test_NullWalletFounder() public { createUsers(10, 1 ether); address[] memory wallets = new address[](10); uint256[] memory percents = new uint256[](10); uint256[] memory vestExpirys = new uint256[](10); uint256 pct = 2; uint256 end = 4 weeks; unchecked { for (uint256 i; i < 10; ++i) { wallets[i] = otherUsers[i]; percents[i] = pct; vestExpirys[i] = end; } wallets[5] = address(0); //make one wallet address zero } // 10 founders. each with a pct of 2. which means totalOwnership is 10*2=20. deployWithCustomFounders(wallets, percents, vestExpirys); // some routine checking assertEq(token.totalFounders(), 10); assertEq(token.totalFounderOwnership(), 20); vm.prank(token.auction()); token.mint(); //6th founder will not receive any token. and he/she wont be warned about it. }
The following forge test command was ran:
forge test --match-contract Token --match-test test_NullWalletFounder -vvvv
And the output looked like this: Test Result
As you can see the 6th founder is skipped silently in the mint()
method because the founder's wallet address was zero.
This is because in _isForFounder
method, we have the following if
condition which skips any zero wallet addresses.
if (tokenRecipient[baseTokenId].wallet == address(0)) { return false;
Though it is fine to assume that a wallet address with zero value means he/she doesnt have any right over tokens, since their ownershipPct
is more than zero, we should consider the possibility that it was more of a typo rather than their actual intention.
Once set, the founder parameters cannot be edited. Then we have only two options. Either to redeploy everything with correct settings or let that unfortunate founder miss out on his dear tokens.
VSCode, Foundry, Manual analysis
This can be avoided by simply adding a new condition below line 85 of Token.sol
.
if (founderPct == 0) continue; if ( _founders[i].wallet == address(0) ) revert INVALID_FOUNDER_WALLET();
This will cause the _addFounders
method to revert if the wallet address is zero for a founder with non-zero ownershipPct
.
#0 - GalloDaSballo
2022-09-17T01:40:47Z
Historically Low, I think Low is more appropriate
#1 - GalloDaSballo
2022-09-17T01:41:04Z
L