Platform: Code4rena
Start Date: 01/08/2023
Pot Size: $91,500 USDC
Total HM: 14
Participants: 80
Period: 6 days
Judge: gzeon
Total Solo HM: 6
Id: 269
League: ETH
Rank: 36/80
Findings: 2
Award: $229.51
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Team_FliBit
Also found by: 0x70C9, 3docSec, 8olidity, DavidGiladi, Krace, LokiThe5th, Rolezn, Sathish9098, UniversalCrypto, banpaleo5, catellatech, digitizeworx, fatherOfBlocks, hpsb, j4ld1na, josephdara, kutugu, niser93, nonseodion, oakcobalt, osmanozdemir1, pep7siup, ravikiranweb3, said, sivanesh_808
212.1661 USDC - $212.17
Issue | Contexts | |
---|---|---|
LOW‑1 | Array lengths not checked | 2 |
LOW‑2 | Consider the case where totalsupply is 0 | 14 |
LOW‑3 | Constant decimal values | 7 |
LOW‑4 | Do not allow fees to be set to 100% | 1 |
LOW‑5 | Function could be used to add with an arbitary id, which could be problematic in upcoming future | 1 |
LOW‑6 | Large transfers may not work with some ERC20 tokens | 3 |
LOW‑7 | Remove unused code | 1 |
LOW‑8 | Usage of payable.transfer can lead to loss of funds | 1 |
Total: 57 contexts over 8 issues
Issue | Contexts | |
---|---|---|
NC‑1 | address shouldn't be hard-coded | 2 |
NC‑2 | Declare interfaces on separate files | 6 |
NC‑3 | Duplicated require() /revert() Checks Should Be Refactored To A Modifier Or Function | 24 |
NC‑4 | Duplicate imports | 4 |
NC‑5 | Some functions contain the same exact logic | 4 |
NC‑6 | Omissions in Events | 1 |
NC‑7 | override function arguments that are unused should have the variable name removed or commented out to avoid compiler warnings | 2 |
NC‑8 | Zero as a function argument should have a descriptive meaning | 20 |
Total: 63 contexts over 8 issues
If the length of the arrays are not required to be of the same length, user operations may not be fully executed
File: OptionsPositionManager.sol function executeBuyOptions( uint poolId, address[] calldata assets, uint256[] calldata amounts, address user, address[] memory sourceSwap ) internal {
File: OptionsPositionManager.sol function executeLiquidation( uint poolId, address[] calldata assets, uint256[] calldata amounts, address user, address collateral ) internal {
totalsupply
is 0Consider the case where totalsupply
is 0. When totalsupply
is 0, it should return 0 directly, because there will be an error of dividing by 0.
This would cause the affected functions to revert and as a result can lead to potential loss.
File: GeVault.sol 221: uint valueX8 = vaultValueX8 * liquidity / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L221
File: GeVault.sol 293: priceX8 = vaultValue * 1e18 / supply;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L293
File: TokenisableRange.sol 294: uint removedLiquidity = uint(liquidity) * lp / totalSupply(); 316: TOKEN0.token.safeTransfer( msg.sender, fee0 * lp / totalSupply()); 317: removed0 += fee0 * lp / totalSupply(); 318: fee0 -= fee0 * lp / totalSupply(); 321: TOKEN1.token.safeTransfer( msg.sender, fee1 * lp / totalSupply()); 322: removed1 += fee1 * lp / totalSupply(); 323: fee1 -= fee1 * lp / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L294
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L316
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L317
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L318
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L321
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L322
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L323
File: TokenisableRange.sol 356: return totalValue * 1e18 / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L356
File: TokenisableRange.sol 371: (token0Amount, token1Amount) = LiquidityAmounts.getAmountsForLiquidity( sqrtPriceX96, TickMath.getSqrtRatioAtTick(lowerTick), TickMath.getSqrtRatioAtTick(upperTick), uint128 ( uint(liquidity) * amount / totalSupply() ) );
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L371
File: TokenisableRange.sol 379: token0Amount += fee0 * amount / totalSupply(); 380: token1Amount += fee1 * amount / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L379-L380
File: LPOracle.sol 103: return int(val / LP_TOKEN.totalSupply());
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/LPOracle.sol#L103
</details>Add check for zero value and return 0.
if ( totalSupply() == 0) return 0;
The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.
Resolution: Always retrieve and use the decimals()
function from the token contract itself when performing calculations involving token amounts. This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under/overflows that could jeopardize contract integrity and user funds.
File: GeVault.sol 275: liquidity = valueX8 * 1e10;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L275
File: GeVault.sol 293: priceX8 = vaultValue * 1e18 / supply;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L293
File: GeVault.sol 396: valueX8 += bal * t.latestAnswer() / 1e18;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L396
File: TokenisableRange.sol 99: int24 _upperTick = TickMath.getTickAtSqrtRatio( uint160( 2**48 * sqrt( (2 ** 96 * (10 ** TOKEN1.decimals)) * 1e10 / (uint256(startX10) * 10 ** TOKEN0.decimals) ) ) ); 100: int24 _lowerTick = TickMath.getTickAtSqrtRatio( uint160( 2**48 * sqrt( (2 ** 96 * (10 ** TOKEN1.decimals)) * 1e10 / (uint256(endX10 ) * 10 ** TOKEN0.decimals) ) ) );
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L99-L100
File: TokenisableRange.sol 356: return totalValue * 1e18 / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L356
</details>File: OptionsPositionManager.sol 341: uint debtValue = TokenisableRange(debtAsset).latestAnswer() * debtAmount / 1e18;
100%
It is recommended from a risk perspective to disallow setting 100% fees at all. A reasonable fee maximum should be checked for instead.
There is currently no limit to what newBaseFeeX4
can be set to.
File: GeVault.sol function setBaseFee(uint newBaseFeeX4) public onlyOwner { require(newBaseFeeX4 < 1e4, "GEV: Invalid Base Fee"); baseFeeX4 = newBaseFeeX4; emit SetFee(newBaseFeeX4); }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L183
ERC20
tokensSome IERC20
implementations (e.g UNI
, COMP
) may fail if the valued transferred is larger than uint96
. Source
File: GeVault.sol 227: ERC20(token).safeTransfer(treasury, fee); 267: ERC20(token).safeTransfer(treasury, fee);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L227
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L267
</details>File: OptionsPositionManager.sol 370: IERC20(collateralAsset).safeTransfer(ROEROUTER.treasury(), feeAmount);
This code is not used in the main project contract files, remove it or add event-emit Code that is not in use, suggests that they should not be present and could potentially contain insecure functionalities.
File: PositionManager.sol function validateValuesAgainstOracle
payable.transfer
can lead to loss of fundsThe funds that are to be sent can be lost. The issues with transfer()
and send()
are outlined <a href="https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/">here</a>:
The transfer()
call requires that the recipient to be either an EOA account, or a contract that has a payable
callback. For the contract case, the transfer()
call only provides 2300 gas for the contract to complete its operations. This means the following cases can cause the transfer to fail:
payable
callbackpayable
callback spends more than 2300 gas (which is only enough to emit something)Address.sendValue()
insteadFile: GeVault.sol 232: payable(msg.sender).transfer(bal);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L232
Using low-level call.value(amount)
with the corresponding result check or using the OpenZeppelin Address.sendValue
is advised:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L60
address
shouldn't be hard-codedIt is often better to declare address
es as immutable
, and assign them via constructor arguments. This allows the code to remain the same across deployments on different networks, and avoids recompilation when addresses need to change.
File: TokenisableRange.sol 54: address public TREASURY_DEPRECATED = 0x22Cc3f665ba4C898226353B672c5123c58751692; 60: address constant public treasury = 0x22Cc3f665ba4C898226353B672c5123c58751692;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L54
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L60
</details>Interfaces should be declared on a separate file and not on the same file where the contract resides in.
File: LPOracle.sol 6: interface UniswapV2Pair {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/LPOracle.sol#L6
File: LPOracle.sol 13: interface IERC20 {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/LPOracle.sol#L13
File: V3Proxy.sol 9: interface ISwapRouter {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L9
File: V3Proxy.sol 38: interface IQuoter {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L38
File: V3Proxy.sol 56: interface IWETH9 is IERC20 {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L56
File: OptionsPositionManager.sol 7: interface AmountsRouter {
require()
/revert()
Checks Should Be Refactored To A Modifier Or FunctionSaves deployment costs
File: GeVault.sol 120: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 141: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 170: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 203: require(poolMatchesOracle(), "GEV: Oracle Error"); 215: require(poolMatchesOracle(), "GEV: Oracle Error"); 250: require(poolMatchesOracle(), "GEV: Oracle Error");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L120
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L141
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L170
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L203
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L215
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L250
File: V3Proxy.sol 87: require(acceptPayable, "CannotReceiveETH"); 91: require(acceptPayable, "CannotReceiveETH"); 99: require(path.length == 2, "Direct swap only"); 106: require(path.length == 2, "Direct swap only"); 113: require(path.length == 2, "Direct swap only"); 125: require(path.length == 2, "Direct swap only"); 138: require(path.length == 2, "Direct swap only"); 148: require(path.length == 2, "Direct swap only"); 161: require(path.length == 2, "Direct swap only"); 179: require(path.length == 2, "Direct swap only"); 139: require(path[0] == ROUTER.WETH9(), "Invalid path"); 149: require(path[0] == ROUTER.WETH9(), "Invalid path"); 162: require(path[1] == ROUTER.WETH9(), "Invalid path"); 180: require(path[1] == ROUTER.WETH9(), "Invalid path");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L87
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L91
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L99
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L106
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L113
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L125
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L138
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L148
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L161
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L179
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L139
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L149
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L162
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L180
File: OptionsPositionManager.sol 69: require( address(lendingPool) == msg.sender, "OPM: Call Unallowed"); 91: require( address(lendingPool) == msg.sender, "OPM: Call Unallowed"); 393: require( LP.getReserveData(optionAddress).aTokenAddress != address(0x0), "OPM: Invalid Address" ); 420: require( LP.getReserveData(optionAddress).aTokenAddress != address(0x0), "OPM: Invalid Address" );
There are several occasions where there several imports of the same file
File: RangeManager.sol 5: import "./openzeppelin-solidity/contracts/token/ERC20/utils/SafeERC20.sol"; 7: import "./openzeppelin-solidity/contracts/token/ERC20/utils/SafeERC20.sol";
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L5
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L7
File: TokenisableRange.sol 13: import "../interfaces/IAaveOracle.sol"; 14: import "../interfaces/IAaveOracle.sol";
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L13-L14
These functions might be a problem if the logic changes before the contract is deployed, as the developer must remember to syncronize the logic between all the function instances.
Consider using a single function instead of duplicating the code, for example by using a library
, or through inheritance.
File: LPOracle.sol 38: function decimals() external pure returns (uint8) { return 8; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/LPOracle.sol#L38
File: OracleConvert.sol 30: function decimals() external pure returns (uint8) { return 8; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/OracleConvert.sol#L30
File: OracleConvert.sol 37: function getAnswer(AggregatorV3Interface priceFeed) internal view returns (int256) { ( , int price, , uint timeStamp, ) = priceFeed.latestRoundData(); require(timeStamp > 0, "Round not complete"); return price; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/OracleConvert.sol#L37
File: LPOracle.sol 56: function getAnswer(AggregatorV3Interface priceFeed) internal view returns (int256) { ( , int price, , uint timeStamp, ) = priceFeed.latestRoundData(); require(timeStamp > 0, "Round not complete"); return price; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/LPOracle.sol#L56
Throughout the codebase, events are generally emitted when sensitive changes are made to the contracts. However, some events are missing important parameters The following events should also add the previous original value in addition to the new value.
File: RoeRouter.sol 14: event UpdateTreasury(address treasury);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RoeRouter.sol#L14
The events should include the new value and old value where possible.
override
function arguments that are unused should have the variable name removed or commented out to avoid compiler warningsFile: OptionsPositionManager.sol 38: function executeOperation : uint256[] calldata premiums 38: function executeOperation : address initiator
Consider using descriptive constants or an enum instead of passing zero directly on function calls, as that might be error-prone, to fully describe the caller's intention.
File: GeVault.sol 357: depositAndStash(ticks[tick1Index], 0, availToken1 / 2); 358: depositAndStash(ticks[tick1Index+1], 0, availToken1 / 2);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L357-L358
File: RangeManager.sol 119: tokenisedRanges[step].withdraw(trAmt, 0, 0); 131: tokenisedTicker[step].withdraw(ttAmt, 0, 0);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L119
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L131
File: TokenisableRange.sol 198: amount0Min: 0, 199: amount1Min: 0,
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L198-L199
</details>File: OptionsPositionManager.sol 135: (uint256 amount0, uint256 amount1) = TokenisableRange(flashAsset).withdraw(flashAmount, 0, 0);
#0 - 141345
2023-08-10T10:10:41Z
#1 - c4-judge
2023-08-20T16:37:51Z
gzeon-c4 marked the issue as grade-a
🌟 Selected for report: JCK
Also found by: 0xAnah, 0xhex, 0xta, DavidGiladi, K42, Rageur, Raihan, ReyAdmirado, Rolezn, SAQ, SY_S, Sathish9098, dharma09, hunter_w3b, matrix_0wl, naman1778, petrichor, wahedtalash77
17.345 USDC - $17.34
Issue | Contexts | Estimated Gas Saved | |
---|---|---|---|
GAS‑1 | abi.encode() is less efficient than abi.encodepacked() | 2 | 106 |
GAS‑2 | <array>.length Should Not Be Looked Up In Every Loop Of A For-loop | 3 | 291 |
GAS‑3 | Use assembly in place of abi.decode to extract calldata values more efficiently | 2 | 224 |
GAS‑4 | Use assembly to emit events | 35 | 1330 |
GAS‑5 | Avoid emitting event on every iteration | 1 | 375 |
GAS‑6 | Counting down in for statements is more gas efficient | 9 | 2313 |
GAS‑7 | Duplicated require() /revert() Checks Should Be Refactored To A Modifier Or Function | 24 | 672 |
GAS‑8 | Multiple accesses of a mapping/array should use a local variable cache | 21 | 1680 |
GAS‑9 | The result of a function call should be cached rather than re-calling the function | 20 | 1000 |
GAS‑10 | Splitting require() Statements That Use && Saves Gas | 10 | 90 |
GAS‑11 | Structs can be packed into fewer storage slots | 1 | 2000 |
GAS‑12 | Use do while loops instead of for loops | 9 | 36 |
GAS‑13 | Use nested if and avoid multiple check combinations | 8 | 48 |
GAS‑14 | Using XOR (^) and AND (&) bitwise equivalents | 48 | 624 |
Total: 227 contexts over 14 issues
abi.encode()
is less efficient than abi.encodepacked()
See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison
File: OptionsPositionManager.sol 168: bytes memory params = abi.encode(0, poolId, msg.sender, sourceSwap);
File: OptionsPositionManager.sol 199: bytes memory params = abi.encode(1, poolId, user, collateralAsset);
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(); c1.optimized(); } } contract Contract0 { string a = "Code4rena"; function not_optimized() public returns(bytes32){ return keccak256(abi.encode(a)); } } contract Contract1 { string a = "Code4rena"; function optimized() public returns(bytes32){ return keccak256(abi.encodePacked(a)); } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
101871 | 683 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 2661 | 2661 | 2661 | 2661 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
99465 | 671 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 2608 | 2608 | 2608 | 2608 | 1 |
<array>.length
Should Not Be Looked Up In Every Loop Of A For-loopThe overheads outlined below are PER LOOP, excluding the first loop
storage arrays incur a Gwarmaccess (100 gas) memory arrays use MLOAD (3 gas) calldata arrays use CALLDATALOAD (3 gas)
Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset
These instances include instances that haven't been detected in the automated findings.
File: GeVault.sol 314: for (uint k = 0; k < ticks.length; k++){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L314
File: GeVault.sol 393: for(uint k=0; k<ticks.length; k++){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L393
</details>File: OptionsPositionManager.sol 203: for (uint8 i = 0; i< options.length; ){
abi.decode
to extract calldata values more efficientlyInstead of using abi.decode
, we can use assembly to decode our desired calldata values directly. This will allow us to avoid decoding calldata values that we will not use.
For example, for a generic abi.decode
call:
(bytes32 varA, bytes32 varB, uint256 varC, uint256 varD) = abi.decode(metadata, (bytes32, bytes32, uint256, uint256));
We can use the following assembly call to extract the relevant metadata:
bytes32 varA; bytes32 varB; uint256 varC; uint256 varD; { // used to discard `data` variable and avoid extra stack manipulation bytes calldata data = metadata; assembly { varA := calldataload(add(data.offset, 0x00)) varB := calldataload(add(data.offset, 0x20)) varC := calldataload(add(data.offset, 0x40)) varD := calldataload(add(data.offset, 0x60)) } }
File: OptionsPositionManager.sol 48: (, uint poolId, address user, address[] memory sourceSwap) = abi.decode(params, (uint8, uint, address, address[])); 53: (, uint poolId, address user, address collateral) = abi.decode(params, (uint8, uint, address, address));
We can use assembly to emit events efficiently by utilizing scratch space
and the free memory pointer
. This will allow us to potentially avoid memory expansion costs.
Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.
For example, for a generic emit
event for eventSentAmountExample
:
// uint256 id, uint256 value, uint256 amount emit eventSentAmountExample(id, value, amount);
We can use the following assembly emit events:
assembly { let memptr := mload(0x40) mstore(0x00, calldataload(0x44)) mstore(0x20, calldataload(0xa4)) mstore(0x40, amount) log1( 0x00, 0x60, // keccak256("eventSentAmountExample(uint256,uint256,uint256)") 0xa622cf392588fbf2cd020ff96b2f4ebd9c76d7a4bc7f3e6b2f18012312e76bc3 ) mstore(0x40, memptr) }
File: GeVault.sol 103: emit SetEnabled(_isEnabled);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L103
File: GeVault.sol 110: emit SetTreasury(newTreasury);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L110
File: GeVault.sol 131: emit PushTick(tr);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L131
File: GeVault.sol 160: emit ShiftTick(tr);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L160
File: GeVault.sol 172: emit ModifyTick(tr, index);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L172
File: GeVault.sol 186: emit SetFee(newBaseFeeX4);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L186
File: GeVault.sol 193: emit SetTvlCap(newTvlCap);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L193
File: GeVault.sol 240: emit Withdraw(msg.sender, token, amount, liquidity);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L240
File: GeVault.sol 283: emit Deposit(msg.sender, token, amount, liquidity);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L283
File: GeVault.sol 362: emit Rebalance(tickIndex);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L362
File: RangeManager.sol 87: emit AddRange(startX10, endX10, tokenisedRanges.length - 1);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L87
File: RangeManager.sol 120: emit Withdraw(msg.sender, address(tokenisedRanges[step]), trAmt); 132: emit Withdraw(msg.sender, address(tokenisedTicker[step]), trAmt);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L120
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L132
File: RangeManager.sol 164: emit Deposit(msg.sender, address(tr), lpAmt);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L164
File: RoeRouter.sol 50: emit DeprecatePool(poolId);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RoeRouter.sol#L50
File: RoeRouter.sol 78: emit AddPool(poolId, lendingPoolAddressProvider);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RoeRouter.sol#L78
File: RoeRouter.sol 86: emit UpdateTreasury(newTreasury);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RoeRouter.sol#L86
File: TokenisableRange.sol 119: emit InitTR(address(asset0), address(asset1), startX10, endX10);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L119
File: TokenisableRange.sol 162: emit Deposit(msg.sender, 1e18);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L162
File: TokenisableRange.sol 214: emit ClaimFees(newFee0, newFee1);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L214
File: TokenisableRange.sol 284: emit Deposit(msg.sender, lpAmt);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L284
File: TokenisableRange.sol 326: emit Withdraw(msg.sender, lp);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L326
File: FixedOracle.sol 26: emit SetHardcodedPrice(_hardcodedPrice);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/FixedOracle.sol#L26
File: V3Proxy.sol 121: emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L121
File: V3Proxy.sol 134: emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L134
File: V3Proxy.sol 143: emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L143
File: V3Proxy.sol 157: emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L157
File: V3Proxy.sol 175: emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L175
File: V3Proxy.sol 193: emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L193
File: OptionsPositionManager.sol 106: emit LiquidatePosition(user, debtAsset, debt, amt0 - amts[0], amt1 - amts[1]);
File: OptionsPositionManager.sol 148: emit BuyOptions(user, flashAsset, flashAmount, amount0, amount1);
File: OptionsPositionManager.sol 240: emit ReducedPosition(user, debtAsset, debt);
File: OptionsPositionManager.sol 323: emit ClosePosition(user, debtAsset, debt, amt0, amt1);
File: OptionsPositionManager.sol 401: emit SellOptions(msg.sender, optionAddress, deposited, amount0, amount1 );
</details>File: OptionsPositionManager.sol 455: emit Swap(msg.sender, path[0], amount, path[1], received);
Expensive operations should always try to be avoided within loops. Such operations include: reading/writing to storage, heavy calculations, external calls, and emitting events. In this instance, an event is being emitted every iteration. Events have a base cost of Glog (375 gas)
per emit and Glogdata (8 gas) * number of bytes in event
. We can avoid incurring those costs each iteration by emitting the event outside of the loop.
File: OptionsPositionManager.sol 93: for ( uint8 k =0; k<assets.length; k++){ address debtAsset = assets[k]; uint amount = amounts[k]; checkSetAllowance(debtAsset, address(lendingPool), amount); lendingPool.liquidationCall(collateral, debtAsset, user, amount, false); uint debt = closeDebt(poolId, address(this), debtAsset, amount, collateral); uint amt0 = ERC20(token0).balanceOf(address(this)); uint amt1 = ERC20(token1).balanceOf(address(this)); emit LiquidatePosition(user, debtAsset, debt, amt0 - amts[0], amt1 - amts[1]); amts[0] = amt0; amts[1] = amt1; }
for
statements is more gas efficientCounting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.
File: GeVault.sol 299: for (uint k = 0; k < ticks.length; k++){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L299
File: GeVault.sol 314: for (uint k = 0; k < ticks.length; k++){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L314
File: GeVault.sol 393: for(uint k=0; k<ticks.length; k++){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L393
File: GeVault.sol 431: for (activeTickIndex = 0; activeTickIndex < ticks.length - 3; activeTickIndex++){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L431
File: RangeManager.sol 62: for (uint i = 0; i < len; i++) {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L62
File: OptionsPositionManager.sol 71: for ( uint8 k = 0; k<assets.length; k++){
File: OptionsPositionManager.sol 93: for ( uint8 k =0; k<assets.length; k++){
File: OptionsPositionManager.sol 172: for (uint8 i = 0; i< options.length; ){
</details>File: OptionsPositionManager.sol 203: for (uint8 i = 0; i< options.length; ){
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.AddNum(); c1.AddNum(); } } contract Contract0 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=0;i<=9;i++){ _num = _num +1; } num = _num; } } contract Contract1 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=9;i>=0;i--){ _num = _num +1; } num = _num; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
77011 | 311 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 7040 | 7040 | 7040 | 7040 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
73811 | 295 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 3819 | 3819 | 3819 | 3819 | 1 |
require()
/revert()
Checks Should Be Refactored To A Modifier Or FunctionSaves deployment costs
File: GeVault.sol 120: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 141: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 170: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 203: require(poolMatchesOracle(), "GEV: Oracle Error"); 215: require(poolMatchesOracle(), "GEV: Oracle Error"); 250: require(poolMatchesOracle(), "GEV: Oracle Error");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L120
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L141
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L170
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L203
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L215
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L250
File: V3Proxy.sol 87: require(acceptPayable, "CannotReceiveETH"); 91: require(acceptPayable, "CannotReceiveETH"); 99: require(path.length == 2, "Direct swap only"); 106: require(path.length == 2, "Direct swap only"); 113: require(path.length == 2, "Direct swap only"); 125: require(path.length == 2, "Direct swap only"); 138: require(path.length == 2, "Direct swap only"); 148: require(path.length == 2, "Direct swap only"); 161: require(path.length == 2, "Direct swap only"); 179: require(path.length == 2, "Direct swap only"); 139: require(path[0] == ROUTER.WETH9(), "Invalid path"); 149: require(path[0] == ROUTER.WETH9(), "Invalid path"); 162: require(path[1] == ROUTER.WETH9(), "Invalid path"); 180: require(path[1] == ROUTER.WETH9(), "Invalid path");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L87
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L91
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L99
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L106
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L113
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L125
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L138
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L148
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L161
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L179
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L139
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L149
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L162
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L180
File: OptionsPositionManager.sol 69: require( address(lendingPool) == msg.sender, "OPM: Call Unallowed"); 91: require( address(lendingPool) == msg.sender, "OPM: Call Unallowed"); 393: require( LP.getReserveData(optionAddress).aTokenAddress != address(0x0), "OPM: Invalid Address" ); 420: require( LP.getReserveData(optionAddress).aTokenAddress != address(0x0), "OPM: Invalid Address" );
Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times saves ~42 gas per access due to not having to perform the same offset calculation every time. Help the Optimizer by saving a storage variable's reference instead of repeatedly fetching it
To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array. As an example, instead of repeatedly calling someMap[someIndex]
, save its reference like this: SomeStruct storage someStruct = someMap[someIndex]
and use it.
File: GeVault.sol 125: require( t.lowerTick() > ticks[ticks.length-1].upperTick(), "GEV: Push Tick Overlap"); 127: require( t.upperTick() < ticks[ticks.length-1].lowerTick(), "GEV: Push Tick Overlap");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L125
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L127
File: GeVault.sol 146: require( t.lowerTick() > ticks[0].upperTick(), "GEV: Shift Tick Overlap"); 148: require( t.upperTick() < ticks[0].lowerTick(), "GEV: Shift Tick Overlap"); 158: ticks[0] = t;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L146
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L148
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L158
File: RangeManager.sol 111: trAmt = ERC20(LENDING_POOL.getReserveData(address(tokenisedRanges[step])).aTokenAddress).balanceOf(msg.sender); 114: LENDING_POOL.getReserveData(address(tokenisedRanges[step])).aTokenAddress, 118: trAmt = LENDING_POOL.withdraw(address(tokenisedRanges[step]), type(uint256).max, address(this)); 119: tokenisedRanges[step].withdraw(trAmt, 0, 0); 120: emit Withdraw(msg.sender, address(tokenisedRanges[step]), trAmt); 123: trAmt = ERC20(LENDING_POOL.getReserveData(address(tokenisedTicker[step])).aTokenAddress).balanceOf(msg.sender); 126: LENDING_POOL.getReserveData(address(tokenisedTicker[step])).aTokenAddress, 130: uint256 ttAmt = LENDING_POOL.withdraw(address(tokenisedTicker[step]), type(uint256).max, address(this)); 131: tokenisedTicker[step].withdraw(ttAmt, 0, 0); 132: emit Withdraw(msg.sender, address(tokenisedTicker[step]), trAmt);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L111
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L114
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L118
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L119
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L120
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L123
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L126
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L130
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L131
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L132
File: V3Proxy.sol 190: weth.withdraw(amounts[1]); 192: payable(msg.sender).call{value: amounts[1]}("");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L190
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L192
</details>File: OptionsPositionManager.sol 296: path[0] = token1; 297: path[1] = token0; 301: path[0] = token0; 302: path[1] = token1;
External calls are expensive. Results of external function calls should be cached rather than call them multiple times. Consider caching the following:
File: GeVault.sol 269: require(tvlCap > valueX8 + getTVL(), "GEV: Max Cap Reached"); 220: uint vaultValueX8 = getTVL(); 271: uint vaultValueX8 = getTVL();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L269-L271
File: TokenisableRange.sol 225: require(totalSupply() > 0, "TR Closed"); 278: lpAmt = totalSupply() * newLiquidity / (liquidity + feeLiquidity);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L225
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L278
File: TokenisableRange.sol 294: uint removedLiquidity = uint(liquidity) * lp / totalSupply(); 316: TOKEN0.token.safeTransfer( msg.sender, fee0 * lp / totalSupply()); 317: removed0 += fee0 * lp / totalSupply(); 318: fee0 -= fee0 * lp / totalSupply(); 321: TOKEN1.token.safeTransfer( msg.sender, fee1 * lp / totalSupply()); 322: removed1 += fee1 * lp / totalSupply(); 323: fee1 -= fee1 * lp / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L294
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L316
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L317
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L318
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L321
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L322
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L323
File: TokenisableRange.sol 353: if ( totalSupply() == 0 ) return 0; 356: return totalValue * 1e18 / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L353
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L356
File: TokenisableRange.sol 379: token0Amount += fee0 * amount / totalSupply(); 380: token1Amount += fee1 * amount / totalSupply();
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L379-L380
File: V3Proxy.sol 162: require(path[1] == ROUTER.WETH9(), "Invalid path"); 180: require(path[1] == ROUTER.WETH9(), "Invalid path"); 170: IWETH9 weth = IWETH9(ROUTER.WETH9()); 188: IWETH9 weth = IWETH9(ROUTER.WETH9());
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L162
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L180
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L170
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L188
</details>require()
statements that use &&
saves gasInstead of using operator &&
on a single require
. Using a two require
can save more gas.
i.e.
for require(version == 1 && _bytecodeHash[1] == bytes1(0), "zf");
use:
require(version == 1); require(_bytecodeHash[1] == bytes1(0));
File: GeVault.sol 120: require(t0 == token0 && t1 == token1, "GEV: Invalid TR");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L120
File: GeVault.sol 141: require(t0 == token0 && t1 == token1, "GEV: Invalid TR");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L141
File: GeVault.sol 170: require(t0 == token0 && t1 == token1, "GEV: Invalid TR");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L170
File: RangeManager.sol 108: require(step < tokenisedRanges.length && step < tokenisedTicker.length, "Invalid step");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L108
File: TokenisableRange.sol 209: require(addedValue > liquidityValue * 95 / 100 && liquidityValue > addedValue * 95 / 100, "TR: Claim Fee Slippage");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L209
File: LPOracle.sol 29: require(lpToken != address(0x0) && clToken0 != address(0x0) && clToken1 != address(0x0), "Invalid address");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/LPOracle.sol#L29
File: LPOracle.sol 101: require(decimalsA <= 18 && decimalsB <= 18, "Incorrect tokens");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/LPOracle.sol#L101
File: OptionsPositionManager.sol 167: require(options.length == amounts.length && sourceSwap.length == options.length, "OPM: Array Length Mismatch");
File: OptionsPositionManager.sol 495: require( amountsIn[0] <= maxAmount && amountsIn[0] > 0, "OPM: Invalid Swap Amounts" );
</details>File: OptionsPositionManager.sol 536: require(token0 == address(t0) && token1 == address(t1), "OPM: Invalid Debt Asset");
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings
File: V3Proxy.sol 22: struct ExactOutputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; --- uint256 deadline; +++ uint64 deadline; // @audit can be packed to lower uint64 as it is unlikely for deadline to ever reach uint64.max uint256 amountOut; uint256 amountInMaximum; uint160 sqrtPriceLimitX96; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L22
A do while loop will cost less gas since the condition is not being checked for the first iteration.
File: GeVault.sol 298: function getReserves for (uint k = 0; k < ticks.length; k++){ TokenisableRange t = ticks[k]; address aTick = lendingPool.getReserveData(address(t)).aTokenAddress; uint bal = ERC20(aTick).balanceOf(address(this)); (uint amt0, uint amt1) = t.getTokenAmounts(bal); amount0 += amt0; amount1 += amt1; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L298
File: GeVault.sol 313: function removeFromAllTicks for (uint k = 0; k < ticks.length; k++){ removeFromTick(k); }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L313
File: GeVault.sol 392: function getTVL for(uint k=0; k<ticks.length; k++){ TokenisableRange t = ticks[k]; uint bal = getTickBalance(k); valueX8 += bal * t.latestAnswer() / 1e18; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L392
File: GeVault.sol 428: function getActiveTickIndex for (activeTickIndex = 0; activeTickIndex < ticks.length - 3; activeTickIndex++){ (uint amt0, uint amt1) = ticks[activeTickIndex+1].getTokenAmountsExcludingFees(1e18); (uint amt0n, uint amt1n) = ticks[activeTickIndex+2].getTokenAmountsExcludingFees(1e18); if ( (amt0 == 0 && amt0n > 0) || (amt1 == 0 && amt1n > 0) ) break; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L428
File: RangeManager.sol 59: function checkNewRange for (uint i = 0; i < len; i++) { if (start >= stepList[i].end || end <= stepList[i].start) { continue; }
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L59
File: OptionsPositionManager.sol 61: function executeBuyOptions for ( uint8 k = 0; k<assets.length; k++){ address asset = assets[k]; uint amount = amounts[k]; withdrawOptionAssets(poolId, asset, amount, sourceSwap[k], user); }
File: OptionsPositionManager.sol 83: function executeLiquidation for ( uint8 k =0; k<assets.length; k++){ address debtAsset = assets[k]; uint amount = amounts[k]; checkSetAllowance(debtAsset, address(lendingPool), amount); lendingPool.liquidationCall(collateral, debtAsset, user, amount, false); uint debt = closeDebt(poolId, address(this), debtAsset, amount, collateral); uint amt0 = ERC20(token0).balanceOf(address(this)); uint amt1 = ERC20(token1).balanceOf(address(this)); emit LiquidatePosition(user, debtAsset, debt, amt0 - amts[0], amt1 - amts[1]); amts[0] = amt0; amts[1] = amt1; }
File: OptionsPositionManager.sol 159: function buyOptions for (uint8 i = 0; i< options.length; ){ flashtype[i] = 2; unchecked { i+=1; }
</details>File: OptionsPositionManager.sol 189: function liquidate for (uint8 i = 0; i< options.length; ){ flashtype[i] = 0; unchecked { i+=1; }
if
and avoid multiple check combinationsUsing nested if
, is cheaper than using &&
multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.
File: GeVault.sol 377: if (oraclePrice < priceX8 * 101 / 100 && oraclePrice > priceX8 * 99 / 100) matches = true;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L377
File: GeVault.sol 434: if ( (amt0 == 0 && amt0n > 0) || (amt1 == 0 && amt1n > 0) )
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L434
File: TokenisableRange.sol 177: if ((newFee0 == 0) && (newFee1 == 0)) return; 190: if ((fee0 * 100 > bal0 ) && (fee1 * 100 > bal1)) {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L177
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L190
File: TokenisableRange.sol 237: if ( fee0+fee1 > 0 && ( n0 > 0 || fee0 == 0) && ( n1 > 0 || fee1 == 0 ) ){ 268: if ( newFee0 == 0 && newFee1 == 0 ){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L237
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L268
File: OptionsPositionManager.sol 234: if ( repayAmount > 0 && repayAmount < debt ) debt = repayAmount;
</details>File: OptionsPositionManager.sol 473: if (amount > 0 && AmountsRouter(address(ammRouter)).getAmountsOut(amount, path)[1] > 0){
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.checkAge(19); c1.checkAgeOptimized(19); } } contract Contract0 { function checkAge(uint8 _age) public returns(string memory){ if(_age>18 && _age<22){ return "Eligible"; } } } contract Contract1 { function checkAgeOptimized(uint8 _age) public returns(string memory){ if(_age>18){ if(_age<22){ return "Eligible"; } } } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
76923 | 416 | ||||
Function Name | min | avg | median | max | # calls |
checkAge | 651 | 651 | 651 | 651 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
76323 | 413 | ||||
Function Name | min | avg | median | max | # calls |
checkAgeOptimized | 645 | 645 | 645 | 645 | 1 |
Given 4 variables a, b, c and d represented as such:
0 0 0 0 0 1 1 0 <- a 0 1 1 0 0 1 1 0 <- b 0 0 0 0 0 0 0 0 <- c 1 1 1 1 1 1 1 1 <- d
To have a == b means that every 0 and 1 match on both variables. Meaning that a XOR (operator ^) would evaluate to 0 ((a ^ b) == 0), as it excludes by definition any equalities.Now, if a != b, this means that there’s at least somewhere a 1 and a 0 not matching between a and b, making (a ^ b) != 0.Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.These are logically equivalent too, as the OR bitwise operator (|) would result in a 1 somewhere if any value is not 0 between the XOR (^) statements, meaning if any XOR (^) statement verifies that its arguments are different.
File: GeVault.sol 120: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 121: if (ticks.length == 0) ticks.push(t);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L120-L121
File: GeVault.sol 141: require(t0 == token0 && t1 == token1, "GEV: Invalid TR"); 142: if (ticks.length == 0) ticks.push(t);
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L141-L142
File: GeVault.sol 170: require(t0 == token0 && t1 == token1, "GEV: Invalid TR");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L170
File: GeVault.sol 216: if (liquidity == 0) liquidity = balanceOf(msg.sender); 223: uint fee = amount * getAdjustedBaseFee(token == address(token1)) / 1e4; 230: if (token == address(WETH)){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L216
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L223
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L230
File: GeVault.sol 251: require(token == address(token0) || token == address(token1), "GEV: Invalid Token"); 252: require(amount > 0 || msg.value > 0, "GEV: Deposit Zero"); 256: require(token == address(WETH), "GEV: Invalid Weth"); 266: uint fee = amount * getAdjustedBaseFee(token == address(token0)) / 1e4; 274: if (tSupply == 0 || vaultValueX8 == 0)
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L251
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L252
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L256
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L266
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L274
File: GeVault.sol 291: if (supply == 0) return 0;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L291
File: GeVault.sol 434: if ( (amt0 == 0 && amt0n > 0) || (amt1 == 0 && amt1n > 0) )
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/GeVault.sol#L434
File: RangeManager.sol 63: if (start >= stepList[i].end || end <= stepList[i].start) {
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/RangeManager.sol#L63
File: TokenisableRange.sol 87: require(status == ProxyState.INIT_PROXY, "!InitProxy");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L87
File: TokenisableRange.sol 135: require(status == ProxyState.INIT_LP, "!InitLP"); 136: require(msg.sender == creator, "Unallowed call");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L135-L136
File: TokenisableRange.sol 177: if ((newFee0 == 0) && (newFee1 == 0)) return;
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L177
File: TokenisableRange.sol 237: if ( fee0+fee1 > 0 && ( n0 > 0 || fee0 == 0) && ( n1 > 0 || fee1 == 0 ) ){ 268: if ( newFee0 == 0 && newFee1 == 0 ){
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L237
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L268
File: TokenisableRange.sol 335: if (TOKEN0_PRICE == 0) TOKEN0_PRICE = ORACLE.getAssetPrice(address(TOKEN0.token)); 336: if (TOKEN1_PRICE == 0) TOKEN1_PRICE = ORACLE.getAssetPrice(address(TOKEN1.token));
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/TokenisableRange.sol#L335-L336
File: V3Proxy.sol 99: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L99
File: V3Proxy.sol 106: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L106
File: V3Proxy.sol 113: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L113
File: V3Proxy.sol 125: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L125
File: V3Proxy.sol 138: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L138
File: V3Proxy.sol 148: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L148
File: V3Proxy.sol 161: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L161
File: V3Proxy.sol 179: require(path.length == 2, "Direct swap only");
https://github.com/code-423n4/2023-08-goodentry/tree/main/contracts/helper/V3Proxy.sol#L179
File: OptionsPositionManager.sol 47: if ( mode == 0 ){
File: OptionsPositionManager.sol 137: require(sourceSwap == token0 || sourceSwap == token1, "OPM: Invalid Swap Token"); 140: path[1] = sourceSwap == token0 ? token1 : token0; 141: uint amount = sourceSwap == token0 ? amount0 : amount1; 145: amount0 = sourceSwap == token0 ? 0 : amount0 + received; 146: amount1 = sourceSwap == token1 ? 0 : amount1 + received;
File: OptionsPositionManager.sol 167: require(options.length == amounts.length && sourceSwap.length == options.length, "OPM: Array Length Mismatch");
File: OptionsPositionManager.sol 198: require(options.length == amounts.length, "ARRAY_LEN_MISMATCH");
File: OptionsPositionManager.sol 261: require(collateralAsset == token0 || collateralAsset == token1, "OPM: Invalid Collateral Asset"); 283: if (collateralAsset == token0) amtA -= feeAmount; 327: if (user == msg.sender) swapTokens(poolId, collateralAsset == token0 ? token1 : token0, 0);
File: OptionsPositionManager.sol 441: require(sourceAsset == token0 || sourceAsset == token1, "OPM: Invalid Swap Asset"); 442: if (amount == 0) { 444: if (amount == 0) return 0; 450: path[1] = sourceAsset == token0 ? token1 : token0;
</details>File: OptionsPositionManager.sol 536: require(token0 == address(t0) && token1 == address(t1), "OPM: Invalid Debt Asset");
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(1,2); c1.optimized(1,2); } } contract Contract0 { function not_optimized(uint8 a,uint8 b) public returns(bool){ return ((a==1) || (b==1)); } } contract Contract1 { function optimized(uint8 a,uint8 b) public returns(bool){ return ((a ^ 1) & (b ^ 1)) == 0; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
46099 | 261 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 456 | 456 | 456 | 456 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
42493 | 243 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 430 | 430 | 430 | 430 | 1 |
#0 - c4-judge
2023-08-20T17:03:37Z
gzeon-c4 marked the issue as grade-b