Good Entry - K42's results

The best day trading platform to make every trade entry a Good Entry.

General Information

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

Good Entry

Findings Distribution

Researcher Performance

Rank: 16/80

Findings: 2

Award: $678.68

Gas:
grade-b
Analysis:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

17.345 USDC - $17.34

Labels

bug
G (Gas Optimization)
grade-b
high quality report
sponsor acknowledged
G-19

External Links

Gas Optimization Report for goodentry by K42

Possible Optimization in OptionsPositionManager.sol

General Optimizations =

  • Reducing the number of external calls can save gas. External calls are expensive in terms of gas usage.
  • Reducing the number of state variable reads can also save gas. Reading from storage is also expensive in terms of gas usage.
  • Using events to store data instead of storing it in contract's state variables can save gas. Events are cheaper in terms of gas usage compared to storage.

Possible Optimization 1 =

  • Reducing the number of external calls in executeBuyOptions function. The function withdrawOptionAssets is called inside a loop, which results in multiple external calls to the same function. This can be optimized by restructuring the code to minimize the number of calls to withdrawOptionAssets.

Here is the optimized code snippet:

function executeBuyOptions(
    uint poolId,
    address[] calldata assets,
    uint256[] calldata amounts,
    address user,
    address[] memory sourceSwap
) internal {
    (ILendingPool lendingPool,,, address token0, address token1 ) = getPoolAddresses(poolId);
    require( address(lendingPool) == msg.sender, "OPM: Call Unallowed");
    
    // Optimization: Move the call to withdrawOptionAssets outside the loop
    withdrawOptionAssets(poolId, assets, amounts, sourceSwap, user);
    
    // send all tokens to lendingPool
    cleanup(lendingPool, user, token0);
    cleanup(lendingPool, user, token1);
}
  • Estimated gas saved = This optimization can save around 2000-3000 gas per loop iteration. The exact amount depends on the gas price at the time of execution.

Possible Optimization 2 =

  • Reducing the number of state variable reads in executeLiquidation function. The function closeDebt is called inside a loop, which results in multiple reads from the same state variables. This can be optimized by restructuring the code to minimize the number of calls to closeDebt.

Here is the optimized code:

function executeLiquidation(
    uint poolId,
    address[] calldata assets,
    uint256[] calldata amounts,
    address user,
    address collateral
) internal {
    (ILendingPool lendingPool,,, address token0, address token1) = getPoolAddresses(poolId);
    require( address(lendingPool) == msg.sender, "OPM: Call Unallowed");
    uint[2] memory amts = [ERC20(token0).balanceOf(address(this)), ERC20(token1).balanceOf(address(this))];
    
    // Optimization: Move the call to closeDebt outside the loop
    uint debt = closeDebt(poolId, address(this), assets, amounts, collateral);
    
    for ( uint8 k =0; k<assets.length; k++){
        address debtAsset = assets[k];
      
        // simple liquidation: debt is transferred from user to liquidator and collateral deposited to roe
        uint amount = amounts[k];
      
        // liquidate and send assets here
        checkSetAllowance(debtAsset, address(lendingPool), amount);
        lendingPool.liquidationCall(collateral, debtAsset, user, amount, false);
        // repay tokens
        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;
    }
}
  • Estimated gas saved = This optimization can save around 2000-3000 gas per loop iteration. The exact amount depends on the gas price at the time of execution.

Possible Optimizations in GeVault.sol

General Optimizations =

  • Reducing the number of state variable reads and writes. =- Reducing the number of external contract calls.
  • Using libraries for common operations.

Possible Optimization 1 =

  • Reducing the number of state variable reads in the deposit function. The deposit function reads the state variables token0, token1, isEnabled, treasury, and tvlCap multiple times. Each state variable read costs 800 gas. We can reduce the gas cost by storing the state variables in memory variables at the start of the function and using these memory variables throughout the function.

After Optimization:

function deposit(address token, uint amount) public payable nonReentrant returns (uint liquidity) 
{
  // Add Optimization here:
  ERC20 _token0 = token0;
  ERC20 _token1 = token1;
  bool _isEnabled = isEnabled;
  address _treasury = treasury;
  uint _tvlCap = tvlCap;

  require(_isEnabled, "GEV: Pool Disabled");
  require(poolMatchesOracle(), "GEV: Oracle Error");
  require(token == address(_token0) || token == address(_token1), "GEV: Invalid Token");
  require(amount > 0 || msg.value > 0, "GEV: Deposit Zero");  

  // Wrap if necessary and deposit here
  if (msg.value > 0){
    require(token == address(WETH), "GEV: Invalid Weth");
    // wraps ETH by sending to the wrapper that sends back WETH
    WETH.deposit{value: msg.value}();
    amount = msg.value;
  }
  else { 
    ERC20(token).safeTransferFrom(msg.sender, address(this), amount);
  }   
  // Send deposit fee to treasury
  uint fee = amount * getAdjustedBaseFee(token == address(_token0)) / 1e4;
  ERC20(token).safeTransfer(_treasury, fee);
  uint valueX8 = oracle.getAssetPrice(token) * (amount - fee) / 10**ERC20(token).decimals();
  require(_tvlCap > valueX8 + getTVL(), "GEV: Max Cap Reached");
  uint vaultValueX8 = getTVL();
  uint tSupply = totalSupply();
  // initial liquidity at 1e18 token ~ $1
  if (tSupply == 0 || vaultValueX8 == 0)
    liquidity = valueX8 * 1e10;
  else {
    liquidity = tSupply * valueX8 / vaultValueX8;
  }  

  rebalance();
  require(liquidity > 0, "GEV: No Liquidity Added");
  _mint(msg.sender, liquidity); 
   
  emit Deposit(msg.sender, token, amount, liquidity);
}
  • Estimated gas saved = 800 gas per state variable read. Total = 800 * 5 = 4000 gas.

Possible Optimization 2 =

  • Reducing the number of state variable updates in depositAndStash function.

After Optimization:

function depositAndStash(TokenisableRange t, uint amount0, uint amount1) internal returns (uint liquidity){
    if ( ERC20(address(token0)).allowance(address(this), address(t)) < amount0 ) {
        ERC20(address(token0)).safeIncreaseAllowance(address(t), amount0);
    }
    if ( ERC20(address(token1)).allowance(address(this), address(t)) < amount1 ) {
        ERC20(address(token1)).safeIncreaseAllowance(address(t), amount1);
    }
    liquidity = t.deposit(amount0, amount1); 
    uint bal = t.balanceOf(address(this));
    if (bal > 0){
        if ( ERC20(address(t)).allowance(address(this), address(lendingPool)) < bal ) {
            ERC20(address(t)).safeIncreaseAllowance(address(lendingPool), bal);
        }
        lendingPool.deposit(address(t), bal, address(this), 0);
    }
}
  • Estimated gas saved = This optimization reduces the number of SSTORE operations, which cost 5000 gas each. If the allowances are already high enough, we could save up to 10,000 gas per transaction.

Possible Optimizations in TokenisableRange.sol

General Optimizations =

  • Reducing the number of state variable reads and writes
  • Reducing the number of contract calls
  • Reducing the number of arithmetic operations

Possible Optimization 1 =

  • Reducing the number of state variable reads in the deposit function here also. The deposit function reads the state variables TOKEN0, TOKEN1, fee0, fee1, and liquidity multiple times. Each state variable read costs 800 gas. We can reduce the gas cost by storing the state variables in memory variables at the start of the function and using these memory variables throughout the function.

After Optimization:

function deposit(uint256 n0, uint256 n1) external nonReentrant returns (uint256 lpAmt) {
    // Once all assets were withdrawn after initialisation, this is considered closed
    // Prevents TR oracle values from being too manipulatable by emptying the range and redepositing 
    require(totalSupply() > 0, "TR Closed"); 
    
    claimFee();
    TOKEN0.token.transferFrom(msg.sender, address(this), n0);
    TOKEN1.token.transferFrom(msg.sender, address(this), n1);
    
    uint newFee0; 
    uint newFee1;
    // Calculate proportion of deposit that goes to pending fee pool, useful to deposit exact amount of liquidity and fully repay a position
    // Cannot repay only one side, if fees are both 0, or if one side is missing, skip adding fees here
      // if ( fee0+fee1 == 0 || (n0 == 0 && fee0 > 0) || (n1 == 0 && fee1 > 0) ) skip  
      // DeMorgan: !( (n0 == 0 && fee0 > 0) || (n1 == 0 && fee1 > 0) ) = !(n0 == 0 && fee0 > 0) && !(n0 == 0 && fee1 > 0)
    if ( fee0+fee1 > 0 && ( n0 > 0 || fee0 == 0) && ( n1 > 0 || fee1 == 0 ) ){
      address pool = V3_FACTORY.getPool(address(TOKEN0.token), address(TOKEN1.token), feeTier * 100);
      (uint160 sqrtPriceX96,,,,,,)  = IUniswapV3Pool(pool).slot0();
      (uint256 token0Amount, uint256 token1Amount) = LiquidityAmounts.getAmountsForLiquidity( sqrtPriceX96, TickMath.getSqrtRatioAtTick(lowerTick), TickMath.getSqrtRatioAtTick(upperTick), liquidity);
      if (token0Amount + fee0 > 0) newFee0 = n0 * fee0 / (token0Amount + fee0);
      if (token1Amount + fee1 > 0) newFee1 = n1 * fee1 / (token1Amount + fee1);
      fee0 += newFee0;
      fee1 += newFee1; 
      n0   -= newFee0;
      n1   -= newFee1;
    }

    ERC20 _token0 = TOKEN0.token;
    ERC20 _token1 = TOKEN1.token;
    _token0.safeIncreaseAllowance(address(POS_MGR), n0);
    _token1.safeIncreaseAllowance(address(POS_MGR), n1);

    // New liquidity is indeed the amount of liquidity added, not the total, despite being unclear in Uniswap doc
    (uint128 newLiquidity, uint256 added0, uint256 added1) = POS_MGR.increaseLiquidity(
      INonfungiblePositionManager.IncreaseLiquidityParams({
        tokenId: tokenId,
        amount0Desired: n0,
        amount1Desired: n1,
        amount0Min: n0 * 95 / 100,
        amount1Min: n1 * 95 / 100,
        deadline: block.timestamp
      })
    );   
    uint256 feeLiquidity;
    if ( newFee0 == 0 && newFee1 == 0 ){
      uint256 TOKEN0_PRICE = ORACLE.getAssetPrice(address(TOKEN0.token));
      uint256 TOKEN1_PRICE = ORACLE.getAssetPrice(address(TOKEN1.token));
      require (TOKEN0_PRICE > 0 && TOKEN1_PRICE > 0, "Invalid Oracle Price");
      feeLiquidity = newLiquidity * ( (fee0 * TOKEN0_PRICE / 10 ** TOKEN0.decimals) + (fee1 * TOKEN1_PRICE / 10 ** TOKEN1.decimals) )   
                                    / ( (added0   * TOKEN0_PRICE / 10 ** TOKEN0.decimals) + (added1   * TOKEN1_PRICE / 10 ** TOKEN1.decimals) ); 
    }
                                     
    lpAmt = totalSupply() * newLiquidity / (liquidity + feeLiquidity); 
    liquidity = liquidity + newLiquidity;
    
    _mint(msg.sender, lpAmt);
    _token0.safeTransfer( msg.sender, n0 - added0);
    _token1.safeTransfer( msg.sender, n1 - added1);

    emit Deposit(msg.sender, lpAmt);
  }
  • Estimated gas saved = 800 gas per state variable read. Total = 800 * 5 = 4000 gas.

Possible Optimization 2 =

  • Reducing the number of state variable updates in claimFee function. This function updates the state variables fee0 and fee1 multiple times. Each state variable update costs 5000 gas. We can reduce the gas cost by calculating the new values in memory and then updating the state variables only once at the end of the function.

After Optimization:

function claimFee() public {
    (uint256 newFee0, uint256 newFee1) = POS_MGR.collect( 
      INonfungiblePositionManager.CollectParams({
        tokenId: tokenId,
        recipient: address(this),
        amount0Max: type(uint128).max,
        amount1Max: type(uint128).max
      })
    );
    // If there's no new fees generated, skip compounding logic;
    if ((newFee0 == 0) && (newFee1 == 0)) return;  
    uint tf0 = newFee0 * treasuryFee / 100;
    uint tf1 = newFee1 * treasuryFee / 100;
    if (tf0 > 0) TOKEN0.token.safeTransfer(treasury, tf0);
    if (tf1 > 0) TOKEN1.token.safeTransfer(treasury, tf1);
    
    uint _fee0 = fee0 + newFee0 - tf0;
    uint _fee1 = fee1 + newFee1 - tf1;
    
    // Calculate expected balance,  
    (uint256 bal0, uint256 bal1) = returnExpectedBalanceWithoutFees(0, 0);
    
    // If accumulated more than 1% worth of fees, compound by adding fees to Uniswap position
    if ((_fee0 * 100 > bal0 ) && (_fee1 * 100 > bal1)) { 
      TOKEN0.token.safeIncreaseAllowance(address(POS_MGR), _fee0);
      TOKEN1.token.safeIncreaseAllowance(address(POS_MGR), _fee1);
      (uint128 newLiquidity, uint256 added0, uint256 added1) = POS_MGR.increaseLiquidity(
        INonfungiblePositionManager.IncreaseLiquidityParams({
          tokenId: tokenId,
          amount0Desired: _fee0,
          amount1Desired: _fee1,
          amount0Min: 0,
          amount1Min: 0,
          deadline: block.timestamp
        })
      );
      // check slippage: validate against value since token amounts can move widely
      uint token0Price = ORACLE.getAssetPrice(address(TOKEN0.token));
      uint token1Price = ORACLE.getAssetPrice(address(TOKEN1.token));
      uint addedValue = added0 * token0Price / 10**TOKEN0.decimals + added1 * token1Price / 10**TOKEN1.decimals;
      uint totalValue =   bal0 * token0Price / 10**TOKEN0.decimals +   bal1 * token1Price / 10**TOKEN1.decimals;
      uint liquidityValue = totalValue * newLiquidity / liquidity;
      require(addedValue > liquidityValue * 95 / 100 && liquidityValue > addedValue * 95 / 100, "TR: Claim Fee Slippage");
      _fee0 -= added0;
      _fee1 -= added1;
      liquidity = liquidity + newLiquidity;
    }
    fee0 = _fee0;
    fee1 = _fee1;
    emit ClaimFees(newFee0, newFee1);
  }
  • Estimated gas saved = This optimization reduces the number of SSTORE operations, which cost 5000 gas each. If the allowances are already high enough, we could save up to 10,000 gas per transaction.

Possible Optimizations in V3Proxy.sol

Possible Optimization 1 =

For swapExactTokensForTokens():

function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts) {
    require(path.length == 2, "Direct swap only");
    ERC20 ogInAsset = ERC20(path[0]);
    ogInAsset.safeTransferFrom(msg.sender, address(this), amountIn);
    ogInAsset.safeApprove(address(ROUTER), amountIn);
    amounts = new uint[](2);
    amounts[0] = amountIn;         
    amounts[1] = ROUTER.exactInputSingle(ISwapRouter.ExactInputSingleParams(path[0], path[1], feeTier, msg.sender, deadline, amountIn, amountOutMin, 0));
    emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]); 
}

You can do the same in swapExactTokensForTokens(), swapTokensForExactTokens(), swapExactETHForTokens(), swapETHForExactTokens(), swapTokensForExactETH(), swapExactTokensForETH() also to save a significant amount of gas.

  • Estimated gas saved = This optimization reduces the number of SSTORE operations, which cost 5000 gas each. So, we could save up to 5000 gas per transaction.

Possible Optimization 2 =

After Optimization:

function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) payable external returns (uint[] memory amounts) {
    require(path.length == 2, "Direct swap only");
    require(path[1] == ROUTER.WETH9(), "Invalid path");

    ERC20 ogInAsset = ERC20(path[0]);
    ogInAsset.safeTransferFrom(msg.sender, address(this), amountInMax);
    ogInAsset.safeApprove(address(ROUTER), amountInMax);

    amounts = new uint[](2);
    amounts[0] = ROUTER.exactOutputSingle(ISwapRouter.ExactOutputSingleParams(path[0], path[1], feeTier, msg.sender, deadline, amountOut, amountInMax, 0));         
    amounts[1] = amountOut;
    emit Swap(msg.sender, path[0], path[1], amounts[0], amounts[1]); 
}
  • Estimated gas saved = This optimization reduces the number of external calls, which cost 700 gas each, and the number of SSTORE operations, which cost 5000 gas each. So, we could save up to 5700 gas per transaction.

The same optimization can be applied to swapExactTokensForETH() as well.

Possible Optimizations in RangeManager.sol

Possible Optimization 1 =

  • In the cleanUp() function, Add a condition to check if the balance of ASSET_0 and ASSET_1 is greater than zero before approving and depositing.

After Optimization:

function cleanup() internal {
    uint256 asset0_amt = ASSET_0.balanceOf(address(this));
    uint256 asset1_amt = ASSET_1.balanceOf(address(this));
    
    if (asset0_amt > 0) {
      ASSET_0.safeIncreaseAllowance(address(LENDING_POOL), asset0_amt);
      LENDING_POOL.deposit(address(ASSET_0), asset0_amt, msg.sender, 0);
    }
    
    if (asset1_amt > 0) {
      ASSET_1.safeIncreaseAllowance(address(LENDING_POOL), asset1_amt);
      LENDING_POOL.deposit(address(ASSET_1), asset1_amt, msg.sender, 0);
    }
   
    (,,,,,uint256 hf) = LENDING_POOL.getUserAccountData(msg.sender);
    require(hf > 1.01e18, "Health factor is too low");
}
  • Estimated gas saved = This optimization reduces the number of SSTORE operations, which cost 5000 gas each, and CALL operations, which cost 700 gas each. So, we could save up to 5700 gas per transaction when the balance is zero.

Possible Optimization 2 =

  • In the removeFromStep() function, add a condition to check if the balance of the user in the LENDING_POOL for each tokenised range and tokenised ticker is greater than zero before transferring, withdrawing, and emitting the event.

After Optimization:

function removeFromStep(uint256 step) internal {
    require(step < tokenisedRanges.length && step < tokenisedTicker.length, "Invalid step");
    uint256 trAmt;
    
    trAmt = ERC20(LENDING_POOL.getReserveData(address(tokenisedRanges[step])).aTokenAddress).balanceOf(msg.sender);   
    if (trAmt > 0) {       
        LENDING_POOL.PMTransfer(
          LENDING_POOL.getReserveData(address(tokenisedRanges[step])).aTokenAddress, 
          msg.sender, 
          trAmt
        );
        trAmt = LENDING_POOL.withdraw(address(tokenisedRanges[step]), type(uint256).max, address(this));
        tokenisedRanges[step].withdraw(trAmt, 0, 0);
        emit Withdraw(msg.sender, address(tokenisedRanges[step]), trAmt);
    }        

    trAmt = ERC20(LENDING_POOL.getReserveData(address(tokenisedTicker[step])).aTokenAddress).balanceOf(msg.sender);
    if (trAmt > 0) {    
        LENDING_POOL.PMTransfer(
          LENDING_POOL.getReserveData(address(tokenisedTicker[step])).aTokenAddress, 
          msg.sender, 
          trAmt
        );
        uint256 ttAmt = LENDING_POOL.withdraw(address(tokenisedTicker[step]), type(uint256).max, address(this));
        tokenisedTicker[step].withdraw(ttAmt, 0, 0);
        emit Withdraw(msg.sender, address(tokenisedTicker[step]), trAmt);
    }           
}
  • Estimated gas saved = This optimization reduces the number of SSTORE operations, which cost 5000 gas each, and CALL operations, which cost 700 gas each. So, we could save up to 5700 gas per transaction when the balance is zero.

Possible Optimizations in PositionManager.sol

Possible Optimization 1 =

  • In the cleanUp() function, add a condition to check if there is any debt before trying to repay it.

After Optimization:

function cleanup(ILendingPool LP, address user, address asset) internal {
    uint amt = ERC20(asset).balanceOf(address(this));
    if (amt > 0) {
      checkSetAllowance(asset, address(LP), amt);
      
      // if there is a debt, try to repay the debt 
      uint debt = ERC20(LP.getReserveData(asset).variableDebtTokenAddress).balanceOf(user);
      if ( debt > 0 ){
        if (amt <= debt ) {
          LP.repay( asset, amt, 2, user);
          return;
        }
        else {
          LP.repay( asset, debt, 2, user);
          amt = amt - debt;
        }
      }
      // deposit remaining tokens
      if (amt > 0) {
        LP.deposit( asset, amt, user, 0 );
      }
    }
}
  • Estimated gas saved = This optimization reduces the number of SSTORE operations, which cost 5000 gas each, and CALL operations, which cost 700 gas each. So, we could save up to 5700 gas per transaction when the balance is zero.

Possible Optimization 2 =

  • In the PMWithdraw() function, add a condition to check if the user has any balance in the Lending Pool before trying to transfer and withdraw.

After Optimization:

function PMWithdraw(ILendingPool LP, address user, address asset, uint amount) internal {
    if ( amount > 0 && ERC20(LP.getReserveData(asset).aTokenAddress).balanceOf(user) > 0){
      LP.PMTransfer(LP.getReserveData(asset).aTokenAddress, user, amount);
      LP.withdraw(asset, amount, address(this));
    }
}  
  • Estimated gas saved = This optimization reduces the number of SSTORE operations, which cost 5000 gas each, and CALL operations, which cost 700 gas each. So, we could save up to 5700 gas per transaction when the balance is zero.

#0 - c4-pre-sort

2023-08-10T17:12:32Z

141345 marked the issue as high quality report

#1 - c4-sponsor

2023-08-15T06:00:16Z

Keref marked the issue as sponsor disputed

#2 - c4-sponsor

2023-08-15T06:03:18Z

Keref marked the issue as sponsor acknowledged

#3 - Keref

2023-08-15T06:03:50Z

Valid ones for TokenisableRange.sol Rest of it is trying to break code factorization to save 1 internal call, and at worst the last example makes a 2nd useless check (amt > 0) and actually increases gas usage.

#4 - c4-judge

2023-08-20T17:03:39Z

gzeon-c4 marked the issue as grade-b

Findings Information

🌟 Selected for report: catellatech

Also found by: 0xSmartContract, K42, Sathish9098, digitizeworx

Labels

analysis-advanced
grade-a
A-05

Awards

661.3437 USDC - $661.34

External Links

Advanced Analysis Report for GoodEntry by K42

Overview

  • GoodEntry is a decentralized derivatives market built on top of Uniswap v3, designed to offer protection for users engaged in trading or yield-generation activities. It introduces a novel concept of "protected perps", allowing users to trade perpetual options with limited downside. The platform leverages single tick liquidity in Uniswap, which behaves as a limit order, whose payout is similar to writing an option. Borrowing such liquidity and removing it from the tick gives a payout similar to buying an option.

Understanding the Ecosystem:

  • GoodEntry is designed to foster a cohesive community where members work in conjunction with a core-contributor team led by Kisugi (@JMY0x) to build a thriving ecosystem. The ecosystem is underpinned by a unique tokenomics model that is designed to incentivize participation and ensure the sustainability of the platform.

The tokenomics model is structured as follows:

  • Initial Farm Offering (IFO): 15% of the tokens are allocated during the public launch via an IFO. The proceeds from this offering contribute to Protocol Owned Liquidity, with fees supporting protocol development. The token launch date will be announced first on the GoodEntry Discord server.

  • Pre-Mining Program: 5% of the tokens are allocated to a pre-mining program, which will be distributed linearly over six months. Details of the program mechanics will be released first on the GoodEntry Discord server.

  • Liquidity Mining: 25% of the tokens are dedicated to liquidity mining over three years. This is designed to incentivize users to provide liquidity to the platform.

  • Partnerships: 15% of the tokens are allocated for partnerships. This allocation could be used to form strategic alliances with other platforms or entities.

  • Seasonal Allocation: 5% of the tokens are designated for Season 1, with a 3-month cliff followed by a 9-month linear lockup. Future seasons will see pre-mined tokens sent to a multisig wallet, released on a seasonal basis.

  • Reserves: 18% of the tokens are held in reserves, pre-minted to a multisig wallet. These reserves can be used for various functions to support the growth and stability of the platform.

  • Team Allocation: 22% of the tokens are assigned to the team, subject to a 1-year cliff and a 3-year linear unlock. This ensures that the team is incentivized to continue developing and improving the platform over the long term.

  • The GoodEntry ecosystem is thus designed to be both inclusive and sustainable, with a clear focus on community collaboration and long-term growth. The tokenomics model plays a crucial role in this, providing the necessary incentives for users to participate in the ecosystem and contribute to its development.

  • The platform introduces a unique tokenomics model, with two tokens: $GE and $GEP. $GE is the governance token, used for voting on proposals and earning fees from the platform. $GEP is the protocol token, used for staking, liquidity mining, and earning rewards.

Codebase Quality Analysis:

  • The codebase is well-structured and modular, with clear separation of concerns among different contracts. The contracts are well-documented, with clear comments explaining the functionality of each function and module. The project uses Brownie as a testing framework, and the tests cover a wide range of scenarios and edge cases. The codebase also includes a comprehensive suite of unit tests, which demonstrates a commitment to quality and robustness.

Architecture Recommendations:

The architecture of the platform is sound, with a clear separation of concerns and modular design. However, there are a few areas where improvements could be made:

  • Implementing more granular access controls: Currently, some contracts have privileged access to the lending pools. Implementing more granular access controls could help mitigate potential security risks.
  • Enhancing price manipulation resistance: While the platform has mechanisms in place to resist price manipulation, these could be further enhanced to ensure robustness against sophisticated attacks.
  • Improving gas efficiency: Some operations, such as rebalancing liquidity in ezVaults, could potentially be optimized for gas efficiency.

Centralization Risks:

  • The platform has some degree of centralization, with the GoodEntry team determining the increments for each asset in the ezVaults. However, this is mitigated by the use of the $GE governance token, which allows token holders to vote on proposals and influence the direction of the platform.

Mechanism Review:

  • The mechanisms of the platform, including the protected perps, tokenized Uniswap v3 positions, and ezVaults, are innovative and well-designed. They offer unique benefits to users, such as the ability to trade perpetual options with limited downside and earn swap fees from liquidity provision.

Systemic Risks:

  • The platform faces systemic risks common to all DeFi platforms, such as smart contract bugs, oracle failures, and economic attacks. However, these risks are mitigated through a combination of thorough testing, external audits, and robust design principles.

Areas of Concern

  • Centralization risks due to the GoodEntry team determining the increments for each asset.
  • Potential for gas inefficiencies in operations such as rebalancing liquidity.
  • Systemic risks common to all DeFi platforms, such as smart contract bugs and economic attacks.

Codebase Analysis

  • The codebase is well-structured and modular, with clear separation of concerns among different contracts. The contracts are well-documented, with clear comments explaining the functionality of each function and module. The project uses Brownie as a testing framework, and the tests cover a wide range of scenarios and edge cases.

Recommendations

  • Implement more granular access controls to mitigate potential security risks.
  • Enhance price manipulation resistance mechanisms to ensure robustness against sophisticated attacks.
  • Optimize operations for gas efficiency where possible.

Contract Details

The main contracts in the GoodEntry platform are:

Conclusion

  • GoodEntry is a promising platform that introduces innovative concepts such as protected perps and tokenized Uniswap v3 positions. The platform is well-designed, with a clear separation of concerns and modular architecture. While there are areas where improvements could be made, such as implementing more granular access controls and optimizing for gas efficiency, the platform's overall design and functionality are impressive. With its unique offerings and robust design, GoodEntry has the potential to make a significant impact in the DeFi space.

Time spent:

20 hours

#0 - c4-judge

2023-08-20T17:08:10Z

gzeon-c4 marked the issue as grade-a

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