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: 102/168
Findings: 2
Award: $95.55
🌟 Selected for report: 0
🚀 Solo Findings: 0
34.7819 USDC - $34.78
https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/metadata/MetadataRenderer.sol#L127 https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/metadata/MetadataRenderer.sol#L140 https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/metadata/MetadataRenderer.sol#L179 https://github.com/code-423n4/2022-09-nouns-builder/blob/7e9fddbbacdd7d7812e912a369cfd862ee67dc03/src/token/metadata/MetadataRenderer.sol#L194
If a property is added to the metadata renderer that causes the total properties to reach more than 15 properties, the auctions/DAO will halt and will require an upgrade of the contracts to resolve the issue.
Initially MetadataRenderer is owned by a founder address who is responsible for adding the *initial* properties.
During the first execution of the addProperties
function by the founder address, the ownership is transferred to the treasury.
The MetadataRenderer's storage properties, ipfsData, and Property.items are dynamically sized arrays, hence unbounded.
During the onMint
function, for each property, one random property item is selected as a token attribute. The token attributes are stored into a statically sized array of 16 elements via a mapping where the first element of the array is reserved to store the number of properties will be used to generate the token attributes. Because of the +1 offset, the maximum number of properties that can be used for the attributes generation is 15. If by any chance, either during founder initial call, or more easily later via proposal, a new property is added via addProperty
function upgrading the new total properties to 16 or more, the onMinted
function will start to revert due to array access out of bounds during token attributes assignment.
Because of the chain of calls Auction.createAuction Auction._createAuction -> Token.mint -> Token._mint -> MetadataRenderer.onMinted will revert, the DAO will pause and settling and unpausing will not resolve the problem. Only an upgrade of the contracts with a workaround will resolve the problem.
n/a
There are two possible fixes, see below.
Simple preventative fix, add count check to addProperties
function after line 117 and line 140 as follows (see + for insertions)
function addProperties( string[] calldata _names, ItemParam[] calldata _items, IPFSGroup calldata _ipfsGroup ) external onlyOwner { // Cache the existing amount of IPFS data stored uint256 dataLength = ipfsData.length; // If this is the first time adding properties and/or items: if (dataLength == 0) { // Transfer ownership to the DAO treasury transferOwnership(settings.treasury); } // Add the IPFS group information ipfsData.push(_ipfsGroup); // Cache the number of existing properties uint256 numStoredProperties = properties.length; // Cache the number of new properties uint256 numNewProperties = _names.length; // Cache the number of new items uint256 numNewItems = _items.length; unchecked { + + if(numStoredProperties + numNewProperties > 15) revert TOOMANY_PROPERTIES(); + // For each new property: for (uint256 i = 0; i < numNewProperties; ++i) { // Append storage space properties.push(); // Get the new property id uint256 propertyId = numStoredProperties + i; // Store the property name properties[propertyId].name = _names[i]; emit PropertyAdded(propertyId, _names[i]); } // For each new item: for (uint256 i = 0; i < numNewItems; ++i) { // Cache the id of the associated property uint256 _propertyId = _items[i].propertyId; // Offset the id if the item is for a new property // Note: Property ids under the hood are offset by 1 if (_items[i].isNewProperty) { _propertyId += numStoredProperties; + + if(_propertyId > 15) revert TOOMANY_PROPERTIES(); + } // Get the pointer to the other items for the property Item[] storage items = properties[_propertyId].items; // Append storage space items.push(); // Get the index of the new item // Cannot underflow as the items array length is ensured to be at least 1 uint256 newItemIndex = items.length - 1; // Store the new item Item storage newItem = items[newItemIndex]; // Store the new item's name and reference slot newItem.name = _items[i].name; newItem.referenceSlot = uint16(dataLength); emit ItemAdded(_propertyId, newItemIndex); } } }
An alternative option would be to make the tokeAttributes a dynamic array in the onMinted
function of MetadataRenderer by replacing the fixed size array in mapping(uint256 => uint16[16]) attributes
to a dynamic array, and have the tokenAttributes[i + 1] = uint16(seed % numItems);
replaced by [tokenAttributes.push(uint16(seed % numItems));
but this would require other changes in few other places, a new seed every 16 properties, and it would be consuming more gas.
#0 - GalloDaSballo
2022-09-20T18:44:10Z
🌟 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
Verified use case with @kulk from the team and "a user can place a bid without attaching any ETH. If that is the only bid then they can win the auction". The current implementation, when reservePrice is zero, allows the next zero bid always win over the previous zero bid.
In createBid, the minBid for the next bid is calculated as follows:
minBid = highestBid + ((highestBid * settings.minBidIncrement) / 100);
When a bidder started with a 0 ETH bid, then the next bidder can win the bid with another 0 ETH bid due to the (highestBid * settings.minBidIncrement) resulting in minBid being still zero.
n/a
A possible solution would be to change from: if (msg.value < minBid) revert MINIMUM_BID_NOT_MET(); to: if (msg.value < minBid || minBid == highestBid) revert MINIMUM_BID_NOT_MET();
#0 - GalloDaSballo
2022-09-26T21:32:26Z
1 L