Platform: Code4rena
Start Date: 10/02/2022
Pot Size: $30,000 USDC
Total HM: 5
Participants: 24
Period: 3 days
Judge: harleythedog
Total Solo HM: 3
Id: 86
League: ETH
Rank: 4/24
Findings: 3
Award: $2,466.86
🌟 Selected for report: 2
🚀 Solo Findings: 0
This issue has been created to upgrade a QA report submission to a medium severity finding. From pauliax:
function rebuildCache() in MixinOperatorResolver does not delete removed operators from operatorCache. resolverOperatorsRequired return current active operators, so it will not contain removed operators, e.g. operator was removed by calling removeOperator in the factory, then rebuildCache is called, and the cache will still contain this removed operator, and it will be possible to callOperator on this operator.
#0 - harleythedogC4
2022-03-03T01:02:58Z
And this is a duplicate of #38.
758.213 USDC - $758.21
if (address(_tokens[i]) == weth) IWETH(weth).withdraw(amount); (bool success, ) = _msgSender().call{ value: amount }(""); require(success, "FS: ETH_TRANFER_ERROR");
Releasing weth token should be left as a valid option if the user prefers wrapped ERC20 eth, and I think for this native purpose there is a not used storage variable named ETH:
address private constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
Based on my assumptions, the intention was:
if (address(_tokens[i]) == ETH) ...
or if you do not want to implement this change, then at least remove this unused variable to save some gas. However, the issue is small, because the user can always retrieve weth by using another function named releaseTokensNoETH.
require(StorageSlot.getAddressSlot(_ADMIN_SLOT).value == msg.sender, "OFP: FORBIDDEN");
function rebuildCache() in MixinOperatorResolver does not delete removed operators from operatorCache. resolverOperatorsRequired return current active operators, so it will not contain removed operators, e.g. operator was removed by calling removeOperator in the factory, then rebuildCache is called, and the cache will still contain this removed operator, and it will be possible to callOperator on this operator.
Consider introducing an upper limit for _timestamp in updateLockTimestamp, e.g. max 1 year from current block timestamp, otherwise it may be possible to accidentally lock the token forever.
if removeFactory has this check:
require(supportedFactories[_factory], "OFH: NOT_SUPPORTED");
then I think addFactory should have an analogous check:
require(!supportedFactories[_factory], "OFH: ALREADY_SUPPORTED");
require(assetTokensLength > 1, "NF: UNALLOWED_EMPTY_PORTFOLIO");
#0 - maximebrugel
2022-02-15T14:43:54Z
https://github.com/code-423n4/2022-02-nested-findings/issues/15
No meta-transaction support for this admin function.
https://github.com/code-423n4/2022-02-nested-findings/issues/18
We are not sure about an upper limit to set.
No need for a require as long as supportedFactories[_factory] = true
does not disrupt the protocol state.
I don’t really know what is misleading. You can’t withdraw the last token and keep an empty portfolio.
We will fix this issue by adding a require in the receive
function. Also, the user can't send more ETH than needed with the _checkMsgValue
function.
#1 - harleythedogC4
2022-03-03T01:44:37Z
My personal judgements:
#2 - harleythedogC4
2022-03-03T02:31:10Z
Now, here is the methodology I used for calculating a score for each QA report. I first assigned each submission to be either non-critical (1 point), very-low-critical (5 points) or low-critical (10 points), depending on how severe/useful the issue is. The score of a QA report is the sum of these points, divided by the maximum number of points achieved by a QA report. This maximum number was 26 points, achieved by https://github.com/code-423n4/2022-02-nested-findings/issues/66.
The number of points achieved by this report is 26 points. Thus the final score of this QA report is (26/26)*100 = 100.
🌟 Selected for report: pauliax
Also found by: 0x1f8b, Dravee, GreyArt, Omik, ShippooorDAO, Tomio, bobi, cmichel, csanuragjain, defsec, gzeon, kenta, kenzo, m_smirnova2020, rfa, robee, sirhashalot, ye0lde
683.8064 USDC - $683.81
function transfer in NestedReserve is never used and can only be called by the factory (onlyFactory), so consider removing it because I think the factory uses a withdraw function from the Reserve.
Currently never used:
function setReserve onlyFactory
You can remove it to save some gas, or leave it if it was intended for future use with other factories.
functions that add or remove operators or shareholders iterate over the whole array, so you can consider using EnumerableSet to store them: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/EnumerableSet.sol
Could just use msg.sender and do not call an owner() function here:
function unlockTokens(IERC20 _token) external override onlyOwner { ... _token.safeTransfer(owner(), amount);
require(nestedRecords.getAssetReserve(_nftId) == address(reserve), "NF: RESERVE_MISMATCH");
but this could be solved if you first mint the NFT and then invoke _processInputOrders from create.
uint256 assetTokensLength = nestedRecords.getAssetTokensLength(_nftId); ... address token = nestedRecords.getAssetTokens(_nftId)[_tokenIndex];
I think it could just substitute these links by first fetching all the tokens, and then calculating the length itself instead of making 2 external calls for pretty much the same data.
if (_amountToSpend > amounts[1]) { IERC20(_inputToken).safeTransfer(_msgSender(), _amountToSpend - amounts[1]); }
#0 - maximebrugel
2022-02-17T14:11:42Z
_checkMsgValue
refactoring (Disputed)"Could be". You need to pre-calculate the amount of ETH needed to check msg.value in a simple way.
#1 - harleythedogC4
2022-03-13T03:47:19Z
My personal judgments:
#2 - harleythedogC4
2022-03-13T03:48:57Z
Also, #66 has the additional issue: 7. "remove this unused variable to save some gas (ETH)". Valid and small-optimization.
#3 - harleythedogC4
2022-03-13T06:22:32Z
Now, here is the methodology I used for calculating a score for each gas report. I first assigned each submission to be either small-optimization (1 point), medium-optimization (5 points) or large-optimization (10 points), depending on how useful the optimization is. The score of a gas report is the sum of these points, divided by the maximum number of points achieved by a gas report. This maximum number was 10 points, achieved by #67.
The number of points achieved by this report is 10 points. Thus the final score of this gas report is (10/10)*100 = 100.