Skip to content

Latest commit

 

History

History
755 lines (418 loc) · 21.2 KB

Rolezn-G.md

File metadata and controls

755 lines (418 loc) · 21.2 KB

Summary

Gas Optimizations

Issue Contexts Estimated Gas Saved
GAS‑1 abi.encode() is less efficient than abi.encodepacked() 6 600
GAS‑2 Setting the constructor to payable 5 65
GAS‑3 Duplicated require()/revert() Checks Should Be Refactored To A Modifier Or Function 2 56
GAS‑4 Use assembly to write address storage values 15 -
GAS‑5 Use hardcoded address instead address(this) 16 -
GAS‑6 Optimize names to save gas 9 198
GAS‑7 <x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables 10 -
GAS‑8 Public Functions To External 8 -
GAS‑9 Splitting require() Statements That Use && Saves Gas 2 18
GAS‑10 Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 25 -
GAS‑11 Using unchecked blocks to save gas 3 408
GAS‑12 Use solidity version 0.8.17 to gain some gas boost 15 1320
GAS‑13 Using storage instead of memory saves gas 4 1400

Total: 159 contexts over 22 issues

Gas Optimizations

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

Proof Of Concept

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

Saves ~13 gas per instance

Proof Of Concept

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

Saves deployment costs

Proof Of Concept

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

Proof Of Concept

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

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

Proof Of Concept

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

Recommended Mitigation Steps

Use hardcoded address

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 here

Proof Of Concept

All in-scope contracts

Recommended Mitigation Steps

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.

Proof Of Concept

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

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

Proof Of Concept

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

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);

Proof Of Concept

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

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

Proof Of Concept

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

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

Proof Of Concept

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

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

Proof Of Concept

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

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

Proof Of Concept

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