Platform: Code4rena
Start Date: 11/11/2021
Pot Size: $50,000 USDC
Total HM: 9
Participants: 27
Period: 7 days
Judge: alcueca
Total Solo HM: 5
Id: 53
League: ETH
Rank: 6/27
Findings: 2
Award: $2,257.36
🌟 Selected for report: 4
🚀 Solo Findings: 0
🌟 Selected for report: GiveMeTestEther
Also found by: pauliax
pauliax
totalWeights might be zero and this will cause division by 0 runtime error. function _computeShareCount divides by _totalWeights:
return (_amount * _weight) / _totalWeights;
Admin can accidentaly set totalWeights to 0 in function setRoyaltiesWeight when totalWeights = royaltiesWeight and _weight = 0.
Consider algorithmitcally enforcing totalWeights > 0 or handle this special case in function _computeShareCount.
#0 - adrien-supizet
2021-11-22T10:21:07Z
duplicate #43
🌟 Selected for report: xYrYuYx
Also found by: PierrickGT, loop, palina, pauliax
pauliax
function setReserve should check that _reserve address is not empty. function setNestedReserve should check that _nstReserve is not an empty address unless burning reserve tokens may be intended. function setFeeSplitter should check that _feeSplitter is not an empty address. function backfillTokenURI should validate that _tokenId exists, otherwise it will be possible to set metadata of tokens that are not minted yet.
There are more functions that could enforce similar validations but I am not sure if you are interested in this or is this an intended behavior to not validate this.
#0 - adrien-supizet
2021-11-22T15:29:22Z
duplicate #83
#1 - alcueca
2021-12-03T10:40:53Z
Taking #108 as main
🌟 Selected for report: pauliax
866.6085 USDC - $866.61
pauliax
NestedRecords contains no removeFactory function so there is no way to revoke a factory in case you no longer want to support it. This function is present in NestedAsset contract so I thought you might want to also have it here.
Consider if you are missing removeFactory or is this an intended functionality.
pauliax
function unlockTokens in NestedFactory uses a regular .transfer function while even an audit from Red4Sec suggested using safeTransfer from OZ library. This suggestion was implemented in other places but left untouched here.
Consider also using safeTransfer here.
#0 - adrien-supizet
2021-11-19T15:55:39Z
duplicate #78
🌟 Selected for report: hyh
Also found by: MaCree, elprofesor, fatima_naz, gpersoon, gzeon, loop, palina, pauliax, pmerkleplant, xYrYuYx, ye0lde
pauliax
function removeOperator has this check:
(i > 0, "NestedFactory::removeOperator: Cant remove non-existent operator");
However, 0 is actually a valid index of the array. It represents the first operator.
A potential solution is to use the EnumerableSet to manage the operators: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/EnumerableSet.sol
#0 - maximebrugel
2021-11-18T07:16:59Z
Duplicated : #58
#1 - alcueca
2021-12-03T11:05:44Z
Duplicate of #220
pauliax
.length in a loop can be extracted into a variable and used where necessary to reduce the number of storage reads. Cache the length of the array and use this local variable when iterating over the storage array. This can be applied in functions findShareholder and _sendFees:
for (uint256 i = 0; i < shareholders.length; i++)
unit shareholdersLength = shareholders.length; for (uint256 i = 0; i < shareholdersLength; i++)
#0 - adrien-supizet
2021-11-19T10:32:20Z
duplicated #7
10.4335 USDC - $10.43
pauliax
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition has been met. There are many long revert messages across contracts, e.g.: "NestedRecords::increaseLockTimestamp: Can't decrease timestamp".
#0 - maximebrugel
2021-11-18T11:47:18Z
Duplicated : #14
🌟 Selected for report: TomFrenchBlockchain
Also found by: pauliax
47.7068 USDC - $47.71
pauliax
setMaxAllowance is not very efficient. functions _transferFeeWithRoyalty and _handleUnderSpending invoke setMaxAllowance which does the job but my assumption is that it is not very efficient even when the allowance is a bit lower than a max uint value, but sufficient, it will still re-approve for max. This happens when approving a fee splitter in the functions mentioned above. Usually, it is a good practice not to approve more when the amount is known (and in this case it is), but if you want to optimize for gas, a one-time approval for max will work in your case as I assume fee splitter is a trusted contract. My recommendation is that you replace setMaxAllowance (or introduce a new function) that also skips the approval process when the current allowance is sufficient.
#0 - adrien-supizet
2021-11-19T15:58:48Z
this code will be removed in #34
🌟 Selected for report: GiveMeTestEther
19.3212 USDC - $19.32
pauliax
Using the unchecked keyword to avoid redundant arithmetic underflow/overflow checks to save gas when an underflow/overflow cannot happen, e.g. the unchecked keyword can be applied in the following lines of code:
if (_amountToSpent - _amountSpent > 0) { ... feeSplitter.sendFees(_token, _amountToSpent - _amountSpent); //unchecked } if (_inputTokenAmounts[i] - amountSpent > 0) { _transferFeeWithRoyalty(_inputTokenAmounts[i] - amountSpent, _inputToken, _nftId); //unchecked }
#0 - maximebrugel
2021-11-22T16:29:04Z
Duplicated : #46
47.7068 USDC - $47.71
pauliax
This can be simplified to reduce gas costs by eliminating math operation:
// before require(_accountIndex + 1 <= shareholders.length, "FeeSplitter: INVALID_ACCOUNT_INDEX"); // after require(_accountIndex < shareholders.length, "FeeSplitter: INVALID_ACCOUNT_INDEX");
47.7068 USDC - $47.71
pauliax
There are places where the same storage variable is accessed multiple times in the same function. It would be more gas efficient to cache these variables and re-use them where necessary. E.g. function createRecord accesses records[_nftId].reserve 2 times in a require statement. function _transferToReserveAndStore accesses "reserve" 4 times. function isResolverCached accesses addressCache[name] 2 times.
Also, can save some gas here if you use local variables instead:
emit BurnPartUpdated(burnPercentage); emit ReserveUpdated(nstReserve); emit FeeSplitterUpdated(feeSplitter); emit BurnPartUpdated(burnPercentage);
There might be more places where this suggestion can be applied. Consider caching repeated storage access to save some gas.
#0 - adrien-supizet
2021-11-19T16:18:48Z
duplicate #102
🌟 Selected for report: pauliax
106.015 USDC - $106.02
pauliax
I think it is not necessary to have function _burnNST as a separate private function. It is called only once and has just one LOC so it just incurs in extra gas cost which can be avoided by moving this line to function trigger and getting rid of _burnNST.
🌟 Selected for report: pauliax
106.015 USDC - $106.02
pauliax
function "mintWithMetadata" does not need onlyFactory modifier as it will be checked in function "mint" later.
pauliax
function triggerForToken calls ExchangeHelpers.fillQuote and continues the execution without checking the returned value. fillQuote returns a boolean flag indicating if the low-level call succeeded or failed.
Consider adding a require statement that this fillQuote call succeeded.
#0 - adrien-supizet
2021-11-19T17:02:24Z
duplicate #76