Numoen contest - Rolezn's results

Automated exchange for power perpetuals.

General Information

Platform: Code4rena

Start Date: 26/01/2023

Pot Size: $60,500 USDC

Total HM: 7

Participants: 31

Period: 6 days

Judge: berndartmueller

Total Solo HM: 3

Id: 207

League: ETH

Numoen

Findings Distribution

Researcher Performance

Rank: 19/31

Findings: 2

Award: $187.91

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: CodingNameKiki

Also found by: 0xAgro, 0xSmartContract, IllIllI, Rolezn, SleepingBugs, btk, chrisdior4, matrix_0wl

Labels

bug
grade-b
QA (Quality Assurance)
Q-09

Awards

142.4841 USDC - $142.48

External Links

Summary<a name="Summary">

Low Risk Issues

IssueContexts
LOW‑1Possible rounding issue1
LOW‑2Use _safeMint instead of _mint1
LOW‑3Missing parameter validation in constructor9

Total: 27 contexts over 6 issues

Non-critical Issues

IssueContexts
NC‑1Compliance with Solidity Style rules in Constant expressions3
NC‑2Duplicated require()/revert() Checks Should Be Refactored To A Modifier Or Function2
NC‑3Function writing that does not comply with the Solidity Style GuideAll in-scope contracts
NC‑4NatSpec return parameters should be included in contractsAll in-scope contracts
NC‑5NatSpec comments should be increased in contractsAll in-scope contracts
NC‑6Use a more recent version of Solidity15
NC‑7Use bytes.concat()2
NC‑8Use of Block.Timestamp2

Total: 138 contexts over 18 issues

Low Risk Issues

<a href="#Summary">[LOW‑1]</a><a name="LOW&#x2011;1"> Possible rounding issue

There might be a rounding issue. For example, if totalLiquidity is 20*1e18 and borrowedLiquidity is 19, would lead to return 0 value

<ins>Proof Of Concept</ins>
42: return (borrowedLiquidity * 1e18) / totalLiquidity;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/JumpRate.sol#L42

<a href="#Summary">[LOW‑2]</a><a name="LOW&#x2011;2"> Use _safeMint instead of _mint

According to openzepplin's ERC721, the use of _mint is discouraged, use _safeMint whenever possible. https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721-_mint-address-uint256-

<ins>Proof Of Concept</ins>
93: _mint(to, shares);

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L93

<ins>Recommended Mitigation Steps</ins>

Use _safeMint whenever possible instead of _mint

<a href="#Summary">[LOW‑3]</a><a name="LOW&#x2011;3"> Missing parameter validation in constructor

Some parameters of constructors are not checked for invalid values.

<ins>Proof Of Concept</ins>
50: address _factory
51: address _uniswapV2Factory
52: address _uniswapV3Factory
53: address _weth

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L50-L53

75: address _factory
75: address _weth

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L75-L75

17: address _weth

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L17

29: address _uniswapV2Factory
29: address _uniswapV3Factory

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/SwapHelper.sol#L29-L29

<ins>Recommended Mitigation Steps</ins>

Validate the parameters.

Non Critical Issues

<a href="#Summary">[NC‑1]</a><a name="NC&#x2011;1"> Compliance with Solidity Style rules in Constant expressions

<ins>Proof Of Concept</ins>
7: uint256 public constant override kink = 0.8 ether;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/JumpRate.sol#L7

9: uint256 public constant override multiplier = 1.375 ether;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/JumpRate.sol#L9

11: uint256 public constant override jumpMultiplier = 44.5 ether;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/JumpRate.sol#L11

<ins>Recommended Mitigation Steps</ins>

Variables are declared as constant utilize the UPPER_CASE_WITH_UNDERSCORES format.

<a href="#Summary">[NC‑2]</a><a name="NC&#x2011;2"> Duplicated require()/revert() Checks Should Be Refactored To A Modifier Or Function

Saves deployment costs

<ins>Proof Of Concept</ins>
61: require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
79: require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L61

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L79

<a href="#Summary">[NC‑3]</a><a name="NC&#x2011;3"> Function writing that does not comply with the Solidity Style Guide

Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.

https://docs.soliditylang.org/en/v0.8.17/style-guide.html

Functions should be grouped according to their visibility and ordered:

  • constructor
  • receive function (if exists)
  • fallback function (if exists)
  • external
  • public
  • internal
  • private
  • within a grouping, place the view and pure functions last
<ins>Proof Of Concept</ins>

All in-scope contracts

<a href="#Summary">[NC‑4]</a><a name="NC&#x2011;4"> NatSpec return parameters should be included in contracts

It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.

https://docs.soliditylang.org/en/v0.8.15/natspec-format.html

<ins>Proof Of Concept</ins>

All in-scope contracts

<ins>Recommended Mitigation Steps</ins>

Include return parameters in NatSpec comments

Recommendation Code Style: (from Uniswap3)

    /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
    /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
    /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
    /// on tickLower, tickUpper, the amount of liquidity, and the current price.
    /// @param recipient The address for which the liquidity will be created
    /// @param tickLower The lower tick of the position in which to add liquidity
    /// @param tickUpper The upper tick of the position in which to add liquidity
    /// @param amount The amount of liquidity to mint
    /// @param data Any data that should be passed through to the callback
    /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
    /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external returns (uint256 amount




### <a href="#Summary">[NC&#x2011;5]</a><a name="NC&#x2011;5"> NatSpec comments should be increased in contracts

It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability. https://docs.soliditylang.org/en/v0.8.15/natspec-format.html

#### <ins>Proof Of Concept</ins>


All in-scope contracts


#### <ins>Recommended Mitigation Steps</ins>

NatSpec comments should be increased in contracts



### <a href="#Summary">[NC&#x2011;6]</a><a name="NC&#x2011;6"> Use a more recent version of Solidity

<a href="https://blog.soliditylang.org/2021/04/21/solidity-0.8.4-release-announcement/">0.8.4</a>:
bytes.concat() instead of abi.encodePacked(<bytes>,<bytes>)

<a href="https://blog.soliditylang.org/2022/02/16/solidity-0.8.12-release-announcement/">0.8.12</a>: 
string.concat() instead of abi.encodePacked(<str>,<str>)

<a href="https://blog.soliditylang.org/2022/03/16/solidity-0.8.13-release-announcement/">0.8.13</a>: 
Ability to use using for with a list of free functions

<a href="https://blog.soliditylang.org/2022/05/18/solidity-0.8.14-release-announcement/">0.8.14</a>:

ABI Encoder: When ABI-encoding values from calldata that contain nested arrays, correctly validate the nested array length against calldatasize() in all cases.
Override Checker: Allow changing data location for parameters only when overriding external functions.

<a href="https://blog.soliditylang.org/2022/06/15/solidity-0.8.15-release-announcement/">0.8.15</a>:

Code Generation: Avoid writing dirty bytes to storage when copying bytes arrays.
Yul Optimizer: Keep all memory side-effects of inline assembly blocks.

<a href="https://blog.soliditylang.org/2022/08/08/solidity-0.8.16-release-announcement/">0.8.16</a>:

Code Generation: Fix data corruption that affected ABI-encoding of calldata values represented by tuples: structs at any nesting level; argument lists of external functions, events and errors; return value lists of external functions. The 32 leading bytes of the first dynamically-encoded value in the tuple would get zeroed when the last component contained a statically-encoded array.

<a href="https://blog.soliditylang.org/2022/09/08/solidity-0.8.17-release-announcement/">0.8.17</a>:

Yul Optimizer: Prevent the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call.

#### <ins>Proof Of Concept</ins>


```solidity
pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Factory.sol#L2

pragma solidity ^0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/ImmutableState.sol#L2

pragma solidity ^0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/JumpRate.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/libraries/Position.sol#L2

pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/libraries/PositionMath.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/libraries/Balance.sol#L2

pragma solidity ^0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/libraries/SafeCast.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/SwapHelper.sol#L2

pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/libraries/LendgineAddress.sol#L2

pragma solidity >=0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L1

<ins>Recommended Mitigation Steps</ins>

Consider updating to a more recent solidity version.

<a href="#Summary">[NC‑7]</a><a name="NC&#x2011;7"> Use bytes.concat()

Solidity version 0.8.4 introduces bytes.concat() (vs abi.encodePacked(<bytes>,<bytes>))

<ins>Proof Of Concept</ins>
25: abi.encodePacked(
              hex"ff",
              factory,
              keccak256(abi.encode(token0, token1, token0Exp, token1Exp, upperBound)

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/libraries/LendgineAddress.sol#L25

23: abi.encodePacked(
              hex"ff",
              factory,
              keccak256(abi.encodePacked(token0, token1)

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L23

<ins>Recommended Mitigation Steps</ins>

Use bytes.concat() and upgrade to at least Solidity version 0.8.4 if required.

<a href="#Summary">[NC‑8]</a><a name="NC&#x2011;8"> Use of Block.Timestamp

Block timestamps have historically been used for a variety of applications, such as entropy for random numbers (see the Entropy Illusion for further details), locking funds for periods of time, and various state-changing conditional statements that are time-dependent. Miners have the ability to adjust timestamps slightly, which can prove to be dangerous if block timestamps are used incorrectly in smart contracts. References: SWC ID: 116

<ins>Proof Of Concept</ins>
66: if (deadline < block.timestamp) revert LivelinessError();

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L66

84: if (deadline < block.timestamp) revert LivelinessError();

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L84

<ins>Recommended Mitigation Steps</ins>

Block timestamps should not be used for entropy or generating random numbers—i.e., they should not be the deciding factor (either directly or through some derivation) for winning a game or changing an important state.

Time-sensitive logic is sometimes required; e.g., for unlocking contracts (time-locking), completing an ICO after a few weeks, or enforcing expiry dates. It is sometimes recommended to use block.number and an average block time to estimate times; with a 10 second block time, 1 week equates to approximately, 60480 blocks. Thus, specifying a block number at which to change a contract state can be more secure, as miners are unable to easily manipulate the block number.

#0 - c4-judge

2023-02-16T11:40:58Z

berndartmueller marked the issue as grade-b

Awards

45.4256 USDC - $45.43

Labels

bug
G (Gas Optimization)
grade-b
G-16

External Links

Summary<a name="Summary">

Gas Optimizations

IssueContextsEstimated Gas Saved
GAS‑1abi.encode() is less efficient than abi.encodepacked()6600
GAS‑2Setting the constructor to payable565
GAS‑3Duplicated require()/revert() Checks Should Be Refactored To A Modifier Or Function256
GAS‑4Use assembly to write address storage values15-
GAS‑5Use hardcoded address instead address(this)16-
GAS‑6Optimize names to save gas9198
GAS‑7<x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables10-
GAS‑8Public Functions To External8-
GAS‑9Splitting require() Statements That Use && Saves Gas218
GAS‑10Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead25-
GAS‑11Using unchecked blocks to save gas3408
GAS‑12Use solidity version 0.8.17 to gain some gas boost151320
GAS‑13Using storage instead of memory saves gas41400

Total: 159 contexts over 22 issues

Gas Optimizations

<a href="#Summary">[GAS‑1]</a><a name="GAS&#x2011;1"> abi.encode() is less efficient than abi.encodepacked()

See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison

<ins>Proof Of Concept</ins>
82: lendgine = address(new Lendgine{ salt: keccak256(abi.encode(token0, token1, token0Exp, token1Exp, upperBound)) }());

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Factory.sol#L82

150: abi.encode(

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L150

268: abi.encode(

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L268

160: abi.encode(

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L160

108: abi.encode(params.tokenIn)

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/SwapHelper.sol#L108

28: keccak256(abi.encode(token0, token1, token0Exp, token1Exp, upperBound)),

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/libraries/LendgineAddress.sol#L28

<a href="#Summary">[GAS‑2]</a><a name="GAS&#x2011;2"> Setting the constructor to payable

Saves ~13 gas per instance

<ins>Proof Of Concept</ins>
27: constructor()

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/ImmutableState.sol#L27

49: constructor(
    address _factory,
    address _uniswapV2Factory,
    address _uniswapV3Factory,
    address _weth
  )
    SwapHelper(_uniswapV2Factory, _uniswapV3Factory)
    Payment(_weth)

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L49

75: constructor(address _factory, address _weth) Payment(_weth)

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L75

17: constructor(address _weth)

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L17

29: constructor(address _uniswapV2Factory, address _uniswapV3Factory)

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/SwapHelper.sol#L29

<a href="#Summary">[GAS‑3]</a><a name="GAS&#x2011;3"> Duplicated require()/revert() Checks Should Be Refactored To A Modifier Or Function

Saves deployment costs

<ins>Proof Of Concept</ins>
61: require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
79: require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L61

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L79

<a href="#Summary">[GAS‑4]</a><a name="GAS&#x2011;4"> Use assembly to write address storage values

<ins>Proof Of Concept</ins>
135: _totalPositionSize = totalPositionSize; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L135

163: _totalPositionSize = totalPositionSize; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L163

164: _totalLiquidity = totalLiquidity; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L164

214: _totalLiquidityBorrowed = totalLiquidityBorrowed; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L214

247: _totalLiquidityBorrowed = totalLiquidityBorrowed; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L247

267: _rewardPerPositionStored = rewardPerPositionStored; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L267

73: _totalLiquidity = totalLiquidity; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L73

96: _totalLiquidity = totalLiquidity; // SLOAD

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L96

47: _positionInfo = positionInfo;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/libraries/Position.sol#L47

<a href="#Summary">[GAS‑5]</a><a name="GAS&#x2011;5"> Use hardcode address instead address(this)

Instead of using address(this), it is more gas-efficient to pre-calculate and use the hardcoded address. Foundry's script.sol and solmate's LibRlp.sol contracts can help achieve this.

References: https://book.getfoundry.sh/reference/forge-std/compute-create-address

https://twitter.com/transmissions11/status/1518507047943245824

<ins>Proof Of Concept</ins>
108: uint256 shares = balanceOf[address(this)];
115: _burn(address(this), shares);

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L108

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L115

14: token.staticcall(abi.encodeWithSelector(bytes4(keccak256(bytes("balanceOf(address)"))), address(this)));

https://github.com/code-423n4/2023-01-numoen/tree/main/src/libraries/Balance.sol#L14

148: address(this),

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L148

235: if (decoded.recipient != address(this)) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L235

262: address recipient = params.recipient == address(0) ? address(this) : params.recipient;
267: address(this),

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L262

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L267

158: address(this),
177: (, uint256 rewardPerPositionPaid,) = ILendgine(lendgine).positions(address(this));

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L158

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L177

206: address recipient = params.recipient == address(0) ? address(this) : params.recipient;
213: (, uint256 rewardPerPositionPaid,) = ILendgine(lendgine).positions(address(this));

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L206

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L213

233: address recipient = params.recipient == address(0) ? address(this) : params.recipient;
237: (, uint256 rewardPerPositionPaid,) = ILendgine(params.lendgine).positions(address(this));

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L233

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L237

45: if (address(this).balance > 0) SafeTransferLib.safeTransferETH(msg.sender, address(this).balance);

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L45

53: if (token == weth && address(this).balance >= value) {
57: } else if (payer == address(this)) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L53

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L57

<ins>Recommended Mitigation Steps</ins>

Use hardcoded address

<a href="#Summary">[GAS‑6]</a><a name="GAS&#x2011;6"> Optimize names to save gas

Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.

See more <a href="https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92">here</a>

<ins>Proof Of Concept</ins>

All in-scope contracts

<ins>Recommended Mitigation Steps</ins>

Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas For example, the function IDs in the Gauge.sol contract will be the most used; A lower method ID may be given.

<a href="#Summary">[GAS‑7]</a><a name="GAS&#x2011;7"> <x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables

<ins>Proof Of Concept</ins>
91: totalLiquidityBorrowed += liquidity;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L91

114: totalLiquidityBorrowed -= liquidity;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L114

176: totalPositionSize -= size;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L176

257: rewardPerPositionStored += FullMath.mulDiv(dilutionSpeculative, 1e18, totalPositionSize);

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L257

178: position.tokensOwed += FullMath.mulDiv(position.size, rewardPerPositionPaid - position.rewardPerPositionPaid, 1e18);
180: position.size += size;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L178

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L180

214: position.tokensOwed += FullMath.mulDiv(position.size, rewardPerPositionPaid - position.rewardPerPositionPaid, 1e18);
216: position.size -= params.size;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L214

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L216

238: position.tokensOwed += FullMath.mulDiv(position.size, rewardPerPositionPaid - position.rewardPerPositionPaid, 1e18);
242: position.tokensOwed -= amount;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L238

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L242

<a href="#Summary">[GAS‑8]</a><a name="GAS&#x2011;8"> Public Functions To External

The following functions could be set external to save gas and improve code quality. External call cost is less expensive than of public functions.

<ins>Proof Of Concept</ins>
function getBorrowRate(uint256 borrowedLiquidity, uint256 totalLiquidity) public pure override returns (uint256 rate) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/JumpRate.sol#L13

function convertLiquidityToShare(uint256 liquidity) public view override returns (uint256) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L213

function convertShareToLiquidity(uint256 shares) public view override returns (uint256) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L219

function convertCollateralToLiquidity(uint256 collateral) public view override returns (uint256) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L224

function convertLiquidityToCollateral(uint256 liquidity) public view override returns (uint256) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L229

function invariant(uint256 amount0, uint256 amount1, uint256 liquidity) public view override returns (bool) {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L53

function unwrapWETH(uint256 amountMinimum, address recipient) public payable {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L25

function sweepToken(address token, uint256 amountMinimum, address recipient) public payable {

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L35

<a href="#Summary">[GAS‑9]</a><a name="GAS&#x2011;9"> Splitting require() statements that use && saves gas

Instead of using operator && on a single require. Using a two require can save more gas.

i.e. for require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); use:

require(reserveIn > 0); require(reserveOut > 0);
<ins>Proof Of Concept</ins>
61: require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L61

79: require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L79

<a href="#Summary">[GAS‑10]</a><a name="GAS&#x2011;10"> Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

<ins>Proof Of Concept</ins>
50: uint128 token0Exp;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Factory.sol#L50

51: uint128 token1Exp;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Factory.sol#L51

30: uint128 _token0Exp;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/ImmutableState.sol#L30

31: uint128 _token1Exp;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/ImmutableState.sol#L31

40: uint120 public override reserve0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L40

43: uint120 public override reserve1;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L43

119: uint120 _reserve0 = reserve0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L119

120: uint120 _reserve1 = reserve1;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L120

62: uint24 fee;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/SwapHelper.sol#L62

<a href="#Summary">[GAS‑11]</a><a name="GAS&#x2011;11"> Using unchecked blocks to save gas

Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block

<ins>Proof Of Concept</ins>
14: require((z = x - uint256(-y)) < x, "LS");

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/libraries/PositionMath.sol#L14

107: zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/SwapHelper.sol#L107

81: uint256 denominator = (reserveOut - amountOut) * 997;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L81

<a href="#Summary">[GAS‑12]</a><a name="GAS&#x2011;12"> Use solidity version 0.8.17 to gain some gas boost

Upgrade to the latest solidity version 0.8.17 to get additional gas savings.

<ins>Proof Of Concept</ins>
pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Factory.sol#L2

pragma solidity ^0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/ImmutableState.sol#L2

pragma solidity ^0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/JumpRate.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Pair.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/libraries/Position.sol#L2

pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/libraries/PositionMath.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/libraries/Balance.sol#L2

pragma solidity ^0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/libraries/SafeCast.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LendgineRouter.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/Payment.sol#L2

pragma solidity ^0.8.4;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/SwapHelper.sol#L2

pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/libraries/LendgineAddress.sol#L2

pragma solidity >=0.8.0;

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/UniswapV2/libraries/UniswapV2Library.sol#L1

<a href="#Summary">[GAS‑13]</a><a name="GAS&#x2011;13"> Using storage instead of memory saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

<ins>Proof Of Concept</ins>
167: Position.Info memory positionInfo = positions[msg.sender];

https://github.com/code-423n4/2023-01-numoen/tree/main/src/core/Lendgine.sol#L167

175: Position memory position = positions[params.recipient][lendgine];

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L175

211: Position memory position = positions[msg.sender][lendgine];

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L211

235: Position memory position = positions[msg.sender][params.lendgine];

https://github.com/code-423n4/2023-01-numoen/tree/main/src/periphery/LiquidityManager.sol#L235

#0 - c4-judge

2023-02-16T11:11:46Z

berndartmueller marked the issue as grade-b

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