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: 31/67
Findings: 2
Award: $63.84
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
37.8829 USDC - $37.88
Please lock all solidity compiler versions for best optimization
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JB721TieredGovernance.sol#L312 delete _tokenId as it has never needed
One suggestion is to use custom error Revert to process exception in most write transactions
Use a locked version of solidity compiler for all contracts
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JB721TieredGovernance.sol#L177 Suggest to change function setTierDelegate to external if it will not be called by the contract itself
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JB721TieredGovernance.sol#L44-L53 Consider combing the two mappings into one mapping from address->uint256->struct
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JBTiered721Delegate.sol#L216-L218 use custom error revert instead of require without an error msg
I suggest to lock all solidity compiler versions for all files and use the most recent version
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/libraries/JBTiered721FundingCycleMetadataResolver.sol all the three functions can be simplified to save gas if these functions are called in other write transactions.
change return (_data & 1) == 1; to return _data & 1;
change return ((_data >> 1) & 1) == 1; to return (_data & 2) == 2;
change return ((_data >> 2) & 1) == 1; to return (_data & 4) == 4;
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JBTiered721DelegateStore.sol#L1123 Consider to combine several of mappings Address - X (.....maxTierIdOf, tierBalanceOf, numberOfReservesMintedFor, numberOfBurnedFor, defaultReservedTokenBeneficiaryOf, firstOwnerOf, baseUriOf, tokenUriResolverOf, contractUriOf, encodedIPFSUriOf) into one mapping Address-> Struct to improve code readability and possibly gas saving due to packing In particular define struct NFTContractInfo { mapping(uint256 => uint256) maxTierIdOf; mapping(uint256 => address) _reservedTokenBeneficiaryOf; mapping(uint256 => JBStored721Tier) _storedTierOf; JBTiered721Flags _flagsOf; mapping(uint256 => uint256) _isTierRemoved; uint256 trackedLastSortTierIdOf; uint256 maxTierIdOf; mapping(address => mapping(uint256 => uint256)) tierBalanceOf; .... } Then define a mapping(address => NFTContractInfo) This is just an example, the authors can define several structs as well, to group related variables in one struct, for example.
It might be helpful to refactor all custom erros into one file and import in each contract.
Maybe it is a good idea to unify the concepts of JB721Tier and JBStored721Tier to avoid the conversion between them back and forth. Much code looks like this:
JB721Tier({ id: _id, contributionFloor: _storedTier.contributionFloor, lockedUntil: _storedTier.lockedUntil, remainingQuantity: _storedTier.remainingQuantity, initialQuantity: _storedTier.initialQuantity, votingUnits: _storedTier.votingUnits, reservedRate: _storedTier.reservedRate, reservedTokenBeneficiary: reservedTokenBeneficiaryOf(_nft, _id), encodedIPFSUri: encodedIPFSUriOf[_nft][_id], allowManualMint: _storedTier.allowManualMint }); which wastes much gas.
#0 - drgorillamd
2022-10-27T20:04:10Z
Nice findings, JB721Tier and stored tier use to be the same, it's a choice we took at some point (to have both)
The bitshift are correct, but we've found return ((_data >> 2) & 1) == 1;
to be more readable (the optimiser will, iic, remove the ==1 anyway)
#1 - drgorillamd
2022-10-27T20:08:17Z
Tagged as high quality even tho the writing itself is awful, these are nice/original findings (in a sea of slither copy-paste), thanks for taking the time to write these down:)
#2 - Picodes
2022-11-04T08:45:13Z
Indeed, some ideas and it looks like the warden spent some time on the code. If I may I suggest in the future the warden:
#3 - c4-judge
2022-11-04T08:45:25Z
Picodes marked the issue as grade-b
#4 - Picodes
2022-11-04T08:47:36Z
Also this report is a mix of QA and Gas savings
🌟 Selected for report: Jeiwan
Also found by: 0x1f8b, 0x4non, 0x5rings, 0xSmartContract, Awesome, Aymen0909, Bnke0x0, CodingNameKiki, Diana, DimSon, JC, JrNet, LeoS, RaymondFam, ReyAdmirado, Saintcode_, Shinchan, __141345__, berndartmueller, bharg4v, brgltd, carlitox477, ch0bu, chaduke, cryptostellar5, emrekocak, gogo, lukris02, martin, mcwildy, sakman, trustindistrust, zishansami
25.9629 USDC - $25.96
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JB721TieredGovernance.sol#L133 as >= is more expensive than < Change the code to the following:
if (_blockNumber < block.number) return _totalTierCheckpoints[_tier].getAtBlock(_blockNumber); else revert BLOCK_NOT_YET_MINED();
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JB721TieredGovernance.sol#L147 change the argument from memory data to calldata to save gas
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JB721TieredGovernance.sol#L160 No need to cache it into _data, user the _setTierDelegatesData calldata variable directly.
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JB721TieredGovernance.sol#L313 change _tier to a calldata argument to save gas
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JBTiered721Delegate.sol change codeOrigin, store, fundingCycleStore, prices, pricingCurrency, pricingDecimals, creditsOf to immmutable or possiblly private too since they will set once by the constructor to save gas
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JBTiered721Delegate.sol#L233 check !=0 is cheaper than checking change 240 to : if (_pricing.tiers.length != 0) _store.recordAddTiers(_pricing.tiers);
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JBTiered721Delegate.sol#L202 First of all, change memory _mintReservesForTiersData to calldata _mintReservesForTiersData to save gas since the argument will never be updated. Second, no need to cache it to _data in 273, read each field from _mintReservesForTiersData directly to save gas.
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JBTiered721Delegate.sol#L290 First of all, change memory _mintForTiersData to calldata _mintForTiersData to save gas since the argument will never be updated. Second, no need to cache it to _data in 300, read each field from _mintForTiersData directly to save gas.
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/libraries/JBBitmap.sol#L52
since it is a storage variable, it can save gas by changing
self[_depth] |= uint256(1 << (_index % 256));
to
self[_depth] = self[_depth] | uint256(1 << (_index % 256));
if the function is called within another write transaction
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/libraries/JBIpfsDecoder.sol#L44 if function _toBase58 will be called in another write transaction, then there would be several gas-saving opportunities:
_source can be changed to a calldata variable as it has never been updated
_source.length should be cached in a local variable to avoid repeated call in a for loop
for (uint256 i = 0; i < _source.length; ++i) should be changed to for (uint256; i < sourcelength; unchecked{++i}) to avoid unnecessary initialization, repleated calling of _source.length (use the cached variable), and the unnecessary check
change for (uint256 j = 0; j < digitlength; ++j) to for (uint256 j; j < digitlength; unchecked{++j}) for similar reason
Line 59, change digitallength++ to ++digitallength to save gas
similar for loop optimization can be done for lines 68, 76, and 84.
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/libraries/JBIpfsDecoder.sol#L66 _array can be changed to a calldata variable as it is never updated in the function
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/libraries/JBIpfsDecoder.sol#L74 _input can be changed to a calldata variable as it is never updated in the function
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/libraries/JBIpfsDecoder.sol#L83 _indices can be changed to a calldata variable as it is never updated in the function
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/JBTiered721DelegateStore.sol#L349 change the forloop to iterate from zero to save the first assignment gas
At the end of B721TieredGovernenance, the _add and _subtract functions are not necessary, simply using SafeMath or compiler version > 8.0 will take care of overflow/underflow issues, and if that is not a concern, simply used unchecked{}.
For function payParams in contract JB721Delegate, the returned value delegateAllocations has no dependency on the input, the function can be simplified to save gas
https://github.com/jbx-protocol/juice-nft-rewards/blob/f9893b1497098241dd3a664956d8016ff0d0efd0/contracts/abstract/JB721Delegate.sol#L203 change _name and _symbol to calldata to save gas for the _initialize function
#0 - drgorillamd
2022-10-27T20:14:27Z
the _add and _sub function are not equivalent to safemath (as they are unsafe if used in solc < 0.8). They are declared so a pointer to them can be use in the OZ Checkpoints logic (see ln296) <img width="896" alt="image" src="https://user-images.githubusercontent.com/83670532/198389011-fe137392-91cd-40db-b740-37b4e61d7fdc.png">
#1 - Picodes
2022-11-04T08:58:18Z
Some valid findings, especially regarding calldata
vs memory
Some invalid stuffs as well: payParams
is designed like this to follow an interface, and add
and sub
are used as pointers
Please make your reports more readable
#2 - c4-judge
2022-11-04T08:58:27Z
Picodes marked the issue as grade-b