Nested Finance contest - ye0lde'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: 23/24

Findings: 1

Award: $75.57

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

75.5694 USDC - $75.57

Labels

bug
G (Gas Optimization)
sponsor confirmed

External Links

Nested Finance Gas Optimization Report

Unless otherwise noted, manual auditing and testing were done using Visual Studio Code and Remix. The sponsor-provided test suite was used to verify the findings.

The audit was done from February 10-12, 2022 by ye0lde through code4rena.

Findings

G-1 - Function store can be more efficient (NestedRecords.sol)

Impact

Caching the references to records[_nftId] in the store function will decrease gas usage as store is called frequently from NestedFactory.sol

Below are the relevant numbers from the sponsor's test suite before and after the change:

FunctionBefore (AVG)After (AVG)
create701747701219
processInputAndOutputOrders871716871184
processInputOrders406972406779
processOutputOrders449829449712
store8392783684
Proof of Concept

The store function is here: https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/NestedRecords.sol#L111-L132

function store( uint256 _nftId, address _token, uint256 _amount, address _reserve ) external onlyFactory { uint256 amount = records[_nftId].holdings[_token]; if (amount != 0) { require(records[_nftId].reserve == _reserve, "NRC: RESERVE_MISMATCH"); updateHoldingAmount(_nftId, _token, amount + _amount); return; } require(records[_nftId].tokens.length < maxHoldingsCount, "NRC: TOO_MANY_TOKENS"); require( _reserve != address(0) && (_reserve == records[_nftId].reserve || records[_nftId].reserve == address(0)), "NRC: INVALID_RESERVE" ); records[_nftId].holdings[_token] = _amount; records[_nftId].tokens.push(_token); records[_nftId].reserve = _reserve; }

I suggest the following changes:

function store( uint256 _nftId, address _token, uint256 _amount, address _reserve ) external onlyFactory { NftRecord storage nftRecord = records[_nftId]; uint256 amount = nftRecord.holdings[_token]; if (amount != 0) { require(nftRecord.reserve == _reserve, "NRC: RESERVE_MISMATCH"); updateHoldingAmount(_nftId, _token, amount + _amount); return; } require(nftRecord.tokens.length < maxHoldingsCount, "NRC: TOO_MANY_TOKENS"); require( _reserve != address(0) && (_reserve == nftRecord.reserve || nftRecord.reserve == address(0)), "NRC: INVALID_RESERVE" ); nftRecord.holdings[_token] = _amount; nftRecord.tokens.push(_token); nftRecord.reserve = _reserve; }

G-2 - Save gas and retain code clarity with the unchecked keyword (NestedFactory.sol)

Impact

In a previous Code4rena audit, various "unchecked" optimizations were suggested. Some of which were implemented and some were not because the sponsor's focus was code clarity over optimization.

I believe this suggestion meets the requirements for both optimization and clarity. Below are the relevant numbers from the sponsor's test suite before and after the change:

FunctionBefore (AVG)After (AVG)
create701747701623
processInputAndOutputOrders871716871468
processInputOrders406972406784
Proof of Concept

The code that can be unchecked is here: https://github.com/code-423n4/2022-02-nested/blob/fe6f9ef7783c3c84798c8ab5fc58085a55cebcfc/contracts/NestedFactory.sol#L339-L347

The "unchecked" keyword can be applied here since there is a require statement at #337 that ensures the arithmetic operations would not cause an integer underflow or overflow.

uint256 underSpentAmount = _inputTokenAmount - feesAmount - amountSpent; if (underSpentAmount != 0) { tokenSold.safeTransfer(_fromReserve ? address(reserve) : _msgSender(), underSpentAmount); } // If input is from the reserve, update the records if (_fromReserve) { _decreaseHoldingAmount(_nftId, address(tokenSold), _inputTokenAmount - underSpentAmount); }

Add unchecked around #L339-L347 as shown below.

require(amountSpent <= _inputTokenAmount - feesAmount, "NF: OVERSPENT"); unchecked { uint256 underSpentAmount = _inputTokenAmount - feesAmount - amountSpent; if (underSpentAmount != 0) { tokenSold.safeTransfer(_fromReserve ? address(reserve) : _msgSender(), underSpentAmount); } // If input is from the reserve, update the records if (_fromReserve) { _decreaseHoldingAmount(_nftId, address(tokenSold), _inputTokenAmount - underSpentAmount); } }

#0 - maximebrugel

2022-02-17T17:06:53Z

"Function store can be more efficient (NestedRecords.sol)" (Confirmed)

701781 to 701247 => -534

"Save gas and retain code clarity with the unchecked keyword (NestedFactory.sol)" (Confirmed)

#1 - harleythedogC4

2022-03-13T03:40:55Z

My personal judgments:

  1. "Function store can be more efficient (NestedRecords.sol)". Valid and small-optimization.
  2. "unchecked keyword". Although the sponsor confirms here, this was disputed in other gas optimization reports since it already has been raised in the previous audits. To be fair, it should be invalid here too. Invalid.

#2 - harleythedogC4

2022-03-13T06:22:14Z

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 1 points. Thus the final score of this gas report is (1/10)*100 = 10.

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