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: 14/168
Findings: 4
Award: $1,905.70
🌟 Selected for report: 2
🚀 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/debe9b792cc70510eadf9b3728cde5b0f2ec9a1f/src/token/Token.sol#L88 https://github.com/code-423n4/2022-09-nouns-builder/blob/debe9b792cc70510eadf9b3728cde5b0f2ec9a1f/src/token/Token.sol#L157
Deployments with totalOwnership = 100
are supported (the value is not allowed to be greater than 100) and this can also be a valid scenario (e.g., founders get 100% of the allocation for the first few weeks). However, in such situations, tokenRecipient[0] ... tokenRecipient[99]
will be set. Because of that, minting will not be possible, i.e. the deployment is non-functional
Imagine we are in a situation with totalOwnership = 100
(it does not matter if there is one or multiple founders) and mint
is called. In the while loop, the tokenId will be increased until tokenRecipient[tokenId % 100].wallet != address(0)
. However, because tokenRecipient[0] ... tokenRecipient[99]
is set, this will never be the case, meaning the mint function will run out of gas at some point.
Either disallow 100% total ownership (probably a bad idea) or handle this case explicitly. For instance, 100 tokens for the founders could be minted, and then one for a user would be minted.
#0 - horsefacts
2022-09-15T21:08:11Z
#1 - GalloDaSballo
2022-09-20T19:47:30Z
1216.3218 USDC - $1,216.32
Because of the "greedy" minting scheme for founders (tokens to founders are minted until _isForFounder
returns false
, i.e. until there is an unset tokenRecipient[tokenId % 100]
), it can happen that the actual percentages of tokens that the founders receive deviate significantly from the desired percentages:
Imagine we are in a situation where one founder has a 51% share and the other a 48% share. Because schedule
is set to 1 for the first founder, tokenRecipient[0] ... tokenRecipient[50]
will be set to his address. tokenRecipient[51], tokenRecipient[53], ...
is set to the address of the second founder. Now let's say a mint happens just before the vestExpiry
and when tokenId % 100 == 0
. In such a situation, founder 1 will get 51 tokens (because of the consecutive entries in tokenRecipients
) and founder 2 will get 1 token (because of the entry in tokenRecipient[51]
, which is also consecutive. Let's say that the next mint happens after the vest expiration, which means that no founders get additional tokens.
In such a situation, founder 1 got 51 of the "last 100" token IDs, whereas founder 2 only got 1. Therefore, the overall percentage of tokens that those founders got will not be 51% and 40%. When the vest expiration was set to a time far in the future, it will be close to it, but when the vest timespan was only short, it can be very bad. In the extreme case where the expiration is set such that only 1 mint call causes mints for founders, founder 1 will have 51 tokens and founder 2 only 1, meaning the percentages are 51% / 1% instead of 51% / 48%!
Consider using another distribution scheme. Instead of the current "greedy" scheme (minting until a slot is free), it would make sense to mint the tokens for the founders every 100 tokens, i.e. everytime when tokenId % 100 == 0
. Like that, it is ensured that the actual percentages are equal to the desired percentages.
#0 - GalloDaSballo
2022-09-26T22:50:44Z
The warden has shown a specific combination of founderPct
that will unfairly reward one founder at the detriment of another.
Personally I'd recommend simming a set of schedules, perhaps via Fuzzing (which would have also enriched this submission), to find a solution that solves most use cases.
Because the submission shows a way to lose tokens, in a way that is reliant on configuration, I agree with Medium Severity
🌟 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
594.8814 USDC - $594.88
Token
, when a founder has wallet
set to address(0)
, he will not receive any tokens (which is good), but he is still included in all of the calculations such as numFounders
or totalOwnership
, meaning that they will be wrong in such situations.Token
, totalFounderOwnership()
does not incorparate the vesting period. It is just the total percent ownership that the founders received at one point in time, but this metric is pretty useless without incorporating the timespan (as 100% for 1 week is very different to 10% for 10 years).MetadataRenderer._getItemImage
, a dot is missing between the item name and extension (https://github.com/code-423n4/2022-09-nouns-builder/blob/debe9b792cc70510eadf9b3728cde5b0f2ec9a1f/src/token/metadata/MetadataRenderer.sol#L259).MetadataRenderer.addProperties
seems to be very error-prone to me. When an item is added for a new property, isNewProperty
needs to be set and you need to keep in mind that the property ID refers to the ID of the newly added property, not the global ID that the property will get.Auction._handleOutgoingTransfer
, it is not checked if the WETH transfer succeeds (when WETH is used). The commonly used WETH implementation will revert on failure, but this is no requirement of EIP-20, so using another WETH implementation could lead to a problem there.Treasury.isExpired
returns true for non-existing proposals, which could lead to wrong states for third-party applications that use this function. Consider reverting for non-existing proposals.Governor
contract would ever contain ETH (e.g, after a selfdestruct
of some other contract), it could not be retrieved, as the execute
function can only forward the received ETH, but not use its own.Auction
can only be paused / unpaused after a vote with the corresponding delays. This can be too slow in certain scenarios (e.g., emergency response after a security incident). Consider introducing an optional emergency pause feature (e.g., by the founder).#0 - GalloDaSballo
2022-09-27T01:19:12Z
L
R
Also don't get it, code compiles
R
NC
L
Disagree as it has to be voted back in
R
Disagree as it keeps it fair for people in the auction
2L 3R
#1 - GalloDaSballo
2022-09-28T23:20:18Z
All in all a custom report + well done performance overall, fairly won, gratz!
🌟 Selected for report: pfapostol
Also found by: 0x1f8b, 0x4non, 0x5rings, 0xA5DF, 0xSmartContract, 0xc0ffEE, 0xkatana, Aymen0909, Bnke0x0, CertoraInc, Chandr, CodingNameKiki, Cr4ckM3, Deivitto, DimSon, Franfran, JAGADESH, JC, Jeiwan, Lambda, LeoS, Matin, Metatron, Migue, MiloTruck, PPrieditis, PaludoX0, R2, RaymondFam, Respx, ReyAdmirado, Rolezn, Saintcode_, Samatak, SnowMan, StevenL, Tointer, TomJ, Tomo, WatchDogs, Waze, _Adam, __141345__, ajtra, asutorufos, ballx, brgltd, bulej93, c3phas, ch0bu, dharma09, djxploit, durianSausage, easy_peasy, fatherOfBlocks, gianganhnguyen, gogo, imare, leosathya, lucacez, martin, oyc_109, pauliax, peiw, prasantgupta52, ret2basic, rfa, robee, sikorico, simon135, tofunmi, volky, wagmi, zishansami
45.4217 USDC - $45.42
Governer.propose
, the value of proposalThreshold()
is first cached, but the function is then executed again (https://github.com/code-423n4/2022-09-nouns-builder/blob/debe9b792cc70510eadf9b3728cde5b0f2ec9a1f/src/governance/governor/Governor.sol#L128). Consider using the cached value.#0 - GalloDaSballo
2022-09-26T16:22:46Z
200 gas