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: 3/24
Findings: 4
Award: $3,657.96
🌟 Selected for report: 1
🚀 Solo Findings: 0
1024.836 USDC - $1,024.84
https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/abstracts/MixinOperatorResolver.sol#L32-L46 https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/abstracts/MixinOperatorResolver.sol#L55
Currently, many core operations (like NestedFactory.create()
, NestedFactory.swapTokenForTokens()
) are dependent on the assumption that the cache is well synced before these functions are executed however this may not necessarily be the case.
The requireAndGetAddress method used in callOperator requires the operatorCache
to be perfectly synchronized, or else it might make unwanted operator calls.
The rebuildCache
method does not take into account that stale entries remain in the cache after being called with different results in the bytes32[] memory requiredOperators = resolverOperatorsRequired();
For example:
resolverOperatorsRequired
returns A,B,C and DrebuildCache
= all it's OKresolverOperatorsRequired
returns only ArebuildCache
= WRONG, it will retain in the cache A,B,C and DThe same problem exists in the isResolverCached
method, it iterate through the current operators (requiredOperators
), which in the case of being less (like empty), does not prevent the previously cached ones from remaining in the cache (and be called).
Note: The issue 217 related with a wrong cache was marked in the past as Medium
Remove the old entries in rebuildCache
and verify that the stored operators are exactly the same as you want (resolverOperatorsRequired
).
#0 - maximebrugel
2022-02-16T17:21:57Z
The mitigation found is to remove from cache in the removeOperator
function.
#1 - harleythedogC4
2022-02-27T18:00:39Z
I am going to mark this as a duplicate of #38. Both submissions discuss the issue that changes in the NestedFactory
are not being properly being reflected in the MixinOperatorResolver
. The second point in the other issue (labelled "Using both removeOperator() and rebuildCache() does not prevent create() from using the operator") is the exact issue described here.
🌟 Selected for report: robee
Also found by: 0x1f8b, csanuragjain
This issue has been created to upgrade a QA report submission to a medium severity finding. From 0x1f8b:
The store method allows you to store the same token twice in NestedRecords.sol#L130 To do this you must first call store(_nftId,_tokenA,0,_reserve) and then store(_nftId,_tokenA,100000,_reserve) because it use records[_nftId].holdings[_token] as a flag and not checking if the amount is greater than zero, duplicate entries can be created, causing unwanted logics.
#0 - harleythedogC4
2022-03-03T01:00:22Z
And it is a duplicate of #6.
🌟 Selected for report: 0x1f8b
1518.2755 USDC - $1,518.28
The logic related to the areOperatorsImported
method is incorrect and can cause an operator not to be updated because the owner thinks it is already updated, and a vulnerable or defective one can be used.
The operators
mapping is made up of a key bytes32 name
and a value made up of two values: implementation
and selector
, both of which identify the contract and function to be called when an operator is invoked.
The areOperatorsImported
method tries to check if the operators to check already exist, however, the check is not done correctly, since && is used instead of ||.
If the operator with name A
and value {implementation=0x27f8d03b3a2196956ed754badc28d73be8830a6e,selector="performSwapVulnerable"}
exists, and the owner try to check if the operator with name A
and value {implementation=0x27f8d03b3a2196956ed754badc28d73be8830a6e,selector="performSwapFixed"}
exists, that function will return true
, and the owner may decide not to import it , producing unexpected errors.
Because operators manage the tokens, this error can produce a token lost.
Change && by ||
#0 - harleythedogC4
2022-02-27T17:37:57Z
Good catch, I agree with severity.
🌟 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
90.0012 USDC - $90.00
i++
to ++i
in order to save some opcodes:false
or address(0)
)destination
it's equal to address(0)
at OperatorResolver.sol#L63function _setOwner(address newOwner) private { emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; }
#0 - adrien-supizet
2022-02-15T11:18:22Z
#1 - harleythedogC4
2022-03-13T03:14:29Z
My personal judgements:
#2 - harleythedogC4
2022-03-13T06:19:12Z
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 2 points. Thus the final score of this gas report is (2/10)*100 = 20.