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
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
-
C-01 Critical Vested Calculations Are Broken DoS / Gaming Resolved
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 thatvesting.lastClaimis never initialized.There are two main impacts with this issue:
- 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
- A malicious deployer can set a vest time equal to the current
block.timestampand 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
lastClaimparam when deploying the vest:deploymentVesting.lastClaim = uint40(block.timestamp);Resolution
G8Keep Team: Resolved.
-
H-01 High DoS for LPs Removing Liquidity Logical Error Acknowledged
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_transferflow.This occurs because at the time
_adjustAmountOut()is called, the tokens fortoken0have already been transferred to the user. Which, in turn, will causebalance0to be less thanreserve0, triggering anInsufficientPoolInputrevert.Recommendation
Instead of reverting when this occurs, return the value of
amount1Outso 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.
-
H-02 High Reduced Penalty When Trading In Batches Gaming Acknowledged
Description
Proof of concept: PoC
The protocol states
Purchases that would decrease the balance below the expected balance arepenalized exponentially by reducing the output amount.The issue relies on the
adjustedAmount1Outcalculation 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.
-
H-03 High Deployers Can Escape From Initial Liquidity Fee Gaming Resolved
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 fromg8keepFactoryto 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
pairedTokenbalance 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
-
M-01 Medium Missing Validation For _initialLiquidity Validation Resolved
Description
Proof of concept: PoC
Users that choose WETH as the paired token will need to send the
_initialLiquidityas ETH usingmsg.value. Therefore, all ETH sent will be converted to WETH, but any excess ETH sent above_initialLiquidityvalue will not be used for adding liquidity to the pool, and left stuck in the contract.The
g8keepFactorywill need to usewithdrawTokento 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 == _initialLiquiditywhen_pairedToken == WETH.Resolution
G8Keep Team: Resolved.
-
M-02 Medium Liquidity Providers Are Charged Trading Fees Logical Error Acknowledged
Description
The g8Token
_transferfunction includes a fee on transfer logic, which means users incur sell fees whento == UNISWAP_V2_PAIRand buy fees whenfrom == 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
_transferexecution, we suggest documenting this behavior. This way, liquidity providers can be informed about the buy/sell fee.Resolution
G8Keep Team: Acknowledged.
-
M-03 Medium Excessive Trade Penalty Charged Logical Error Resolved
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
maxSnipeProtectionBuyWithoutPenaltyfunction 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
minimumToken1Balanceaims to determine the amount of token1s required to remain in the pair to satisfyx*y=k. Uniswap already performs the calculations necessary to achieve this without worrying aboutx*y=kbeing thrown out of sync.Simply set
minimumToken1Balancetobalance1 - amount1Out.balance1is used instead ofreserve1to stay in-line with themaxSnipeProtectionBuyWithoutPenaltyfunctionality, which also relies on the balance rather than the reserve. Then you can set the initial value ofadjustedAmount1Outtoamount1Out. This will make the logic more accurate and reduce gas costs.Resolution
G8Keep Team: Resolved.
-
M-04 Medium Flash Swaps Unavailable During Snipe Protection Logical Error Acknowledged
Description
Flash swaps is a core feature of Uniswap V2. Users are able to use the
swapfunction to obtain tokens in the pool, and use theuniswapCallcallback to repay the tokens. The issue arises when users try to flash swap the g8keep token during sniping protection window, as theminimumToken1Balancewill be the same as thebalance1, soadjustedAmount1Outwill 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
_adjustAmountOutis 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.
-
M-05 Medium Swap Fees Values Not Supported Logical Error Resolved
Description
Users can deploy tokens with specific buy and sell fees. These values are passed as params in the
g8keepFactoryconstructor. The issue is that both_buyFeeand_sellFeeareuint8, so it won't support any values above 255, although the max value for both fees is set to 500.Recommendation
Consider updating the
_buyFeeand_sellFeevalue type touint16to support higher fee values.Resolution
G8Keep Team: Resolved.
-
M-06 Medium All Token Deployments Can Be DoS’ed DoS / Gaming Resolved
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
g8keepTokenconstructor with this line:UNISWAP_V2_PAIR= IUniswapV2Factory(uniswapV2Router.factory()).createPair(address(this), _pairedToken)The
createPairfunction 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,
g8keepFactorycontract usescreate2method while deployingg8keepTokens, which means anyone can precompute the futureg8keepTokenaddress. An attacker can precompute the new token address and directly call thecreatePairfunction 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.
-
M-07 Medium Malicious Code Can Be Set In Token Name / Symbol XSS Resolved
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
_nameand_symbolpassed into thedeployTokenfunction from theg8keepFactorycontract.Resolution
G8Keep Team: Addressed in g8keep UI/backend integration.
-
M-08 Medium Gatekeep May Avoid Buy Taxes Gaming Resolved
Description
In the
_transferfunction for theg8keepTokencontract 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
withdrawTokenfunction or sold with thesellTokensfunction. 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 == G8KEEPcondition in the _transfer function so that buy fees cannot be avoided by the g8keep owner.Resolution
G8Keep Team: Resolved.
-
M-09 Medium Minimum Token Balance Can Be Inaccurate Logical Error Resolved
Description
In the
_adjustAmountOutfunction the amount0In amount computed by theg8keepTokencontract is based upon thebalance0 - reserve0. However in the UniswapV2 pair contract a user may specify a nonzero amount0Out and a nonzero amount1Out.In this case the
amount0Inin theswapfunction is computed asbalance0 - (_reserve0 - amount0Out)which does not match theamount0Incomputation in the g8keepToken_adjustAmountOutfunction.As a result the
amount0Invalue in the_adjustAmountOutfunction is smaller in these cases, causing theminimumToken1Balanceto be smaller and thus theadjustedAmount1Outto 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
_adjustAmountOutfunction and document it's effect on the snipe protection penalty.Resolution
G8Keep Team: M-03 recommendations implemented.
-
M-10 Medium Lacking SafeCast Usage Best practice Resolved
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
deploymentVestfunction invalidates the invariantGK-32,”Vesting end shouldalways be greater than the vesting start”as theblock.timestamp + _vestTimecan exceed the maximumuint40value when the provided_vestTimeis 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.
-
L-01 Low Tokens Remain Max Approved Logical Error Resolved
Description
When removing a token from
allowedPairsusingsetPairedTokenSettings(), 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
allowedis 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.
-
L-02 Low Unexpected Claims For Token Deployer Access Control Resolved
Description
The
claimfunction 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 priceRecommendation
Consider adding an access control, to only allow vesting recipient to claim tokens.
Resolution
G8Keep Team: Resolved.
-
L-03 Low Unused Code Optimization Resolved
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.
-
L-04 Low Users Can't Deploy Tokens With WETH Logical Error Resolved
Description
Users will use WETH as the main paired token when deploying
g8keepTokens. The contract will wrap the ETH send inmsg.valueto 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
_pairedTokenandmsg.valuechecks, to allow users to transfer in WETH tokens when_pairedToken==WETHandmsg.value == 0.Resolution
G8Keep Team: Resolved.
-
L-05 Low Avoid Using block.timestamp For Swaps Logical Error Resolved
Description
The factory contract will receive g8keepToken fees for swaps. Owner will then use the
sellTokensadmin function to sell these fees for the paired token.The issue arises when using
block.timestampas the deadline parameter forswapExactTokensForTokensSupportingFeeOnTransferTokens. 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
deadlineparameter to thesellTokensand use this instead ofblock.timestampfor all the swaps.Resolution
G8Keep Team: Resolved.
-
L-06 Low Avoid Dumping Token Fees During Snipe Protection Logical Error Resolved
Description
The owner of
g8keepFactoryhas the ability to executesellTokensin 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 thetoken1reserves, hindering the proper application of penalties.Recommendation
It is advised to restrict the owner from executing
sellTokenswhen the g8keepToken has active snipe protection.Resolution
G8Keep Team: Resolved.
-
L-07 Low Paired Token Should Use SafeTransferLib Best practice Resolved
Description
Any paired token can be added by the owner. In order to maintain compatibility with as many tokens as possible,
safeTransferandsafeTransferFromought to be used to validate returned values.Recommendation
Use
safeTransferandsafeTransferFromfrom the soladySafeTransferLibwhen transferring the paired token.Resolution
G8Keep Team: Resolved.
-
L-08 Low Deployer Fees Can Be Burned Validation Resolved
Description
The
treasuryWalletreceives buy and sell fees in g8Token, during_applyFeesexecution. The issue is that this address is not validated neither in the Factory contract or token deployment, like it's done in theupdateTreasuryWalletowner function.Therefore,
address(0)is be a valid value, so every buy and sell fee tokens will be burned, and emit aTransferevent withtoaddress equal toaddress(0). Burning tokens should reduce thetotalSupplybut this is an immutable variable, as minting and burning should not be allowed.Recommendation
Validate if
_treasuryWalletis notaddress(0)during token deployment.Resolution
G8Keep Team: Resolved.
-
L-09 Low Lack Of Events Emitted Best practice Resolved
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.
-
L-10 Low Router Returns Higher Amounts Logical Error Acknowledged
Description
The
UniswapRouterV2includes aswapTokensForExactTokensfunction. When a user directly interacts with the router contract and utilizes this function to swap WETH for tokens, theamountOutreturned 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
swapTokensForExactTokensare informed about the accurate amount swapped using the router contract.Resolution
G8Keep Team: Acknowledge and document.
-
L-11 Low Penalty Logic Should Be Documented Documentation Acknowledged
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.
-
L-12 Low Redundant Timestamp Check In Vesting Superfluous Code Resolved
Description
The
g8keepVestercontract calculates thevestedAmountin the internal_vestedfunction which is used in theclaimfunction as the amount of vested tokens to send to the deployer. There is a check if theblock.timestampis less than thevestingStartthen thevestedAmountreturned should be zero.This suggests that the vesting can start at a future date. However, this is a redundant check since the
vestingStartis always initialized to theblock.timestampsuch that it will always be in the past.Recommendation
The check in L69 in the
claimfunction and on L92 in the_vestedfunction are not required and can be removed.Resolution
G8Keep Team: Resolved.
-
L-13 Low Redundant Withdraw Function Superfluous Code Resolved
Description
The g8keepToken has a
withdrawETHfunction 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, thewithdrawETHfunction is considered redundant.Recommendation
Consider removing redundant function.
Resolution
G8Keep Team: Resolved.
-
L-14 Low Revert On Zero Transfer Tokens Can Cause DoS Non-Standard Tokens Resolved
Description
Proof of concept: PoC
The
g8KeepAdmincan add new ERC20 tokens to theallowedPairswhitelist. This allows the whitelisted token to be used as liquidity in the UniswapV2 pool. Moreover,g8KeepAdmincan adjust the fee that is used for deployments so that the GateKeep protocol can run promotions.However, if the
allowedPairsincludes a token that reverts on zero transfers and theg8keepInitialLiquidityFeeis set to zero during a promotion, then thedeployTokenfunction in theg8keepFactorycontract will revert.This is because on L119 in the
g8keepFactorycontract the fee to transfer to theg8keepFeeWalletwill be zero and given the_pairedTokenin this scenario will revert on zero transfers this will prevent users from deploying a newg8KeepToken.This is a very specific scenario where the user wants to deploy a new
g8KeepTokenpaired with a token that reverts on zero transfers during a promotion when the fee is set to zero.Recommendation
Include a check that the
g8keepInitialLiquidityFeeis greater than zero before performing the fee transfer to theg8keepFeeWallet.Resolution
G8Keep Team: Resolved.
-
L-15 Low Unused Custom Error Superfluous Code Resolved
Description
The custom errors
FeeTransferFaileding8keepFactorycontract andPoolReservesNotSynceding8keepTokencontract are defined but are never used.Recommendation
Remove unused errors.
Resolution
G8Keep Team: Resolved.
-
L-16 Low Unexpected Transfer Amount Emitted Best practice Acknowledged
Description
In the transfer function the
Transferevent 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.
-
L-17 Low Max Balance Transfer Tokens Behavior Non-Standard Tokens Resolved
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
deployTokenfunction and specifying an_initialLiquidityoftype(uint256).max, there may be some edge cases depending on theg8keepInitialLiquidityFeeassignment that would allows this unexpected token behavior to be used.In such a case the caller may be able to circumvent the
pairedTokenMinimumLiquidityvalue or utilize paired tokens that were held in the g8keepFactory contract.Recommendation
Consider re-assigning the
_initialLiquidityamount to the amount that is received after transferring in the_pairedTokenamount. Consequently this will also fix any mis-accounting for fee-on-transfer or rebase tokens.Resolution
G8Keep Team: Resolved.
-
L-18 Low Lacking Approval Event Best practice Resolved
Description
In the constructor for the
g8keepTokencontract the_allowancesmapping is directly written to for theg8keepFactory, however noApprovalevent is emitted.Recommendation
Add the following event emission to the constructor:
emit Approval(msg.sender, _uniswapV2Router, type(uint256).max);.Resolution
G8Keep Team: Resolved.
-
L-19 Low Misleading Swap Events Best practice Resolved
Description
During the snipe protection window the
amount1Outis adjusted to be the maximum amount extractable via the calculatedminimumToken1Balance. However the Uniswap V2 pair contract will emit theamount1Outthat 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
_adjustAmountOutfunction 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
amount1Outemitted 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.
-
L-20 Low Lacking Vester Access Control Access Control Acknowledged
Description
The
deploymentVestfunction is an external function with no deliberate access control. As a result arbitrary addresses may call thedeploymentVestfunction 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
DeploymentVestCreatedevent 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
deploymentVestfunction. 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.
-
L-21 Low Unrestricted maximumSnipeProtectionSeconds Validation Resolved
Description
In the
setDeploymentSettingsfunction there is no maximum threshold which the_maximumSnipeProtectionSecondsvalue is validated against. As a result the g8keep owner may configure a highmaximumSnipeProtectionSecondswhich 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
maximumSnipeProtectionSecondsconfiguration. Otherwise carefully consider the issue described in H-01 when assigning this value.Resolution
G8Keep Team: Resolved.
-
L-22 Low maxSnipeProtectionBuyWithoutPenalty Inconsistency Validation Resolved
Description
The
maxSnipeProtectionBuyWithoutPenaltyview function uses thetoken1balance 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_adjustAmountOutfunction 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
maxSnipeProtectionBuyWithoutPenaltyinaccurately reports a buy size that is larger than an amount which will go without penalty. Users who use the inaccuratemaxSnipeProtectionBuyWithoutPenaltywill be unexpectedly penalized for their entire buy amount.Recommendation
Modify the
_adjustAmountOutfunction such that theminimumToken1Balancerelies on the token1 balance similar to themaxSnipeProtectionBuyWithoutPenaltyfunction. This has been implemented in the M-03 recommendation.Resolution
G8Keep Team: M-03 recommendations implemented.
No findings match.
Invariants 35
The review's fuzzing suite asserted 35 invariants. 28 held and 7 did not.
Every invariant tested
| ID | Invariant | Result |
|---|---|---|
GK-01 | When buying g8keepTokens during snipe protection period adjustedAmountOut must be less than uniswapAmountOut | Held |
GK-01R | (amount1Out) When buying g8keepTokens during snipe protection period adjustedAmountOut must be less than or equal to | Broken |
GK-02 | uniswapAmountOut (amount1Out) adjustedAmountOut < balance1 - minimumToken1Balance (If buy amount exceeds | Broken |
GK-02R | maxSnipeProtectionBuyWithoutPenalty users should be penalized) adjustedAmountOut < balance1 - cachedThirdPartyLPAmount (If buy amount exceeds | Broken |
GK-03 | maxSnipeProtectionBuyWithoutPenalty users should be penalized) When buying tokens and amountOutExpected is less than maxSnipeProtectionBuyWithoutPenalty amountOutAdjusted == uniswapAmountOut (amount1Out) | Held |
GK-03R | Cached third Party LP amount should never be greater than adjustedBalance1 | Broken |
GK-04 | When buying tokens outside of the snipe protection period amountOutReceived == | Held |
GK-05 | uniswapAmountOut (amount1Out) Selling g8keepTokens should deduct the correct number of tokens from | Held |
GK-06 | sender balance When selling g8keepTokens amountOutReceived ==~ | Held |
GK-07 | uniswapAmountOut (amount1Out) Adding liquidity must deduct correct number of asset 0 tokens | Held |
GK-08 | Adding liquidity must deduct correct number of asset 1 tokens | Held |
GK-09 | Adding liquidity must credit recipient lp tokens calculated including fees taken | Held |
GK-10 | out for g8keep Removing liquidity must deduct the correct number of UniswapV2Pair | Held |
GK-11 | assets from sender Removing liquidity must credit the correct number of paired tokens to | Held |
GK-12 | recipient Removing liquidity must credit the correct number of g8keepTokens to recipient minus fees | Held |
GK-13 | g8keepToken.approve() must set allowance of spender for owner to | Held |
GK-14 | amount g8keepToken.transfer() must not cause sender balance to underflow | Held |
GK-15 | g8keepToken.transfer() must deduct the correct number of tokens from sender | Held |
GK-16 | g8keepToken.transfer() must not cause recipient balance to overflow | Held |
GK-17 | g8keepToken.transfer() must credit the correct number of tokens to recipient | Held |
GK-18 | g8keepToken.transfer() to self must not affect sender balance on self transfer | Held |
GK-19 | g8keepToken.transfer() to self must not affect recipient balance on self transfer | Held |
GK-20 | g8keepToken.transferFrom() must not cause sender balance to underflow | Held |
GK-21 | g8keepToken.transferFrom() must deduct the correct number of tokens | Held |
GK-22 | from sender g8keepToken.transferFrom() must not cause recipient balance to overflow | Held |
GK-23 | g8keepToken.transferFrom() must credit the correct number of tokens to recipient | Held |
GK-24 | g8keepToken.transferFrom() to self must not affect sender balance on self | Held |
GK-25 | transfer g8keepToken.transferFrom() to self must not affect recipient balance on | Held |
GK-26 | self transfer If sender allowance is not type(uint256).max, g8keepToken.transferFrom() must not | Held |
GK-27 | cause sender allowance to underflow If sender allowance is not type(uint256).max, g8keepToken.transferFrom() must | Held |
GK-28 | deduct allowance from sender g8keepVester.claim() must credit the correct number of vested tokens to | Held |
GK-29 | vesting recipient vested() token amount is always <= contract balance | Broken |
GK-30 | vested() amount is always less than or equal to total tokens vested | Broken |
GK-31 | totalSupply of g8keep should be equal to sum of balances | Held |
GK-32 | Vesting end should should always be greater than start | Broken |
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.
