Audit · firm’s term: audit report
QuillAudits reviewed Guess Meme Rust (Guess Meme Solidity) on 26 Feb 2025: 24 findings, 0 critical; 9 marked fixed, 15 open.
Report hosted on quillaudits.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 1 | 0 | 0 |
| Medium | 5 | 0 | 2 |
| Low | 9 | 0 | 7 |
The new change added to validate if the user should be able to buy the tokens based on the max limit introduces a bug where <strong>msg.sender </strong>will be able to send his tokens to another address while buying, just to buy more amount and pass the check/validation where the current balance of <strong>msg.sender </strong>plus <strong>_amount </strong>is validated.<br> <br>Example:<br>1. User1 bought the maximum amount of tokens that can be bought by one user (based on the <strong>maxSupplyPercentage </strong>)<br>2. User1 calls <strong>buyTokens()</strong> again but before that the User1 transfers some or the whole amount to another address, so that on <a href="https://github.com/sardar-khan/guess-meme-contracts/blob/main/ethereum/guess_meme_latest.sol#L754" target="_blank">L754</a> when the user’s balance will be fetched, it will be 0. And the user will be able to buy an additional <strong>allowedToBuy </strong>amount of tokens.<br>3. This action can be repeated multiple times.<br><br>(Note: It should be noted that even if other functionality will be used where the user’s bought amount will be stored in the variable for each user’s address, the user can still create different addresses to trick that functionality.)
In <strong>buyTokens()</strong> the ETH amount that the user needs to pay for buying a certain <strong>_amount </strong>of tokens can vary if other <strong>buyTokens() </strong>transactions execute before it. <br>If other <strong>buyTokens()</strong> transactions execute before the user's <strong>buyTokens() </strong>transaction then the amount of eth user needs to pay for buying the same amount of <strong>_amount </strong>would be more than what was calculated before. <br>In this case, care can be taken by sending more amount eth as the <strong>buyTokens() </strong>allows sending <strong>msg.value </strong>greater than <strong>ethToBuy + fee</strong>. <br>(Note: As mentioned in another issue the extra dust ETH amount should be sent back to the user/msg.sender.)<br> <br>In <strong>sellTokens()</strong> the amount of ETH the user gets after selling <strong>_amount </strong>tokens can vary if other <strong>sellTokens() </strong>transactions execute before the user's call where the user was expecting to receive the specific ETH amount calculated with <strong>sellQuote()</strong>. <br> <br>Because of this, it's important to provide the slippage limit while selling tokens so that it can be checked if the amount is not much less than what was expected to be received.
According to the code <strong>bondingCurve[_token].isCompleted </strong>is set to <strong>true</strong>, if the <strong>bondingCurve[_token].realTokenReserves </strong>becomes 0 on <a href="https://github.com/sardar-khan/guess-meme-contracts/blob/main/ethereum/eth_guessmeme.sol#L739" target="_blank">L739</a> while the user buys tokens.<br> <br>It should be noted that it’s not guaranteed that all the tokens will be sold in the specific period and hence the ETH accumulated by selling those tokens can’t removed as the bonding curve is not complete because the <strong>realTokenReserves </strong>has not reached 0.
While buying, a very small <strong>_amount </strong>can lead the <strong>ethToBuy </strong>to <strong>0 </strong>and eventually fee calculated based on <strong>ethToBuy </strong>would be 0, the minimum limit should be added while buying the tokens with <strong>buyTokens().</strong> This limit can be added by calculating <strong>ethToBuy </strong>and <strong>fee </strong>for a certain <strong>_amount </strong>and checking that it would not be 0.<br> <br>E.g. In <strong>buyTokens()</strong> it can be checked that if <strong>_amount </strong>is <strong>1000000000000000 </strong>(i.e.<strong> 0.001e18</strong>) the <strong>ethToBuy </strong>and <strong>fee </strong>are non-zero for hardcoded reserve amounts. so based on that the limit can be set for the <strong>_amount </strong>to be greater than or equal to <strong>1000000000000000</strong>.<br> <br>Similarly, while selling with <strong>sellTokens()</strong> very small amount can lead <strong>ethToReceive </strong>to go close to 0. The limit can be added by calculating <strong>ethToReceive </strong>and <strong>fee</strong>, for a certain <strong>_amount </strong>and checking that it would not be 0.<br> <br>E.g In <strong>sellTokens()</strong> if <strong>_amount </strong>is <strong>1000000000000000 </strong>(i.e. <strong>0.001e18</strong>) the <strong>ethToReceive </strong>and <strong>fee</strong> is non-zero for hardcoded reserve amounts. so based on that the limit can be set for the <strong>_amount </strong>to be greater than equal to <strong>1000000000000000</strong>.
<strong>buyTokens() </strong>allows sending <strong>msg.value </strong>that is greater than or equal to <strong>ethToBuy + fee. </strong><br>This means an extra amount can be sent than what is required to buy <strong>_amount</strong>. Which will help users to handle some slippage (if ETH price for token goes up than what was calculated).<br> <br>However, since the extra amount will not be utilized every time. It is necessary to send the extra/dust ETH amount back to the <strong>msg.sender</strong>.<br>
Since some critical functionality like withdrawals of the fund is managed by a single EOA/owner address. It's important that a multi-sig should be used as an owner for managing the functionality.<br><br>Additionally, it should be noted that some functions like <strong>withdrawStuckEth()</strong>, <strong>transferStuckTokens()</strong> create centralization concerns as they grant direct access to the owner to remove the ETH and tokens without any restrictions.
Users can sell the tokens after buying even while the sale is live.<br>We encourage your team to analyze and understand the implications of this scenario and add certain restrictions for the users before they start moving the funds.
Validation should be added to check if the contract has enough amount of ETH available when the user sells a certain amount of tokens back to the contract to avoid unexpected failing.
It should be noted that when deploying this smart contract the ownership is transferred to the deployer of the smart contract, unlike the current OpenZeppelin ownable implementation where you get the option to pass the <strong>initialOwner </strong>argument. So the deployer would be able to call the authorized functions like <strong>withdraw()</strong>, <strong>changeFeeBasisPoints()</strong>, <strong>withdrawStuckEth()</strong>, <strong>transferStuckTokens()</strong>.
<strong>updateMetadata() </strong>updates/reveals the token name and symbol. This should be noted that the <strong>updateMetadata() </strong>is owner only function and for every token that is created by users, the owner of the factory contract can change the name and symbol, and not the user who created that token.
It should be noted that the reserves are hardcoded in the contract that means every time a new token sale is created, the same reserve values will be used.
This should be noted that for every created token sale/pool only the protocol owner can withdraw the tokens and not the creator of that pool.
While the pausing functionality should be normally avoided. Given that the contracts are non-upgradeable, it would be a good idea to add pausing functionality for buy and sell functionality to ensure the safe withdrawal of funds by the owner in any unexpected event.
<strong>tokenCount </strong>is declared on <strong>L652 </strong>is increamented in <strong>createToken()</strong> on <strong>L720</strong>. This variable is then never used anywhere and the variable is declared as private so no one can directly know how many tokens are created as there would be no getter method created for private variable.
<strong>initialVirtualEthReserves </strong>set the value of <strong>1900000000000000000 </strong>and is never changing hence it can be declared as a constant.<br><br><strong>feeRecipient </strong>and <strong>tokenFeeRecipient </strong>can be declared as immutable variables.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (3)
350 projects reviewed · 459 reports · latest Apr 2025
See QuillAudits’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us