Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · July 2024

Token Launchpad

for g8keep

G8Keep engaged Guardian to review the security of its token launchpad. From the 8th of July to the 15th of July, a team of 7 auditors reviewed the source code in scope.

Published
Review window
July 8 to 15, 2024
Language
Solidity
Chains
Ethereum, Base
Sector
Token launches
  • 1 Critical
  • 3 High
  • 10 Medium
  • 22 Low
  • 0 Informational

28 resolved · 8 acknowledged

Scope

Overview

G8Keep engaged Guardian to review the security of its token launchpad. From the 8th of July to the 15th of July, a team of 7 auditors reviewed the source code in scope.

Findings 36

  1. C-01 Critical Vested Calculations Are Broken DoS / Gaming Resolved
    Location
    g8keepVester.sol: 99

    Description

    Proof of concept: PoC

    Users can deploy a token and select a portion of the initial supply to vest over a period of time. The issue arises when calculating the vested amount, specifically in timeSinceLastClaim, as the number will be huge due to the fact that vesting.lastClaim is never initialized.

    There are two main impacts with this issue:

    1. Deployer will be DoS'ed when trying to claim tokens, as the vested amount calculation will be

    much higher than the contract balance. Consider this scenario:

    • block.timestamp = 1720483200
    • vestingPeriod = 7 days
    • vestingAmount = 1000e18
    • vestedAmount = 1000e18 * 1720483200 / (7*24*60*60) ~= 2844714e18
    1. A malicious deployer can set a vest time equal to the current block.timestamp and immediately

    claim all tokens after deployment. Therefore, deployer can trick the token holders to think the vesting time is huge (i.e. 55 years) but deployer can claim them at any time and sell at market price, rug pulling all users. Consider this scenario:

    • block.timestamp = 1720483200
    • vestingPeriod = 1720483200
    • vestingAmount = 1000e18
    • vestedAmount = 1000e18 * 1720483200 / 1720483200 = 1000e18

    Recommendation

    Initialize the lastClaim param when deploying the vest: deploymentVesting.lastClaim = uint40(block.timestamp);

    Resolution

    G8Keep Team: Resolved.

  2. H-01 High DoS for LPs Removing Liquidity Logical Error Acknowledged
    Location
    g8keepToken.sol: 269

    Description

    During the snipe protection phase, a user who decides to add liquidity to the uniswap pool will be prevented from removing the liquidity. Their tokens will remain locked in the pool until _adjustAmountOut() will no longer be called in the _transfer flow.

    This occurs because at the time _adjustAmountOut() is called, the tokens for token0 have already been transferred to the user. Which, in turn, will cause balance0 to be less than reserve0, triggering an InsufficientPoolInput revert.

    Recommendation

    Instead of reverting when this occurs, return the value of amount1Out so that users can LP to the pool without facing price impact.

    Resolution

    G8Keep Team: Should document removing liquidity is not allowed until after the end of snipe protection.

  3. H-02 High Reduced Penalty When Trading In Batches Gaming Acknowledged
    Location
    g8keepToken.sol: 287

    Description

    Proof of concept: PoC

    The protocol states Purchases that would decrease the balance below the expected balance are penalized exponentially by reducing the output amount.

    The issue relies on the adjustedAmount1Out calculation when applying penalty, as users can opt to do multiple smaller swaps instead of a big swap to reduce the penalty imposed.

    Recommendation

    Consider making the snipe protection penalty linear so it cannot be gamed by batch buys. Otherwise to maintain the penalty's exponential nature consider basing the exponential cost on the aggregate buys that have exceeded the expected amount in the current block rather than the current buy.

    Or base this penalty on the amount of buys within a given lookback period. This way malicious actors cannot game the penalty with multiple buys in a single transaction and therefore must risk spreading their buys out over multiple blocks.

    Finally if this behavior is acceptable, be aware of this gaming mechanism and warn users.

    Resolution

    G8Keep Team: Acknowledge and document.

  4. H-03 High Deployers Can Escape From Initial Liquidity Fee Gaming Resolved
    Location
    g8keepFactory.sol: 66

    Description

    Proof of concept: PoC

    Users can permissionlessly deploy g8keepTokens and they have to pay 2% initial liquidity fee for deployment. All of the initial liquidity except this fee, and all of the total supply except deployer’s share will be added to liquidity pool during deployment.

    During the addLiquidity, Uniswap router transfers tokens from g8keepFactory to Uniswap pair contract, and liquidity amount is calculated based on previous reserves and added token balances. Since this function is called immediately after deployment, reserves are 0 for both tokens and total LP supply is also 0. However, balances of the pair contract can be manipulated before its deployment.

    Consider a scenario where a deployer wants to deploy 100 WETH / 10_000_000 g8Token pair. If the user deploys this as expected, they will have to pay 2 WETH initial liquidity fee. However, they can transfer ~98 WETH directly to the pair by precomputing the pair address, and deploy with 0.1 WETH / 10_000_000 g8Token parameters using the g8keepFactory. This way, they will only pay 0.002 WETH fee while deploying the exact same amount of tokens.

    Additionally, deployers may avoid paying the liquidity fee to gatekeep by first deploying a small amount of paired token liquidity and then adding more paired tokens directly after launch and triggering a sync in Uniswap V2.

    Recommendation

    Check the pairedToken balance of the newly deployed pair contract before adding the initial liquidity, and revert if a user preemptively added some tokens to this address. Additionally consider the case where users add paired tokens after the initial deployment and set the minimum paired liquidity requirement accordingly.

    Resolution

    G8Keep Team: We skim the pool for any value that is deposited pre-deployment, add that amount to the initial liquidity and apply the g8keep liquidity fee to the full amount. 16

  5. M-01 Medium Missing Validation For _initialLiquidity Validation Resolved
    Location
    g8keepFactory.sol: 91

    Description

    Proof of concept: PoC

    Users that choose WETH as the paired token will need to send the _initialLiquidity as ETH using msg.value. Therefore, all ETH sent will be converted to WETH, but any excess ETH sent above _initialLiquidity value will not be used for adding liquidity to the pool, and left stuck in the contract.

    The g8keepFactory will need to use withdrawToken to recover these stuck WETH funds from users. If an attacker realized the Factory contains WETH tokens, he can trigger a new token deployment, and use the WETH as part of their own liquidity, and send less or no ETH with msg.value.

    Recommendation

    Consider validating if msg.value == _initialLiquidity when _pairedToken == WETH.

    Resolution

    G8Keep Team: Resolved.

  6. M-02 Medium Liquidity Providers Are Charged Trading Fees Logical Error Acknowledged
    Location
    g8keepToken.sol: 206

    Description

    The g8Token _transfer function includes a fee on transfer logic, which means users incur sell fees when to == UNISWAP_V2_PAIR and buy fees when from == UNISWAP_V2_PAIR. The problem with this logic is that liquidity providers will face fees when adding or removing liquidity, contrary to the intended protocol design.

    Recommendation

    As it is not straightforward to distinguish between a liquidity action and a swap during the _transfer execution, we suggest documenting this behavior. This way, liquidity providers can be informed about the buy/sell fee.

    Resolution

    G8Keep Team: Acknowledged.

  7. M-03 Medium Excessive Trade Penalty Charged Logical Error Resolved
    Location
    g8keepToken.sol: 276

    Description

    A penalty is only supposed to be applied to a trade that drops the amount of gatekeep tokens to below the expected balance. Since 996 is used instead of 997 in the calculation of minimumToken1Balance, a user will be charged an additional 1/3 of the Uniswap fee when the expected balance is in surplus.

    Additionally, this creates a divergence between the reported maximum buy that would not be penalized from the maxSnipeProtectionBuyWithoutPenalty function which will be displayed on the frontend and the amount that is actually allowed without penalization upon a transfer from the Uniswap pair.

    Recommendation

    The calculation of minimumToken1Balance aims to determine the amount of token1s required to remain in the pair to satisfy x*y=k. Uniswap already performs the calculations necessary to achieve this without worrying about x*y=k being thrown out of sync.

    Simply set minimumToken1Balance to balance1 - amount1Out. balance1 is used instead of reserve1 to stay in-line with the maxSnipeProtectionBuyWithoutPenalty functionality, which also relies on the balance rather than the reserve. Then you can set the initial value of adjustedAmount1Out to amount1Out. This will make the logic more accurate and reduce gas costs.

    Resolution

    G8Keep Team: Resolved.

  8. M-04 Medium Flash Swaps Unavailable During Snipe Protection Logical Error Acknowledged
    Location
    g8keepToken.sol: 276

    Description

    Flash swaps is a core feature of Uniswap V2. Users are able to use the swap function to obtain tokens in the pool, and use the uniswapCall callback to repay the tokens. The issue arises when users try to flash swap the g8keep token during sniping protection window, as the minimumToken1Balance will be the same as the balance1, so adjustedAmount1Out will be 0.

    Therefore, the flash swap feature will not transfer tokens to the user during sniping protection window. Additionally, after the window is over, flash swaps are enabled, as _adjustAmountOut is not invoked, but users will be charged both a buy fee to take the flash loan and sell fee during repayment.

    Recommendation

    Document this behavior so users are aware that flash swaps are disabled during sniping protection.

    Resolution

    G8Keep Team: Acknowledged.

  9. M-05 Medium Swap Fees Values Not Supported Logical Error Resolved
    Location
    g8keepFactory.sol: 71

    Description

    Users can deploy tokens with specific buy and sell fees. These values are passed as params in the g8keepFactory constructor. The issue is that both _buyFee and _sellFee are uint8, so it won't support any values above 255, although the max value for both fees is set to 500.

    Recommendation

    Consider updating the _buyFee and _sellFee value type to uint16 to support higher fee values.

    Resolution

    G8Keep Team: Resolved.

  10. M-06 Medium All Token Deployments Can Be DoS’ed DoS / Gaming Resolved
    Location
    g8keepToken.sol: 93

    Description

    Proof of concept: PoC

    Users can permissionlessly create new g8keepTokens via the factory contract. This process includes deployment of the new token, creation of a pair in Uniswap with this new token, and adding liquidity to the Uniswap pair. All of these actions happen in a single transaction.

    New Uniswap pair creation is done in the g8keepToken constructor with this line: UNISWAP_V2_PAIR = IUniswapV2Factory(uniswapV2Router.factory()).createPair(address(this), _pairedToken)

    The createPair function in Uniswap factory: 1. Reverts when there is already a pair with same token addresses. 2. Does not check whether inputted token addresses are actually exist or not. 3. Can be called by anyone.

    Lastly, g8keepFactory contract uses create2 method while deploying g8keepTokens, which means anyone can precompute the future g8keepToken address. An attacker can precompute the new token address and directly call the createPair function in Uniswap factory before it is deployed, causing the deployment to fail.

    Recommendation

    Consider performing an action similar to _addLiquidity function in UniswapRouter (Source), and check if the pair already exists instead of always calling createPair in the constructor.

    Resolution

    G8Keep Team: Resolved with try/catch for pair creation.

  11. M-07 Medium Malicious Code Can Be Set In Token Name / Symbol XSS Resolved
    Location
    g8keepFactory.sol: 67

    Description

    The g8keepFactory allows users to deploy a custom g8keepToken including the ability to set a name and symbol for the new token. However, it is possible for an attacker to craft a name or symbol such that it includes markup that can contain Javascript code.

    If loaded into a frontend without XSS protection, this can cause potential harm to users of the platform as was the case in the EtherDelta exploit. For more details on the EtherDelta exploit please refer to this article.

    Recommendation

    Sanitize or limit the length of the token _name and _symbol passed into the deployToken function from the g8keepFactory contract.

    Resolution

    G8Keep Team: Addressed in g8keep UI/backend integration.

  12. M-08 Medium Gatekeep May Avoid Buy Taxes Gaming Resolved
    Location
    g8keepToken.sol: 206

    Description

    In the _transfer function for the g8keepToken contract the buy taxes implemented by the token deployer are ignored if the receiver of the buy is the gatekeep factory contract. However under normal operation there is no use-case for the gatekeep factory to be the receiver of a buy.

    This behavior allows the gatekeep owner to buy deployed g8keep tokens without the buy tax. These tokens may be withdrawn with the withdrawToken function or sold with the sellTokens function. As this is functionality is not planned to be an explicit feature of the system, the admin should not be able to circumvent the buy fees.

    Recommendation

    Consider removing the !to == G8KEEP condition in the _transfer function so that buy fees cannot be avoided by the g8keep owner.

    Resolution

    G8Keep Team: Resolved.

  13. M-09 Medium Minimum Token Balance Can Be Inaccurate Logical Error Resolved
    Location
    g8keepToken.sol: 273

    Description

    In the _adjustAmountOut function the amount0In amount computed by the g8keepToken contract is based upon the balance0 - reserve0. However in the UniswapV2 pair contract a user may specify a nonzero amount0Out and a nonzero amount1Out.

    In this case the amount0In in the swap function is computed as balance0 - (_reserve0 - amount0Out) which does not match the amount0In computation in the g8keepToken _adjustAmountOut function.

    As a result the amount0In value in the _adjustAmountOut function is smaller in these cases, causing the minimumToken1Balance to be smaller and thus the adjustedAmount1Out to be larger than it should be.

    Recommendation

    Consider implementing the simplification mentioned in M-03 which avoids this accounting difference. Otherwise be aware of this difference in the swap fees computed by the Uniswap V2 pair and the swap fees computed in the _adjustAmountOut function and document it's effect on the snipe protection penalty.

    Resolution

    G8Keep Team: M-03 recommendations implemented.

  14. M-10 Medium Lacking SafeCast Usage Best practice Resolved
    Location
    Global

    Description

    Throughout the g8keep codebase raw casts are made which potentially dangerously downcast values that may overflow the uint sizes they are casted into.

    One instance in the deploymentVest function invalidates the invariant GK-32, ”Vesting end should always be greater than the vesting start” as the block.timestamp + _vestTime can exceed the maximum uint40 value when the provided _vestTime is exceptionally large.

    Recommendation

    Implement SafeCast throughout the codebase to avoid all potential overflow issues. Otherwise carefully examine and implement the appropriate validations for all cases where values are downcast.

    Resolution

    G8Keep Team: Resolved with check on vestingEnd to not be greater than type(uint40).max, other casts such as balance/reserve to uint112/uint128 are safe as total supply is checked to not exceed type(uint40).max, added constant for max setting of max snipe protection seconds.

  15. L-01 Low Tokens Remain Max Approved Logical Error Resolved
    Location
    g8keepFactory.sol: 144

    Description

    When removing a token from allowedPairs using setPairedTokenSettings(), the token is set to max approval again. This means once a token is given max approval, the token will always have max approval.

    Recommendation

    If allowed is false, set the token approval to 0.

    Resolution

    G8Keep Team: Resolved. Added a function setApprovalToUniswapRouter that performs a similar function to the setPairedTokenSettings without adding it to allowed pairs, this serves to allow the sellTokens function to be utilized in the event that a token approval is accidentally revoked through setPairedTokenSettings without having to temporarily allow it to be a paired token.

  16. L-02 Low Unexpected Claims For Token Deployer Access Control Resolved
    Location
    g8keepVester.sol: 66

    Description

    The claim function does not have any access control, therefore any user can claim on behalf of the deployer. Although deployer will be the recipient of the tokens, he might not want to claim them yet and leave tokens in the Vester contract. Might want to wait the whole vesting period to show the commitment to the project, as claiming them can suggest he will sell and dump the price

    Recommendation

    Consider adding an access control, to only allow vesting recipient to claim tokens.

    Resolution

    G8Keep Team: Resolved.

  17. L-03 Low Unused Code Optimization Resolved
    Location
    g8keepToken.sol, g8keepFactory.sol

    Description

    The following functions have internal visibility, but they are never used:

    • _getToken0Reserves

    a Guardian proof of concept

    • _getToken1Reserves

    a Guardian proof of concept

    Additionally, the FeeTransferFailed in the Factory contract is never used:

    a Guardian proof of concept

    Recommendation

    Remove the unused code from the contracts.

    Resolution

    G8Keep Team: Removed the unused error, _getToken0Reserves and _getToken1Reserves are now used in different functions and _getTokenReserves is unused/deleted.

  18. L-04 Low Users Can't Deploy Tokens With WETH Logical Error Resolved
    Location
    g8keepFactory.sol: 90

    Description

    Users will use WETH as the main paired token when deploying g8keepTokens. The contract will wrap the ETH send in msg.value to obtain WETH tokens. If users have WETH token balance and not ether, they will need to unwrap them first and then deploy the g8keepToken.

    Recommendation

    Consider refactoring the _pairedToken and msg.value checks, to allow users to transfer in WETH tokens when _pairedToken==WETH and msg.value == 0.

    Resolution

    G8Keep Team: Resolved.

  19. L-05 Low Avoid Using block.timestamp For Swaps Logical Error Resolved
    Location
    g8keepFactory.sol: 216

    Description

    The factory contract will receive g8keepToken fees for swaps. Owner will then use the sellTokens admin function to sell these fees for the paired token.

    The issue arises when using block.timestamp as the deadline parameter for swapExactTokensForTokensSupportingFeeOnTransferTokens. A malicious block builder will be able to execute this at any time, when such transaction is useful for manipulating the price.

    Recommendation

    Add a deadline parameter to the sellTokens and use this instead of block.timestamp for all the swaps.

    Resolution

    G8Keep Team: Resolved.

  20. L-06 Low Avoid Dumping Token Fees During Snipe Protection Logical Error Resolved
    Location
    g8keepFactory.sol: 203

    Description

    The owner of g8keepFactory has the ability to execute sellTokens in order to unload all fees acquired from g8token exchanges, without any sell fees being charged, which is in line with expectations. However, when snipe protection is activated, selling off the tokens not only decreases the token price, but also increases the token1 reserves, hindering the proper application of penalties.

    Recommendation

    It is advised to restrict the owner from executing sellTokens when the g8keepToken has active snipe protection.

    Resolution

    G8Keep Team: Resolved.

  21. L-07 Low Paired Token Should Use SafeTransferLib Best practice Resolved
    Location
    g8keepFactory.sol

    Description

    Any paired token can be added by the owner. In order to maintain compatibility with as many tokens as possible, safeTransfer and safeTransferFrom ought to be used to validate returned values.

    Recommendation

    Use safeTransfer and safeTransferFrom from the solady SafeTransferLib when transferring the paired token.

    Resolution

    G8Keep Team: Resolved.

  22. L-08 Low Deployer Fees Can Be Burned Validation Resolved
    Location
    g8keepToken.sol: 227

    Description

    The treasuryWallet receives buy and sell fees in g8Token, during _applyFees execution. The issue is that this address is not validated neither in the Factory contract or token deployment, like it's done in the updateTreasuryWallet owner function.

    Therefore, address(0) is be a valid value, so every buy and sell fee tokens will be burned, and emit a Transfer event with to address equal to address(0). Burning tokens should reduce the totalSupply but this is an immutable variable, as minting and burning should not be allowed.

    Recommendation

    Validate if _treasuryWallet is not address(0) during token deployment.

    Resolution

    G8Keep Team: Resolved.

  23. L-09 Low Lack Of Events Emitted Best practice Resolved
    Location
    Global

    Description

    The following main functions lack event emissions:

    g8keepFactory

    • setDeploymentSettings
    • setPairedTokenSettings

    g8keepVester

    • claim

    g8keepToken

    • updateTreasuryWallet

    Emitting events will facilitate state changes tracking by off chain services.

    Recommendation

    Consider emitting events from the above functions.

    Resolution

    G8Keep Team: Resolved.

  24. L-10 Low Router Returns Higher Amounts Logical Error Acknowledged
    Location
    UniswapRouterV2.sol

    Description

    The UniswapRouterV2 includes a swapTokensForExactTokens function. When a user directly interacts with the router contract and utilizes this function to swap WETH for tokens, the amountOut returned is higher than the actual amount received by the user. Additionally, when swapping tokens for WETH, the transaction will revert.

    Recommendation

    It is advised to document this behavior to ensure that protocols integrating swapTokensForExactTokens are informed about the accurate amount swapped using the router contract.

    Resolution

    G8Keep Team: Acknowledge and document.

  25. L-11 Low Penalty Logic Should Be Documented Documentation Acknowledged
    Location
    g8keepToken.sol

    Description

    The protocol implements a snipe protection logic to prevent huge purchases. Users can buy g8keepTokens up to a point without penalty but they have to pay a penalty after that point. Penalty calculation is done by adjusting the output amount of the g8keepToken. If the remaining balance in the Uniswap enters to penalty zone, output adjustment will be performed.

    According to docs: “any amount of balance over the expected balance may be purchased from the pool with zero penalty. Purchases that would decrease the balance below the expected balance are penalized exponentially by reducing the output amount”.

    However, penalty is applied to the entire output amount of the trade, not just the amount that would cause the balance to go below expected balance. In some scenarios, resulted amount may become even less than the no penalty amount due to exponential penalty, and might brake “any amount of balance over the expected balance may be purchased from the pool with zero penalty” statement.

    Also, since the intention is penalizing the whole trade amount, users can game the penalty logic by buying up to the max limit without penalty first, and then buying the remaining part in a second transaction to pay less penalty.

    Recommendation

    Consider documenting this behaviour for the users to prevent misunderstandings and possible loss of funds.

    Resolution

    G8Keep Team: Acknowledge and document.

  26. L-12 Low Redundant Timestamp Check In Vesting Superfluous Code Resolved
    Location
    g8keepVester.sol: 92

    Description

    The g8keepVester contract calculates the vestedAmount in the internal _vested function which is used in the claim function as the amount of vested tokens to send to the deployer. There is a check if the block.timestamp is less than the vestingStart then the vestedAmount returned should be zero.

    This suggests that the vesting can start at a future date. However, this is a redundant check since the vestingStart is always initialized to the block.timestamp such that it will always be in the past.

    Recommendation

    The check in L69 in the claim function and on L92 in the _vested function are not required and can be removed.

    Resolution

    G8Keep Team: Resolved.

  27. L-13 Low Redundant Withdraw Function Superfluous Code Resolved
    Location
    g8keepToken.sol: 176

    Description

    The g8keepToken has a withdrawETH function to withdraw ETH balances from the contract. However, this contract does not have a receive function or any payable functions, meaning it cannot hold ETH balances. As a result, the withdrawETH function is considered redundant.

    Recommendation

    Consider removing redundant function.

    Resolution

    G8Keep Team: Resolved.

  28. L-14 Low Revert On Zero Transfer Tokens Can Cause DoS Non-Standard Tokens Resolved
    Location
    g8keepFactory.sol: 119

    Description

    Proof of concept: PoC

    The g8KeepAdmin can add new ERC20 tokens to the allowedPairs whitelist. This allows the whitelisted token to be used as liquidity in the UniswapV2 pool. Moreover, g8KeepAdmin can adjust the fee that is used for deployments so that the GateKeep protocol can run promotions.

    However, if the allowedPairs includes a token that reverts on zero transfers and the g8keepInitialLiquidityFee is set to zero during a promotion, then the deployToken function in the g8keepFactory contract will revert.

    This is because on L119 in the g8keepFactory contract the fee to transfer to the g8keepFeeWallet will be zero and given the _pairedToken in this scenario will revert on zero transfers this will prevent users from deploying a new g8KeepToken.

    This is a very specific scenario where the user wants to deploy a new g8KeepToken paired with a token that reverts on zero transfers during a promotion when the fee is set to zero.

    Recommendation

    Include a check that the g8keepInitialLiquidityFee is greater than zero before performing the fee transfer to the g8keepFeeWallet.

    Resolution

    G8Keep Team: Resolved.

  29. L-15 Low Unused Custom Error Superfluous Code Resolved
    Location
    g8keepVester.sol: 40

    Description

    The custom errors FeeTransferFailed in g8keepFactory contract and PoolReservesNotSynced in g8keepToken contract are defined but are never used.

    Recommendation

    Remove unused errors.

    Resolution

    G8Keep Team: Resolved.

  30. L-16 Low Unexpected Transfer Amount Emitted Best practice Acknowledged
    Location
    g8keepToken.sol: 216

    Description

    In the transfer function the Transfer event emits the toAmount which has had any buy or sell fees applied. As a result the Transfer event will emit the received amount which does not include these fees.

    This may be unexpected for consumers of the Transfer event which would attempt to track the amount which was removed from the from address.

    Recommendation

    Keep this potentially unexpected behavior in mind and consider documenting this for integrators.

    Resolution

    G8Keep Team: Transfer amounts from the from address to the fee recipients are emitted in the _applyFees function that should appropriately balance the total amount deducted from from and sent to other addresses.

  31. L-17 Low Max Balance Transfer Tokens Behavior Non-Standard Tokens Resolved
    Location
    g8keepFactory.sol: 96

    Description

    Some ERC20 tokens such as cUSDCv3 have the behavior that transferring the maximum value transfers the entire account balance. Though in most cases this will result in a revert when calling the deployToken function and specifying an _initialLiquidity of type(uint256).max, there may be some edge cases depending on the g8keepInitialLiquidityFee assignment that would allows this unexpected token behavior to be used.

    In such a case the caller may be able to circumvent the pairedTokenMinimumLiquidity value or utilize paired tokens that were held in the g8keepFactory contract.

    Recommendation

    Consider re-assigning the _initialLiquidity amount to the amount that is received after transferring in the _pairedToken amount. Consequently this will also fix any mis-accounting for fee-on-transfer or rebase tokens.

    Resolution

    G8Keep Team: Resolved.

  32. L-18 Low Lacking Approval Event Best practice Resolved
    Location
    g8keepToken.sol: 102

    Description

    In the constructor for the g8keepToken contract the _allowances mapping is directly written to for the g8keepFactory, however no Approval event is emitted.

    Recommendation

    Add the following event emission to the constructor: emit Approval(msg.sender, _uniswapV2Router, type(uint256).max);.

    Resolution

    G8Keep Team: Resolved.

  33. L-19 Low Misleading Swap Events Best practice Resolved
    Location
    g8KeepToken.sol

    Description

    During the snipe protection window the amount1Out is adjusted to be the maximum amount extractable via the calculated minimumToken1Balance. However the Uniswap V2 pair contract will emit the amount1Out that was originally specified by the user which is not accurate to the adjusted amount which was sent.

    For example, if the user sends 10 WETH and can receive up to 100 g8 tokens out of the swap, but only specifies an amountOut of 1 wei, the _adjustAmountOut function will adjust the amount they receive to be the maximum 100 g8 tokens, but the Swap event emitted by the Uniswap V2 pair will emit the original 1 wei amount.

    Additionally the amount1Out emitted by the Uniswap pair contract will not include any buy or taxes which may be further misleading.

    Recommendation

    Consider implementing the recommended simplifications from M-03 to use the amount1Out as the adjustedAmount basis.

    Additionally be aware that the amount emitted in the Uniswap Swap event does not include buy taxes and consider documenting this where appropriate.

    Resolution

    G8Keep Team: M-03 recommendations implemented.

  34. L-20 Low Lacking Vester Access Control Access Control Acknowledged
    Location
    g8keepVester.sol: 34

    Description

    The deploymentVest function is an external function with no deliberate access control. As a result arbitrary addresses may call the deploymentVest function and create invalid vests for tokens which are either invalid tokens or tokens which are not supported gatekeep tokens.

    Depending on the frontend and off-chain systems this may have an effect as the DeploymentVestCreated event will be emitted for an invalid token.

    Recommendation

    Consider implementing some form of access control which would verify that only valid g8keep tokens which have been created through the official g8keepFactory contract can call the deploymentVest function. Otherwise be sure that these invalid event emissions will not effect the frontend or other off-chain systems.

    Resolution

    G8Keep Team: Acknowledge and implement controls in g8keep backend.

  35. L-21 Low Unrestricted maximumSnipeProtectionSeconds Validation Resolved
    Location
    g8keepFactory.sol: 147

    Description

    In the setDeploymentSettings function there is no maximum threshold which the _maximumSnipeProtectionSeconds value is validated against. As a result the g8keep owner may configure a high maximumSnipeProtectionSeconds which can be problematic as described in H-01 where LP funds are locked until the snipe protection window is over.

    Recommendation

    Consider implementing a maximum threshold for the maximumSnipeProtectionSeconds configuration. Otherwise carefully consider the issue described in H-01 when assigning this value.

    Resolution

    G8Keep Team: Resolved.

  36. L-22 Low maxSnipeProtectionBuyWithoutPenalty Inconsistency Validation Resolved
    Location
    g8keepToken.sol: 120

    Description

    The maxSnipeProtectionBuyWithoutPenalty view function uses the token1 balance of the Uniswap pair contract to determine the maximum buy that will not experience a snipe protection penalty. However the actual snipe protection penalty which is applied in the _adjustAmountOut function relies on the reserves of the Uniswap V2 pair.

    As a result in cases where there are token1s in excess of the Uniswap V2 pair reserves the maxSnipeProtectionBuyWithoutPenalty inaccurately reports a buy size that is larger than an amount which will go without penalty. Users who use the inaccurate maxSnipeProtectionBuyWithoutPenalty will be unexpectedly penalized for their entire buy amount.

    Recommendation

    Modify the _adjustAmountOut function such that the minimumToken1Balance relies on the token1 balance similar to the maxSnipeProtectionBuyWithoutPenalty function. This has been implemented in the M-03 recommendation.

    Resolution

    G8Keep Team: M-03 recommendations implemented.

Invariants 35

The review's fuzzing suite asserted 35 invariants. 28 held and 7 did not.

Every invariant tested
IDInvariantResult
GK-01When buying g8keepTokens during snipe protection period adjustedAmountOut must be less than uniswapAmountOutHeld
GK-01R(amount1Out) When buying g8keepTokens during snipe protection period adjustedAmountOut must be less than or equal toBroken
GK-02uniswapAmountOut (amount1Out) adjustedAmountOut < balance1 - minimumToken1Balance (If buy amount exceedsBroken
GK-02RmaxSnipeProtectionBuyWithoutPenalty users should be penalized) adjustedAmountOut < balance1 - cachedThirdPartyLPAmount (If buy amount exceedsBroken
GK-03maxSnipeProtectionBuyWithoutPenalty users should be penalized) When buying tokens and amountOutExpected is less than maxSnipeProtectionBuyWithoutPenalty amountOutAdjusted == uniswapAmountOut (amount1Out)Held
GK-03RCached third Party LP amount should never be greater than adjustedBalance1Broken
GK-04When buying tokens outside of the snipe protection period amountOutReceived ==Held
GK-05uniswapAmountOut (amount1Out) Selling g8keepTokens should deduct the correct number of tokens fromHeld
GK-06sender balance When selling g8keepTokens amountOutReceived ==~Held
GK-07uniswapAmountOut (amount1Out) Adding liquidity must deduct correct number of asset 0 tokensHeld
GK-08Adding liquidity must deduct correct number of asset 1 tokensHeld
GK-09Adding liquidity must credit recipient lp tokens calculated including fees takenHeld
GK-10out for g8keep Removing liquidity must deduct the correct number of UniswapV2PairHeld
GK-11assets from sender Removing liquidity must credit the correct number of paired tokens toHeld
GK-12recipient Removing liquidity must credit the correct number of g8keepTokens to recipient minus feesHeld
GK-13g8keepToken.approve() must set allowance of spender for owner toHeld
GK-14amount g8keepToken.transfer() must not cause sender balance to underflowHeld
GK-15g8keepToken.transfer() must deduct the correct number of tokens from senderHeld
GK-16g8keepToken.transfer() must not cause recipient balance to overflowHeld
GK-17g8keepToken.transfer() must credit the correct number of tokens to recipientHeld
GK-18g8keepToken.transfer() to self must not affect sender balance on self transferHeld
GK-19g8keepToken.transfer() to self must not affect recipient balance on self transferHeld
GK-20g8keepToken.transferFrom() must not cause sender balance to underflowHeld
GK-21g8keepToken.transferFrom() must deduct the correct number of tokensHeld
GK-22from sender g8keepToken.transferFrom() must not cause recipient balance to overflowHeld
GK-23g8keepToken.transferFrom() must credit the correct number of tokens to recipientHeld
GK-24g8keepToken.transferFrom() to self must not affect sender balance on selfHeld
GK-25transfer g8keepToken.transferFrom() to self must not affect recipient balance onHeld
GK-26self transfer If sender allowance is not type(uint256).max, g8keepToken.transferFrom() must notHeld
GK-27cause sender allowance to underflow If sender allowance is not type(uint256).max, g8keepToken.transferFrom() mustHeld
GK-28deduct allowance from sender g8keepVester.claim() must credit the correct number of vested tokens toHeld
GK-29vesting recipient vested() token amount is always <= contract balanceBroken
GK-30vested() amount is always less than or equal to total tokens vestedBroken
GK-31totalSupply of g8keep should be equal to sum of balancesHeld
GK-32Vesting end should should always be greater than startBroken

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote