Nested Finance contest - 0x1f8b's results

The one-stop Defi app to build, manage and monetize your portfolio.

General Information

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

Nested Finance

Findings Distribution

Researcher Performance

Rank: 3/24

Findings: 4

Award: $3,657.96

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: IllIllI

Also found by: 0x1f8b, hyh, pauliax

Labels

bug
duplicate
2 (Med Risk)
sponsor confirmed

Awards

1024.836 USDC - $1,024.84

External Links

Lines of code

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

Vulnerability details

Impact

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.

Proof of Concept

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 D
  • call rebuildCache = all it's OK
  • next time, because the contract's logic, resolverOperatorsRequired returns only A
  • call rebuildCache = WRONG, it will retain in the cache A,B,C and D

The 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.

Findings Information

🌟 Selected for report: robee

Also found by: 0x1f8b, csanuragjain

Labels

duplicate
2 (Med Risk)

Awards

1024.836 USDC - $1,024.84

External Links

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.

Findings Information

🌟 Selected for report: 0x1f8b

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

1518.2755 USDC - $1,518.28

External Links

Lines of code

https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/OperatorResolver.sol#L42-L43

Vulnerability details

Impact

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.

Proof of Concept

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.

Findings Information

Awards

90.0012 USDC - $90.00

Labels

bug
duplicate
G (Gas Optimization)

External Links

GAS issues

  1. Change the incremental logic from i++ to ++i in order to save some opcodes:
  1. The following structs could be optimized moving the position of certains values in order to save slot storages:
  1. Use delete instead of set to default value (false or address(0))
  1. Change the logic in order to save one variable
function _setOwner(address newOwner) private { emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; }

#0 - adrien-supizet

2022-02-15T11:18:22Z

  1. already surfaced in first audit
  2. duplicated https://github.com/code-423n4/2022-02-nested-findings/issues/28
  3. Invalid, this makes no difference
  4. acknowledged

#1 - harleythedogC4

2022-03-13T03:14:29Z

My personal judgements:

  1. "Change the incremental logic from i++ to ++i". Agree with sponsor. Invalid.
  2. "The following structs could be optimized". I am going to disagree with the sponsor here, in theory there is an optimzation here, and obviously I can't reproduce the sponsor's claim of the gas costs actually increasing. I will mark as Valid and small-optimization.
  3. "Use delete instead of set to default value". Agree with sponsor, according to here, it seems that there is no difference in this case. Invalid.
  4. "Change the logic in order to save one variable". Valid and small-optimization

#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.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter