Platform: Code4rena
Start Date: 18/10/2022
Pot Size: $50,000 USDC
Total HM: 13
Participants: 67
Period: 5 days
Judge: Picodes
Total Solo HM: 7
Id: 172
League: ETH
Rank: 3/67
Findings: 3
Award: $3,820.10
🌟 Selected for report: 2
🚀 Solo Findings: 1
🌟 Selected for report: Lambda
2675.4584 USDC - $2,675.46
When the reservedTokenBeneficiary
of a tier is equal to defaultReservedTokenBeneficiaryOf[msg.sender]
, it is not explicitly set for this tier. This generally works well because in the function reservedTokenBeneficiaryOf(address _nft, uint256 _tierId)
, defaultReservedTokenBeneficiaryOf[_nft]
is used as a backup when _reservedTokenBeneficiaryOf[_nft][_tierId]
is not set. However, it will lead to the wrong beneficiary when defaultReservedTokenBeneficiaryOf[msg.sender]
is later changed, as this new beneficiary will be used for the tier, which is not the intended one.
defaultReservedTokenBeneficiaryOf[address(delegate)]
is originally set to address(Bob)
when the following happens:
1.) A new tier 42 is added with _tierToAdd.reservedTokenBeneficiary = address(Bob)
. Because this is equal to defaultReservedTokenBeneficiaryOf[address(delegate)]
, _reservedTokenBeneficiaryOf[msg.sender][_tierId]
is not set.
2.) The owner calls setDefaultReservedTokenBeneficiary
to change the default beneficiary (i.e., the value defaultReservedTokenBeneficiaryOf[address(delegate)]
) to address(Alice)
.
3.) Now, every call to reservedTokenBeneficiaryOf(address(delegate), 42)
will return address(Alice)
, meaning she will get these reserved tokens. This is of course wrong, the tier was explicitly created with Bob as the beneficiary.
Also set _reservedTokenBeneficiaryOf[msg.sender][_tierId]
when it is equal to the current default beneficiary.
#0 - drgorillamd
2022-10-24T11:07:59Z
Hedge case but valid imo! Nice finding!
#1 - mejango
2022-10-24T17:30:12Z
yep. valid imo too!
#2 - c4-judge
2022-11-08T08:42:57Z
Picodes marked the issue as satisfactory
802.6375 USDC - $802.64
JBTiered721DelegateStore.recordMintBestAvailableTier
potentially iterates over all tiers to find the one with the highest contribution floor that is lower than _amount
. When there are many tiers, this loop can always run out of gas, which will cause some transactions (the ones that have a high _leftoverAmount
within _processPayment
) to always revert. The (implicit) limit for the number of tiers is 2^16 - 1, so it is possible that this happens in practice.
Let's say that 1,000 tiers are registered for a project. Small payments without a leftover amount or a small amount will be succesfully processed by _processPayment
, because _mintBestAvailableTier
is either not called or it is called with a small amount, meaning that recordMintBestAvailableTier
will exit the loop early (when it is called with a small amount). However, if a payment with a large leftover amount (let's say greater than the highest contribution floor) is processed, it is necessary to iterate over all tiers, which will use too much gas and cause the processing to revert.
Use a binary search (which requires some architectural changes) for determining the best available tier. Then, the gas usage grows logarithmically (instead of linear with the current design) with the number of tiers, meaning that it would only be ~16 times higher for 65535 tiers as for 2 tiers.
#0 - drgorillamd
2022-10-24T10:56:01Z
Duplicate #226
#1 - c4-judge
2022-12-03T19:10:32Z
Picodes marked the issue as not a duplicate
#2 - c4-judge
2022-12-03T19:13:26Z
Picodes marked the issue as duplicate of #226
#3 - c4-judge
2022-12-03T19:14:06Z
Picodes marked the issue as selected for report
#4 - c4-judge
2022-12-07T07:29:52Z
Simon-Busch marked the issue as not a duplicate
#5 - c4-judge
2022-12-07T07:29:57Z
Simon-Busch marked the issue as primary issue
🌟 Selected for report: berndartmueller
Also found by: 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, Aymen0909, BClabs, Diana, Jeiwan, Lambda, LeoS, RaoulSchaffranek, RaymondFam, RedOneN, ReyAdmirado, Rolezn, SaharAP, Trust, V_B, __141345__, a12jmx, bharg4v, brgltd, carlitox477, ch0bu, chaduke, cloudjunky, cryptostellar5, cryptphi, csanuragjain, d3e4, delfin454000, erictee, fatherOfBlocks, hansfriese, ignacio, joestakey, karanctf, ladboy233, lukris02, mcwildy, minhtrng, peanuts, ret2basic, seyni, slowmoses, svskaushik, tnevler, yixxas
341.9967 USDC - $342.00
According to EIP-721, tokenURI
"Throws if _tokenId
is not a valid NFT." This is not the case for JBTiered721Delegate
. In such situations, an empty string is returned.
According to EIP-721, a valid way to determine if a token ID exists is:
bool exists = true; try nft.tokenURI(_i) returns (string memory uri) { // Some other code } catch { exists = false; }
For JBTiered721Delegate
, this will always result in the token existing, even if it does not. Depending on the applications, this can have negative consequences and lead to malfunctioning or a loss of funds.
Throw if the token ID does not exist.
#0 - drgorillamd
2022-10-24T11:09:37Z
Duplicate #218
#1 - Picodes
2022-11-08T08:40:55Z
As tokenURI
is within the extension ERC721Metadata
and is meant to be called off-chain, downgrading to Low ("function incorrect as to spec")
#2 - c4-judge
2022-11-08T08:41:10Z
Picodes changed the severity to QA (Quality Assurance)
#3 - c4-judge
2022-11-08T15:10:30Z
Picodes marked the issue as grade-a