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
Rank: 30/31
Findings: 1
Award: $45.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: NoamYakov
Also found by: 0xSmartContract, 0xackermann, Aymen0909, Deivitto, Diana, IllIllI, RaymondFam, ReyAdmirado, Rolezn, antonttc, arialblack14, c3phas, cryptostellar5, matrix_0wl, nadin, oyc_109
45.4256 USDC - $45.43
Number | Issue details | Instances |
---|---|---|
G-1 | Use calldata instead of memory. | 1 |
G-2 | Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate. | 2 |
G-3 | Use++i instead of i++ | 1 |
G-4 | Usage ofuint s/int s smaller than 32 bytes (256 bits) incurs overhead. | 17 |
G-5 | abi.encode() is less efficient than abi.encodePacked() | 5 |
G-6 | Internal functions only called once can be inlined to save gas. | 5 |
G-7 | >= costs less gas than > . | 30 |
G-8 | <x> += <y> costs more gas than <x> = <x> + <y> for state variables | 10 |
Total: 8 issues.
Use calldata instead of memory for function parameters saves gas if the function argument is only read.
Use calldata instead of memory.
File: 2023-01-numoen/src/periphery/SwapHelper.sol
69: function swap(SwapType swapType, SwapParams memory params, bytes memory data) internal returns (uint256 amount) {
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
Where appropriate, you can combine multiple address mappings into a single mapping of an address to a struct.
File: 2023-01-numoen/src/core/Factory.sol
39: mapping(address => mapping(address => mapping(uint256 => mapping(uint256 => mapping(uint256 => address)))))
File: 2023-01-numoen/src/periphery/LiquidityManager.sol
69: mapping(address => mapping(address => Position)) public positions;
++i
instead of i++
Source: ++i
costs less gas compared to i++
or i += 1
for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.
Example:
i++
increments i
and returns the initial value of i
. Which means:
uint i = 1; i++; // == 1 but i == 2
But ++i
returns the actual incremented value:
uint i = 1;
++i; // == 2 and i == 2 too
, so no need for a temporary variable
In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2
Also check this out.
Change from i++
to ++i
.
File: 2023-01-numoen/src/libraries/FullMath.sol
126: result++;
uint
s/int
s 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.
Use uint256
instead if appropriate.
File: 2023-01-numoen/src/core/Factory.sol
50: uint128 token0Exp; 51: uint128 token1Exp; 66: uint8 token0Exp, 67: uint8 token1Exp,
File: 2023-01-numoen/src/core/ImmutableState.sol
30: uint128 _token0Exp; 31: uint128 _token1Exp;
File: 2023-01-numoen/src/core/Pair.sol
40: uint120 public override reserve0; 43: uint120 public override reserve1; 71: uint120 _reserve0 = reserve0; // SLOAD 72: uint120 _reserve1 = reserve1; // SLOAD 94: uint120 _reserve0 = reserve0; // SLOAD 95: uint120 _reserve1 = reserve1; // SLOAD 119: uint120 _reserve0 = reserve0; // SLOAD 120: uint120 _reserve1 = reserve1; // SLOAD
File: 2023-01-numoen/src/libraries/SafeCast.sol
8: function toUint120(uint256 y) internal pure returns (uint120 z) { 9: require((z = uint120(y)) == y);
File: 2023-01-numoen/src/periphery/SwapHelper.sol
62: uint24 fee;
abi.encode()
is less efficient than abi.encodePacked()
abi.encode
will apply ABI encoding rules. Therefore all elementary types are padded to 32 bytes and dynamic arrays include their length. Therefore it is possible to also decode this data again (with abi.decode
) when the type are known.
abi.encodePacked
will only use the only use the minimal required memory to encode the data. E.g. an address will only use 20 bytes and for dynamic arrays only the elements will be stored without length. For more info see the Solidity docs for packed mode.
For the input of the keccak
method it is important that you can ensure that the resulting bytes of the encoding are unique. So if you always encode the same types and arrays always have the same length then there is no problem. But if you switch the parameters that you encode or encode multiple dynamic arrays you might have conflicts.
For example:
abi.encodePacked(address(0x0000000000000000000000000000000000000001), uint(0))
encodes to the same as abi.encodePacked(uint(0x0000000000000000000000000000000000000001000000000000000000000000), address(0))
and abi.encodePacked(uint[](1,2), uint[](3))
encodes to the same as abi.encodePacked(uint[](1), uint[](2,3))
Therefore these examples will also generate the same hashes even so they are different inputs.
On the other hand you require less memory and therefore in most cases abi.encodePacked uses less gas than abi.encode.
Use abi.encodePacked()
where possible to save gas
File: 2023-01-numoen/src/core/Factory.sol
82: lendgine = address(new Lendgine{ salt: keccak256(abi.encode(token0, token1, token0Exp, token1Exp, upperBound)) }());
File: 2023-01-numoen/src/periphery/LendgineRouter.sol
150: abi.encode( 268: abi.encode(
File: 2023-01-numoen/src/periphery/LiquidityManager.sol
160: abi.encode(
File: 2023-01-numoen/src/periphery/SwapHelper.sol
108: abi.encode(params.tokenIn)
Not inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
Check this out: link
Note: To sum up, when there is an internal function that is only called once, there is no point for that function to exist.
Remove the function and make it inline if it is a simple function.
File: 2023-01-numoen/src/core/Pair.sol
93: function burn(address to, uint256 liquidity) internal returns (uint256 amount0, uint256 amount1) {
File: 2023-01-numoen/src/core/libraries/Position.sol
69: function newTokensOwed(Position.Info memory position, uint256 rewardPerPosition) internal pure returns (uint256) {
File: 2023-01-numoen/src/core/libraries/PositionMath.sol
12: function addDelta(uint256 x, int256 y) internal pure returns (uint256 z) {
File: 2023-01-numoen/src/libraries/SafeCast.sol
8: function toUint120(uint256 y) internal pure returns (uint120 z) { 15: function toInt256(uint256 y) internal pure returns (int256 z) {
>=
costs less gas than >
.The compiler uses opcodes GT
and ISZERO
for solidity code that uses >
, but only requires LT
for >=
, which saves 3 gas
Use >=
instead if appropriate.
File: 2023-01-numoen/src/core/Factory.sol
77: if (token0Exp > 18 || token0Exp < 6 || token1Exp > 18 || token1Exp < 6) revert ScaleError();
File: 2023-01-numoen/src/core/Lendgine.sol
87: if (liquidity > totalLiquidity) revert CompleteUtilizationError(); 89: if (totalSupply > 0 && totalLiquidityBorrowed == 0) revert CompleteUtilizationError(); 142: if (totalLiquiditySupplied == 0 && totalPositionSize > 0) revert CompleteUtilizationError(); 172: if (size > positionInfo.size) revert InsufficientPositionError(); 173: if (liquidity > _totalLiquidity) revert CompleteUtilizationError(); 198: collateral = collateralRequested > tokensOwed ? tokensOwed : collateralRequested; 200: if (collateral > 0) { 253: uint256 dilutionLP = dilutionLPRequested > _totalLiquidityBorrowed ? _totalLiquidityBorrowed : dilutionLPRequested;
File: 2023-01-numoen/src/core/Pair.sol
59: if (scale1 > 2 * upperBound) revert InvariantError(); 102: if (amount0 > 0) SafeTransferLib.safeTransfer(token0, to, amount0); 103: if (amount1 > 0) SafeTransferLib.safeTransfer(token1, to, amount1); 122: if (amount0Out > 0) SafeTransferLib.safeTransfer(token0, to, amount0Out); 123: if (amount1Out > 0) SafeTransferLib.safeTransfer(token1, to, amount1Out);
File: 2023-01-numoen/src/core/libraries/Position.sol
50: if (_positionInfo.size > 0) { 64: if (tokensOwed > 0) positionInfo.tokensOwed = _positionInfo.tokensOwed + tokensOwed;
File: 2023-01-numoen/src/libraries/FullMath.sol
37: require(denominator > 0); 46: require(denominator > prod1); 125: if (mulmod(a, b, denominator) > 0) {
File: 2023-01-numoen/src/periphery/LendgineRouter.sol
121: if (collateralIn > decoded.collateralMax) revert AmountError();
File: 2023-01-numoen/src/periphery/LiquidityManager.sol
112: if (decoded.amount0 > 0) pay(decoded.token0, decoded.payer, msg.sender, decoded.amount0); 113: if (decoded.amount1 > 0) pay(decoded.token1, decoded.payer, msg.sender, decoded.amount1); 241: amount = params.amountRequested > position.tokensOwed ? position.tokensOwed : params.amountRequested;
File: 2023-01-numoen/src/periphery/Payment.sol
29: if (balanceWETH > 0) { 39: if (balanceToken > 0) { 45: if (address(this).balance > 0) SafeTransferLib.safeTransferETH(msg.sender, address(this).balance);
File: 2023-01-numoen/src/periphery/SwapHelper.sol
42: SafeTransferLib.safeTransfer(tokenIn, msg.sender, amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta)); 76: amount = params.amount > 0 81: params.amount > 0 ? (uint256(params.amount), amount) : (amount, uint256(-params.amount)); 111: if (params.amount > 0) {
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves 113 gas
Use <x> = <x> + <y>
instead.
File: 2023-01-numoen/src/core/Lendgine.sol
91: totalLiquidityBorrowed += liquidity; 114: totalLiquidityBorrowed -= liquidity; 176: totalPositionSize -= size; 257: rewardPerPositionStored += FullMath.mulDiv(dilutionSpeculative, 1e18, totalPositionSize);
File: 2023-01-numoen/src/periphery/LiquidityManager.sol
178: position.tokensOwed += FullMath.mulDiv(position.size, rewardPerPositionPaid - position.rewardPerPositionPaid, 1e18); 180: position.size += size; 214: position.tokensOwed += FullMath.mulDiv(position.size, rewardPerPositionPaid - position.rewardPerPositionPaid, 1e18); 216: position.size -= params.size; 238: position.tokensOwed += FullMath.mulDiv(position.size, rewardPerPositionPaid - position.rewardPerPositionPaid, 1e18); 242: position.tokensOwed -= amount;
#0 - c4-judge
2023-02-16T11:22:30Z
berndartmueller marked the issue as grade-b